diff --git a/src/__tests__/webInputHistory.test.tsx b/src/__tests__/webInputHistory.test.tsx new file mode 100644 index 00000000..d5279e6e --- /dev/null +++ b/src/__tests__/webInputHistory.test.tsx @@ -0,0 +1,115 @@ +import {expect} from '@jest/globals'; +import InputHistory from '../web/InputHistory'; + +const testingHistory = [ + {text: 'Hello world!', cursorPosition: 12}, + {text: 'Hello *world*!', cursorPosition: 14}, + {text: 'Hello _*world*_!', cursorPosition: 16}, +]; +const depth = testingHistory.length; + +test('add history action', () => { + const history = new InputHistory(depth); + testingHistory.forEach((item) => { + history.add(item.text, item.cursorPosition); + }); + + expect(history.history).toEqual(testingHistory); + expect(history.getCurrentItem()).toEqual(testingHistory[testingHistory.length - 1]); +}); + +test('history depth', () => { + const history = new InputHistory(depth); + const text = '> Hello _*world*_!'; + + history.setHistory(testingHistory); + history.add(text, text.length); + + const newItem = {text, cursorPosition: text.length}; + const currentHistory = [...testingHistory.slice(1), newItem]; + + expect(history.history).toEqual(currentHistory); + expect(history.getCurrentItem()).toEqual(newItem); +}); + +describe('debounce add history action', () => { + const text = 'Hello world!'; + const newItem = {text, cursorPosition: text.length}; + const text2 = 'Hello world 2!'; + const newItem2 = {text: text2, cursorPosition: text2.length}; + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + test('should debounce', () => { + const history = new InputHistory(depth, 300); + history.debouncedAdd(newItem.text, newItem.cursorPosition); + expect(history.history).toEqual([]); + jest.advanceTimersByTime(300); + expect(history.history).toEqual([newItem]); + }); + + test('should cancel previous invocation', () => { + const history = new InputHistory(depth, 300); + history.debouncedAdd(newItem.text, newItem.cursorPosition); + jest.advanceTimersByTime(100); + history.debouncedAdd(newItem2.text, newItem2.cursorPosition); + jest.advanceTimersByTime(300); + expect(history.history).toEqual([newItem2]); + }); + + test('undo before debounce invokes the function', () => { + const history = new InputHistory(depth, 300); + history.debouncedAdd(newItem.text, newItem.cursorPosition); + expect(history.undo()).toEqual(null); + jest.advanceTimersByTime(300); + expect(history.history).toEqual([]); + }); + + test('redo before debounce invokes the function', () => { + const history = new InputHistory(depth, 300); + history.debouncedAdd(newItem.text, newItem.cursorPosition); + expect(history.redo()).toEqual(null); + jest.advanceTimersByTime(300); + expect(history.history).toEqual([]); + }); +}); + +test('undo history action', () => { + const history = new InputHistory(depth); + history.setHistory(testingHistory); + + expect(history.undo()).toEqual(testingHistory[1]); + + history.setHistoryIndex(0); + expect(history.undo()).toEqual(null); +}); + +test('redo history action', () => { + const history = new InputHistory(depth); + history.setHistory(testingHistory); + expect(history.redo()).toEqual(null); + + history.setHistoryIndex(1); + expect(history.redo()).toEqual(testingHistory[2]); +}); + +test('clearing history after adding new text after undo', () => { + const history = new InputHistory(depth); + history.setHistory(testingHistory); + history.setHistoryIndex(0); + + const text = '> Hello _*world*_!'; + const newItem = {text, cursorPosition: text.length}; + + history.add(newItem.text, newItem.cursorPosition); + + expect(history.history).toEqual([testingHistory[0], newItem]); + expect(history.getCurrentItem()).toEqual(newItem); +}); diff --git a/src/__tests__/index.test.tsx b/src/__tests__/webParser.test.tsx similarity index 100% rename from src/__tests__/index.test.tsx rename to src/__tests__/webParser.test.tsx diff --git a/src/web/InputHistory.ts b/src/web/InputHistory.ts index 48ff58e5..56e9d70b 100644 --- a/src/web/InputHistory.ts +++ b/src/web/InputHistory.ts @@ -23,6 +23,24 @@ export default class InputHistory { this.debounceTime = debounceTime; } + getCurrentItem(): HistoryItem | null { + return this.history[this.historyIndex] || null; + } + + setHistory(newHistory: HistoryItem[]): void { + this.history = newHistory.slice(newHistory.length - this.depth); + this.historyIndex = newHistory.length - 1; + } + + setHistoryIndex(index: number): void { + this.historyIndex = index; + } + + clear(): void { + this.history = []; + this.historyIndex = 0; + } + debouncedAdd(text: string, cursorPosition: number): void { this.currentText = text; @@ -65,7 +83,7 @@ export default class InputHistory { return this.history[this.history.length - 1] || null; } - if (this.history.length === 0) { + if (this.history.length === 0 || this.historyIndex - 1 < 0) { return null; } @@ -76,7 +94,12 @@ export default class InputHistory { } redo(): HistoryItem | null { - if (this.history.length === 0 || (this.currentText !== null && this.timeout)) { + if (this.currentText !== null && this.timeout) { + clearTimeout(this.timeout); + return this.history[this.history.length - 1] || null; + } + + if (this.history.length === 0 || this.historyIndex + 1 > this.history.length) { return null; } @@ -85,6 +108,7 @@ export default class InputHistory { } else { return null; } + return this.history[this.historyIndex] || null; } }