mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-06 08:08:10 +08:00
291 lines
11 KiB
JavaScript
291 lines
11 KiB
JavaScript
/*
|
|
* decaffeinate suggestions:
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
*/
|
|
const Message = require('../../src/flux/models/message').default;
|
|
const Actions = require('../../src/flux/actions').default;
|
|
const DatabaseStore = require('../../src/flux/stores/database-store').default;
|
|
const DraftEditingSession = require('../../src/flux/stores/draft-editing-session').default;
|
|
const { DraftChangeSet } = DraftEditingSession;
|
|
const _ = require('underscore');
|
|
|
|
|
|
xdescribe("DraftEditingSession Specs", function() {
|
|
describe("DraftChangeSet", function() {
|
|
beforeEach(function() {
|
|
this.onDidAddChanges = jasmine.createSpy('onDidAddChanges');
|
|
this.onWillAddChanges = jasmine.createSpy('onWillAddChanges');
|
|
this.commitResolve = null;
|
|
this.commitResolves = [];
|
|
this.onCommit = jasmine.createSpy('commit').andCallFake(() => {
|
|
new Promise((resolve, reject) => {
|
|
this.commitResolves.push(resolve);
|
|
this.commitResolve = resolve;
|
|
});
|
|
});
|
|
|
|
this.changeSet = new DraftChangeSet({
|
|
onDidAddChanges: this.onDidAddChanges,
|
|
onWillAddChanges: this.onWillAddChanges,
|
|
onCommit: this.onCommit,
|
|
});
|
|
this.changeSet._pending =
|
|
{subject: 'Change to subject line'};
|
|
});
|
|
|
|
describe("teardown", () =>
|
|
it("should remove all of the pending and saving changes", function() {
|
|
this.changeSet.teardown();
|
|
expect(this.changeSet._saving).toEqual({});
|
|
expect(this.changeSet._pending).toEqual({});
|
|
})
|
|
);
|
|
|
|
describe("add", function() {
|
|
it("should mark that the draft is not pristine", function() {
|
|
this.changeSet.add({body: 'Hello World!'});
|
|
expect(this.changeSet._pending.pristine).toEqual(false);
|
|
});
|
|
|
|
it("should add the changes to the _pending set", function() {
|
|
this.changeSet.add({body: 'Hello World!'});
|
|
expect(this.changeSet._pending.body).toEqual('Hello World!');
|
|
});
|
|
|
|
describe("otherwise", () =>
|
|
it("should commit after thirty seconds", function() {
|
|
spyOn(this.changeSet, 'commit');
|
|
this.changeSet.add({body: 'Hello World!'});
|
|
expect(this.changeSet.commit).not.toHaveBeenCalled();
|
|
advanceClock(31000);
|
|
expect(this.changeSet.commit).toHaveBeenCalled();
|
|
})
|
|
);
|
|
});
|
|
|
|
describe("commit", function() {
|
|
it("should resolve immediately if the pending set is empty", function() {
|
|
this.changeSet._pending = {};
|
|
waitsForPromise(() => {
|
|
return this.changeSet.commit().then(() => {
|
|
expect(this.onCommit).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should move changes to the saving set", function() {
|
|
const pendingBefore = Object.assign({}, this.changeSet._pending);
|
|
expect(this.changeSet._saving).toEqual({});
|
|
this.changeSet.commit();
|
|
advanceClock();
|
|
expect(this.changeSet._pending).toEqual({});
|
|
expect(this.changeSet._saving).toEqual(pendingBefore);
|
|
});
|
|
|
|
it("should call the commit handler and then clear the saving set", function() {
|
|
this.changeSet.commit();
|
|
advanceClock();
|
|
expect(this.changeSet._saving).not.toEqual({});
|
|
this.commitResolve();
|
|
advanceClock();
|
|
expect(this.changeSet._saving).toEqual({});
|
|
});
|
|
|
|
describe("concurrency", () =>
|
|
it("the commit function should always run serially", function() {
|
|
let firstFulfilled = false;
|
|
let secondFulfilled = false;
|
|
|
|
this.changeSet._pending = {subject: 'A'};
|
|
this.changeSet.commit().then(() => {
|
|
this.changeSet._pending = {subject: 'B'};
|
|
firstFulfilled = true;
|
|
});
|
|
this.changeSet.commit().then(() => {
|
|
secondFulfilled = true;
|
|
});
|
|
|
|
advanceClock();
|
|
expect(firstFulfilled).toBe(false);
|
|
expect(secondFulfilled).toBe(false);
|
|
this.commitResolves[0]();
|
|
advanceClock();
|
|
expect(firstFulfilled).toBe(true);
|
|
expect(secondFulfilled).toBe(false);
|
|
this.commitResolves[1]();
|
|
advanceClock();
|
|
expect(firstFulfilled).toBe(true);
|
|
expect(secondFulfilled).toBe(true);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("DraftEditingSession", function() {
|
|
describe("constructor", function() {
|
|
it("should make a query to fetch the draft", function() {
|
|
spyOn(DatabaseStore, 'run').andCallFake(() => {
|
|
return new Promise((resolve, reject) => {});
|
|
});
|
|
const session = new DraftEditingSession('client-id');
|
|
expect(DatabaseStore.run).toHaveBeenCalled();
|
|
});
|
|
|
|
describe("when given a draft object", function() {
|
|
beforeEach(function() {
|
|
spyOn(DatabaseStore, 'run');
|
|
this.draft = new Message({draft: true, body: '123'});
|
|
this.session = new DraftEditingSession('client-id', this.draft);
|
|
});
|
|
|
|
it("should not make a query for the draft", () => expect(DatabaseStore.run).not.toHaveBeenCalled());
|
|
|
|
it("prepare should resolve without querying for the draft", function() {
|
|
waitsForPromise(() => this.session.prepare().then(() => {
|
|
expect(this.session.draft()).toBeDefined();
|
|
expect(DatabaseStore.run).not.toHaveBeenCalled();
|
|
})
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("teardown", () =>
|
|
it("should mark the session as destroyed", function() {
|
|
spyOn(DraftEditingSession.prototype, "prepare");
|
|
const session = new DraftEditingSession('client-id');
|
|
session.teardown();
|
|
expect(session._destroyed).toEqual(true);
|
|
})
|
|
);
|
|
|
|
describe("prepare", function() {
|
|
beforeEach(function() {
|
|
this.draft = new Message({draft: true, body: '123', id: 'client-id'});
|
|
spyOn(DraftEditingSession.prototype, "prepare");
|
|
this.session = new DraftEditingSession('client-id');
|
|
spyOn(this.session, '_setDraft').andCallThrough();
|
|
spyOn(DatabaseStore, 'run').andCallFake(modelQuery => {
|
|
return Promise.resolve(this.draft);
|
|
});
|
|
jasmine.unspy(DraftEditingSession.prototype, "prepare");
|
|
});
|
|
|
|
it("should call setDraft with the retrieved draft", function() {
|
|
waitsForPromise(() => {
|
|
return this.session.prepare().then(() => {
|
|
expect(this.session._setDraft).toHaveBeenCalledWith(this.draft);
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should resolve with the DraftEditingSession", function() {
|
|
waitsForPromise(() => {
|
|
return this.session.prepare().then(val => {
|
|
expect(val).toBe(this.session);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("error handling", function() {
|
|
it("should reject if the draft session has already been destroyed", function() {
|
|
this.session._destroyed = true;
|
|
waitsForPromise(() => {
|
|
return this.session.prepare().then(() => {
|
|
expect(false).toBe(true);
|
|
}).catch(val => {
|
|
expect(val instanceof Error).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should reject if the draft cannot be found", function() {
|
|
this.draft = null;
|
|
waitsForPromise(() => {
|
|
return this.session.prepare().then(() => {
|
|
expect(false).toBe(true);
|
|
}).catch(val => {
|
|
expect(val instanceof Error).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when a draft changes", function() {
|
|
beforeEach(function() {
|
|
this.draft = new Message({draft: true, id: 'client-id', body: 'A', subject: 'initial'});
|
|
this.session = new DraftEditingSession('client-id', this.draft);
|
|
advanceClock();
|
|
|
|
spyOn(Actions, "queueTask").andReturn(Promise.resolve());
|
|
});
|
|
|
|
it("should ignore the update unless it applies to the current draft", function() {
|
|
spyOn(this.session, 'trigger');
|
|
this.session._onDraftChanged({objectClass: 'message', objects: [new Message()]});
|
|
expect(this.session.trigger).not.toHaveBeenCalled();
|
|
this.session._onDraftChanged({objectClass: 'message', objects: [this.draft]});
|
|
expect(this.session.trigger).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should apply the update to the current draft", function() {
|
|
const updatedDraft = this.draft.clone();
|
|
updatedDraft.subject = 'This is the new subject';
|
|
spyOn(this.session, '_setDraft');
|
|
|
|
this.session._onDraftChanged({objectClass: 'message', objects: [updatedDraft]});
|
|
expect(this.session._setDraft).toHaveBeenCalled();
|
|
const draft = this.session._setDraft.calls[0].args[0];
|
|
expect(draft.subject).toEqual(updatedDraft.subject);
|
|
});
|
|
|
|
it("atomically commits changes", function() {
|
|
spyOn(DatabaseStore, "run").andReturn(Promise.resolve(this.draft));
|
|
spyOn(DatabaseStore, 'inTransaction').andCallThrough();
|
|
this.session.changes.add({body: "123"});
|
|
waitsForPromise(() => {
|
|
return this.session.changes.commit().then(() => {
|
|
expect(DatabaseStore.inTransaction).toHaveBeenCalled();
|
|
expect(DatabaseStore.inTransaction.calls.length).toBe(1);
|
|
});
|
|
});
|
|
});
|
|
|
|
it("persist the applied changes", function() {
|
|
spyOn(DatabaseStore, "run").andReturn(Promise.resolve(this.draft));
|
|
this.session.changes.add({body: "123"});
|
|
waitsForPromise(() => {
|
|
return this.session.changes.commit().then(() => {
|
|
expect(updated.body).toBe("123");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when findBy does not return a draft", () =>
|
|
it("continues and persists it's local draft reference, so it is resaved and draft editing can continue", function() {
|
|
spyOn(DatabaseStore, "run").andReturn(Promise.resolve(null));
|
|
this.session.changes.add({body: "123"});
|
|
waitsForPromise(() => {
|
|
return this.session.changes.commit().then(() => {
|
|
expect(updated.body).toBe("123");
|
|
});
|
|
});
|
|
})
|
|
);
|
|
|
|
it("does nothing if the draft is marked as destroyed", function() {
|
|
spyOn(DatabaseStore, "run").andReturn(Promise.resolve(this.draft));
|
|
spyOn(DatabaseStore, 'inTransaction').andCallThrough();
|
|
return waitsForPromise(() => {
|
|
this.session._destroyed = true;
|
|
this.session.changes.add({body: "123"});
|
|
return this.session.changes.commit().then(() => {
|
|
expect(DatabaseStore.inTransaction).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|