undoredomanager_test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.editor.plugins.UndoRedoManagerTest');
  15. goog.setTestOnly('goog.editor.plugins.UndoRedoManagerTest');
  16. goog.require('goog.editor.plugins.UndoRedoManager');
  17. goog.require('goog.editor.plugins.UndoRedoState');
  18. goog.require('goog.events');
  19. goog.require('goog.testing.StrictMock');
  20. goog.require('goog.testing.jsunit');
  21. var mockState1;
  22. var mockState2;
  23. var mockState3;
  24. var states;
  25. var manager;
  26. var stateChangeCount;
  27. var beforeUndoCount;
  28. var beforeRedoCount;
  29. var preventDefault;
  30. function setUp() {
  31. manager = new goog.editor.plugins.UndoRedoManager();
  32. stateChangeCount = 0;
  33. goog.events.listen(
  34. manager, goog.editor.plugins.UndoRedoManager.EventType.STATE_CHANGE,
  35. function() { stateChangeCount++; });
  36. beforeUndoCount = 0;
  37. preventDefault = false;
  38. goog.events.listen(
  39. manager, goog.editor.plugins.UndoRedoManager.EventType.BEFORE_UNDO,
  40. function(e) {
  41. beforeUndoCount++;
  42. if (preventDefault) {
  43. e.preventDefault();
  44. }
  45. });
  46. beforeRedoCount = 0;
  47. goog.events.listen(
  48. manager, goog.editor.plugins.UndoRedoManager.EventType.BEFORE_REDO,
  49. function(e) {
  50. beforeRedoCount++;
  51. if (preventDefault) {
  52. e.preventDefault();
  53. }
  54. });
  55. mockState1 = new goog.testing.StrictMock(goog.editor.plugins.UndoRedoState);
  56. mockState2 = new goog.testing.StrictMock(goog.editor.plugins.UndoRedoState);
  57. mockState3 = new goog.testing.StrictMock(goog.editor.plugins.UndoRedoState);
  58. states = [mockState1, mockState2, mockState3];
  59. mockState1.equals = mockState2.equals =
  60. mockState3.equals = function(state) { return this == state; };
  61. mockState1.isAsynchronous = mockState2.isAsynchronous =
  62. mockState3.isAsynchronous = function() { return false; };
  63. }
  64. function tearDown() {
  65. goog.events.removeAll(manager);
  66. manager.dispose();
  67. }
  68. /**
  69. * Adds all the mock states to the undo-redo manager.
  70. */
  71. function addStatesToManager() {
  72. manager.addState(states[0]);
  73. for (var i = 1; i < states.length; i++) {
  74. var state = states[i];
  75. manager.addState(state);
  76. }
  77. stateChangeCount = 0;
  78. }
  79. /**
  80. * Resets all mock states so that they are ready for testing.
  81. */
  82. function resetStates() {
  83. for (var i = 0; i < states.length; i++) {
  84. states[i].$reset();
  85. }
  86. }
  87. function testSetMaxUndoDepth() {
  88. manager.setMaxUndoDepth(2);
  89. addStatesToManager();
  90. assertArrayEquals(
  91. 'Undo stack must contain only the two most recent states.',
  92. [mockState2, mockState3], manager.undoStack_);
  93. }
  94. function testAddState() {
  95. var stateAddedCount = 0;
  96. goog.events.listen(
  97. manager, goog.editor.plugins.UndoRedoManager.EventType.STATE_ADDED,
  98. function() { stateAddedCount++; });
  99. manager.addState(mockState1);
  100. assertArrayEquals(
  101. 'Undo stack must contain added state.', [mockState1], manager.undoStack_);
  102. assertEquals(
  103. 'Manager must dispatch one state change event on ' +
  104. 'undo stack 0->1 transition.',
  105. 1, stateChangeCount);
  106. assertEquals('State added must have dispatched once.', 1, stateAddedCount);
  107. mockState1.$reset();
  108. // Test adding same state twice.
  109. manager.addState(mockState1);
  110. assertArrayEquals(
  111. 'Undo stack must not contain two equal, sequential states.', [mockState1],
  112. manager.undoStack_);
  113. assertEquals(
  114. 'Manager must not dispatch state change event when nothing is ' +
  115. 'added to the stack.',
  116. 1, stateChangeCount);
  117. assertEquals('State added must have dispatched once.', 1, stateAddedCount);
  118. // Test adding a second state.
  119. manager.addState(mockState2);
  120. assertArrayEquals(
  121. 'Undo stack must contain both states.', [mockState1, mockState2],
  122. manager.undoStack_);
  123. assertEquals(
  124. 'Manager must not dispatch state change event when second ' +
  125. 'state is added to the stack.',
  126. 1, stateChangeCount);
  127. assertEquals('State added must have dispatched twice.', 2, stateAddedCount);
  128. // Test adding a state when there is state on the redo stack.
  129. manager.undo();
  130. assertEquals(
  131. 'Manager must dispatch state change when redo stack goes to 1.', 2,
  132. stateChangeCount);
  133. manager.addState(mockState3);
  134. assertArrayEquals(
  135. 'Undo stack must contain states 1 and 3.', [mockState1, mockState3],
  136. manager.undoStack_);
  137. assertEquals(
  138. 'Manager must dispatch state change event when redo stack ' +
  139. 'goes to zero.',
  140. 3, stateChangeCount);
  141. assertEquals(
  142. 'State added must have dispatched three times.', 3, stateAddedCount);
  143. }
  144. function testHasState() {
  145. assertFalse('New manager must have no undo state.', manager.hasUndoState());
  146. assertFalse('New manager must have no redo state.', manager.hasRedoState());
  147. manager.addState(mockState1);
  148. assertTrue('Manager must have only undo state.', manager.hasUndoState());
  149. assertFalse('Manager must have no redo state.', manager.hasRedoState());
  150. manager.undo();
  151. assertFalse('Manager must have no undo state.', manager.hasUndoState());
  152. assertTrue('Manager must have only redo state.', manager.hasRedoState());
  153. }
  154. function testClearHistory() {
  155. addStatesToManager();
  156. manager.undo();
  157. stateChangeCount = 0;
  158. manager.clearHistory();
  159. assertFalse('Undo stack must be empty.', manager.hasUndoState());
  160. assertFalse('Redo stack must be empty.', manager.hasRedoState());
  161. assertEquals(
  162. 'State change count must be 1 after clear history.', 1, stateChangeCount);
  163. manager.clearHistory();
  164. assertEquals(
  165. 'Repeated clearHistory must not change state change count.', 1,
  166. stateChangeCount);
  167. }
  168. function testUndo() {
  169. addStatesToManager();
  170. mockState3.undo();
  171. mockState3.$replay();
  172. manager.undo();
  173. assertEquals(
  174. 'Adding first item to redo stack must dispatch state change.', 1,
  175. stateChangeCount);
  176. assertEquals('Undo must cause before action to dispatch', 1, beforeUndoCount);
  177. mockState3.$verify();
  178. preventDefault = true;
  179. mockState2.$replay();
  180. manager.undo();
  181. assertEquals(
  182. 'No stack transitions between 0 and 1, must not dispatch ' +
  183. 'state change.',
  184. 1, stateChangeCount);
  185. assertEquals('Undo must cause before action to dispatch', 2, beforeUndoCount);
  186. mockState2.$verify(); // Verify that undo was prevented.
  187. preventDefault = false;
  188. mockState1.undo();
  189. mockState1.$replay();
  190. manager.undo();
  191. assertEquals(
  192. 'Doing last undo operation must dispatch state change.', 2,
  193. stateChangeCount);
  194. assertEquals('Undo must cause before action to dispatch', 3, beforeUndoCount);
  195. mockState1.$verify();
  196. }
  197. function testUndo_Asynchronous() {
  198. // Using a stub instead of a mock here so that the state can behave as an
  199. // EventTarget and dispatch events.
  200. var stubState = new goog.editor.plugins.UndoRedoState(true);
  201. var undoCalled = false;
  202. stubState.undo = function() { undoCalled = true; };
  203. stubState.redo = goog.nullFunction;
  204. stubState.equals = function() { return false; };
  205. manager.addState(mockState2);
  206. manager.addState(mockState1);
  207. manager.addState(stubState);
  208. manager.undo();
  209. assertTrue('undoCalled must be true (undo must be called).', undoCalled);
  210. assertEquals('Undo must cause before action to dispatch', 1, beforeUndoCount);
  211. // Calling undo shouldn't actually undo since the first async undo hasn't
  212. // fired an event yet.
  213. mockState1.$replay();
  214. manager.undo();
  215. mockState1.$verify();
  216. assertEquals(
  217. 'Before action must not dispatch for pending undo.', 1, beforeUndoCount);
  218. // Dispatching undo completed on first undo, should cause the second pending
  219. // undo to happen.
  220. mockState1.$reset();
  221. mockState1.undo();
  222. mockState1.$replay();
  223. mockState2.$replay(); // Nothing should happen to mockState2.
  224. stubState.dispatchEvent(goog.editor.plugins.UndoRedoState.ACTION_COMPLETED);
  225. mockState1.$verify();
  226. mockState2.$verify();
  227. assertEquals(
  228. 'Second undo must cause before action to dispatch', 2, beforeUndoCount);
  229. // Test last undo.
  230. mockState2.$reset();
  231. mockState2.undo();
  232. mockState2.$replay();
  233. manager.undo();
  234. mockState2.$verify();
  235. assertEquals(
  236. 'Third undo must cause before action to dispatch', 3, beforeUndoCount);
  237. }
  238. function testRedo() {
  239. addStatesToManager();
  240. manager.undo();
  241. manager.undo();
  242. manager.undo();
  243. resetStates();
  244. stateChangeCount = 0;
  245. mockState1.redo();
  246. mockState1.$replay();
  247. manager.redo();
  248. assertEquals(
  249. 'Pushing first item onto undo stack during redo must dispatch ' +
  250. 'state change.',
  251. 1, stateChangeCount);
  252. assertEquals(
  253. 'First redo must cause before action to dispatch', 1, beforeRedoCount);
  254. mockState1.$verify();
  255. preventDefault = true;
  256. mockState2.$replay();
  257. manager.redo();
  258. assertEquals(
  259. 'No stack transitions between 0 and 1, must not dispatch ' +
  260. 'state change.',
  261. 1, stateChangeCount);
  262. assertEquals(
  263. 'Second redo must cause before action to dispatch', 2, beforeRedoCount);
  264. mockState2.$verify(); // Verify that redo was prevented.
  265. preventDefault = false;
  266. mockState3.redo();
  267. mockState3.$replay();
  268. manager.redo();
  269. assertEquals(
  270. 'Removing last item from redo stack must dispatch state change.', 2,
  271. stateChangeCount);
  272. assertEquals(
  273. 'Third redo must cause before action to dispatch', 3, beforeRedoCount);
  274. mockState3.$verify();
  275. mockState3.$reset();
  276. mockState3.undo();
  277. mockState3.$replay();
  278. manager.undo();
  279. assertEquals(
  280. 'Putting item on redo stack must dispatch state change.', 3,
  281. stateChangeCount);
  282. assertEquals('Undo must cause before action to dispatch', 4, beforeUndoCount);
  283. mockState3.$verify();
  284. }
  285. function testRedo_Asynchronous() {
  286. var stubState = new goog.editor.plugins.UndoRedoState(true);
  287. var redoCalled = false;
  288. stubState.redo = function() { redoCalled = true; };
  289. stubState.undo = goog.nullFunction;
  290. stubState.equals = function() { return false; };
  291. manager.addState(stubState);
  292. manager.addState(mockState1);
  293. manager.addState(mockState2);
  294. manager.undo();
  295. manager.undo();
  296. manager.undo();
  297. stubState.dispatchEvent(goog.editor.plugins.UndoRedoState.ACTION_COMPLETED);
  298. resetStates();
  299. manager.redo();
  300. assertTrue('redoCalled must be true (redo must be called).', redoCalled);
  301. // Calling redo shouldn't actually redo since the first async redo hasn't
  302. // fired an event yet.
  303. mockState1.$replay();
  304. manager.redo();
  305. mockState1.$verify();
  306. // Dispatching redo completed on first redo, should cause the second pending
  307. // redo to happen.
  308. mockState1.$reset();
  309. mockState1.redo();
  310. mockState1.$replay();
  311. mockState2.$replay(); // Nothing should happen to mockState1.
  312. stubState.dispatchEvent(goog.editor.plugins.UndoRedoState.ACTION_COMPLETED);
  313. mockState1.$verify();
  314. mockState2.$verify();
  315. // Test last redo.
  316. mockState2.$reset();
  317. mockState2.redo();
  318. mockState2.$replay();
  319. manager.redo();
  320. mockState2.$verify();
  321. }
  322. function testUndoAndRedoPeek() {
  323. addStatesToManager();
  324. manager.undo();
  325. assertEquals(
  326. 'redoPeek must return the top of the redo stack.',
  327. manager.redoStack_[manager.redoStack_.length - 1], manager.redoPeek());
  328. assertEquals(
  329. 'undoPeek must return the top of the undo stack.',
  330. manager.undoStack_[manager.undoStack_.length - 1], manager.undoPeek());
  331. }