diff --git a/src/json-crdt-extensions/peritext/slice/PersistedSlice.ts b/src/json-crdt-extensions/peritext/slice/PersistedSlice.ts index 295bb98716..4f7a1ac4e2 100644 --- a/src/json-crdt-extensions/peritext/slice/PersistedSlice.ts +++ b/src/json-crdt-extensions/peritext/slice/PersistedSlice.ts @@ -62,11 +62,6 @@ export class PersistedSlice extends Range implements MutableSlice return this.behavior === SliceBehavior.Split; } - protected tagNode(): JsonNode | undefined { - // TODO: Normalize `.get()` and `.getNode()` methods across VecNode and ArrNode. - return this.tuple.get(3); - } - protected tupleApi() { return this.txt.model.api.wrap(this.tuple); } @@ -109,7 +104,7 @@ export class PersistedSlice extends Range implements MutableSlice } public data(): unknown | undefined { - return this.tuple.get(4)?.view(); + return this.tuple.get(SliceTupleIndex.Data)?.view(); } public dataNode() { diff --git a/src/json-crdt-extensions/peritext/slice/Slices.ts b/src/json-crdt-extensions/peritext/slice/Slices.ts index e46d830164..7ab183ae73 100644 --- a/src/json-crdt-extensions/peritext/slice/Slices.ts +++ b/src/json-crdt-extensions/peritext/slice/Slices.ts @@ -98,6 +98,7 @@ export class Slices implements Stateful, Printable { } public del(id: ITimestampStruct): void { + this.list.del(id); const api = this.txt.model.api; api.builder.del(this.set.id, [tss(id.sid, id.time, 1)]); api.apply(); diff --git a/src/json-crdt-extensions/peritext/slice/__tests__/PersistedSlice.spec.ts b/src/json-crdt-extensions/peritext/slice/__tests__/PersistedSlice.spec.ts new file mode 100644 index 0000000000..7d595dd708 --- /dev/null +++ b/src/json-crdt-extensions/peritext/slice/__tests__/PersistedSlice.spec.ts @@ -0,0 +1,76 @@ +import {SliceBehavior} from '../constants'; +import {setup} from './setup'; + +const setupSlice = () => { + const deps = setup(); + const range = deps.peritext.rangeAt(2, 3); + const slice = deps.peritext.slices.insSplit(range, 0); + return {...deps, range, slice}; +}; + +test('can read slice data', () => { + const {range, slice} = setupSlice(); + expect(slice.isSplit()).toBe(true); + expect(slice.behavior).toBe(SliceBehavior.Split); + expect(slice.type).toBe(0); + expect(slice.data()).toBe(undefined); + expect(slice.start).not.toBe(range.start); + expect(slice.start.cmp(range.start)).toBe(0); + expect(slice.end).not.toBe(range.end); + expect(slice.end.cmp(range.end)).toBe(0); +}); + +describe('.update()', () => { + const testUpdate = (name: string, update: (deps: ReturnType) => void) => { + test('can update: ' + name, () => { + const deps = setupSlice(); + const {slice} = deps; + const hash1 = slice.refresh(); + const hash2 = slice.refresh(); + expect(hash1).toBe(hash2); + update(deps); + const hash3 = slice.refresh(); + expect(hash3).not.toBe(hash2); + }); + }; + + testUpdate('behavior', ({slice}) => { + slice.update({behavior: SliceBehavior.Erase}); + expect(slice.behavior).toBe(SliceBehavior.Erase); + }); + + testUpdate('type', ({slice}) => { + slice.update({type: 1}); + expect(slice.type).toBe(1); + }); + + testUpdate('data', ({slice}) => { + slice.update({data: 123}); + expect(slice.data()).toBe(123); + }); + + testUpdate('range', ({peritext, slice}) => { + const range2 = peritext.rangeAt(0, 1); + slice.update({range: range2}); + expect(slice.cmp(range2)).toBe(0); + }); +}); + +describe('.del() and .isDel()', () => { + test('can delete a slice', () => { + const {peritext, slice} = setupSlice(); + expect(peritext.model.view().slices.length).toBe(1); + expect(slice.isDel()).toBe(false); + const slice2 = peritext.slices.get(slice.id)!; + expect(peritext.model.view().slices.length).toBe(1); + expect(slice2.isDel()).toBe(false); + expect(slice2).toBe(slice); + slice.del(); + expect(peritext.model.view().slices.length).toBe(0); + expect(slice.isDel()).toBe(true); + expect(slice2.isDel()).toBe(true); + const slice3 = peritext.slices.get(slice.id); + expect(slice3).toBe(undefined); + }); +}); + diff --git a/src/json-crdt-extensions/peritext/slice/__tests__/Slices.spec.ts b/src/json-crdt-extensions/peritext/slice/__tests__/Slices.spec.ts index 27f21fa7eb..7144ef64c1 100644 --- a/src/json-crdt-extensions/peritext/slice/__tests__/Slices.spec.ts +++ b/src/json-crdt-extensions/peritext/slice/__tests__/Slices.spec.ts @@ -1,31 +1,10 @@ -import {Model, ObjApi} from '../../../../json-crdt/model'; +import {Model} from '../../../../json-crdt/model'; import {Peritext} from '../../Peritext'; import {Range} from '../../rga/Range'; import {Anchor} from '../../rga/constants'; import {PersistedSlice} from '../PersistedSlice'; import {SliceBehavior} from '../constants'; - -const setup = () => { - const model = Model.withLogicalClock(12345678); - model.api.root({ - text: '', - slices: [], - }); - model.api.str(['text']).ins(0, 'wworld'); - model.api.str(['text']).ins(0, 'helo '); - model.api.str(['text']).ins(2, 'l'); - model.api.str(['text']).del(7, 1); - model.api.str(['text']).ins(11, ' this game is awesome'); - const peritext = new Peritext(model, model.api.str(['text']).node, model.api.arr(['slices']).node); - const slices = peritext.slices; - const encodeAndDecode = () => { - const buf = model.toBinary(); - const model2 = Model.fromBinary(buf); - const peritext2 = new Peritext(model2, model2.api.str(['text']).node, model2.api.arr(['slices']).node); - return {model2, peritext2}; - }; - return {model, peritext, slices, encodeAndDecode}; -}; +import {setup} from './setup'; test('initially slice list is empty', () => { const {peritext} = setup(); diff --git a/src/json-crdt-extensions/peritext/slice/__tests__/setup.ts b/src/json-crdt-extensions/peritext/slice/__tests__/setup.ts new file mode 100644 index 0000000000..f31bb4c04b --- /dev/null +++ b/src/json-crdt-extensions/peritext/slice/__tests__/setup.ts @@ -0,0 +1,24 @@ +import {Model} from '../../../../json-crdt/model'; +import {Peritext} from '../../Peritext'; + +export const setup = () => { + const model = Model.withLogicalClock(12345678); + model.api.root({ + text: '', + slices: [], + }); + model.api.str(['text']).ins(0, 'wworld'); + model.api.str(['text']).ins(0, 'helo '); + model.api.str(['text']).ins(2, 'l'); + model.api.str(['text']).del(7, 1); + model.api.str(['text']).ins(11, ' this game is awesome'); + const peritext = new Peritext(model, model.api.str(['text']).node, model.api.arr(['slices']).node); + const slices = peritext.slices; + const encodeAndDecode = () => { + const buf = model.toBinary(); + const model2 = Model.fromBinary(buf); + const peritext2 = new Peritext(model2, model2.api.str(['text']).node, model2.api.arr(['slices']).node); + return {model2, peritext2}; + }; + return {model, peritext, slices, encodeAndDecode}; +};