mirror of
				https://github.com/Foundry376/Mailspring.git
				synced 2025-10-31 00:16:58 +08:00 
			
		
		
		
	fix(counts): compute deltas for unpersist events, more specs
This fix should resolve #489
This commit is contained in:
		
							parent
							
								
									c0badff4da
								
							
						
					
					
						commit
						d19533ff7f
					
				
					 4 changed files with 110 additions and 68 deletions
				
			
		|  | @ -141,6 +141,7 @@ describe "DatabaseStore", -> | ||||||
|           change = DatabaseStore._accumulateAndTrigger.mostRecentCall.args[0] |           change = DatabaseStore._accumulateAndTrigger.mostRecentCall.args[0] | ||||||
|           expect(change).toEqual |           expect(change).toEqual | ||||||
|             objectClass: TestModel.name, |             objectClass: TestModel.name, | ||||||
|  |             objectIds: [testModelInstanceA.id, testModelInstanceB.id] | ||||||
|             objects: [testModelInstanceA, testModelInstanceB] |             objects: [testModelInstanceA, testModelInstanceB] | ||||||
|             type:'persist' |             type:'persist' | ||||||
| 
 | 
 | ||||||
|  | @ -189,8 +190,12 @@ describe "DatabaseStore", -> | ||||||
|         advanceClock() |         advanceClock() | ||||||
|         expect(@beforeDatabaseChange).toHaveBeenCalledWith( |         expect(@beforeDatabaseChange).toHaveBeenCalledWith( | ||||||
|           DatabaseStore._query, |           DatabaseStore._query, | ||||||
|           [testModelInstanceA, testModelInstanceB], |           { | ||||||
|           [testModelInstanceA.id, testModelInstanceB.id], |             objects: [testModelInstanceA, testModelInstanceB] | ||||||
|  |             objectIds: [testModelInstanceA.id, testModelInstanceB.id] | ||||||
|  |             objectClass: testModelInstanceA.constructor.name | ||||||
|  |             type: 'persist' | ||||||
|  |           }, | ||||||
|           undefined |           undefined | ||||||
|         ) |         ) | ||||||
|         expect(DatabaseStore._writeModels).not.toHaveBeenCalled() |         expect(DatabaseStore._writeModels).not.toHaveBeenCalled() | ||||||
|  | @ -203,8 +208,12 @@ describe "DatabaseStore", -> | ||||||
|         advanceClock() |         advanceClock() | ||||||
|         expect(@afterDatabaseChange).toHaveBeenCalledWith( |         expect(@afterDatabaseChange).toHaveBeenCalledWith( | ||||||
|           DatabaseStore._query, |           DatabaseStore._query, | ||||||
|           [testModelInstanceA, testModelInstanceB], |           { | ||||||
|           [testModelInstanceA.id, testModelInstanceB.id], |             objects: [testModelInstanceA, testModelInstanceB] | ||||||
|  |             objectIds: [testModelInstanceA.id, testModelInstanceB.id] | ||||||
|  |             objectClass: testModelInstanceA.constructor.name | ||||||
|  |             type: 'persist' | ||||||
|  |           }, | ||||||
|           "value" |           "value" | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  | @ -263,7 +272,12 @@ describe "DatabaseStore", -> | ||||||
|           expect(DatabaseStore._accumulateAndTrigger).toHaveBeenCalled() |           expect(DatabaseStore._accumulateAndTrigger).toHaveBeenCalled() | ||||||
| 
 | 
 | ||||||
|           change = DatabaseStore._accumulateAndTrigger.mostRecentCall.args[0] |           change = DatabaseStore._accumulateAndTrigger.mostRecentCall.args[0] | ||||||
|           expect(change).toEqual({objectClass: TestModel.name, objects: [testModelInstance], type:'unpersist'}) |           expect(change).toEqual({ | ||||||
|  |             objectClass: TestModel.name, | ||||||
|  |             objectIds: [testModelInstance.id] | ||||||
|  |             objects: [testModelInstance], | ||||||
|  |             type:'unpersist' | ||||||
|  |           }) | ||||||
| 
 | 
 | ||||||
|     describe "when the model has collection attributes", -> |     describe "when the model has collection attributes", -> | ||||||
|       it "should delete all of the elements in the join tables", -> |       it "should delete all of the elements in the join tables", -> | ||||||
|  |  | ||||||
|  | @ -243,39 +243,61 @@ describe "CategoryDatabaseMutationObserver", -> | ||||||
|       labels: [@label1, @label3] |       labels: [@label1, @label3] | ||||||
| 
 | 
 | ||||||
|   describe "given a set of modifying models", -> |   describe "given a set of modifying models", -> | ||||||
|     it "should call countsDidChange with the folder / label membership deltas", -> |     scenarios = [{ | ||||||
|       queryResolves = [] |         type: 'persist', | ||||||
|       query = jasmine.createSpy('query').andCallFake => |         expected: { | ||||||
|         new Promise (resolve, reject) -> |           l3: -1, | ||||||
|           queryResolves.push(resolve) |           l2: -1, | ||||||
|  |           l4: 1 | ||||||
|  |         } | ||||||
|  |       },{ | ||||||
|  |         type: 'unpersist', | ||||||
|  |         expected: { | ||||||
|  |           l1: -1, | ||||||
|  |           l3: -2, | ||||||
|  |           l2: -1 | ||||||
|  |         } | ||||||
|  |       }] | ||||||
|  |     scenarios.forEach ({type, expected}) -> | ||||||
|  |       it "should call countsDidChange with the folder / label membership deltas (#{type})", -> | ||||||
|  |         queryResolves = [] | ||||||
|  |         query = jasmine.createSpy('query').andCallFake => | ||||||
|  |           new Promise (resolve, reject) -> | ||||||
|  |             queryResolves.push(resolve) | ||||||
| 
 | 
 | ||||||
|       countsDidChange = jasmine.createSpy('countsDidChange') |         countsDidChange = jasmine.createSpy('countsDidChange') | ||||||
|       m = new ThreadCountsStore.CategoryDatabaseMutationObserver(countsDidChange) |         m = new ThreadCountsStore.CategoryDatabaseMutationObserver(countsDidChange) | ||||||
| 
 | 
 | ||||||
|       beforePromise = m.beforeDatabaseChange(query, [@threadA, @threadB, @threadC], [@threadA.id, @threadB.id, @threadC.id]) |         beforePromise = m.beforeDatabaseChange(query, { | ||||||
|       expect(query.callCount).toBe(2) |           type: type | ||||||
|       expect(query.calls[0].args[0]).toEqual("SELECT `Thread`.id as id, `Thread-Label`.`value` as catId FROM `Thread` INNER JOIN `Thread-Label` ON `Thread`.`id` = `Thread-Label`.`id` WHERE `Thread`.id IN ('A','B','C') AND `Thread`.unread = 1") |           objects: [@threadA, @threadB, @threadC], | ||||||
|       expect(query.calls[1].args[0]).toEqual("SELECT `Thread`.id as id, `Thread-Folder`.`value` as catId FROM `Thread` INNER JOIN `Thread-Folder` ON `Thread`.`id` = `Thread-Folder`.`id` WHERE `Thread`.id IN ('A','B','C') AND `Thread`.unread = 1") |           objectIds: [@threadA.id, @threadB.id, @threadC.id] | ||||||
|       queryResolves[0]([ |           objectClass: Thread.name | ||||||
|         {id: @threadA.id, catId: @label1.id}, |         }) | ||||||
|         {id: @threadA.id, catId: @label3.id}, |         expect(query.callCount).toBe(2) | ||||||
|         {id: @threadB.id, catId: @label2.id}, |         expect(query.calls[0].args[0]).toEqual("SELECT `Thread`.id as id, `Thread-Label`.`value` as catId FROM `Thread` INNER JOIN `Thread-Label` ON `Thread`.`id` = `Thread-Label`.`id` WHERE `Thread`.id IN ('A','B','C') AND `Thread`.unread = 1") | ||||||
|         {id: @threadB.id, catId: @label3.id}, |         expect(query.calls[1].args[0]).toEqual("SELECT `Thread`.id as id, `Thread-Folder`.`value` as catId FROM `Thread` INNER JOIN `Thread-Folder` ON `Thread`.`id` = `Thread-Folder`.`id` WHERE `Thread`.id IN ('A','B','C') AND `Thread`.unread = 1") | ||||||
|       ]) |         queryResolves[0]([ | ||||||
|       queryResolves[1]([]) |           {id: @threadA.id, catId: @label1.id}, | ||||||
|  |           {id: @threadA.id, catId: @label3.id}, | ||||||
|  |           {id: @threadB.id, catId: @label2.id}, | ||||||
|  |           {id: @threadB.id, catId: @label3.id}, | ||||||
|  |         ]) | ||||||
|  |         queryResolves[1]([]) | ||||||
| 
 | 
 | ||||||
|       waitsForPromise => |         waitsForPromise => | ||||||
|         beforePromise.then (result) => |           beforePromise.then (result) => | ||||||
|           expect(result).toEqual({ |             expect(result).toEqual({ | ||||||
|             categories: { |               categories: { | ||||||
|               l1: -1, |                 l1: -1, | ||||||
|               l3: -2, |                 l3: -2, | ||||||
|               l2: -1 |                 l2: -1 | ||||||
|             } |               } | ||||||
|           }) |             }) | ||||||
|           m.afterDatabaseChange(query, [@threadA, @threadB, @threadC], [@threadA.id, @threadB.id, @threadC.id], result) |             m.afterDatabaseChange(query, { | ||||||
|           expect(countsDidChange).toHaveBeenCalledWith({ |               type: type | ||||||
|             l3: -1, |               objects: [@threadA, @threadB, @threadC], | ||||||
|             l2: -1, |               objectIds: [@threadA.id, @threadB.id, @threadC.id] | ||||||
|             l4: 1 |               objectClass: Thread.name | ||||||
|           }) |             }, result) | ||||||
|  |             expect(countsDidChange).toHaveBeenCalledWith(expected) | ||||||
|  |  | ||||||
|  | @ -447,17 +447,20 @@ class DatabaseStore extends NylasStore | ||||||
|       clones.push(model.clone()) |       clones.push(model.clone()) | ||||||
|       ids[model.id] = true |       ids[model.id] = true | ||||||
| 
 | 
 | ||||||
|     ids = Object.keys(ids) |     # Note: It's important that we clone the objects since other code could mutate | ||||||
|  |     # them during the save process. We want to guaruntee that the models you send to | ||||||
|  |     # persistModels are saved exactly as they were sent. | ||||||
| 
 | 
 | ||||||
|     @atomicMutation => |     @atomicMutation => | ||||||
|       @_runMutationHooks('beforeDatabaseChange', clones, ids).then (data) => |       metadata = | ||||||
|  |         objectClass: clones[0].constructor.name | ||||||
|  |         objectIds: Object.keys(ids) | ||||||
|  |         objects: clones | ||||||
|  |         type: 'persist' | ||||||
|  |       @_runMutationHooks('beforeDatabaseChange', metadata).then (data) => | ||||||
|         @_writeModels(clones).then => |         @_writeModels(clones).then => | ||||||
|           @_runMutationHooks('afterDatabaseChange', clones, ids, data) |           @_runMutationHooks('afterDatabaseChange', metadata, data) | ||||||
|           @_accumulateAndTrigger({ |           @_accumulateAndTrigger(metadata) | ||||||
|             objectClass: clones[0].constructor.name |  | ||||||
|             objects: clones |  | ||||||
|             type: 'persist' |  | ||||||
|           }) |  | ||||||
| 
 | 
 | ||||||
|   # Public: Asynchronously removes `model` from the cache and triggers a change event. |   # Public: Asynchronously removes `model` from the cache and triggers a change event. | ||||||
|   # |   # | ||||||
|  | @ -472,14 +475,15 @@ class DatabaseStore extends NylasStore | ||||||
|     model = model.clone() |     model = model.clone() | ||||||
| 
 | 
 | ||||||
|     @atomicMutation => |     @atomicMutation => | ||||||
|       @_runMutationHooks('beforeDatabaseChange', [model], [model.id]).then (data) => |       metadata = | ||||||
|  |         objectClass: model.constructor.name, | ||||||
|  |         objectIds: [model.id] | ||||||
|  |         objects: [model], | ||||||
|  |         type: 'unpersist' | ||||||
|  |       @_runMutationHooks('beforeDatabaseChange', metadata).then (data) => | ||||||
|         @_deleteModel(model).then => |         @_deleteModel(model).then => | ||||||
|           @_runMutationHooks('afterDatabaseChange', [model], [model.id], data) |           @_runMutationHooks('afterDatabaseChange', metadata, data) | ||||||
|           @_accumulateAndTrigger({ |           @_accumulateAndTrigger(metadata) | ||||||
|             objectClass: model.constructor.name, |  | ||||||
|             objects: [model], |  | ||||||
|             type: 'unpersist' |  | ||||||
|           }) |  | ||||||
| 
 | 
 | ||||||
|   persistJSONObject: (key, json) -> |   persistJSONObject: (key, json) -> | ||||||
|     jsonString = serializeRegisteredObjects(json) |     jsonString = serializeRegisteredObjects(json) | ||||||
|  | @ -512,10 +516,10 @@ class DatabaseStore extends NylasStore | ||||||
|   removeMutationHook: (hook) -> |   removeMutationHook: (hook) -> | ||||||
|     @_databaseMutationHooks = _.without(@_databaseMutationHooks, hook) |     @_databaseMutationHooks = _.without(@_databaseMutationHooks, hook) | ||||||
| 
 | 
 | ||||||
|   _runMutationHooks: (selectorName, models, ids, data = []) -> |   _runMutationHooks: (selectorName, metadata, data = []) -> | ||||||
|     beforePromises = @_databaseMutationHooks.map (hook, idx) => |     beforePromises = @_databaseMutationHooks.map (hook, idx) => | ||||||
|       Promise.try => |       Promise.try => | ||||||
|         hook[selectorName](@_query, models, ids, data[idx]) |         hook[selectorName](@_query, metadata, data[idx]) | ||||||
| 
 | 
 | ||||||
|     Promise.all(beforePromises).catch (e) => |     Promise.all(beforePromises).catch (e) => | ||||||
|       unless NylasEnv.inSpecMode() |       unless NylasEnv.inSpecMode() | ||||||
|  |  | ||||||
|  | @ -10,14 +10,14 @@ Folder = require '../models/folder' | ||||||
| Label = require '../models/label' | Label = require '../models/label' | ||||||
| WindowBridge = require '../../window-bridge' | WindowBridge = require '../../window-bridge' | ||||||
| 
 | 
 | ||||||
| JSONObjectKey = 'UnreadCounts-V1' | JSONObjectKey = 'UnreadCounts-V2' | ||||||
| 
 | 
 | ||||||
| class CategoryDatabaseMutationObserver | class CategoryDatabaseMutationObserver | ||||||
|   constructor: (@_countsDidChange) -> |   constructor: (@_countsDidChange) -> | ||||||
| 
 | 
 | ||||||
|   beforeDatabaseChange: (query, models, ids) => |   beforeDatabaseChange: (query, {type, objects, objectIds, objectClass}) => | ||||||
|     if models[0].constructor.name is 'Thread' |     if objectClass is Thread.name | ||||||
|       idString = "'" + ids.join("','") +  "'" |       idString = "'" + objectIds.join("','") +  "'" | ||||||
|       Promise.props |       Promise.props | ||||||
|         labelData: query("SELECT `Thread`.id as id, `Thread-Label`.`value` as catId FROM `Thread` INNER JOIN `Thread-Label` ON `Thread`.`id` = `Thread-Label`.`id` WHERE `Thread`.id IN (#{idString}) AND `Thread`.unread = 1", []) |         labelData: query("SELECT `Thread`.id as id, `Thread-Label`.`value` as catId FROM `Thread` INNER JOIN `Thread-Label` ON `Thread`.`id` = `Thread-Label`.`id` WHERE `Thread`.id IN (#{idString}) AND `Thread`.unread = 1", []) | ||||||
|         folderData: query("SELECT `Thread`.id as id, `Thread-Folder`.`value` as catId FROM `Thread` INNER JOIN `Thread-Folder` ON `Thread`.`id` = `Thread-Folder`.`id` WHERE `Thread`.id IN (#{idString}) AND `Thread`.unread = 1", []) |         folderData: query("SELECT `Thread`.id as id, `Thread-Folder`.`value` as catId FROM `Thread` INNER JOIN `Thread-Folder` ON `Thread`.`id` = `Thread-Folder`.`id` WHERE `Thread`.id IN (#{idString}) AND `Thread`.unread = 1", []) | ||||||
|  | @ -31,16 +31,18 @@ class CategoryDatabaseMutationObserver | ||||||
|     else |     else | ||||||
|       Promise.resolve() |       Promise.resolve() | ||||||
| 
 | 
 | ||||||
|   afterDatabaseChange: (query, models, ids, beforeResolveValue) => |   afterDatabaseChange: (query, {type, objects, objectIds, objectClass}, beforeResolveValue) => | ||||||
|     if models[0].constructor.name is 'Thread' |     if objectClass is Thread.name | ||||||
|       {categories} = beforeResolveValue |       {categories} = beforeResolveValue | ||||||
|       for thread in models | 
 | ||||||
|         continue unless thread.unread |       if type is 'persist' | ||||||
|         for collection in ['labels', 'folders'] |         for thread in objects | ||||||
|           if thread[collection] |           continue unless thread.unread | ||||||
|             for cat in thread[collection] |           for collection in ['labels', 'folders'] | ||||||
|               categories[cat.id] ?= 0 |             if thread[collection] | ||||||
|               categories[cat.id] += 1 |               for cat in thread[collection] | ||||||
|  |                 categories[cat.id] ?= 0 | ||||||
|  |                 categories[cat.id] += 1 | ||||||
| 
 | 
 | ||||||
|       for key, val of categories |       for key, val of categories | ||||||
|         delete categories[key] if val is 0 |         delete categories[key] if val is 0 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue