Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions packages/collaboration-manager/src/CollaborationManager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
import { createDataKey, IndexBuilder } from '@editorjs/model';
import { EditorJSModel } from '@editorjs/model';
import { beforeAll, jest } from '@jest/globals';
import { CollaborationManager } from './CollaborationManager.js';
import { Operation, OperationType } from './Operation.js';

Expand Down Expand Up @@ -261,6 +262,10 @@ describe('CollaborationManager', () => {
});

describe('undo logic', () => {
beforeAll(() => {
jest.useFakeTimers();
});

it('should invert Insert operation', () => {
const model = new EditorJSModel();

Expand Down Expand Up @@ -767,4 +772,107 @@ describe('CollaborationManager', () => {
properties: {},
});
});

it('should undo batch', () => {
const model = new EditorJSModel();

model.initializeDocument({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: '',
$t: 't',
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index1 = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([0, 0])
.build();
const operation1 = new Operation(OperationType.Insert, index1, {
payload: 'te',
});

const index2 = new IndexBuilder().from(index1)
.addTextRange([1, 1])
.build();
const operation2 = new Operation(OperationType.Insert, index2, {
payload: 'st',
});

collaborationManager.applyOperation(operation1);
collaborationManager.applyOperation(operation2);

collaborationManager.undo();

expect(model.serialized).toStrictEqual({
blocks: [ {
name: 'paragraph',
tunes: {},
data: {
text: {
$t: 't',
value: '',
fragments: [],
},
},
} ],
properties: {},
});
});

it('should redo batch', () => {
const model = new EditorJSModel();

model.initializeDocument({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: '',
$t: 't',
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index1 = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([0, 0])
.build();
const operation1 = new Operation(OperationType.Insert, index1, {
payload: 'te',
});

const index2 = new IndexBuilder().from(index1)
.addTextRange([1, 1])
.build();
const operation2 = new Operation(OperationType.Insert, index2, {
payload: 'st',
});

collaborationManager.applyOperation(operation1);
collaborationManager.applyOperation(operation2);

collaborationManager.undo();
collaborationManager.redo();

expect(model.serialized).toStrictEqual({
blocks: [ {
name: 'paragraph',
tunes: {},
data: {
text: {
$t: 't',
value: 'test',
fragments: [],
},
},
} ],
properties: {},
});
});
});
34 changes: 32 additions & 2 deletions packages/collaboration-manager/src/CollaborationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
TextFormattedEvent, TextRemovedEvent,
TextUnformattedEvent
} from '@editorjs/model';
import { OperationsBatch } from './OperationsBatch.js';
import { type ModifyOperationData, Operation, OperationType } from './Operation.js';
import { UndoRedoManager } from './UndoRedoManager.js';

Expand All @@ -30,6 +31,7 @@
*/
#shouldHandleEvents = true;

#currentBatch: OperationsBatch | null = null;

/**
* Creates an instance of CollaborationManager
Expand All @@ -46,6 +48,8 @@
* Undo last operation in the local stack
*/
public undo(): void {
this.#currentBatch?.terminate();

const operation = this.#undoRedoManager.undo();

if (operation === undefined) {
Expand All @@ -65,6 +69,8 @@
* Redo last undone operation in the local stack
*/
public redo(): void {
this.#currentBatch?.terminate();

const operation = this.#undoRedoManager.redo();

if (operation === undefined) {
Expand Down Expand Up @@ -157,8 +163,32 @@
console.error('Unknown event type', e);
}

if (operation !== null) {
this.#undoRedoManager.put(operation);
if (operation === null) {
return;
}

const onBatchTermination = (batch: OperationsBatch, lastOp?: Operation): void => {
const effectiveOp = batch.getEffectiveOperation();

if (effectiveOp) {
this.#undoRedoManager.put(effectiveOp);
}

/**
* lastOp is the operation on which the batch was terminated.
* So if there is one, we need to create a new batch
*
* lastOp could be null if the batch was terminated by time out
*/
this.#currentBatch = lastOp === undefined ? null : new OperationsBatch(onBatchTermination, lastOp);

Check warning on line 183 in packages/collaboration-manager/src/CollaborationManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
};

if (this.#currentBatch === null) {
this.#currentBatch = new OperationsBatch(onBatchTermination, operation);

return;
}

this.#currentBatch.add(operation);
}
}
Loading