summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorPaul Slaughter <pslaughter@gitlab.com>2019-08-01 12:31:13 -0500
committerPaul Slaughter <pslaughter@gitlab.com>2019-08-01 12:31:13 -0500
commit84b6c7a5f3bf3d6f96331d73225903d3fd92b4e2 (patch)
tree9cf524013fb1d488528aaa3fd831e253cee850ff /spec/frontend
parent536ebecf7eec95bd2cc1b542f1c35d70f814731e (diff)
downloadgitlab-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.js371
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js180
-rw-r--r--spec/frontend/lib/utils/undo_stack_spec.js237
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 = '&#8984;';
-
-// 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]);
- });
- });
-});