Skip to content

Commit 232457b

Browse files
committed
feat(json-crdt-extensions): 🎸 cleanup internal data structures after local changes
1 parent 52cf2b9 commit 232457b

File tree

5 files changed

+85
-17
lines changed

5 files changed

+85
-17
lines changed

‎src/json-crdt-extensions/peritext/Peritext.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {Range} from './rga/Range';
55
import {Editor} from './editor/Editor';
66
import {ArrNode, StrNode} from '../../json-crdt/nodes';
77
import {Slices} from './slice/Slices';
8+
import {LocalSlices} from './slice/LocalSlices';
89
import {Overlay} from './overlay/Overlay';
910
import {Chars} from './constants';
1011
import {interval} from '../../json-crdt-patch/clock';
@@ -63,7 +64,9 @@ export class Peritext implements Printable {
6364
const localModel = Model
6465
.withLogicalClock(SESSION.LOCAL)
6566
.setSchema(s.vec(s.arr([])));
66-
this.localSlices = new Slices(localModel, localModel.root.node().get(0)!, this.str);
67+
const localApi = localModel.api;
68+
localApi.onLocalChange.listen(() => { localApi.flush(); });
69+
this.localSlices = new LocalSlices(localModel, localModel.root.node().get(0)!, this.str);
6770

6871
this.editor = new Editor(this, this.localSlices);
6972
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {Model} from '../../../json-crdt/model';
2+
import {Peritext} from '../Peritext';
3+
4+
const setup = () => {
5+
const model = Model.withLogicalClock();
6+
model.api.root({
7+
text: '',
8+
slices: [],
9+
});
10+
model.api.str(['text']).ins(0, 'wworld');
11+
model.api.str(['text']).ins(0, 'helo ');
12+
model.api.str(['text']).ins(2, 'l');
13+
model.api.str(['text']).del(7, 1);
14+
const peritext = new Peritext(model, model.api.str(['text']).node, model.api.arr(['slices']).node);
15+
return {model, peritext};
16+
};
17+
18+
test('clears change history', () => {
19+
const {peritext} = setup();
20+
const {editor} = peritext;
21+
editor.cursor.setAt(0);
22+
editor.cursor.setAt(1);
23+
editor.cursor.setAt(2);
24+
editor.cursor.setAt(3);
25+
expect(peritext.localSlices.model.api.flush().ops.length).toBe(0);
26+
});
27+
28+
test('clears slice set tombstones', () => {
29+
const _random = Math.random;
30+
// It is probabilistic, if we set `Math.random` to 0 it will always remove tombstones.
31+
Math.random = () => 0;
32+
const {peritext} = setup();
33+
const slicesRga = peritext.localSlices.model.root.node()!.get(0)!;
34+
const count = slicesRga.size();
35+
const slice1 = peritext.localSlices.insOverwrite(peritext.rangeAt(1, 2), 1);
36+
const slice2 = peritext.localSlices.insOverwrite(peritext.rangeAt(1, 2), 3);
37+
const slice3 = peritext.localSlices.insOverwrite(peritext.rangeAt(1, 2), 2);
38+
expect(slicesRga.size()).toBe(count + 3);
39+
peritext.localSlices.del(slice2.id);
40+
expect(slicesRga.size()).toBe(count + 2);
41+
peritext.localSlices.del(slice1.id);
42+
expect(slicesRga.size()).toBe(count + 1);
43+
peritext.localSlices.del(slice3.id);
44+
expect(slicesRga.size()).toBe(count);
45+
Math.random = _random;
46+
});

‎src/json-crdt-extensions/peritext/editor/Cursor.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class Cursor<T = string> extends PersistedSlice<T> {
2929
});
3030
}
3131

32-
/** Move to persisted slice. */
32+
/** TODO: Move to {@link PersistedSlice}. */
3333
public setAt(start: number, length: number = 0): void {
3434
let at = start;
3535
let len = length;

‎src/json-crdt-extensions/peritext/overlay/__tests__/Overlay.refresh.spec.ts‎

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -142,21 +142,31 @@ describe('Overlay.refresh()', () => {
142142
});
143143

144144
describe('cursor', () => {
145-
test.only('updates state hash, when cursor char ID changes', () => {
146-
const {peritext} = setup();
147-
const overlay = peritext.overlay;
148-
peritext.editor.cursor.setAt(1);
149-
overlay.refresh();
150-
console.log(peritext + '');
151-
peritext.editor.cursor.setAt(2);
152-
overlay.refresh();
153-
console.log(peritext + '');
154-
// const hash1 = overlay.refresh();
155-
// peritext.editor.cursor.setAt(2);
156-
// const hash2 = overlay.refresh();
157-
// const hash3 = overlay.refresh();
158-
// expect(hash1).not.toBe(hash2);
159-
// expect(hash2).toBe(hash3);
145+
describe('updates hash', () => {
146+
testRefresh('when cursor char ID changes', (kit, refresh) => {
147+
kit.peritext.editor.cursor.setAt(1);
148+
refresh();
149+
kit.peritext.editor.cursor.setAt(1);
150+
});
151+
152+
testRefresh('when cursor start anchor changes', (kit, refresh) => {
153+
kit.peritext.editor.cursor.setAt(3, 3);
154+
expect(kit.peritext.editor.cursor.start.anchor).toBe(Anchor.Before);
155+
refresh();
156+
const start = kit.peritext.editor.cursor.start.clone();
157+
start.anchor = Anchor.After;
158+
kit.peritext.editor.cursor.setRange(kit.peritext.range(start, kit.peritext.editor.cursor.end));
159+
});
160+
161+
testRefresh('when cursor end anchor changes', (kit, refresh) => {
162+
kit.peritext.editor.cursor.setAt(3, 3);
163+
expect(kit.peritext.editor.cursor.end.anchor).toBe(Anchor.After);
164+
refresh();
165+
const end = kit.peritext.editor.cursor.start.clone();
166+
end.anchor = Anchor.Before;
167+
kit.peritext.editor.cursor.setRange(kit.peritext.range(kit.peritext.editor.cursor.start, end));
168+
console.log(kit.peritext.localSlices.model.api.flush());
169+
});
160170
});
161171
});
162172
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {Slices} from './Slices';
2+
import type {ITimestampStruct} from '../../../json-crdt-patch';
3+
4+
export class LocalSlices extends Slices {
5+
public del(id: ITimestampStruct): void {
6+
super.del(id);
7+
if (Math.random() < 0.1) this.set.removeTombstones();
8+
}
9+
}

0 commit comments

Comments
 (0)