7.4 KB256 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import {ChunkSelectState} from '../chunkSelectState';
9
10describe('ChunkSelectState', () => {
11 const a = 'aa\nbb\ncc\ndd\n';
12 const b = 'cc\ndd\nee\nff\n';
13
14 describe('fromText()', () => {
15 it('with none selected', () => {
16 const state = ChunkSelectState.fromText(a, b, false);
17 expect(renderLines(state)).toMatchObject([
18 '[ ] - 1 aa',
19 '[ ] - 2 bb',
20 ' 3 1 cc',
21 ' 4 2 dd',
22 '[ ] + 3 ee',
23 '[ ] + 4 ff',
24 ]);
25 expect(state.getSelectedText()).toBe(a);
26 });
27
28 it('with all changes selected', () => {
29 const state = ChunkSelectState.fromText(a, b, true);
30 expect(renderLines(state)).toMatchObject([
31 '[x] - 1 aa',
32 '[x] - 2 bb',
33 ' 3 1 cc',
34 ' 4 2 dd',
35 '[x] + 3 ee',
36 '[x] + 4 ff',
37 ]);
38 expect(state.getSelectedText()).toBe(b);
39 });
40
41 it('with free-form partial selection', () => {
42 const text = 'aa\ncc\ndd\nee\n';
43 const state = ChunkSelectState.fromText(a, b, text);
44 expect(renderLines(state)).toMatchObject([
45 '[ ] - 1 aa',
46 '[x] - 2 bb',
47 ' 3 1 cc',
48 ' 4 2 dd',
49 '[x] + 3 ee',
50 '[ ] + 4 ff',
51 ]);
52 expect(state.getSelectedText()).toBe(text);
53 });
54
55 it('with free-form extra deletions', () => {
56 const text = '';
57 const state = ChunkSelectState.fromText(a, b, text);
58 expect(renderLines(state)).toMatchObject([
59 '[x] - 1 aa',
60 '[x] - 2 bb',
61 ' !- 3 1 cc',
62 ' !- 4 2 dd',
63 '[ ] + 3 ee',
64 '[ ] + 4 ff',
65 ]);
66 expect(state.getSelectedText()).toBe(text);
67 });
68
69 it('with free-form extra insertions', () => {
70 const text = 'aa\ncc\n<insertion>\ndd\nee\n';
71 const state = ChunkSelectState.fromText(a, b, text);
72 expect(renderLines(state)).toMatchObject([
73 '[ ] - 1 aa',
74 '[x] - 2 bb',
75 ' 3 1 cc',
76 ' !+ <insertion>',
77 ' 4 2 dd',
78 '[x] + 3 ee',
79 '[ ] + 4 ff',
80 ]);
81 expect(state.getSelectedText()).toBe(text);
82 });
83
84 it('sorts changes, deletion is before insertion', () => {
85 const state = ChunkSelectState.fromText('aa\naa\n', 'bb\nbb\nbb\n', true);
86 expect(renderLines(state)).toMatchObject([
87 '[x] - 1 aa',
88 '[x] - 2 aa',
89 '[x] + 1 bb',
90 '[x] + 2 bb',
91 '[x] + 3 bb',
92 ]);
93 });
94 });
95
96 describe('setSelectedLines()', () => {
97 it('toggles deleted lines', () => {
98 let state = ChunkSelectState.fromText(a, b, false);
99 state = state.setSelectedLines([
100 [0, true],
101 [1, false],
102 ]);
103 expect(renderLines(state).slice(0, 2)).toMatchObject(['[x] - 1 aa', '[ ] - 2 bb']);
104 state = state.setSelectedLines([
105 [0, false],
106 [1, true],
107 ]);
108 expect(renderLines(state).slice(0, 2)).toMatchObject(['[ ] - 1 aa', '[x] - 2 bb']);
109 });
110
111 it('toggles added lines', () => {
112 let state = ChunkSelectState.fromText(a, b, false);
113 state = state.setSelectedLines([
114 [4, true],
115 [5, false],
116 ]);
117 expect(renderLines(state).slice(4, 6)).toMatchObject(['[x] + 3 ee', '[ ] + 4 ff']);
118 state = state.setSelectedLines([
119 [4, false],
120 [5, true],
121 ]);
122 expect(renderLines(state).slice(4, 6)).toMatchObject(['[ ] + 3 ee', '[x] + 4 ff']);
123 });
124
125 it('does nothing to other lines', () => {
126 const text = 'aa\ncc\n<insertion>\ndd\nee\n';
127 let state = ChunkSelectState.fromText(a, b, text);
128 state = state.setSelectedLines([
129 [2, false],
130 [3, false],
131 [4, true],
132 ]);
133 expect(renderLines(state)).toMatchObject([
134 '[ ] - 1 aa',
135 '[x] - 2 bb',
136 ' 3 1 cc',
137 ' !+ <insertion>',
138 ' 4 2 dd',
139 '[x] + 3 ee',
140 '[ ] + 4 ff',
141 ]);
142 expect(state.getSelectedText()).toBe(text);
143 });
144 });
145
146 describe('setSelectedText()', () => {
147 it('round-trips with getSelectedText()', () => {
148 let state = ChunkSelectState.fromText(a, b, false);
149 const lines = ['aa\n', 'bb\n', 'cc\n', 'ii\n', 'dd\n', 'ee\n', 'ff\n'];
150 // eslint-disable-next-line no-bitwise
151 const end = 1 << lines.length;
152 for (let bits = 0; bits < end; ++bits) {
153 // eslint-disable-next-line no-bitwise
154 const text = lines.map((l, i) => ((bits & (1 << i)) === 0 ? '' : l)).join('');
155 state = state.setSelectedText(text);
156 expect(state.getSelectedText()).toBe(text);
157 }
158 });
159 });
160
161 describe('getInverseText()', () => {
162 it('produces changes with inverse selection', () => {
163 const state = ChunkSelectState.fromText(a, b, a).setSelectedLines([
164 [0, true],
165 [4, true],
166 ]);
167 expect(renderLines(state)).toMatchObject([
168 '[x] - 1 aa',
169 '[ ] - 2 bb',
170 ' 3 1 cc',
171 ' 4 2 dd',
172 '[x] + 3 ee',
173 '[ ] + 4 ff',
174 ]);
175 expect(state.getSelectedText()).toBe('bb\ncc\ndd\nee\n');
176 expect(state.getInverseText()).toBe('aa\ncc\ndd\nff\n');
177 });
178 });
179
180 describe('getLineRegions()', () => {
181 it('produces a region when nothing is changed', () => {
182 const state = ChunkSelectState.fromText(a, a, a);
183 expect(state.getLineRegions()).toMatchObject([
184 {
185 lines: state.getLines(),
186 same: true,
187 collapsed: true,
188 },
189 ]);
190 });
191
192 it('produces a region when everything is changed', () => {
193 const a = 'a\na\na\n';
194 const b = 'b\nb\nb\nb\n';
195 [a, b].forEach(m => {
196 const state = ChunkSelectState.fromText(a, b, m);
197 expect(state.getLineRegions()).toMatchObject([
198 {
199 lines: state.getLines(),
200 same: false,
201 collapsed: false,
202 },
203 ]);
204 });
205 });
206
207 it('produces regions with complex changes', () => {
208 const state = ChunkSelectState.fromText(
209 '1\n2\n3\n4\n8\n9\n',
210 '1\n2\n3\n5\n8\n9\n',
211 '1\n2\n3\n5\n8\n9\n',
212 );
213 const lines = state.getLines();
214 expect(state.getLineRegions()).toMatchObject([
215 {
216 lines: lines.slice(0, 1),
217 collapsed: true,
218 same: true,
219 },
220 {
221 lines: lines.slice(1, 3),
222 collapsed: false,
223 same: true,
224 },
225 {
226 lines: lines.slice(3, 5),
227 collapsed: false,
228 same: false,
229 },
230 {
231 lines: lines.slice(5, 7),
232 collapsed: false,
233 same: true,
234 },
235 ]);
236 });
237 });
238});
239
240/** Visualize line selections in ASCII. */
241function renderLines(state: ChunkSelectState): string[] {
242 return state.getLines().map(l => {
243 const checkbox = {true: '[x]', false: '[ ]', null: ' '}[`${l.selected}`];
244 const aLine = l.aLine === null ? '' : l.aLine.toString();
245 const bLine = l.bLine === null ? '' : l.bLine.toString();
246 return [
247 checkbox.padStart(3),
248 l.sign.padStart(2),
249 aLine.padStart(3),
250 bLine.padStart(3),
251 ' ',
252 l.data.trimEnd(),
253 ].join('');
254 });
255}
256