/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Tests for the CalTransactionManager and the various CalTransaction instances. */ const { CalendarTestUtils } = ChromeUtils.import( "resource://testing-common/calendar/CalendarTestUtils.jsm" ); const { CalEvent } = ChromeUtils.import("resource:///modules/CalEvent.jsm"); const { CalTodo } = ChromeUtils.import("resource:///modules/CalTodo.jsm"); const { CalTransactionManager, CalTransaction, CalBatchTransaction, CalAddTransaction, CalModifyTransaction, CalDeleteTransaction, } = ChromeUtils.import("resource:///modules/CalTransactionManager.jsm"); /** * Records the number of times doTransction() and undoTransction() is called. */ class MockCalTransaction extends CalTransaction { /** * The number of times doTransaction() was called. * * @type {number} */ done = 0; /** * The number of times undoTransaction() was called. */ undone = 0; _writable; constructor(writable = true) { super(); this._writable = writable; } canWrite() { return this._writable; } async doTransaction() { this.done++; } async undoTransaction() { this.undone++; } } /** * Tests a list of CalMockTransactions have the expected "done" and "undone" * values. * * @param {CalMockTransaction[][]} batches The transaction batches to check. * @param {number[][][]} expected - A 3 dimensional array containing * the expected "done" and "undone" values for each transaction in each batch * to be tested. */ function doBatchTest(batches, expected) { for (let [batch, transactions] of batches.entries()) { for (let [index, trn] of transactions.entries()) { let [doneCount, undoneCount] = expected[batch][index]; Assert.equal( trn.done, doneCount, `batch ${batch}, transaction ${index} doTransaction() called ${doneCount} times` ); Assert.equal( trn.undone, undoneCount, `batch ${batch}, transaction ${index} undoTransaction() called ${undoneCount} times` ); } } } add_setup(async function () { await new Promise(resolve => do_load_calmgr(resolve)); }); /** * Tests the CalTransactionManager methods work as expected. */ add_task(async function testCalTransactionManager() { let manager = new CalTransactionManager(); Assert.ok(!manager.canUndo(), "canUndo() returns false with an empty undo stack"); Assert.ok(!manager.canRedo(), "canRedo() returns false with an empty redo stack"); Assert.ok(!manager.peekUndoStack(), "peekUndoStack() returns nothing with an empty undo stack"); Assert.ok(!manager.peekRedoStack(), "peekRedoStack() returns nothing with an empty redo stack"); info("calling CalTransactionManager.commit()"); let trn = new MockCalTransaction(); await manager.commit(trn); Assert.equal(trn.done, 1, "doTransaction() called once"); Assert.equal(trn.undone, 0, "undoTransaction() was not called"); Assert.ok(manager.canUndo(), "canUndo() returned true"); Assert.ok(!manager.canRedo(), "canRedo() returned false"); Assert.equal(manager.peekUndoStack(), trn, "peekUndoStack() returned the transaction"); Assert.ok(!manager.peekRedoStack(), "peekRedoStack() returned nothing"); info("calling CalTransactionManager.undo()"); await manager.undo(); Assert.equal(trn.done, 1, "doTransaction() was not called again"); Assert.equal(trn.undone, 1, "undoTransaction() was called once"); Assert.ok(!manager.canUndo(), "canUndo() returned false"); Assert.ok(manager.canRedo(), "canRedo() returned true"); Assert.ok(!manager.peekUndoStack(), "peekUndoStack() returned nothing"); Assert.equal(manager.peekRedoStack(), trn, "peekRedoStack() returned the transaction"); info("calling CalTransactionManager.redo()"); await manager.redo(); Assert.equal(trn.done, 2, "doTransaction() was called again"); Assert.equal(trn.undone, 1, "undoTransaction() was not called again"); Assert.ok(manager.canUndo(), "canUndo() returned true"); Assert.ok(!manager.canRedo(), "canRedo() returned false"); Assert.equal(manager.peekUndoStack(), trn, "peekUndoStack() returned the transaction"); Assert.ok(!manager.peekRedoStack(), "peekRedoStack() returned nothing"); info("testing CalTransactionManager.beginBatch()"); manager = new CalTransactionManager(); let batch = manager.beginBatch(); Assert.ok(batch instanceof CalBatchTransaction, "beginBatch() returned a CalBatchTransaction"); Assert.equal(manager.undoStack[0], batch, "the CalBatchTransaction is on the undo stack"); }); /** * Tests the BatchTransaction works as expected. */ add_task(async function testBatchTransaction() { let batch = new CalBatchTransaction(); Assert.ok(!batch.canWrite(), "canWrite() returns false for an empty BatchTransaction"); await batch.commit(new MockCalTransaction()); await batch.commit(new MockCalTransaction(false)); await batch.commit(new MockCalTransaction()); Assert.ok(!batch.canWrite(), "canWrite() returns false if any transaction is not writable"); let transactions = [new MockCalTransaction(), new MockCalTransaction(), new MockCalTransaction()]; batch = new CalBatchTransaction(); for (let trn of transactions) { await batch.commit(trn); } Assert.ok(batch.canWrite(), "canWrite() returns true when all transactions are writable"); info("testing commit() calls doTransaction() on each transaction in batch"); doBatchTest( [transactions], [ [ [1, 0], [1, 0], [1, 0], ], ] ); await batch.undoTransaction(); info("testing undoTransaction() called on each transaction in batch"); doBatchTest( [transactions], [ [ [1, 1], [1, 1], [1, 1], ], ] ); await batch.doTransaction(); info("testing doTransaction() called again on each transaction in batch"); doBatchTest( [transactions], [ [ [2, 1], [2, 1], [2, 1], ], ] ); }); /** * Tests that executing multiple batch transactions in sequence works. */ add_task(async function testSequentialBatchTransactions() { let manager = new CalTransactionManager(); let batchTransactions = [ [new MockCalTransaction(), new MockCalTransaction(), new MockCalTransaction()], [new MockCalTransaction(), new MockCalTransaction(), new MockCalTransaction()], [new MockCalTransaction(), new MockCalTransaction(), new MockCalTransaction()], ]; let batch0 = manager.beginBatch(); for (let trn of batchTransactions[0]) { await batch0.commit(trn); } let batch1 = manager.beginBatch(); for (let trn of batchTransactions[1]) { await batch1.commit(trn); } let batch2 = manager.beginBatch(); for (let trn of batchTransactions[2]) { await batch2.commit(trn); } doBatchTest(batchTransactions, [ [ [1, 0], [1, 0], [1, 0], ], [ [1, 0], [1, 0], [1, 0], ], [ [1, 0], [1, 0], [1, 0], ], ]); // Undo the top most batch. await manager.undo(); doBatchTest(batchTransactions, [ [ [1, 0], [1, 0], [1, 0], ], [ [1, 0], [1, 0], [1, 0], ], [ [1, 1], [1, 1], [1, 1], ], ]); // Undo the next batch. await manager.undo(); doBatchTest(batchTransactions, [ [ [1, 0], [1, 0], [1, 0], ], [ [1, 1], [1, 1], [1, 1], ], [ [1, 1], [1, 1], [1, 1], ], ]); // Undo the last batch left. await manager.undo(); doBatchTest(batchTransactions, [ [ [1, 1], [1, 1], [1, 1], ], [ [1, 1], [1, 1], [1, 1], ], [ [1, 1], [1, 1], [1, 1], ], ]); // Redo the first batch. await manager.redo(); doBatchTest(batchTransactions, [ [ [2, 1], [2, 1], [2, 1], ], [ [1, 1], [1, 1], [1, 1], ], [ [1, 1], [1, 1], [1, 1], ], ]); // Redo the second batch. await manager.redo(); doBatchTest(batchTransactions, [ [ [2, 1], [2, 1], [2, 1], ], [ [2, 1], [2, 1], [2, 1], ], [ [1, 1], [1, 1], [1, 1], ], ]); // Redo the last batch. await manager.redo(); doBatchTest(batchTransactions, [ [ [2, 1], [2, 1], [2, 1], ], [ [2, 1], [2, 1], [2, 1], ], [ [2, 1], [2, 1], [2, 1], ], ]); }); /** * Tests CalAddTransaction executes and reverses as expected. */ add_task(async function testCalAddTransaction() { let calendar = CalendarTestUtils.createCalendar("Test", "memory"); let event = new CalEvent(); event.id = "test"; let trn = new CalAddTransaction(event, calendar, null, null); await trn.doTransaction(); let addedEvent = await calendar.getItem("test"); Assert.ok(!!addedEvent, "transaction added event to the calendar"); await trn.undoTransaction(); addedEvent = await calendar.getItem("test"); Assert.ok(!addedEvent, "transaction removed event from the calendar"); CalendarTestUtils.removeCalendar(calendar); }); /** * Tests CalModifyTransaction executes and reverses as expected. */ add_task(async function testCalModifyTransaction() { let calendar = CalendarTestUtils.createCalendar("Test", "memory"); let event = new CalEvent(); event.id = "test"; event.title = "Event"; let addedEvent = await calendar.addItem(event); Assert.ok(!!addedEvent, "event was added to the calendar"); let modifiedEvent = addedEvent.clone(); modifiedEvent.title = "Modified Event"; let trn = new CalModifyTransaction(modifiedEvent, calendar, addedEvent, null); await trn.doTransaction(); modifiedEvent = await calendar.getItem("test"); Assert.ok(!!modifiedEvent); Assert.equal(modifiedEvent.title, "Modified Event", "transaction modified event"); await trn.undoTransaction(); let revertedEvent = await calendar.getItem("test"); Assert.ok(!!revertedEvent); Assert.equal(revertedEvent.title, "Event", "transaction reverted event to original state"); CalendarTestUtils.removeCalendar(calendar); }); /** * Tests CalDeleteTransaction executes and reverses as expected. */ add_task(async function testCalDeleteTransaction() { let calendar = CalendarTestUtils.createCalendar("Test", "memory"); let event = new CalEvent(); event.id = "test"; event.title = "Event"; let addedEvent = await calendar.addItem(event); Assert.ok(!!addedEvent, "event was added to the calendar"); let trn = new CalDeleteTransaction(addedEvent, calendar, null, null); await trn.doTransaction(); let result = await calendar.getItem("test"); Assert.ok(!result, "event was deleted from the calendar"); await trn.undoTransaction(); let revertedEvent = await calendar.getItem("test"); Assert.ok(!!revertedEvent, "event was restored to the calendar"); CalendarTestUtils.removeCalendar(calendar); });