Skip to content

Commit e89ee5b

Browse files
authored
Merge pull request #657 from streamich/peritext-tests
JSON CRDT Peritext high-level tests
2 parents 819f7bd + 4301937 commit e89ee5b

File tree

2 files changed

+579
-0
lines changed

2 files changed

+579
-0
lines changed
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
import {Model} from '../../../json-crdt/model';
2+
import {Peritext} from '../Peritext';
3+
import {Anchor} from '../rga/constants';
4+
import {Kit, setupHelloWorldKit, setupHelloWorldWithFewEditsKit} from './setup';
5+
6+
const run = (setup: () => Kit) => {
7+
test('can run .refresh() on empty state', () => {
8+
const model = Model.withLogicalClock();
9+
model.api.root({
10+
text: '',
11+
slices: [],
12+
});
13+
const peritext = new Peritext(model, model.api.str(['text']).node, model.api.arr(['slices']).node);
14+
peritext.refresh();
15+
});
16+
17+
test('can insert a slice', () => {
18+
const {peritext, model} = setup();
19+
peritext.editor.cursor.setAt(4, 5);
20+
peritext.editor.saved.insMarker('bold', {bold: true});
21+
model.api.apply();
22+
const slices = model.s.text.toExt().slices().view();
23+
expect(slices).toMatchObject([[expect.any(Number), expect.any(Object), expect.any(Number), 'bold', {bold: true}]]);
24+
});
25+
26+
describe('cursor', () => {
27+
test('by default cursor is a collapsed caret', () => {
28+
const {peritext} = setup();
29+
const text = peritext.editor.cursor.text();
30+
expect(text).toBe('');
31+
});
32+
33+
test('can select a local range and get text representation of it', () => {
34+
const {peritext} = setup();
35+
peritext.editor.cursor.setAt(0, 5);
36+
const text = peritext.editor.cursor.text();
37+
expect(text).toBe('hello');
38+
});
39+
40+
test('can select first character', () => {
41+
const {peritext} = setup();
42+
peritext.editor.cursor.setAt(0, 1);
43+
const text = peritext.editor.cursor.text();
44+
expect(text).toBe('h');
45+
});
46+
47+
test('can select second character', () => {
48+
const {peritext} = setup();
49+
peritext.editor.cursor.setAt(1, 1);
50+
const text = peritext.editor.cursor.text();
51+
expect(text).toBe('e');
52+
});
53+
54+
test('can select character one before last', () => {
55+
const {peritext, model} = setup();
56+
const text1 = (model.view() as any).text as string;
57+
peritext.editor.cursor.setAt(text1.length - 2, 1);
58+
const text2 = peritext.editor.cursor.text();
59+
expect(text2).toBe('l');
60+
});
61+
62+
test('can select last character', () => {
63+
const {peritext, model} = setup();
64+
const text1 = (model.view() as any).text as string;
65+
peritext.editor.cursor.setAt(text1.length - 1, 1);
66+
const text2 = peritext.editor.cursor.text();
67+
expect(text2).toBe('d');
68+
});
69+
70+
test('can select the whole text', () => {
71+
const {peritext, model} = setup();
72+
const text1 = (model.view() as any).text as string;
73+
peritext.editor.cursor.setAt(0, text1.length);
74+
const text2 = peritext.editor.cursor.text();
75+
expect(text2).toBe(text1);
76+
});
77+
78+
test('can set an empty (caret) selection', () => {
79+
const {peritext} = setup();
80+
peritext.editor.cursor.setAt(1);
81+
peritext.editor.insert('!');
82+
expect(peritext.str.view()).toBe('h!ello world');
83+
peritext.editor.cursor.setAt(1);
84+
peritext.editor.insert('?');
85+
expect(peritext.str.view()).toBe('h?!ello world');
86+
peritext.editor.cursor.setAt(1);
87+
peritext.editor.insert('+');
88+
expect(peritext.str.view()).toBe('h+?!ello world');
89+
peritext.editor.cursor.setAt(2);
90+
peritext.editor.insert('GG');
91+
expect(peritext.str.view()).toBe('h+GG?!ello world');
92+
});
93+
94+
test('can set an empty (caret) selection at the end of the string', () => {
95+
const {peritext} = setup();
96+
peritext.editor.cursor.setAt(peritext.str.length());
97+
peritext.editor.insert('!');
98+
expect(peritext.str.view()).toBe('hello world!');
99+
peritext.editor.insert('?');
100+
expect(peritext.str.view()).toBe('hello world!?');
101+
peritext.editor.cursor.setAt(peritext.str.length() - 1);
102+
peritext.editor.insert('+');
103+
expect(peritext.str.view()).toBe('hello world!+?');
104+
});
105+
});
106+
107+
describe('.collapseSelection()', () => {
108+
test('does nothing when selection is already collapsed', () => {
109+
const {peritext, model} = setup();
110+
const {editor} = peritext;
111+
expect(editor.cursor.isCollapsed()).toBe(true);
112+
editor.cursor.collapse();
113+
expect(editor.cursor.isCollapsed()).toBe(true);
114+
expect((model.view() as any).text).toBe('hello world');
115+
});
116+
117+
test('removes text that was selected', () => {
118+
const {peritext, model} = setup();
119+
const {editor} = peritext;
120+
editor.cursor.setAt(2, 3);
121+
expect(editor.cursor.isCollapsed()).toBe(false);
122+
editor.cursor.collapse();
123+
expect(editor.cursor.isCollapsed()).toBe(true);
124+
expect((model.view() as any).text).toBe('he world');
125+
});
126+
127+
test('can collapse at the beginning of string twice', () => {
128+
const {peritext, model} = setup();
129+
const {editor} = peritext;
130+
peritext.editor.cursor.setAt(0, 1);
131+
expect(editor.cursor.isCollapsed()).toBe(false);
132+
editor.cursor.collapse();
133+
expect(editor.cursor.isCollapsed()).toBe(true);
134+
expect((model.view() as any).text).toBe('ello world');
135+
editor.cursor.setAt(0, 1);
136+
expect(editor.cursor.isCollapsed()).toBe(false);
137+
editor.cursor.collapse();
138+
expect(editor.cursor.isCollapsed()).toBe(true);
139+
expect((model.view() as any).text).toBe('llo world');
140+
});
141+
142+
test('can collapse at the end of string twice', () => {
143+
const {peritext, model} = setup();
144+
const {editor} = peritext;
145+
editor.cursor.setAt(peritext.str.length() - 1, 1);
146+
expect(editor.cursor.isCollapsed()).toBe(false);
147+
editor.cursor.collapse();
148+
expect(editor.cursor.isCollapsed()).toBe(true);
149+
expect((model.view() as any).text).toBe('hello worl');
150+
peritext.editor.cursor.setAt(peritext.str.length() - 1, 1);
151+
expect(editor.cursor.isCollapsed()).toBe(false);
152+
editor.cursor.collapse();
153+
expect(editor.cursor.isCollapsed()).toBe(true);
154+
expect((model.view() as any).text).toBe('hello wor');
155+
});
156+
157+
test('can collapse the whole string', () => {
158+
const {peritext, model} = setup();
159+
const {editor} = peritext;
160+
editor.cursor.setAt(0, peritext.str.length());
161+
expect(editor.cursor.isCollapsed()).toBe(false);
162+
editor.cursor.collapse();
163+
expect(editor.cursor.isCollapsed()).toBe(true);
164+
expect((model.view() as any).text).toBe('');
165+
editor.insert('abc');
166+
expect((model.view() as any).text).toBe('abc');
167+
});
168+
});
169+
170+
describe('.nextId()', () => {
171+
test('returns next char ID when cursor at string start', () => {
172+
const {peritext, model} = setup();
173+
const {editor} = peritext;
174+
expect(editor.cursor.start.id).toStrictEqual(peritext.str.id);
175+
const nextId = editor.cursor.start.nextId()!;
176+
editor.cursor.setAfter(nextId);
177+
editor.insert('!');
178+
expect((model.view() as any).text).toBe('h!ello world');
179+
});
180+
181+
test('can walk all the way to string end', () => {
182+
const {peritext, model} = setup();
183+
const {editor} = peritext;
184+
expect(editor.cursor.start.id).toStrictEqual(peritext.str.id);
185+
const nextId = editor.cursor.start.nextId()!;
186+
editor.cursor.setAfter(nextId);
187+
editor.insert('!');
188+
expect((model.view() as any).text).toBe('h!ello world');
189+
editor.insert('?');
190+
expect((model.view() as any).text).toBe('h!?ello world');
191+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
192+
editor.insert('.');
193+
expect((model.view() as any).text).toBe('h!?e.llo world');
194+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
195+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
196+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
197+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
198+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
199+
editor.insert('#');
200+
expect((model.view() as any).text).toBe('h!?e.llo w#orld');
201+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
202+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
203+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
204+
editor.cursor.setAfter(editor.cursor.start.nextId()!);
205+
editor.insert('+');
206+
expect((model.view() as any).text).toBe('h!?e.llo w#orld+');
207+
});
208+
});
209+
210+
describe('.insert()', () => {
211+
test('can insert at caret position', () => {
212+
const {peritext, model} = setup();
213+
const {editor} = peritext;
214+
editor.insert('H');
215+
expect((model.view() as any).text).toBe('Hhello world');
216+
});
217+
218+
test('can insert text in when cursor is range', () => {
219+
const {peritext, model} = setup();
220+
const {editor} = peritext;
221+
const firstCharId = peritext.str.find(0)!;
222+
editor.cursor.set(peritext.point(firstCharId, Anchor.Before), peritext.point(firstCharId, Anchor.After));
223+
editor.insert('H');
224+
expect((model.view() as any).text).toBe('Hello world');
225+
});
226+
});
227+
228+
describe('deletions', () => {
229+
test('does nothing when deleting at the start of a string', () => {
230+
const {peritext} = setup();
231+
const {editor} = peritext;
232+
editor.delBwd();
233+
expect(peritext.str.view()).toBe('hello world');
234+
});
235+
236+
test('can delete one character at the beginning of a string', () => {
237+
const {peritext} = setup();
238+
const {editor} = peritext;
239+
editor.cursor.setAt(1);
240+
editor.delBwd();
241+
expect(peritext.str.view()).toBe('ello world');
242+
editor.delBwd();
243+
expect(peritext.str.view()).toBe('ello world');
244+
editor.delBwd();
245+
expect(peritext.str.view()).toBe('ello world');
246+
});
247+
248+
test('can delete two characters at the beginning of a string', () => {
249+
const {peritext} = setup();
250+
const {editor} = peritext;
251+
editor.cursor.setAt(2);
252+
editor.delBwd();
253+
expect(peritext.str.view()).toBe('hllo world');
254+
editor.delBwd();
255+
expect(peritext.str.view()).toBe('llo world');
256+
editor.delBwd();
257+
expect(peritext.str.view()).toBe('llo world');
258+
});
259+
260+
test('can delete a range selection', () => {
261+
const {peritext} = setup();
262+
const {editor} = peritext;
263+
editor.cursor.setAt(2, 3);
264+
editor.delBwd();
265+
expect(peritext.str.view()).toBe('he world');
266+
editor.delBwd();
267+
expect(peritext.str.view()).toBe('h world');
268+
editor.delBwd();
269+
expect(peritext.str.view()).toBe(' world');
270+
editor.delBwd();
271+
expect(peritext.str.view()).toBe(' world');
272+
});
273+
});
274+
};
275+
276+
describe('no edits "hello world"', () => {
277+
run(setupHelloWorldKit);
278+
});
279+
280+
describe('some edits "hello world"', () => {
281+
run(setupHelloWorldWithFewEditsKit);
282+
});

0 commit comments

Comments
 (0)