Skip to content

Commit f794696

Browse files
committed
fix(json-crdt-extensions): 🐛 correct left character retrieval
1 parent e67b999 commit f794696

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

src/json-crdt-extensions/peritext/point/Point.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class Point implements Pick<Stateful, 'refresh'>, Printable {
201201
return new ChunkSlice(chunk, off, 1);
202202
}
203203
const off = this.id.time - chunk.id.time - 1;
204-
if (off > 0) return new ChunkSlice(chunk, off, 1);
204+
if (off >= 0) return new ChunkSlice(chunk, off, 1);
205205
const str = this.txt.str;
206206
chunk = str.prev(chunk);
207207
while (chunk && chunk.del) chunk = str.prev(chunk);

src/json-crdt-extensions/peritext/point/__tests__/Point.spec.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,3 +497,129 @@ describe('.prevId()', () => {
497497
expect(p2After.prevId(8)).toEqual(undefined);
498498
});
499499
});
500+
501+
describe('.rightChar()', () => {
502+
test('returns the right character', () => {
503+
const model = Model.withLogicalClock(123456);
504+
model.api.root({
505+
text: 'abc',
506+
slices: [],
507+
});
508+
const str = model.api.str(['text']).node;
509+
const peritext = new Peritext(model, str, model.api.arr(['slices']).node);
510+
const point0 = peritext.pointAt(0);
511+
const char0 = point0.rightChar()!;
512+
expect(char0.chunk.data!.slice(char0.off, char0.off + 1)).toBe('a');
513+
const point1 = peritext.pointAt(1);
514+
const char1 = point1.rightChar()!;
515+
expect(char1.chunk.data!.slice(char1.off, char1.off + 1)).toBe('b');
516+
const point2 = peritext.pointAt(2);
517+
const char2 = point2.rightChar()!;
518+
expect(char2.chunk.data!.slice(char2.off, char2.off + 1)).toBe('c');
519+
});
520+
521+
test('multi-char chunks with deletes', () => {
522+
const {peritext} = setupWithText();
523+
const res = '012345678';
524+
for (let i = 0; i < res.length; i++) {
525+
const point = peritext.pointAt(i, Anchor.Before);
526+
const char = point.rightChar()!;
527+
expect(char.view()).toBe(res[i]);
528+
}
529+
for (let i = 0; i < res.length - 1; i++) {
530+
const point = peritext.pointAt(i, Anchor.After);
531+
const char = point.rightChar()!;
532+
expect(char.view()).toBe(res[i + 1]);
533+
}
534+
const end = peritext.pointAt(res.length - 1, Anchor.After);
535+
expect(end.rightChar()).toBe(undefined);
536+
});
537+
538+
test('multi-char chunks with deletes (2)', () => {
539+
const {peritext} = setupWithChunkedText();
540+
const res = '123456789';
541+
for (let i = 0; i < res.length; i++) {
542+
const point = peritext.pointAt(i, Anchor.Before);
543+
const char = point.rightChar()!;
544+
expect(char.view()).toBe(res[i]);
545+
}
546+
for (let i = 0; i < res.length - 1; i++) {
547+
const point = peritext.pointAt(i, Anchor.After);
548+
const char = point.rightChar()!;
549+
expect(char.view()).toBe(res[i + 1]);
550+
}
551+
const end = peritext.pointAt(res.length - 1, Anchor.After);
552+
expect(end.rightChar()).toBe(undefined);
553+
});
554+
});
555+
556+
describe('.leftChar()', () => {
557+
test('returns the left character', () => {
558+
const model = Model.withLogicalClock(123456);
559+
model.api.root({
560+
text: 'abc',
561+
slices: [],
562+
});
563+
const str = model.api.str(['text']).node;
564+
const peritext = new Peritext(model, str, model.api.arr(['slices']).node);
565+
model.api.str(['text']).del(0, 3);
566+
model.api.str(['text']).ins(0, '00a1b2c3');
567+
model.api.str(['text']).del(0, 2);
568+
model.api.str(['text']).del(1, 1);
569+
model.api.str(['text']).del(2, 1);
570+
model.api.str(['text']).del(3, 1);
571+
const point0 = peritext.pointAt(2, Anchor.After);
572+
const char0 = point0.leftChar()!;
573+
expect(char0.chunk.data!.slice(char0.off, char0.off + 1)).toBe('c');
574+
const point1 = peritext.pointAt(1, Anchor.After);
575+
const char1 = point1.leftChar()!;
576+
expect(char1.chunk.data!.slice(char1.off, char1.off + 1)).toBe('b');
577+
const point2 = peritext.pointAt(0, Anchor.After);
578+
const char2 = point2.leftChar()!;
579+
expect(char2.chunk.data!.slice(char2.off, char2.off + 1)).toBe('a');
580+
});
581+
582+
test('multi-char chunks with deletes', () => {
583+
const {peritext} = setupWithText();
584+
const res = '012345678';
585+
const start = peritext.pointAt(0, Anchor.Before);
586+
expect(start.leftChar()).toBe(undefined);
587+
const start2 = peritext.pointAt(0, Anchor.After);
588+
expect(start2.leftChar()!.view()).toBe(res[0]);
589+
const start3 = peritext.pointAt(1, Anchor.Before);
590+
const slice = start3.leftChar();
591+
expect(slice!.view()).toBe(res[0]);
592+
for (let i = 1; i < res.length; i++) {
593+
const point = peritext.pointAt(i, Anchor.Before);
594+
const char = point.leftChar()!;
595+
expect(char.view()).toBe(res[i - 1]);
596+
}
597+
for (let i = 0; i < res.length; i++) {
598+
const point = peritext.pointAt(i, Anchor.After);
599+
const char = point.leftChar()!;
600+
expect(char.view()).toBe(res[i]);
601+
}
602+
});
603+
604+
test('multi-char chunks with deletes (2)', () => {
605+
const {peritext} = setupWithChunkedText();
606+
const res = '123456789';
607+
const start = peritext.pointAt(0, Anchor.Before);
608+
expect(start.leftChar()).toBe(undefined);
609+
const start2 = peritext.pointAt(0, Anchor.After);
610+
expect(start2.leftChar()!.view()).toBe(res[0]);
611+
const start3 = peritext.pointAt(1, Anchor.Before);
612+
const slice = start3.leftChar();
613+
expect(slice!.view()).toBe(res[0]);
614+
for (let i = 1; i < res.length; i++) {
615+
const point = peritext.pointAt(i, Anchor.Before);
616+
const char = point.leftChar()!;
617+
expect(char.view()).toBe(res[i - 1]);
618+
}
619+
for (let i = 0; i < res.length; i++) {
620+
const point = peritext.pointAt(i, Anchor.After);
621+
const char = point.leftChar()!;
622+
expect(char.view()).toBe(res[i]);
623+
}
624+
});
625+
});

0 commit comments

Comments
 (0)