diff --git a/code/client/src/domain/editor/connectors/history/connector.ts b/code/client/src/domain/editor/connectors/history/connector.ts index edbc82ad..d639ba5f 100644 --- a/code/client/src/domain/editor/connectors/history/connector.ts +++ b/code/client/src/domain/editor/connectors/history/connector.ts @@ -89,6 +89,12 @@ export default (fugue: Fugue, servicesConnector: ServiceConnector): HistoryConne if (!Text.isText(node)) return; if (!node.text) return; + + if (node.text !== '\n') { + selection.start.column += 1; + selection.end.column += 1; + } + const reviveOperations = fugue.reviveLocal(selection); const styleOperations = styles.map(style => { const styleType = getStyleType(style); diff --git a/code/client/src/domain/editor/connectors/input/connector.ts b/code/client/src/domain/editor/connectors/input/connector.ts index f7e8c903..9adbdd21 100644 --- a/code/client/src/domain/editor/connectors/input/connector.ts +++ b/code/client/src/domain/editor/connectors/input/connector.ts @@ -11,6 +11,7 @@ import { InputConnector } from '@domain/editor/connectors/input/types'; export default (fugue: Fugue, servicesConnector: ServiceConnector): InputConnector => { function insertCharacter(char: string, cursor: Cursor, styles: InlineStyle[] = []) { if (char.length !== 1) throw new Error('Invalid character'); + cursor.column -= 1; // adjust column to insert character at the correct position const operations = fugue.insertLocal(cursor, nodeInsert(char, styles)); servicesConnector.emitOperations(operations); } @@ -28,6 +29,7 @@ export default (fugue: Fugue, servicesConnector: ServiceConnector): InputConnect function deleteCharacter(cursor: Cursor) { // don't delete line if it's not a paragraph - this is to prevent deleting the block style & line simultaneously if (cursor.column === 0 && fugue.getBlockStyle(cursor.line) !== 'paragraph') return; + cursor.column -= 1; // adjust column to delete character at the correct position const operations = fugue.deleteLocalByCursor(cursor); if (operations) { servicesConnector.emitOperations(operations); diff --git a/code/client/src/domain/editor/fugue/Fugue.ts b/code/client/src/domain/editor/fugue/Fugue.ts index 0e7d562e..43ccf1fa 100644 --- a/code/client/src/domain/editor/fugue/Fugue.ts +++ b/code/client/src/domain/editor/fugue/Fugue.ts @@ -169,14 +169,15 @@ export class Fugue { * @param selection */ reviveLocal(selection: Selection): ReviveOperation[] { - const nodes = Array.from(this.traverseBySelection(selection, true)); + const _selection = { start: { ...selection.start }, end: { ...selection.end } }; + const nodes = Array.from(this.traverseBySelection(_selection, true)); return nodes.map(node => { if (node.value === '\n') { - selection.start.line++; - selection.start.column = 0; - } else selection.start.column++; + _selection.start.line++; + _selection.start.column = 0; + } else _selection.start.column++; - return this.reviveNode(node.id, selection.start); + return this.reviveNode(node.id, _selection.start); }); } diff --git a/code/client/src/domain/editor/slate/operations/history/toHistoryOperations.ts b/code/client/src/domain/editor/slate/operations/history/toHistoryOperations.ts index 5c1c8ab8..9c279c63 100644 --- a/code/client/src/domain/editor/slate/operations/history/toHistoryOperations.ts +++ b/code/client/src/domain/editor/slate/operations/history/toHistoryOperations.ts @@ -138,7 +138,7 @@ function nodeOperation( ): InsertNodeOperation | RemoveNodeOperation | undefined { const lineOperation = operation.path.length === 1; - const cursor = pointToCursor(editor, { path: operation.path, offset: 0 }); + const cursor = pointToCursor(editor, { path: operation.path, offset: 0 }, insert_mode); const start = lineOperation ? { line: operation.path[0] + 1, column: 0 } diff --git a/code/client/src/domain/editor/slate/utils/selection.ts b/code/client/src/domain/editor/slate/utils/selection.ts index 3aab86c6..5fa85610 100644 --- a/code/client/src/domain/editor/slate/utils/selection.ts +++ b/code/client/src/domain/editor/slate/utils/selection.ts @@ -39,10 +39,11 @@ const pointsToSelection = (editor: Editor, start: Point, end: Point): Selection * Converts a slate point to a cursor * @param editor * @param point + * @param absolutePosition */ -export function pointToCursor(editor: Editor, point: Point): Cursor { +export function pointToCursor(editor: Editor, point: Point, absolutePosition: boolean = false): Cursor { const line = point.path[0]; - const cursor: Cursor = { line, column: point.offset }; + const cursor: Cursor = { line, column: point.offset + (absolutePosition ? 0 : 1) }; if (point.path[1] === 0) return cursor; diff --git a/code/client/tests/editor/domain/document/inputOperations.test.ts b/code/client/tests/editor/domain/document/inputOperations.test.ts index 9ef1bbcf..56859117 100644 --- a/code/client/tests/editor/domain/document/inputOperations.test.ts +++ b/code/client/tests/editor/domain/document/inputOperations.test.ts @@ -20,7 +20,7 @@ describe('Input Operations', () => { test('should insert character', () => { // given - const cursor = { line: 0, column: 0 }; + const cursor = { line: 0, column: 1 }; // must mimic slate cursor // when _inputConnector.insertCharacter('a', cursor); @@ -31,7 +31,7 @@ describe('Input Operations', () => { test('should insert line break', () => { // given - const cursor = { line: 0, column: 0 }; + const cursor = { line: 0, column: 0 }; // must mimic slate cursor // when _inputConnector.insertLineBreak(cursor); @@ -42,8 +42,8 @@ describe('Input Operations', () => { test('should delete character', () => { // given - const cursor1 = { line: 0, column: 0 }; - const cursor2 = { line: 0, column: 1 }; + const cursor1 = { line: 0, column: 1 }; // must mimic slate cursor + const cursor2 = { line: 0, column: 2 }; _inputConnector.insertCharacter('a', cursor1); // when @@ -55,9 +55,9 @@ describe('Input Operations', () => { test('should delete by selection', () => { // given - const cursor1 = { line: 0, column: 0 }; - const cursor2 = { line: 0, column: 1 }; - const cursor3 = { line: 0, column: 2 }; + const cursor1 = { line: 0, column: 1 }; // must mimic slate cursor + const cursor2 = { line: 0, column: 2 }; + const cursor3 = { line: 0, column: 3 }; _inputConnector.insertCharacter('a', cursor1); _inputConnector.insertCharacter('b', cursor2); @@ -71,12 +71,12 @@ describe('Input Operations', () => { test('should delete word', () => { // given const text = 'hello world'; - const cursor1 = { line: 0, column: text.length }; + const cursor1 = { line: 0, column: text.length + 1 }; const cursor2 = { line: 0, column: 1 }; // when text.split('').forEach((char, index) => { - _inputConnector.insertCharacter(char, { line: 0, column: index }); + _inputConnector.insertCharacter(char, { line: 0, column: index + 1 }); }); _inputConnector.deleteWord(cursor1, true); diff --git a/code/client/tests/editor/slate/operations/history/text-tests/delete-line.test.ts b/code/client/tests/editor/slate/operations/history/text-tests/delete-line.test.ts index dc090cdc..570f390d 100644 --- a/code/client/tests/editor/slate/operations/history/text-tests/delete-line.test.ts +++ b/code/client/tests/editor/slate/operations/history/text-tests/delete-line.test.ts @@ -9,6 +9,7 @@ beforeEach(() => { describe('Undo delete line', () => { describe('No block styles', () => { + // TODO - Add redo tests it('empty line', () => { // Setup the editor pipeline.fugue.insertLocal({ line: 0, column: 0 }, '\n'); diff --git a/code/client/tests/editor/slate/operations/history/text-tests/delete-text.test.ts b/code/client/tests/editor/slate/operations/history/text-tests/delete-text.test.ts index 06bf948a..e650e33c 100644 --- a/code/client/tests/editor/slate/operations/history/text-tests/delete-text.test.ts +++ b/code/client/tests/editor/slate/operations/history/text-tests/delete-text.test.ts @@ -1,5 +1,5 @@ import { HistoryTestPipeline } from '@tests/editor/slate/operations/history/HistoryTestPipeline'; -import { RemoveTextOperation } from 'slate'; +import { Descendant, RemoveTextOperation } from 'slate'; import { NodeInsert } from '@domain/editor/fugue/types'; import { BlockStyles } from '@notespace/shared/src/document/types/styles'; @@ -12,244 +12,403 @@ beforeEach(() => { describe('Undo delete text', () => { describe('Start of line', () => { describe('No block style', () => { - it('No inline style', () => { - // Setup editor - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [ - { type: 'remove_text', path: [0, 0], offset: 0, text: 'Hello World' }, - ]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('No inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 0, text: 'H' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 1 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); + describe('Inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) + ); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 0, text: 'H' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 1 } }); + pipeline.applyOperations(slateOperation); - it('Inline style', () => { - // Setup editor - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [ - { type: 'remove_text', path: [0, 0], offset: 0, text: 'Hello World' }, - ]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); }); describe('Block style', () => { - it('No inline style', () => { - // Setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [ - { type: 'remove_text', path: [0, 0], offset: 0, text: 'Hello World' }, - ]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('No inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 0, text: 'H' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 1 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); - it('Inline style', () => { - // Setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [ - { type: 'remove_text', path: [0, 0], offset: 0, text: 'Hello World' }, - ]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('Inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) + ); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 0, text: 'H' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 0 }, end: { line: 0, column: 1 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); }); }); describe('Middle of line', () => { describe('No block style', () => { - it('No inline style', () => { - // Setup editor - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 6, text: 'World' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 7 }, end: { line: 0, column: 12 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('No inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 5, text: ' ' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 6 }, end: { line: 0, column: 7 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); - it('Inline style', () => { - // Setup editor - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 6, text: 'World' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 7 }, end: { line: 0, column: 12 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('Inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) + ); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 5, text: ' ' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 6 }, end: { line: 0, column: 7 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); }); describe('Block style', () => { - it('No inline style', () => { - // Setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 6, text: 'World' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 7 }, end: { line: 0, column: 12 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('No inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 5, text: ' ' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 6 }, end: { line: 0, column: 7 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); - it('Inline style', () => { - // Setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 6, text: 'World' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 7 }, end: { line: 0, column: 12 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('Inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) + ); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 5, text: ' ' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 6 }, end: { line: 0, column: 7 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); }); }); describe('End of line', () => { describe('No block style', () => { - it('No inline style', () => { - // Setup editor - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('No inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); - it('Inline style', () => { - // Setup editor - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('Inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) + ); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); }); describe('Block style', () => { - it('No inline style', () => { - // Setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('No inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello World'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); - it('Inline style', () => { - // Setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply operations - const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; - pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); - pipeline.applyOperations(slateOperation); - // Get undo operations - const batch = pipeline.extractUndoOperations(); - pipeline.applyHistoryOperations(...batch.operations); - const afterSnapshot = pipeline.takeSnapshot(); - // Compare - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('Inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Setup editor + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + ...'Hello World'.split('').map(value => ({ value, styles: ['bold'] }) as NodeInsert) + ); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply operations + const slateOperation: RemoveTextOperation[] = [{ type: 'remove_text', path: [0, 0], offset: 10, text: 'd' }]; + pipeline.fugue.deleteLocal({ start: { line: 0, column: 11 }, end: { line: 0, column: 11 } }); + pipeline.applyOperations(slateOperation); + + secondSnapshot = pipeline.takeSnapshot(); + // Get undo operations + const batch = pipeline.extractUndoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(firstSnapshot); + }); + it('should redo', () => { + const batch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...batch.operations); + const afterSnapshot = pipeline.takeSnapshot(); + // Compare + expect(afterSnapshot).toEqual(secondSnapshot); + }); }); }); }); diff --git a/code/client/tests/editor/slate/operations/history/text-tests/insert-line.test.ts b/code/client/tests/editor/slate/operations/history/text-tests/insert-line.test.ts index 2c27246d..5ee328df 100644 --- a/code/client/tests/editor/slate/operations/history/text-tests/insert-line.test.ts +++ b/code/client/tests/editor/slate/operations/history/text-tests/insert-line.test.ts @@ -8,6 +8,7 @@ beforeEach(() => { }); describe('Undo new line', () => { + // TODO - Add redo tests it('Start of line', () => { const beforeSnapshot = pipeline.takeSnapshot(); // Apply the operation diff --git a/code/client/tests/editor/slate/operations/history/text-tests/insert-text.test.ts b/code/client/tests/editor/slate/operations/history/text-tests/insert-text.test.ts index cad99ca1..4a37c302 100644 --- a/code/client/tests/editor/slate/operations/history/text-tests/insert-text.test.ts +++ b/code/client/tests/editor/slate/operations/history/text-tests/insert-text.test.ts @@ -1,5 +1,5 @@ import { HistoryTestPipeline } from '@tests/editor/slate/operations/history/HistoryTestPipeline'; -import { BaseOperation } from 'slate'; +import { BaseOperation, Descendant } from 'slate'; import { BlockStyles } from '@notespace/shared/src/document/types/styles'; let pipeline: HistoryTestPipeline; @@ -10,282 +10,401 @@ beforeEach(() => { describe('Undo text insertion', () => { describe('Start of the line', () => { describe('No style line', () => { - it('no inline style', () => { - // Take snapshot for comparison - const snapshot = pipeline.takeSnapshot(); - // Apply editor operations - const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 0, text: 'cd' }]; - // Equivalent Fugue operations - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'cd'.split('')); - - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const newSnapshot = pipeline.takeSnapshot(); - expect(newSnapshot).toEqual(snapshot); + describe('no inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 0, text: 'cd' }]; + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'cd'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); - it('inline style', () => { - // Take snapshot for comparison - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply editor operations - const slateOperations: BaseOperation[] = [ - { type: 'insert_node', path: [0, 0], node: { type: 'bold', text: 'cd' } }, - ]; - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - { value: 'c', styles: ['bold'] }, - { value: 'd', styles: ['bold'] } - ); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [ + { + type: 'insert_node', + path: [0, 0], + node: { bold: true, text: 'cd' }, + }, + ]; + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'cd'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); }); describe('Style line', () => { - it('no inline style', () => { - // Apply block line style - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.setupEditor(); + describe('no inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); + firstSnapshot = pipeline.takeSnapshot(); - // Apply editor operations - const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 0, text: 'cd' }]; - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'cd'.split('')); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); - }); - it('inline style', () => { - // Apply block line style - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 0, text: 'cd' }]; + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'cd'.split('')); + pipeline.applyOperations(slateOperations); - // Apply editor operations - const slateOperations: BaseOperation[] = [ - { type: 'insert_node', path: [0, 0], node: { type: 'bold', text: 'cd' } }, - ]; - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - { value: 'c', styles: ['bold'] }, - { value: 'd', styles: ['bold'] } - ); - pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); + }); + describe('inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + // Apply block line style + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [ + { type: 'insert_node', path: [0, 0], node: { bold: true, text: 'cd' } }, + ]; + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + { value: 'c', styles: ['bold'] }, + { value: 'd', styles: ['bold'] } + ); + pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + // Extract redo operations + const redoBatch = pipeline.extractRedoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...redoBatch.operations); + // Compare snapshots + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); }); }); describe('Middle of the line', () => { describe('No style line', () => { - it('No inline style', () => { - // Setup editor - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply editor operations - const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 1, text: 'cd' }]; - pipeline.fugue.insertLocal({ line: 0, column: 1 }, ...'cd'.split('')); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('no inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 1, text: 'cd' }]; + pipeline.fugue.insertLocal({ line: 0, column: 1 }, ...'cd'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); - it('inline style', () => { - // Setup editor - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply editor operations - const slateOperations: BaseOperation[] = [ - { type: 'split_node', path: [0, 0], position: 1, properties: { type: 'paragraph' } }, - { type: 'insert_node', path: [0, 1], node: { type: 'bold', text: 'cd' } }, - ]; - pipeline.fugue.insertLocal( - { line: 0, column: 1 }, - { value: 'c', styles: ['bold'] }, - { value: 'd', styles: ['bold'] } - ); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [ + //{ type: 'split_node', path: [0, 0], position: 1, properties: { type: 'paragraph' } }, + { type: 'insert_node', path: [0, 1], node: { bold: true, text: 'cd' } }, + ]; + pipeline.fugue.insertLocal( + { line: 0, column: 2 }, + { value: 'c', styles: ['bold'] }, + { value: 'd', styles: ['bold'] } + ); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); }); describe('Style line', () => { - it('no inline style', () => { - // setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - - // Apply editor operations - const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 1, text: 'cd' }]; - pipeline.fugue.insertLocal({ line: 0, column: 1 }, ...'cd'.split('')); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('no inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 1, text: 'cd' }]; + pipeline.fugue.insertLocal({ line: 0, column: 1 }, ...'cd'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); - it('inline style', () => { - // Apply block line style - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - { value: 'a', styles: ['bold'] }, - { value: 'b', styles: ['bold'] } - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - - // Apply editor operations - const slateOperations: BaseOperation[] = [ - { type: 'split_node', path: [0, 0], position: 1, properties: { type: 'paragraph' } }, - { type: 'insert_node', path: [0, 1], node: { type: 'bold', text: 'cd' } }, - ]; - pipeline.fugue.insertLocal( - { line: 0, column: 1 }, - { value: 'c', styles: ['bold'] }, - { value: 'd', styles: ['bold'] } - ); - pipeline.applyOperations(slateOperations); - - // Extract undo operations - pipeline.extractUndoOperations(); - // ... - // Redo the undo operations - pipeline.extractRedoOperations(); // implicitly redoes the undo operation - // Test - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal( + { line: 0, column: 0 }, + { value: 'a', styles: ['bold'] }, + { value: 'b', styles: ['bold'] } + ); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [ + //{ type: 'split_node', path: [0, 0], position: 1, properties: { type: 'paragraph' } }, + { type: 'insert_text', path: [0, 0], offset: 2, text: 'cd' }, + ]; + pipeline.fugue.insertLocal( + { line: 0, column: 2 }, + { value: 'c', styles: ['bold'] }, + { value: 'd', styles: ['bold'] } + ); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); }); }); describe('End of the line', () => { describe('No style line', () => { - it('no inline style', () => { - // Setup editor - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply editor operations - const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 2, text: 'cd' }]; - pipeline.fugue.insertLocal({ line: 0, column: 2 }, ...'cd'.split('')); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('no inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 5, text: ' World' }]; + pipeline.fugue.insertLocal({ line: 0, column: 5 }, ...' World'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); - it('inline style', () => { - // Setup editor - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - // Apply editor operations - const slateOperations: BaseOperation[] = [ - { type: 'insert_node', path: [0, 1], node: { type: 'bold', text: 'cd' } }, - ]; - pipeline.fugue.insertLocal( - { line: 0, column: 2 }, - { value: 'c', styles: ['bold'] }, - { value: 'd', styles: ['bold'] } - ); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [ + { type: 'insert_node', path: [0, 1], node: { bold: true, text: ' World' } }, + ]; + pipeline.fugue.insertLocal({ line: 0, column: 5 }, ...' World'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); }); describe('Style line', () => { - it('no inline style', () => { - // Apply block line style - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'ab'.split('')); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); - - // Apply editor operations - const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 2, text: 'cd' }]; - pipeline.fugue.insertLocal({ line: 0, column: 2 }, ...'cd'.split('')); - pipeline.applyOperations(slateOperations); - // Extract undo operations - pipeline.extractUndoOperations(); - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + describe('no inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); + pipeline.fugue.insertLocal({ line: 0, column: 0 }, ...'Hello'.split('')); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [{ type: 'insert_text', path: [0, 0], offset: 5, text: ' World' }]; + pipeline.fugue.insertLocal({ line: 0, column: 5 }, ...' World'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); - it('inline style', () => { - // Setup editor - pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - pipeline.fugue.insertLocal( - { line: 0, column: 0 }, - { value: 'a', styles: ['bold'] }, - { value: 'b', styles: ['bold'] } - ); - pipeline.setupEditor(); - const beforeSnapshot = pipeline.takeSnapshot(); + describe('inline style', () => { + let firstSnapshot: Descendant[], secondSnapshot: Descendant[]; + beforeEach(() => { + pipeline.fugue.updateBlockStyleLocal(0, BlockStyles.h1); - // Apply editor operations - const slateOperations: BaseOperation[] = [ - { type: 'insert_node', path: [0, 1], node: { type: 'bold', text: 'cd' } }, - ]; - pipeline.fugue.insertLocal( - { line: 0, column: 2 }, - { value: 'c', styles: ['bold'] }, - { value: 'd', styles: ['bold'] } - ); - pipeline.applyOperations(slateOperations); - // Extract undo operations - const batch = pipeline.extractUndoOperations(); - // Apply history operations - pipeline.applyHistoryOperations(...batch.operations); - // Compare snapshots - const afterSnapshot = pipeline.takeSnapshot(); - expect(afterSnapshot).toEqual(beforeSnapshot); + 'Hello'.split('').map((value, index) => { + pipeline.fugue.insertLocal({ line: 0, column: index }, { value, styles: ['bold'] }); + }); + pipeline.setupEditor(); + firstSnapshot = pipeline.takeSnapshot(); + // Apply editor operations + const slateOperations: BaseOperation[] = [{ type: 'insert_node', path: [0, 1], node: { text: ' World' } }]; + pipeline.fugue.insertLocal({ line: 0, column: 5 }, ...' World'.split('')); + pipeline.applyOperations(slateOperations); + secondSnapshot = pipeline.takeSnapshot(); + // Extract undo operations + const batch = pipeline.extractUndoOperations(); + // Apply history operations + pipeline.applyHistoryOperations(...batch.operations); + }); + it('should undo', () => { + // Compare snapshots + const afterSnapshot = pipeline.takeSnapshot(); + expect(afterSnapshot).toEqual(firstSnapshot as Descendant[]); + }); + it('should redo', () => { + const redoBatch = pipeline.extractRedoOperations(); + pipeline.applyHistoryOperations(...redoBatch.operations); + const finalSnapshot = pipeline.takeSnapshot(); + expect(finalSnapshot).toEqual(secondSnapshot); + }); }); }); });