Skip to content

Commit

Permalink
Simple undo/redo logic (#79)
Browse files Browse the repository at this point in the history
* implement basic undo logic

* fix events handling on undo

* fix name

* lint fixes

* implement UndoRedoManager

* review fixes

* Update packages/collaboration-manager/src/CollaborationManager.ts

Co-authored-by: Peter <specc.dev@gmail.com>

* fix test names

---------

Co-authored-by: Peter <specc.dev@gmail.com>
  • Loading branch information
nikmel2803 and neSpecc authored Aug 29, 2024
1 parent dada291 commit 884d0ca
Show file tree
Hide file tree
Showing 4 changed files with 385 additions and 66 deletions.
296 changes: 232 additions & 64 deletions packages/collaboration-manager/src/CollaborationManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,83 +5,251 @@ import { CollaborationManager } from './CollaborationManager.js';
import { Operation, OperationType } from './Operation.js';

describe('CollaborationManager', () => {
it('should add text on apply Insert Operation', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: '',
$t: 't',
describe('applyOperation', () => {
it('should add text on apply Insert Operation', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: '',
$t: 't',
},
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([0, 4])
.build();
const operation = new Operation(OperationType.Insert, index, {
prevValue: '',
newValue: 'test',
} ],
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([0, 4])
.build();
const operation = new Operation(OperationType.Insert, index, {
prevValue: '',
newValue: 'test',
});

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

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

it('should remove text on apply Remove Operation', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: 'hel11lo',
$t: 't',
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([
3, 5])
.build();
const operation = new Operation(OperationType.Delete, index, {
prevValue: '11',
newValue: '',
});

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

describe('undo logic', () => {
it('should invert Insert operation', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: '',
$t: 't',
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([0, 4])
.build();
const operation = new Operation(OperationType.Insert, index, {
prevValue: '',
newValue: 'test',
});

it('should remove text on apply Remove Operation', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: 'hel11lo',
$t: 't',
collaborationManager.applyOperation(operation);
collaborationManager.undo();
expect(model.serialized).toStrictEqual({
blocks: [ {
name: 'paragraph',
tunes: {},
data: {
text: {
$t: 't',
value: '',
fragments: [],
},
},
},
} ],
} ],
properties: {},
});
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([
3, 5])
.build();
const operation = new Operation(OperationType.Delete, index, {
prevValue: '11',
newValue: '',

it('should invert Remove operation', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: 'hel11lo',
$t: 't',
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([
3, 5])
.build();
const operation = new Operation(OperationType.Delete, index, {
prevValue: '11',
newValue: '',
});

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

collaborationManager.applyOperation(operation);
expect(model.serialized).toStrictEqual({
blocks: [ {
name: 'paragraph',
tunes: {},
data: {
text: {
$t: 't',
value: 'hello',
fragments: [],
it('should revert only one operation if stack length is 1', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: '',
$t: 't',
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([0, 4])
.build();
const operation = new Operation(OperationType.Insert, index, {
prevValue: '',
newValue: 'test',
});

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

it('should revert back to original state after undo and redo operations', () => {
const model = new EditorJSModel({
blocks: [ {
name: 'paragraph',
data: {
text: {
value: '',
$t: 't',
},
},
} ],
});
const collaborationManager = new CollaborationManager(model);
const index = new IndexBuilder().addBlockIndex(0)
.addDataKey(createDataKey('text'))
.addTextRange([0, 4])
.build();
const operation = new Operation(OperationType.Insert, index, {
prevValue: '',
newValue: 'test',
});

collaborationManager.applyOperation(operation);
collaborationManager.undo();
collaborationManager.redo();

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

1 comment on commit 884d0ca

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for ./packages/model

St.
Category Percentage Covered / Total
🟢 Statements 100% 742/742
🟢 Branches 99.49% 197/198
🟢 Functions 99.44% 179/180
🟢 Lines 100% 716/716

Test suite run success

389 tests passing in 24 suites.

Report generated by 🧪jest coverage report action from 884d0ca

Please sign in to comment.