diff options
author | Paul Slaughter <pslaughter@gitlab.com> | 2019-08-01 12:31:13 -0500 |
---|---|---|
committer | Paul Slaughter <pslaughter@gitlab.com> | 2019-08-01 12:31:13 -0500 |
commit | 84b6c7a5f3bf3d6f96331d73225903d3fd92b4e2 (patch) | |
tree | 9cf524013fb1d488528aaa3fd831e253cee850ff /spec/frontend | |
parent | 536ebecf7eec95bd2cc1b542f1c35d70f814731e (diff) | |
download | gitlab-ce-84b6c7a5f3bf3d6f96331d73225903d3fd92b4e2.tar.gz |
Revert "Merge branch 'mh/editor-indents' into 'master'"revert-editor-indents
This reverts commit 28f2225bdfee4d353f07a766c6c8b29ba6181397, reversing
changes made to 96ae5bd83da31350e9856a290127d7aa1469710a.
Diffstat (limited to 'spec/frontend')
-rw-r--r-- | spec/frontend/helpers/indent_helper_spec.js | 371 | ||||
-rw-r--r-- | spec/frontend/lib/utils/common_utils_spec.js | 180 | ||||
-rw-r--r-- | spec/frontend/lib/utils/undo_stack_spec.js | 237 |
3 files changed, 0 insertions, 788 deletions
diff --git a/spec/frontend/helpers/indent_helper_spec.js b/spec/frontend/helpers/indent_helper_spec.js deleted file mode 100644 index fca12f0d1ef..00000000000 --- a/spec/frontend/helpers/indent_helper_spec.js +++ /dev/null @@ -1,371 +0,0 @@ -import IndentHelper from '~/helpers/indent_helper'; - -function createMockTextarea() { - const el = document.createElement('textarea'); - el.setCursor = pos => el.setSelectionRange(pos, pos); - el.setCursorToEnd = () => el.setCursor(el.value.length); - el.selection = () => [el.selectionStart, el.selectionEnd]; - el.cursor = () => { - const [start, end] = el.selection(); - return start === end ? start : undefined; - }; - return el; -} - -describe('indent_helper', () => { - let element; - let ih; - - beforeEach(() => { - element = createMockTextarea(); - ih = new IndentHelper(element); - }); - - describe('indents', () => { - describe('a single line', () => { - it('when on an empty line; and cursor follows', () => { - element.value = ''; - ih.indent(); - expect(element.value).toBe(' '); - expect(element.cursor()).toBe(4); - ih.indent(); - expect(element.value).toBe(' '); - expect(element.cursor()).toBe(8); - }); - - it('when at the start of a line; and cursor stays at start', () => { - element.value = 'foobar'; - element.setCursor(0); - ih.indent(); - expect(element.value).toBe(' foobar'); - expect(element.cursor()).toBe(4); - }); - - it('when the cursor is in the middle; and cursor follows', () => { - element.value = 'foobar'; - element.setCursor(3); - ih.indent(); - expect(element.value).toBe(' foobar'); - expect(element.cursor()).toBe(7); - }); - }); - - describe('several lines', () => { - it('when everything is selected; and everything remains selected', () => { - element.value = 'foo\nbar\nbaz'; - element.setSelectionRange(0, 11); - ih.indent(); - expect(element.value).toBe(' foo\n bar\n baz'); - expect(element.selection()).toEqual([0, 23]); - }); - - it('when all lines are partially selected; and the selection adapts', () => { - element.value = 'foo\nbar\nbaz'; - element.setSelectionRange(2, 9); - ih.indent(); - expect(element.value).toBe(' foo\n bar\n baz'); - expect(element.selection()).toEqual([6, 21]); - }); - - it('when some lines are entirely selected; and entire lines remain selected', () => { - element.value = 'foo\nbar\nbaz'; - element.setSelectionRange(4, 11); - ih.indent(); - expect(element.value).toBe('foo\n bar\n baz'); - expect(element.selection()).toEqual([4, 19]); - }); - - it('when some lines are partially selected; and the selection adapts', () => { - element.value = 'foo\nbar\nbaz'; - element.setSelectionRange(5, 9); - ih.indent(); - expect(element.value).toBe('foo\n bar\n baz'); - expect(element.selection()).toEqual([5 + 4, 9 + 2 * 4]); - }); - - it('having different indentation when some lines are entirely selected; and entire lines remain selected', () => { - element.value = ' foo\nbar\n baz'; - element.setSelectionRange(8, 19); - ih.indent(); - expect(element.value).toBe(' foo\n bar\n baz'); - expect(element.selection()).toEqual([8, 27]); - }); - - it('having different indentation when some lines are partially selected; and the selection adapts', () => { - element.value = ' foo\nbar\n baz'; - element.setSelectionRange(9, 14); - ih.indent(); - expect(element.value).toBe(' foo\n bar\n baz'); - expect(element.selection()).toEqual([13, 22]); - }); - }); - }); - - describe('unindents', () => { - describe('a single line', () => { - it('but does nothing if there is not indent', () => { - element.value = 'foobar'; - element.setCursor(2); - ih.unindent(); - expect(element.value).toBe('foobar'); - expect(element.cursor()).toBe(2); - }); - - it('but does nothing if there is a partial indent', () => { - element.value = ' foobar'; - element.setCursor(1); - ih.unindent(); - expect(element.value).toBe(' foobar'); - expect(element.cursor()).toBe(1); - }); - - it('when the cursor is in the line text; cursor follows', () => { - element.value = ' foobar'; - element.setCursor(6); - ih.unindent(); - expect(element.value).toBe('foobar'); - expect(element.cursor()).toBe(2); - }); - - it('when the cursor is in the indent; and cursor goes to start', () => { - element.value = ' foobar'; - element.setCursor(2); - ih.unindent(); - expect(element.value).toBe('foobar'); - expect(element.cursor()).toBe(0); - }); - - it('when the cursor is at line start; and cursor stays at start', () => { - element.value = ' foobar'; - element.setCursor(0); - ih.unindent(); - expect(element.value).toBe('foobar'); - expect(element.cursor()).toBe(0); - }); - - it('when a selection includes part of the indent and text', () => { - element.value = ' foobar'; - element.setSelectionRange(2, 8); - ih.unindent(); - expect(element.value).toBe('foobar'); - expect(element.selection()).toEqual([0, 4]); - }); - - it('when a selection includes part of the indent only', () => { - element.value = ' foobar'; - element.setSelectionRange(0, 4); - ih.unindent(); - expect(element.value).toBe('foobar'); - expect(element.cursor()).toBe(0); - - element.value = ' foobar'; - element.setSelectionRange(1, 3); - ih.unindent(); - expect(element.value).toBe('foobar'); - expect(element.cursor()).toBe(0); - }); - }); - - describe('several lines', () => { - it('when everything is selected', () => { - element.value = ' foo\n bar\n baz'; - element.setSelectionRange(0, 27); - ih.unindent(); - expect(element.value).toBe('foo\n bar\nbaz'); - expect(element.selection()).toEqual([0, 15]); - }); - - it('when all lines are partially selected', () => { - element.value = ' foo\n bar\n baz'; - element.setSelectionRange(5, 26); - ih.unindent(); - expect(element.value).toBe('foo\n bar\nbaz'); - expect(element.selection()).toEqual([1, 14]); - }); - - it('when all lines are entirely selected', () => { - element.value = ' foo\n bar\n baz'; - element.setSelectionRange(8, 27); - ih.unindent(); - expect(element.value).toBe(' foo\n bar\nbaz'); - expect(element.selection()).toEqual([8, 19]); - }); - - it('when some lines are entirely selected', () => { - element.value = ' foo\n bar\n baz'; - element.setSelectionRange(8, 27); - ih.unindent(); - expect(element.value).toBe(' foo\n bar\nbaz'); - expect(element.selection()).toEqual([8, 19]); - }); - - it('when some lines are partially selected', () => { - element.value = ' foo\n bar\n baz'; - element.setSelectionRange(17, 26); - ih.unindent(); - expect(element.value).toBe(' foo\n bar\nbaz'); - expect(element.selection()).toEqual([13, 18]); - }); - - it('when some lines are partially selected within their indents', () => { - element.value = ' foo\n bar\n baz'; - element.setSelectionRange(10, 22); - ih.unindent(); - expect(element.value).toBe(' foo\n bar\nbaz'); - expect(element.selection()).toEqual([8, 16]); - }); - }); - }); - - describe('newline', () => { - describe('on a single line', () => { - it('auto-indents the new line', () => { - element.value = 'foo\n bar\n baz\n qux'; - - element.setCursor(3); - ih.newline(); - expect(element.value).toBe('foo\n\n bar\n baz\n qux'); - expect(element.cursor()).toBe(4); - - element.setCursor(9); - ih.newline(); - expect(element.value).toBe('foo\n\n bar\n \n baz\n qux'); - expect(element.cursor()).toBe(11); - - element.setCursor(19); - ih.newline(); - expect(element.value).toBe('foo\n\n bar\n \n baz\n \n qux'); - expect(element.cursor()).toBe(24); - - element.setCursor(36); - ih.newline(); - expect(element.value).toBe('foo\n\n bar\n \n baz\n \n qux\n '); - expect(element.cursor()).toBe(45); - }); - - it('splits a line and auto-indents', () => { - element.value = ' foobar'; - element.setCursor(7); - ih.newline(); - expect(element.value).toBe(' foo\n bar'); - expect(element.cursor()).toBe(12); - }); - - it('replaces selection with an indented newline', () => { - element.value = ' foobarbaz'; - element.setSelectionRange(7, 10); - ih.newline(); - expect(element.value).toBe(' foo\n baz'); - expect(element.cursor()).toBe(12); - }); - }); - - it('on several lines.replaces selection with indented newline', () => { - element.value = ' foo\n bar\n baz'; - element.setSelectionRange(4, 17); - ih.newline(); - expect(element.value).toBe(' fo\n az'); - expect(element.cursor()).toBe(7); - }); - }); - - describe('backspace', () => { - let event; - - // This suite tests only the special indent-removing behaviour of the - // backspace() method, since non-special cases are handled natively as a - // backspace keypress. - - beforeEach(() => { - event = { preventDefault: jest.fn() }; - }); - - describe('on a single line', () => { - it('does nothing special if in the line text', () => { - element.value = ' foobar'; - element.setCursor(7); - ih.backspace(event); - expect(event.preventDefault).not.toHaveBeenCalled(); - }); - - it('does nothing special if after a non-leading indent', () => { - element.value = ' foo bar'; - element.setCursor(11); - ih.backspace(event); - expect(event.preventDefault).not.toHaveBeenCalled(); - }); - - it('deletes one leading indent', () => { - element.value = ' foo'; - element.setCursor(8); - ih.backspace(event); - expect(event.preventDefault).toHaveBeenCalled(); - expect(element.value).toBe(' foo'); - expect(element.cursor()).toBe(4); - }); - - it('does nothing if cursor is inside the leading indent', () => { - element.value = ' foo'; - element.setCursor(4); - ih.backspace(event); - expect(event.preventDefault).not.toHaveBeenCalled(); - }); - - it('does nothing if cursor is at the start of the line', () => { - element.value = ' foo'; - element.setCursor(0); - ih.backspace(event); - expect(event.preventDefault).not.toHaveBeenCalled(); - }); - - it('deletes one partial indent', () => { - element.value = ' foo'; - element.setCursor(6); - ih.backspace(event); - expect(event.preventDefault).toHaveBeenCalled(); - expect(element.value).toBe(' foo'); - expect(element.cursor()).toBe(4); - }); - - it('deletes indents sequentially', () => { - element.value = ' foo'; - element.setCursor(10); - ih.backspace(event); - ih.backspace(event); - ih.backspace(event); - expect(event.preventDefault).toHaveBeenCalled(); - expect(element.value).toBe('foo'); - expect(element.cursor()).toBe(0); - }); - }); - - describe('on several lines', () => { - it('deletes indent only on its own line', () => { - element.value = ' foo\n bar\n baz'; - element.setCursor(16); - ih.backspace(event); - expect(event.preventDefault).toHaveBeenCalled(); - expect(element.value).toBe(' foo\n bar\n baz'); - expect(element.cursor()).toBe(12); - }); - - it('has no special behaviour with any range selection', () => { - const text = ' foo\n bar\n baz'; - for (let start = 0; start < text.length; start += 1) { - for (let end = start + 1; end < text.length; end += 1) { - element.value = text; - element.setSelectionRange(start, end); - ih.backspace(event); - expect(event.preventDefault).not.toHaveBeenCalled(); - - // Ensure that the backspace() method doesn't change state - // In reality, these two statements won't hold because the browser - // will natively process the backspace event. - expect(element.value).toBe(text); - expect(element.selection()).toEqual([start, end]); - } - } - }); - }); - }); -}); diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js deleted file mode 100644 index e3d3b82d2f3..00000000000 --- a/spec/frontend/lib/utils/common_utils_spec.js +++ /dev/null @@ -1,180 +0,0 @@ -import * as cu from '~/lib/utils/common_utils'; - -const CMD_ENTITY = '⌘'; - -// Redefine `navigator.platform` because it's unsettable by default in JSDOM. -let platform; -Object.defineProperty(navigator, 'platform', { - configurable: true, - get: () => platform, - set: val => { - platform = val; - }, -}); - -describe('common_utils', () => { - describe('platform leader key helpers', () => { - const CTRL_EVENT = { ctrlKey: true }; - const META_EVENT = { metaKey: true }; - const BOTH_EVENT = { ctrlKey: true, metaKey: true }; - - it('should return "ctrl" if navigator.platform is unset', () => { - expect(cu.getPlatformLeaderKey()).toBe('ctrl'); - expect(cu.getPlatformLeaderKeyHTML()).toBe('Ctrl'); - expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(true); - expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(false); - expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true); - }); - - it('should return "meta" on MacOS', () => { - navigator.platform = 'MacIntel'; - expect(cu.getPlatformLeaderKey()).toBe('meta'); - expect(cu.getPlatformLeaderKeyHTML()).toBe(CMD_ENTITY); - expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(false); - expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(true); - expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true); - }); - - it('should return "ctrl" on Linux', () => { - navigator.platform = 'Linux is great'; - expect(cu.getPlatformLeaderKey()).toBe('ctrl'); - expect(cu.getPlatformLeaderKeyHTML()).toBe('Ctrl'); - expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(true); - expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(false); - expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true); - }); - - it('should return "ctrl" on Windows', () => { - navigator.platform = 'Win32'; - expect(cu.getPlatformLeaderKey()).toBe('ctrl'); - expect(cu.getPlatformLeaderKeyHTML()).toBe('Ctrl'); - expect(cu.isPlatformLeaderKey(CTRL_EVENT)).toBe(true); - expect(cu.isPlatformLeaderKey(META_EVENT)).toBe(false); - expect(cu.isPlatformLeaderKey(BOTH_EVENT)).toBe(true); - }); - }); - - describe('keystroke', () => { - const CODE_BACKSPACE = 8; - const CODE_TAB = 9; - const CODE_ENTER = 13; - const CODE_SPACE = 32; - const CODE_4 = 52; - const CODE_F = 70; - const CODE_Z = 90; - - // Helper function that quickly creates KeyboardEvents - const k = (code, modifiers = '') => ({ - keyCode: code, - which: code, - altKey: modifiers.includes('a'), - ctrlKey: modifiers.includes('c'), - metaKey: modifiers.includes('m'), - shiftKey: modifiers.includes('s'), - }); - - const EV_F = k(CODE_F); - const EV_ALT_F = k(CODE_F, 'a'); - const EV_CONTROL_F = k(CODE_F, 'c'); - const EV_META_F = k(CODE_F, 'm'); - const EV_SHIFT_F = k(CODE_F, 's'); - const EV_CONTROL_SHIFT_F = k(CODE_F, 'cs'); - const EV_ALL_F = k(CODE_F, 'scma'); - const EV_ENTER = k(CODE_ENTER); - const EV_TAB = k(CODE_TAB); - const EV_SPACE = k(CODE_SPACE); - const EV_BACKSPACE = k(CODE_BACKSPACE); - const EV_4 = k(CODE_4); - const EV_$ = k(CODE_4, 's'); - - const { keystroke } = cu; - - it('short-circuits with bad arguments', () => { - expect(keystroke()).toBe(false); - expect(keystroke({})).toBe(false); - }); - - it('handles keystrokes using key codes', () => { - // Test a letter key with modifiers - expect(keystroke(EV_F, CODE_F)).toBe(true); - expect(keystroke(EV_F, CODE_F, '')).toBe(true); - expect(keystroke(EV_ALT_F, CODE_F, 'a')).toBe(true); - expect(keystroke(EV_CONTROL_F, CODE_F, 'c')).toBe(true); - expect(keystroke(EV_META_F, CODE_F, 'm')).toBe(true); - expect(keystroke(EV_SHIFT_F, CODE_F, 's')).toBe(true); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'cs')).toBe(true); - expect(keystroke(EV_ALL_F, CODE_F, 'acms')).toBe(true); - - // Test non-letter keys - expect(keystroke(EV_TAB, CODE_TAB)).toBe(true); - expect(keystroke(EV_ENTER, CODE_ENTER)).toBe(true); - expect(keystroke(EV_SPACE, CODE_SPACE)).toBe(true); - expect(keystroke(EV_BACKSPACE, CODE_BACKSPACE)).toBe(true); - - // Test a number/symbol key - expect(keystroke(EV_4, CODE_4)).toBe(true); - expect(keystroke(EV_$, CODE_4, 's')).toBe(true); - - // Test wrong input - expect(keystroke(EV_F, CODE_Z)).toBe(false); - expect(keystroke(EV_SHIFT_F, CODE_F)).toBe(false); - expect(keystroke(EV_SHIFT_F, CODE_F, 'c')).toBe(false); - }); - - it('is case-insensitive', () => { - expect(keystroke(EV_ALL_F, CODE_F, 'ACMS')).toBe(true); - }); - - it('handles bogus inputs', () => { - expect(keystroke(EV_F, 'not a keystroke')).toBe(false); - expect(keystroke(EV_F, null)).toBe(false); - }); - - it('handles exact modifier keys, in any order', () => { - // Test permutations of modifiers - expect(keystroke(EV_ALL_F, CODE_F, 'acms')).toBe(true); - expect(keystroke(EV_ALL_F, CODE_F, 'smca')).toBe(true); - expect(keystroke(EV_ALL_F, CODE_F, 'csma')).toBe(true); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'cs')).toBe(true); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'sc')).toBe(true); - - // Test wrong modifiers - expect(keystroke(EV_ALL_F, CODE_F, 'smca')).toBe(true); - expect(keystroke(EV_ALL_F, CODE_F)).toBe(false); - expect(keystroke(EV_ALL_F, CODE_F, '')).toBe(false); - expect(keystroke(EV_ALL_F, CODE_F, 'c')).toBe(false); - expect(keystroke(EV_ALL_F, CODE_F, 'ca')).toBe(false); - expect(keystroke(EV_ALL_F, CODE_F, 'ms')).toBe(false); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'cs')).toBe(true); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'c')).toBe(false); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 's')).toBe(false); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'csa')).toBe(false); - expect(keystroke(EV_CONTROL_SHIFT_F, CODE_F, 'm')).toBe(false); - expect(keystroke(EV_SHIFT_F, CODE_F, 's')).toBe(true); - expect(keystroke(EV_SHIFT_F, CODE_F, 'c')).toBe(false); - expect(keystroke(EV_SHIFT_F, CODE_F, 'csm')).toBe(false); - }); - - it('handles the platform-dependent leader key', () => { - navigator.platform = 'Win32'; - let EV_UNDO = k(CODE_Z, 'c'); - let EV_REDO = k(CODE_Z, 'cs'); - expect(keystroke(EV_UNDO, CODE_Z, 'l')).toBe(true); - expect(keystroke(EV_UNDO, CODE_Z, 'c')).toBe(true); - expect(keystroke(EV_UNDO, CODE_Z, 'm')).toBe(false); - expect(keystroke(EV_REDO, CODE_Z, 'sl')).toBe(true); - expect(keystroke(EV_REDO, CODE_Z, 'sc')).toBe(true); - expect(keystroke(EV_REDO, CODE_Z, 'sm')).toBe(false); - - navigator.platform = 'MacIntel'; - EV_UNDO = k(CODE_Z, 'm'); - EV_REDO = k(CODE_Z, 'ms'); - expect(keystroke(EV_UNDO, CODE_Z, 'l')).toBe(true); - expect(keystroke(EV_UNDO, CODE_Z, 'c')).toBe(false); - expect(keystroke(EV_UNDO, CODE_Z, 'm')).toBe(true); - expect(keystroke(EV_REDO, CODE_Z, 'sl')).toBe(true); - expect(keystroke(EV_REDO, CODE_Z, 'sc')).toBe(false); - expect(keystroke(EV_REDO, CODE_Z, 'sm')).toBe(true); - }); - }); -}); diff --git a/spec/frontend/lib/utils/undo_stack_spec.js b/spec/frontend/lib/utils/undo_stack_spec.js deleted file mode 100644 index 31ad0e77d6f..00000000000 --- a/spec/frontend/lib/utils/undo_stack_spec.js +++ /dev/null @@ -1,237 +0,0 @@ -import UndoStack from '~/lib/utils/undo_stack'; - -import { isEqual } from 'underscore'; - -describe('UndoStack', () => { - let stack; - - beforeEach(() => { - stack = new UndoStack(); - }); - - afterEach(() => { - // Make sure there's not pending saves - const history = Array.from(stack.history); - jest.runAllTimers(); - expect(stack.history).toEqual(history); - }); - - it('is blank on construction', () => { - expect(stack.isEmpty()).toBe(true); - expect(stack.history).toEqual([]); - expect(stack.cursor).toBe(-1); - expect(stack.canUndo()).toBe(false); - expect(stack.canRedo()).toBe(false); - }); - - it('handles simple undo/redo behaviour', () => { - stack.save(10); - stack.save(11); - stack.save(12); - - expect(stack.history).toEqual([10, 11, 12]); - expect(stack.cursor).toBe(2); - expect(stack.current()).toBe(12); - expect(stack.isEmpty()).toBe(false); - expect(stack.canUndo()).toBe(true); - expect(stack.canRedo()).toBe(false); - - stack.undo(); - expect(stack.history).toEqual([10, 11, 12]); - expect(stack.current()).toBe(11); - expect(stack.canUndo()).toBe(true); - expect(stack.canRedo()).toBe(true); - - stack.undo(); - expect(stack.current()).toBe(10); - expect(stack.canUndo()).toBe(false); - expect(stack.canRedo()).toBe(true); - - stack.redo(); - expect(stack.current()).toBe(11); - - stack.redo(); - expect(stack.current()).toBe(12); - expect(stack.isEmpty()).toBe(false); - expect(stack.canUndo()).toBe(true); - expect(stack.canRedo()).toBe(false); - - // Saving should clear the redo stack - stack.undo(); - stack.save(13); - expect(stack.history).toEqual([10, 11, 13]); - expect(stack.current()).toBe(13); - }); - - it('clear() should clear the undo history', () => { - stack.save(0); - stack.save(1); - stack.save(2); - stack.clear(); - expect(stack.history).toEqual([]); - expect(stack.current()).toBeUndefined(); - }); - - it('undo and redo are no-ops if unavailable', () => { - stack.save(10); - expect(stack.canRedo()).toBe(false); - expect(stack.canUndo()).toBe(false); - - stack.save(11); - expect(stack.canRedo()).toBe(false); - expect(stack.canUndo()).toBe(true); - - expect(stack.redo()).toBeUndefined(); - expect(stack.history).toEqual([10, 11]); - expect(stack.current()).toBe(11); - expect(stack.canRedo()).toBe(false); - expect(stack.canUndo()).toBe(true); - - expect(stack.undo()).toBe(10); - expect(stack.undo()).toBeUndefined(); - expect(stack.history).toEqual([10, 11]); - expect(stack.current()).toBe(10); - expect(stack.canRedo()).toBe(true); - expect(stack.canUndo()).toBe(false); - }); - - it('should not save a duplicate state', () => { - stack.save(10); - stack.save(11); - stack.save(11); - stack.save(10); - stack.save(10); - - expect(stack.history).toEqual([10, 11, 10]); - }); - - it('uses the === operator to detect duplicates', () => { - stack.save(10); - stack.save(10); - expect(stack.history).toEqual([10]); - - // eslint-disable-next-line eqeqeq - expect(2 == '2' && '2' == 2).toBe(true); - stack.clear(); - stack.save(2); - stack.save(2); - stack.save('2'); - stack.save('2'); - stack.save(2); - expect(stack.history).toEqual([2, '2', 2]); - - const obj = {}; - stack.clear(); - stack.save(obj); - stack.save(obj); - stack.save({}); - stack.save({}); - expect(stack.history).toEqual([{}, {}, {}]); - }); - - it('should allow custom comparators', () => { - stack.comparator = isEqual; - const obj = {}; - stack.clear(); - stack.save(obj); - stack.save(obj); - stack.save({}); - stack.save({}); - expect(stack.history).toEqual([{}]); - }); - - it('should enforce a max number of undo states', () => { - // Try 2000 saves. Only the last 1000 should be preserved. - const sequence = Array(2000) - .fill(0) - .map((el, i) => i); - sequence.forEach(stack.save.bind(stack)); - expect(stack.history.length).toBe(1000); - expect(stack.history).toEqual(sequence.slice(1000)); - expect(stack.current()).toBe(1999); - expect(stack.canUndo()).toBe(true); - expect(stack.canRedo()).toBe(false); - - // Saving drops the oldest elements from the stack - stack.save('end'); - expect(stack.history.length).toBe(1000); - expect(stack.current()).toBe('end'); - expect(stack.history).toEqual([...sequence.slice(1001), 'end']); - - // If states were undone but the history is full, can still add. - stack.undo(); - stack.undo(); - expect(stack.current()).toBe(1998); - stack.save(3000); - expect(stack.history.length).toBe(999); - // should be [1001, 1002, ..., 1998, 3000] - expect(stack.history).toEqual([...sequence.slice(1001, 1999), 3000]); - - // Try a different max length - stack = new UndoStack(2); - stack.save(0); - expect(stack.history).toEqual([0]); - stack.save(1); - expect(stack.history).toEqual([0, 1]); - stack.save(2); - expect(stack.history).toEqual([1, 2]); - }); - - describe('scheduled saves', () => { - it('should work', () => { - // Schedules 1000 ms ahead by default - stack.save(0); - stack.scheduleSave(1); - expect(stack.history).toEqual([0]); - jest.advanceTimersByTime(999); - expect(stack.history).toEqual([0]); - jest.advanceTimersByTime(1); - expect(stack.history).toEqual([0, 1]); - }); - - it('should have an adjustable delay', () => { - stack.scheduleSave(2, 100); - jest.advanceTimersByTime(100); - expect(stack.history).toEqual([2]); - }); - - it('should cancel previous scheduled saves', () => { - stack.scheduleSave(3); - jest.advanceTimersByTime(100); - stack.scheduleSave(4); - jest.runAllTimers(); - expect(stack.history).toEqual([4]); - }); - - it('should be canceled by explicit saves', () => { - stack.scheduleSave(5); - stack.save(6); - jest.runAllTimers(); - expect(stack.history).toEqual([6]); - }); - - it('should be canceled by undos and redos', () => { - stack.save(1); - stack.save(2); - stack.scheduleSave(3); - stack.undo(); - jest.runAllTimers(); - expect(stack.history).toEqual([1, 2]); - expect(stack.current()).toBe(1); - - stack.scheduleSave(4); - stack.redo(); - jest.runAllTimers(); - expect(stack.history).toEqual([1, 2]); - expect(stack.current()).toBe(2); - }); - - it('should be persisted immediately with saveNow()', () => { - stack.scheduleSave(7); - stack.scheduleSave(8); - stack.saveNow(); - jest.runAllTimers(); - expect(stack.history).toEqual([8]); - }); - }); -}); |