Mailspring/app/spec/models/mutable-query-result-set-spec.ts

173 lines
6.8 KiB
TypeScript
Raw Normal View History

/* eslint quote-props: 0 */
Replace Babel with TypeScript compiler, switch entire app to TypeScript 🎉 (#1404) * Switch to using Typescript instead of Babel * Switch all es6 / jsx file extensions to ts / tsx * Convert Utils to a TS module from module.exports style module * Move everything from module.exports to typescript exports * Define .d.ts files for mailspring-exports and component kit… Yes it seems this is the best option :( * Load up on those @types * Synthesize TS types from PropTypes for standard components * Add types to Model classes and move constructor constants to instance vars * 9800 => 7700 TS errors * 7700 => 5600 TS errors * 5600 => 5330 TS errors * 5330 => 4866 TS errors * 4866 => 4426 TS errors * 4426 => 2411 TS errors * 2411 > 1598 TS errors * 1598 > 769 TS errors * 769 > 129 TS errors * 129 > 22 TS errors * Fix runtime errors * More runtime error fixes * Remove support for custom .es6 file extension * Remove a few odd remaining references to Nylas * Don’t ship Typescript support in the compiled app for now * Fix issues in compiled app - module resolution in TS is case sensitive? * README updates * Fix a few more TS errors * Make “No Signature” option clickable + selectable * Remove flicker when saving file and reloading keymaps * Fix mail rule item height in preferences * Fix missing spacing in thread sharing popover * Fix scrollbar ticks being nested incorrectly * Add Japanese as a manually reviewed language * Prevent the thread list from “sticking” * Re-use Sheet when switching root tabs, prevent sidebar from resetting * Ensure specs run * Update package configuration to avoid shpping types * Turn eslint back on - we will opt-in to the TS rules one by one
2019-03-05 03:03:12 +08:00
import { MutableQueryResultSet } from '../../src/flux/models/mutable-query-result-set';
import { QueryRange } from '../../src/flux/models/query-range';
2017-09-27 02:33:08 +08:00
describe('MutableQueryResultSet', function MutableQueryResultSetSpecs() {
describe('clipToRange', () => {
it('should do nothing if the clipping range is infinite', () => {
const set = new MutableQueryResultSet({ _ids: ['A', 'B', 'C', 'D', 'E'], _offset: 5 });
const beforeRange = set.range();
set.clipToRange(QueryRange.infinite());
const afterRange = set.range();
expect(beforeRange.isEqual(afterRange)).toBe(true);
});
2017-09-27 02:33:08 +08:00
it('should correctly trim the result set 5-10 to the clipping range 2-9', () => {
const set = new MutableQueryResultSet({ _ids: ['A', 'B', 'C', 'D', 'E'], _offset: 5 });
expect(set.range().isEqual(new QueryRange({ offset: 5, limit: 5 }))).toBe(true);
set.clipToRange(new QueryRange({ offset: 2, limit: 7 }));
expect(set.range().isEqual(new QueryRange({ offset: 5, limit: 4 }))).toBe(true);
expect(set.ids()).toEqual(['A', 'B', 'C', 'D']);
});
2017-09-27 02:33:08 +08:00
it('should correctly trim the result set 5-10 to the clipping range 5-10', () => {
const set = new MutableQueryResultSet({ _ids: ['A', 'B', 'C', 'D', 'E'], _offset: 5 });
set.clipToRange(new QueryRange({ start: 5, end: 10 }));
expect(set.range().isEqual(new QueryRange({ start: 5, end: 10 }))).toBe(true);
expect(set.ids()).toEqual(['A', 'B', 'C', 'D', 'E']);
});
2017-09-27 02:33:08 +08:00
it('should correctly trim the result set 5-10 to the clipping range 6', () => {
const set = new MutableQueryResultSet({ _ids: ['A', 'B', 'C', 'D', 'E'], _offset: 5 });
set.clipToRange(new QueryRange({ offset: 6, limit: 1 }));
expect(set.range().isEqual(new QueryRange({ offset: 6, limit: 1 }))).toBe(true);
expect(set.ids()).toEqual(['B']);
});
2017-09-27 02:33:08 +08:00
it('should correctly trim the result set 5-10 to the clipping range 100-200', () => {
const set = new MutableQueryResultSet({ _ids: ['A', 'B', 'C', 'D', 'E'], _offset: 5 });
set.clipToRange(new QueryRange({ start: 100, end: 200 }));
expect(set.range().isEqual(new QueryRange({ start: 100, end: 100 }))).toBe(true);
expect(set.ids()).toEqual([]);
});
2017-09-27 02:33:08 +08:00
it('should correctly trim the result set 5-10 to the clipping range 0-2', () => {
const set = new MutableQueryResultSet({ _ids: ['A', 'B', 'C', 'D', 'E'], _offset: 5 });
set.clipToRange(new QueryRange({ offset: 0, limit: 2 }));
expect(set.range().isEqual(new QueryRange({ offset: 5, limit: 0 }))).toBe(true);
expect(set.ids()).toEqual([]);
});
2017-09-27 02:33:08 +08:00
it('should trim the models cache to remove models no longer needed', () => {
const set = new MutableQueryResultSet({
_ids: ['A', 'B', 'C', 'D', 'E'],
_offset: 5,
_modelsHash: {
2017-09-27 02:33:08 +08:00
A: { id: 'A' },
B: { id: 'B' },
C: { id: 'C' },
D: { id: 'D' },
E: { id: 'E' },
},
});
set.clipToRange(new QueryRange({ start: 5, end: 8 }));
expect(set._modelsHash).toEqual({
2017-09-27 02:33:08 +08:00
A: { id: 'A' },
B: { id: 'B' },
C: { id: 'C' },
});
});
});
2017-09-27 02:33:08 +08:00
describe('addIdsInRange', () => {
describe('when the set is currently empty', () =>
it('should set the result set to the provided one', () => {
this.set = new MutableQueryResultSet();
2017-09-27 02:33:08 +08:00
this.set.addIdsInRange(['B', 'C', 'D'], new QueryRange({ start: 1, end: 4 }));
expect(this.set.ids()).toEqual(['B', 'C', 'D']);
2017-09-27 02:33:08 +08:00
expect(this.set.range().isEqual(new QueryRange({ start: 1, end: 4 }))).toBe(true);
}));
2017-09-27 02:33:08 +08:00
describe('when the set has existing values', () => {
beforeEach(() => {
this.set = new MutableQueryResultSet({
_ids: ['A', 'B', 'C', 'D', 'E'],
_offset: 5,
2017-09-27 02:33:08 +08:00
_modelsHash: {
A: { id: 'A' },
B: { id: 'B' },
C: { id: 'C' },
D: { id: 'D' },
E: { id: 'E' },
},
});
});
it("should throw an exception if the range provided doesn't intersect (trailing)", () => {
expect(() => {
2017-09-27 02:33:08 +08:00
this.set.addIdsInRange(['G', 'H', 'I'], new QueryRange({ offset: 11, limit: 3 }));
}).toThrow();
expect(() => {
2017-09-27 02:33:08 +08:00
this.set.addIdsInRange(['F', 'G', 'H'], new QueryRange({ offset: 10, limit: 3 }));
}).not.toThrow();
});
it("should throw an exception if the range provided doesn't intersect (leading)", () => {
expect(() => {
2017-09-27 02:33:08 +08:00
this.set.addIdsInRange(['0', '1', '2'], new QueryRange({ offset: 1, limit: 3 }));
}).toThrow();
expect(() => {
2017-09-27 02:33:08 +08:00
this.set.addIdsInRange(['0', '1', '2'], new QueryRange({ offset: 2, limit: 3 }));
}).not.toThrow();
});
2017-09-27 02:33:08 +08:00
it('should work if the IDs array is shorter than the result range they represent (addition)', () => {
this.set.addIdsInRange(['F', 'G', 'H'], new QueryRange({ offset: 10, limit: 5 }));
expect(this.set.ids()).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']);
});
2017-09-27 02:33:08 +08:00
it('should work if the IDs array is shorter than the result range they represent (replacement)', () => {
this.set.addIdsInRange(['A', 'B', 'C'], new QueryRange({ offset: 5, limit: 5 }));
expect(this.set.ids()).toEqual(['A', 'B', 'C']);
});
2017-09-27 02:33:08 +08:00
it('should correctly add ids (trailing) and update the offset', () => {
this.set.addIdsInRange(['F', 'G', 'H'], new QueryRange({ offset: 10, limit: 3 }));
expect(this.set.ids()).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']);
expect(this.set.range().offset).toEqual(5);
});
2017-09-27 02:33:08 +08:00
it('should correctly add ids (leading) and update the offset', () => {
this.set.addIdsInRange(['0', '1', '2'], new QueryRange({ offset: 2, limit: 3 }));
expect(this.set.ids()).toEqual(['0', '1', '2', 'A', 'B', 'C', 'D', 'E']);
expect(this.set.range().offset).toEqual(2);
});
2017-09-27 02:33:08 +08:00
it('should correctly add ids (middle) and update the offset', () => {
this.set.addIdsInRange(
['B-new', 'C-new', 'D-new'],
new QueryRange({ offset: 6, limit: 3 })
);
expect(this.set.ids()).toEqual(['A', 'B-new', 'C-new', 'D-new', 'E']);
expect(this.set.range().offset).toEqual(5);
});
2017-09-27 02:33:08 +08:00
it('should correctly add ids (middle+trailing) and update the offset', () => {
this.set.addIdsInRange(
['D-new', 'E-new', 'F-new'],
new QueryRange({ offset: 8, limit: 3 })
);
expect(this.set.ids()).toEqual(['A', 'B', 'C', 'D-new', 'E-new', 'F-new']);
expect(this.set.range().offset).toEqual(5);
});
});
});
[client-app] Don't show non-existent children on folder creation Summary: Previously, after creating a new folder, the UI would indicate that the new folder had children, even though it didn't. This was caused by duplicate models in our `MutableQueryResultSet` for the user's categories. Basically, we would sync the server version of the folder before the `SyncbackTask` for the new folder returned its `serverId`. Without the `serverId`, the synced version of the folder couldn't yet be tied to the optimistic folder, so a second row was created in the database. This second row is removed when the `syncbackTask` does return the `serverId`, because we persist the optimistic folder with a `REPLACE INTO` query. (This deletes other rows with the same id.) However, since this was done inside a `persist` change with the `serverId` and no `unpersist` was ever recorded for the `clientId`, our `MutableQueryResultSet` never removed the `clientId` model. To address this, this diff adds a check in `updateModel` to see if the `serverId` is being added. If it is, and both the `serverId` and `clientId` exist in the `_ids` list, we remove the `clientId`. The children indicator does still briefly show up while there are still two separate rows for that folder in the database. If we want to get rid of this completely, we would have to ensure that we do not sync the folder before the `syncbackTask` returns the `serverId`. However, this would probably be pretty involved, and for not much gain. This fix is much simpler and reduces most of the issue. Test Plan: manual Reviewers: juan, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D4228
2017-03-16 04:04:31 +08:00
describe('updateModel', () => {
beforeEach(() => {
2017-09-27 02:33:08 +08:00
this.mockModel = id => {
[client-app] Don't show non-existent children on folder creation Summary: Previously, after creating a new folder, the UI would indicate that the new folder had children, even though it didn't. This was caused by duplicate models in our `MutableQueryResultSet` for the user's categories. Basically, we would sync the server version of the folder before the `SyncbackTask` for the new folder returned its `serverId`. Without the `serverId`, the synced version of the folder couldn't yet be tied to the optimistic folder, so a second row was created in the database. This second row is removed when the `syncbackTask` does return the `serverId`, because we persist the optimistic folder with a `REPLACE INTO` query. (This deletes other rows with the same id.) However, since this was done inside a `persist` change with the `serverId` and no `unpersist` was ever recorded for the `clientId`, our `MutableQueryResultSet` never removed the `clientId` model. To address this, this diff adds a check in `updateModel` to see if the `serverId` is being added. If it is, and both the `serverId` and `clientId` exist in the `_ids` list, we remove the `clientId`. The children indicator does still briefly show up while there are still two separate rows for that folder in the database. If we want to get rid of this completely, we would have to ensure that we do not sync the folder before the `syncbackTask` returns the `serverId`. However, this would probably be pretty involved, and for not much gain. This fix is much simpler and reduces most of the issue. Test Plan: manual Reviewers: juan, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D4228
2017-03-16 04:04:31 +08:00
return {
2017-06-22 04:12:49 +08:00
id: id,
[client-app] Don't show non-existent children on folder creation Summary: Previously, after creating a new folder, the UI would indicate that the new folder had children, even though it didn't. This was caused by duplicate models in our `MutableQueryResultSet` for the user's categories. Basically, we would sync the server version of the folder before the `SyncbackTask` for the new folder returned its `serverId`. Without the `serverId`, the synced version of the folder couldn't yet be tied to the optimistic folder, so a second row was created in the database. This second row is removed when the `syncbackTask` does return the `serverId`, because we persist the optimistic folder with a `REPLACE INTO` query. (This deletes other rows with the same id.) However, since this was done inside a `persist` change with the `serverId` and no `unpersist` was ever recorded for the `clientId`, our `MutableQueryResultSet` never removed the `clientId` model. To address this, this diff adds a check in `updateModel` to see if the `serverId` is being added. If it is, and both the `serverId` and `clientId` exist in the `_ids` list, we remove the `clientId`. The children indicator does still briefly show up while there are still two separate rows for that folder in the database. If we want to get rid of this completely, we would have to ensure that we do not sync the folder before the `syncbackTask` returns the `serverId`. However, this would probably be pretty involved, and for not much gain. This fix is much simpler and reduces most of the issue. Test Plan: manual Reviewers: juan, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D4228
2017-03-16 04:04:31 +08:00
constructor: {
attributes: [],
},
2017-09-27 02:33:08 +08:00
};
};
2017-06-22 04:12:49 +08:00
});
[client-app] Don't show non-existent children on folder creation Summary: Previously, after creating a new folder, the UI would indicate that the new folder had children, even though it didn't. This was caused by duplicate models in our `MutableQueryResultSet` for the user's categories. Basically, we would sync the server version of the folder before the `SyncbackTask` for the new folder returned its `serverId`. Without the `serverId`, the synced version of the folder couldn't yet be tied to the optimistic folder, so a second row was created in the database. This second row is removed when the `syncbackTask` does return the `serverId`, because we persist the optimistic folder with a `REPLACE INTO` query. (This deletes other rows with the same id.) However, since this was done inside a `persist` change with the `serverId` and no `unpersist` was ever recorded for the `clientId`, our `MutableQueryResultSet` never removed the `clientId` model. To address this, this diff adds a check in `updateModel` to see if the `serverId` is being added. If it is, and both the `serverId` and `clientId` exist in the `_ids` list, we remove the `clientId`. The children indicator does still briefly show up while there are still two separate rows for that folder in the database. If we want to get rid of this completely, we would have to ensure that we do not sync the folder before the `syncbackTask` returns the `serverId`. However, this would probably be pretty involved, and for not much gain. This fix is much simpler and reduces most of the issue. Test Plan: manual Reviewers: juan, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D4228
2017-03-16 04:04:31 +08:00
});
});