/*global asyncStorage, Drafts, Draft, InterInstanceEventDispatcher, MocksHelper */ 'use strict'; require('/shared/js/event_dispatcher.js'); require('/views/shared/js/utils.js'); require('/services/js/drafts.js'); require('/shared/test/unit/mocks/mock_async_storage.js'); require('/views/shared/test/unit/mock_inter_instance_event_dispatcher.js'); var MocksHelperForDraftsTest = new MocksHelper([ 'asyncStorage', 'InterInstanceEventDispatcher' ]).init(); suite('Drafts', function() { var mocksHelper = MocksHelperForDraftsTest; var threadDraft1, threadDraft2, threadDraft3, threadDraft4, threadDraft5; var draft1, draft2; suiteSetup(function() { mocksHelper.suiteSetup(); threadDraft1 = new Draft({ recipients: ['555', '666'], content: ['This is a draft message'], timestamp: 1, threadId: 42, subject: 'This is a subject', type: 'sms', id: 1 }); threadDraft2 = new Draft({ recipients: ['555'], content: ['This is a draft message'], timestamp: 2, threadId: 44, subject: 'This is a subject', type: 'sms', id: 2 }); threadDraft3 = new Draft({ recipients: ['555', '222'], content: ['This is a draft message'], timestamp: 3, threadId: 1, subject: 'This is a subject', type: 'sms', id: 3 }); threadDraft4 = new Draft({ recipients: ['555', '333'], content: ['This is a draft message'], timestamp: 4, threadId: 2, subject: 'This is a subject', type: 'sms', id: 4 }); draft1 = new Draft({ recipients: ['555', '444'], content: ['This is a draft message'], timestamp: 5, threadId: null, subject: 'This is a subject', type: 'sms', id: 5 }); threadDraft5 = new Draft({ recipients: ['123456'], content: [ 'This is a draft MMS...', { blob: { type: 'audio/ogg', size: 12345 }, name: 'audio.oga' }, '...with a recipient and a thread' ], timestamp: Date.now() - (3600000 * 2), threadId: 8, type: 'mms', id: 6 }); draft2 = new Draft({ recipients: ['555', '444'], content: ['This is a draft message'], timestamp: 5, threadId: null, subject: 'This is a different subject', type: 'sms', id: 7 }); }); suiteTeardown(function() { mocksHelper.suiteTeardown(); }); suite('init() >', function() { var savedHandler; var deletedHandler; setup(function() { this.sinon.spy(InterInstanceEventDispatcher, 'on'); this.sinon.stub(Drafts, 'request').returns(Promise.resolve()); this.sinon.stub(Drafts, 'emit'); Drafts.init(); savedHandler = InterInstanceEventDispatcher.on.firstCall.args[1]; deletedHandler = InterInstanceEventDispatcher.on.secondCall.args[1]; }); test('draft-saved will be dispatched as saved', function(done) { sinon.assert.calledWith(InterInstanceEventDispatcher.on, 'draft-saved'); savedHandler(threadDraft1).then(() => { sinon.assert.calledWith(Drafts.request, true); sinon.assert.calledWith(Drafts.emit, 'saved', threadDraft1); }).then(done, done); }); test('draft-deleted will be dispatched as deleted', function(done) { sinon.assert.calledWith(InterInstanceEventDispatcher.on, 'draft-deleted'); deletedHandler(threadDraft2).then(() => { sinon.assert.calledWith(Drafts.request, true); sinon.assert.calledWith(Drafts.emit, 'deleted', threadDraft2); }).then(done, done); }); }); suite('add() >', function() { var onSavedStub; setup(function() { onSavedStub = sinon.stub(); Drafts.on('saved', onSavedStub); this.sinon.spy(InterInstanceEventDispatcher, 'emit'); }); teardown(function() { Drafts.clear().offAll('saved'); }); test('correctly adds thread bound drafts', function() { Drafts.add(threadDraft1); assert.deepEqual(Drafts.byThreadId(threadDraft1.threadId), threadDraft1); assert.isNull(Drafts.byThreadId(threadDraft2.threadId)); sinon.assert.calledOnce(onSavedStub); sinon.assert.calledWith(onSavedStub, threadDraft1); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', threadDraft1 ); Drafts.add(threadDraft2); assert.deepEqual(Drafts.byThreadId(threadDraft1.threadId), threadDraft1); assert.deepEqual(Drafts.byThreadId(threadDraft2.threadId), threadDraft2); sinon.assert.calledTwice(onSavedStub); sinon.assert.calledWith(onSavedStub, threadDraft2); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', threadDraft2 ); }); test('correctly adds thread less drafts', function() { Drafts.add(draft1); assert.deepEqual(Drafts.byDraftId(draft1.id), draft1); assert.isNull(Drafts.byDraftId(draft2.id)); sinon.assert.calledOnce(onSavedStub); sinon.assert.calledWith(onSavedStub, draft1); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', draft1 ); Drafts.add(draft2); assert.deepEqual(Drafts.byDraftId(draft1.id), draft1); assert.deepEqual(Drafts.byDraftId(draft2.id), draft2); sinon.assert.calledTwice(onSavedStub); sinon.assert.calledWith(onSavedStub, draft2); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', draft2 ); }); test('add draft of same threadId replaces previous', function() { assert.equal( Drafts.add(threadDraft2).byThreadId(threadDraft2.threadId).id, threadDraft2.id ); sinon.assert.calledOnce(onSavedStub); sinon.assert.calledWith(onSavedStub, threadDraft2); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', threadDraft2 ); var updatedDraft = new Draft({ recipients: ['555'], content: ['This is a new draft for thread 44'], subject: 'This is a subject', timestamp: 2, threadId: 44, type: 'sms' }); Drafts.add(updatedDraft); assert.notEqual(Drafts.byThreadId(44).id, threadDraft2.id); sinon.assert.calledTwice(onSavedStub); sinon.assert.calledWith(onSavedStub, updatedDraft); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', updatedDraft ); }); test('add threadless draft of same draft.id replaces previous', function() { assert.equal( Drafts.add(draft1).byDraftId(draft1.id).content, 'This is a draft message' ); sinon.assert.calledOnce(onSavedStub); sinon.assert.calledWith(onSavedStub, draft1); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', draft1 ); var updatedDraft = new Draft({ recipients: ['555', '444'], content: ['This is a new draft message'], timestamp: 5, threadId: null, subject: 'This is a subject', type: 'sms', id: 5 }); Drafts.add(updatedDraft); assert.equal( Drafts.byDraftId(draft1.id).content, 'This is a new draft message' ); sinon.assert.calledTwice(onSavedStub); sinon.assert.calledWith(onSavedStub, updatedDraft); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-saved', updatedDraft ); }); }); suite('delete() >', function() { var onDeletedStub; setup(function() { onDeletedStub = sinon.stub(); Drafts.on('deleted', onDeletedStub); [threadDraft1, threadDraft2, threadDraft5, draft2].forEach( Drafts.add, Drafts ); this.sinon.spy(InterInstanceEventDispatcher, 'emit'); }); teardown(function() { Drafts.clear().offAll('deleted'); }); test('Delete draft with reference', function() { assert.isNotNull(Drafts.byThreadId(threadDraft1.threadId)); assert.isNull( Drafts.delete(threadDraft1).byThreadId(threadDraft1.threadId) ); sinon.assert.calledOnce(onDeletedStub); sinon.assert.calledWith(onDeletedStub, threadDraft1); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-deleted', threadDraft1 ); }); test('Deleting new message drafts', function() { assert.isNotNull(Drafts.byThreadId(threadDraft5.threadId)); assert.isNotNull(Drafts.byDraftId(draft2.id)); // First draft removes only the draft from the drafts assert.isNull( Drafts.delete(threadDraft5).byThreadId(threadDraft5.threadId) ); assert.isNotNull(Drafts.byDraftId(draft2.id)); sinon.assert.calledOnce(onDeletedStub); sinon.assert.calledWith(onDeletedStub, threadDraft5); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-deleted', threadDraft5 ); // The last draft in the thread removes the thread from the index assert.isNull(Drafts.delete(draft2).byDraftId(draft2.id)); sinon.assert.calledTwice(onDeletedStub); sinon.assert.calledWith(onDeletedStub, draft2); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-deleted', draft2 ); }); test('delete by non-draft object', function() { var draft = new Draft({ recipients: ['999999'], content: ['foo'], timestamp: 2, threadId: 999, subject: 'This is a subject', type: 'sms' }); Drafts.add(draft); assert.deepEqual(Drafts.byThreadId(999), draft); var draftToDelete = new Draft({ threadId: 999, id: draft.id }); Drafts.delete(draftToDelete); assert.isNull(Drafts.byThreadId(999)); sinon.assert.calledOnce(onDeletedStub); sinon.assert.calledWith(onDeletedStub, draftToDelete); sinon.assert.calledWith( InterInstanceEventDispatcher.emit, 'draft-deleted', draftToDelete ); }); test('does not fire "deleted" event if nothing was deleted', function() { // Try to delete draft that doesn't exist. Drafts.delete(draft1); sinon.assert.notCalled(onDeletedStub); sinon.assert.notCalled(InterInstanceEventDispatcher.emit); }); }); suite('Select drafts', function() { suiteSetup(function() { [threadDraft1, threadDraft2, threadDraft3, threadDraft4, draft1].forEach( Drafts.add, Drafts ); }); suiteTeardown(function() { Drafts.clear(); }); suite('byThreadId', function() { test('get drafts for id 1', function() { assert.deepEqual( Drafts.byThreadId(threadDraft3.threadId), threadDraft3 ); }); test('throws if threadId is not a number', function() { assert.throws(() => Drafts.byThreadId(null)); }); test('returns null if there is no draft for the thread id', function() { assert.isNull(Drafts.byThreadId(10)); }); }); }); suite('get(id) > ', function() { var id = 101; suiteSetup(function() { Drafts.add(new Draft({ id: id, recipients: [], content: ['A new message draft with no recipients'], type: 'sms' })); }); suiteTeardown(function() { Drafts.clear(); }); test('get draft for id 101', function() { var draft = Drafts.byDraftId(id); assert.equal(draft.id, id); assert.equal(draft.threadId, null); }); test('get no draft for non-existant id', function() { assert.isNull(Drafts.byDraftId(9999999)); }); }); suite('getAll()>', function() { setup(function() { [ threadDraft1, threadDraft2, threadDraft3, threadDraft4, threadDraft5, draft1, draft2 ].forEach(Drafts.add, Drafts); }); teardown(function() { Drafts.clear(); }); test('getAll returns all drafts', function() { var orderedDrafts = [ threadDraft1, threadDraft2, threadDraft3, threadDraft4, threadDraft5, draft1, draft2 ]; for (var draft of Drafts.getAll()) { assert.deepEqual(draft, orderedDrafts.shift()); } // All drafts were examined assert.equal(orderedDrafts.length, 0); }); }); suite('getAllThreadless()>', function() { setup(function() { [ threadDraft1, draft1, draft2 ].forEach(Drafts.add, Drafts); }); teardown(function() { Drafts.clear(); }); test('getAllThreadless returns only thread less drafts', function() { assert.deepEqual(Drafts.getAllThreadless(), [draft1, draft2]); }); }); suite('clear() >', function() { suiteSetup(function() { [threadDraft1, threadDraft2, threadDraft3, threadDraft4].forEach( Drafts.add, Drafts ); }); test('clear the entire draft index', function() { Drafts.clear(); assert.isNull(Drafts.byThreadId(42)); assert.isNull(Drafts.byThreadId(1)); assert.isNull(Drafts.byThreadId(2)); }); }); suite('Draft', function() { var draft; test('Draft', function() { draft = new Draft(); assert.ok(Draft); }); test('Draft from empty object', function() { draft = new Draft([]); assert.deepEqual(draft.recipients, []); assert.deepEqual(draft.content, []); assert.equal(draft.threadId, null); assert.isFalse(draft.isEdited); }); test('Draft from Draft object', function() { draft = new Draft(threadDraft1); assert.deepEqual(draft.recipients, ['555', '666']); assert.deepEqual(draft.content, ['This is a draft message']); assert.equal(draft.timestamp, 1); assert.equal(draft.threadId, 42); assert.equal(draft.type, 'sms'); assert.isFalse(draft.isEdited); }); test('Draft with explicit valid id', function() { draft = new Draft({ id: 101, recipients: [], content: ['An explicit id'], type: 'sms' }); assert.equal(draft.id, 101); }); test('Draft with explicit invalid id', function() { try { draft = new Draft({ id: '101', recipients: [], content: ['An explicit id'], timestamp: Date.now(), type: 'sms' }); assert.ok(false); } catch (e) { assert.ok(true); } }); }); suite('Storage and Retrieval', function() { setup(function() { Drafts.clear(); this.sinon.useFakeTimers(); }); test('Load drafts, has stored data', function(done) { this.sinon.stub(asyncStorage, 'getItem').yields([ [42, [threadDraft1]], [44, [threadDraft2]], [null, [draft1, draft2]] ]); Drafts.request().then(function() { assert.deepEqual(Drafts.byThreadId(42), threadDraft1); assert.deepEqual(Drafts.byThreadId(44), threadDraft2); var threadLessDrafts = Array.from(Drafts.getAll()).filter( (draft) => !draft.threadId ); assert.equal(threadLessDrafts.length, 2); assert.isNull(Drafts.byThreadId(5)); }).then(done, done); }); test('Load, clear, restore drafts', function(done) { this.sinon.stub(asyncStorage, 'getItem').yields([ [42, [threadDraft1]], [44, [threadDraft2]], [null, [draft1, draft2]] ]); // Load Drafts.request().then(function() { assert.equal(Drafts.size, 3); assert.isTrue(asyncStorage.getItem.calledOnce); // Clear (This will set isCached = false) Drafts.clear(); assert.equal(Drafts.size, 0); // Restore return Drafts.request().then(function() { assert.equal(Drafts.size, 3); assert.isTrue(asyncStorage.getItem.calledTwice); }); }).then(done, done); }); test('Load drafts, has no stored data', function(done) { this.sinon.stub(asyncStorage, 'getItem').yields([]); Drafts.request().then(function() { assert.equal(Drafts.size, 0); }).then(done, done); }); suite('drafts index cache >', function() { setup(function() { this.sinon.stub(asyncStorage, 'getItem'). withArgs('draft index'). yields(null); }); test('is properly populated', function(done) { Drafts.request().then(() => { sinon.assert.calledOnce(asyncStorage.getItem); return Drafts.request(); }).then(() => { sinon.assert.calledOnce(asyncStorage.getItem); }).then(done, done); }); test('is properly refreshed with force parameter', function(done) { Drafts.request().then(() => { sinon.assert.calledOnce(asyncStorage.getItem); // "Force" parameter should re-request data from asyncStorage even if // it's already cached return Drafts.request(true); }).then(() => { sinon.assert.calledTwice(asyncStorage.getItem); }).then(done, done); }); }); }); });