123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- // Copyright 2008 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- goog.provide('goog.editor.plugins.UndoRedoManagerTest');
- goog.setTestOnly('goog.editor.plugins.UndoRedoManagerTest');
- goog.require('goog.editor.plugins.UndoRedoManager');
- goog.require('goog.editor.plugins.UndoRedoState');
- goog.require('goog.events');
- goog.require('goog.testing.StrictMock');
- goog.require('goog.testing.jsunit');
- var mockState1;
- var mockState2;
- var mockState3;
- var states;
- var manager;
- var stateChangeCount;
- var beforeUndoCount;
- var beforeRedoCount;
- var preventDefault;
- function setUp() {
- manager = new goog.editor.plugins.UndoRedoManager();
- stateChangeCount = 0;
- goog.events.listen(
- manager, goog.editor.plugins.UndoRedoManager.EventType.STATE_CHANGE,
- function() { stateChangeCount++; });
- beforeUndoCount = 0;
- preventDefault = false;
- goog.events.listen(
- manager, goog.editor.plugins.UndoRedoManager.EventType.BEFORE_UNDO,
- function(e) {
- beforeUndoCount++;
- if (preventDefault) {
- e.preventDefault();
- }
- });
- beforeRedoCount = 0;
- goog.events.listen(
- manager, goog.editor.plugins.UndoRedoManager.EventType.BEFORE_REDO,
- function(e) {
- beforeRedoCount++;
- if (preventDefault) {
- e.preventDefault();
- }
- });
- mockState1 = new goog.testing.StrictMock(goog.editor.plugins.UndoRedoState);
- mockState2 = new goog.testing.StrictMock(goog.editor.plugins.UndoRedoState);
- mockState3 = new goog.testing.StrictMock(goog.editor.plugins.UndoRedoState);
- states = [mockState1, mockState2, mockState3];
- mockState1.equals = mockState2.equals =
- mockState3.equals = function(state) { return this == state; };
- mockState1.isAsynchronous = mockState2.isAsynchronous =
- mockState3.isAsynchronous = function() { return false; };
- }
- function tearDown() {
- goog.events.removeAll(manager);
- manager.dispose();
- }
- /**
- * Adds all the mock states to the undo-redo manager.
- */
- function addStatesToManager() {
- manager.addState(states[0]);
- for (var i = 1; i < states.length; i++) {
- var state = states[i];
- manager.addState(state);
- }
- stateChangeCount = 0;
- }
- /**
- * Resets all mock states so that they are ready for testing.
- */
- function resetStates() {
- for (var i = 0; i < states.length; i++) {
- states[i].$reset();
- }
- }
- function testSetMaxUndoDepth() {
- manager.setMaxUndoDepth(2);
- addStatesToManager();
- assertArrayEquals(
- 'Undo stack must contain only the two most recent states.',
- [mockState2, mockState3], manager.undoStack_);
- }
- function testAddState() {
- var stateAddedCount = 0;
- goog.events.listen(
- manager, goog.editor.plugins.UndoRedoManager.EventType.STATE_ADDED,
- function() { stateAddedCount++; });
- manager.addState(mockState1);
- assertArrayEquals(
- 'Undo stack must contain added state.', [mockState1], manager.undoStack_);
- assertEquals(
- 'Manager must dispatch one state change event on ' +
- 'undo stack 0->1 transition.',
- 1, stateChangeCount);
- assertEquals('State added must have dispatched once.', 1, stateAddedCount);
- mockState1.$reset();
- // Test adding same state twice.
- manager.addState(mockState1);
- assertArrayEquals(
- 'Undo stack must not contain two equal, sequential states.', [mockState1],
- manager.undoStack_);
- assertEquals(
- 'Manager must not dispatch state change event when nothing is ' +
- 'added to the stack.',
- 1, stateChangeCount);
- assertEquals('State added must have dispatched once.', 1, stateAddedCount);
- // Test adding a second state.
- manager.addState(mockState2);
- assertArrayEquals(
- 'Undo stack must contain both states.', [mockState1, mockState2],
- manager.undoStack_);
- assertEquals(
- 'Manager must not dispatch state change event when second ' +
- 'state is added to the stack.',
- 1, stateChangeCount);
- assertEquals('State added must have dispatched twice.', 2, stateAddedCount);
- // Test adding a state when there is state on the redo stack.
- manager.undo();
- assertEquals(
- 'Manager must dispatch state change when redo stack goes to 1.', 2,
- stateChangeCount);
- manager.addState(mockState3);
- assertArrayEquals(
- 'Undo stack must contain states 1 and 3.', [mockState1, mockState3],
- manager.undoStack_);
- assertEquals(
- 'Manager must dispatch state change event when redo stack ' +
- 'goes to zero.',
- 3, stateChangeCount);
- assertEquals(
- 'State added must have dispatched three times.', 3, stateAddedCount);
- }
- function testHasState() {
- assertFalse('New manager must have no undo state.', manager.hasUndoState());
- assertFalse('New manager must have no redo state.', manager.hasRedoState());
- manager.addState(mockState1);
- assertTrue('Manager must have only undo state.', manager.hasUndoState());
- assertFalse('Manager must have no redo state.', manager.hasRedoState());
- manager.undo();
- assertFalse('Manager must have no undo state.', manager.hasUndoState());
- assertTrue('Manager must have only redo state.', manager.hasRedoState());
- }
- function testClearHistory() {
- addStatesToManager();
- manager.undo();
- stateChangeCount = 0;
- manager.clearHistory();
- assertFalse('Undo stack must be empty.', manager.hasUndoState());
- assertFalse('Redo stack must be empty.', manager.hasRedoState());
- assertEquals(
- 'State change count must be 1 after clear history.', 1, stateChangeCount);
- manager.clearHistory();
- assertEquals(
- 'Repeated clearHistory must not change state change count.', 1,
- stateChangeCount);
- }
- function testUndo() {
- addStatesToManager();
- mockState3.undo();
- mockState3.$replay();
- manager.undo();
- assertEquals(
- 'Adding first item to redo stack must dispatch state change.', 1,
- stateChangeCount);
- assertEquals('Undo must cause before action to dispatch', 1, beforeUndoCount);
- mockState3.$verify();
- preventDefault = true;
- mockState2.$replay();
- manager.undo();
- assertEquals(
- 'No stack transitions between 0 and 1, must not dispatch ' +
- 'state change.',
- 1, stateChangeCount);
- assertEquals('Undo must cause before action to dispatch', 2, beforeUndoCount);
- mockState2.$verify(); // Verify that undo was prevented.
- preventDefault = false;
- mockState1.undo();
- mockState1.$replay();
- manager.undo();
- assertEquals(
- 'Doing last undo operation must dispatch state change.', 2,
- stateChangeCount);
- assertEquals('Undo must cause before action to dispatch', 3, beforeUndoCount);
- mockState1.$verify();
- }
- function testUndo_Asynchronous() {
- // Using a stub instead of a mock here so that the state can behave as an
- // EventTarget and dispatch events.
- var stubState = new goog.editor.plugins.UndoRedoState(true);
- var undoCalled = false;
- stubState.undo = function() { undoCalled = true; };
- stubState.redo = goog.nullFunction;
- stubState.equals = function() { return false; };
- manager.addState(mockState2);
- manager.addState(mockState1);
- manager.addState(stubState);
- manager.undo();
- assertTrue('undoCalled must be true (undo must be called).', undoCalled);
- assertEquals('Undo must cause before action to dispatch', 1, beforeUndoCount);
- // Calling undo shouldn't actually undo since the first async undo hasn't
- // fired an event yet.
- mockState1.$replay();
- manager.undo();
- mockState1.$verify();
- assertEquals(
- 'Before action must not dispatch for pending undo.', 1, beforeUndoCount);
- // Dispatching undo completed on first undo, should cause the second pending
- // undo to happen.
- mockState1.$reset();
- mockState1.undo();
- mockState1.$replay();
- mockState2.$replay(); // Nothing should happen to mockState2.
- stubState.dispatchEvent(goog.editor.plugins.UndoRedoState.ACTION_COMPLETED);
- mockState1.$verify();
- mockState2.$verify();
- assertEquals(
- 'Second undo must cause before action to dispatch', 2, beforeUndoCount);
- // Test last undo.
- mockState2.$reset();
- mockState2.undo();
- mockState2.$replay();
- manager.undo();
- mockState2.$verify();
- assertEquals(
- 'Third undo must cause before action to dispatch', 3, beforeUndoCount);
- }
- function testRedo() {
- addStatesToManager();
- manager.undo();
- manager.undo();
- manager.undo();
- resetStates();
- stateChangeCount = 0;
- mockState1.redo();
- mockState1.$replay();
- manager.redo();
- assertEquals(
- 'Pushing first item onto undo stack during redo must dispatch ' +
- 'state change.',
- 1, stateChangeCount);
- assertEquals(
- 'First redo must cause before action to dispatch', 1, beforeRedoCount);
- mockState1.$verify();
- preventDefault = true;
- mockState2.$replay();
- manager.redo();
- assertEquals(
- 'No stack transitions between 0 and 1, must not dispatch ' +
- 'state change.',
- 1, stateChangeCount);
- assertEquals(
- 'Second redo must cause before action to dispatch', 2, beforeRedoCount);
- mockState2.$verify(); // Verify that redo was prevented.
- preventDefault = false;
- mockState3.redo();
- mockState3.$replay();
- manager.redo();
- assertEquals(
- 'Removing last item from redo stack must dispatch state change.', 2,
- stateChangeCount);
- assertEquals(
- 'Third redo must cause before action to dispatch', 3, beforeRedoCount);
- mockState3.$verify();
- mockState3.$reset();
- mockState3.undo();
- mockState3.$replay();
- manager.undo();
- assertEquals(
- 'Putting item on redo stack must dispatch state change.', 3,
- stateChangeCount);
- assertEquals('Undo must cause before action to dispatch', 4, beforeUndoCount);
- mockState3.$verify();
- }
- function testRedo_Asynchronous() {
- var stubState = new goog.editor.plugins.UndoRedoState(true);
- var redoCalled = false;
- stubState.redo = function() { redoCalled = true; };
- stubState.undo = goog.nullFunction;
- stubState.equals = function() { return false; };
- manager.addState(stubState);
- manager.addState(mockState1);
- manager.addState(mockState2);
- manager.undo();
- manager.undo();
- manager.undo();
- stubState.dispatchEvent(goog.editor.plugins.UndoRedoState.ACTION_COMPLETED);
- resetStates();
- manager.redo();
- assertTrue('redoCalled must be true (redo must be called).', redoCalled);
- // Calling redo shouldn't actually redo since the first async redo hasn't
- // fired an event yet.
- mockState1.$replay();
- manager.redo();
- mockState1.$verify();
- // Dispatching redo completed on first redo, should cause the second pending
- // redo to happen.
- mockState1.$reset();
- mockState1.redo();
- mockState1.$replay();
- mockState2.$replay(); // Nothing should happen to mockState1.
- stubState.dispatchEvent(goog.editor.plugins.UndoRedoState.ACTION_COMPLETED);
- mockState1.$verify();
- mockState2.$verify();
- // Test last redo.
- mockState2.$reset();
- mockState2.redo();
- mockState2.$replay();
- manager.redo();
- mockState2.$verify();
- }
- function testUndoAndRedoPeek() {
- addStatesToManager();
- manager.undo();
- assertEquals(
- 'redoPeek must return the top of the redo stack.',
- manager.redoStack_[manager.redoStack_.length - 1], manager.redoPeek());
- assertEquals(
- 'undoPeek must return the top of the undo stack.',
- manager.undoStack_[manager.undoStack_.length - 1], manager.undoPeek());
- }
|