addons/isl/src/stackEdit/__tests__/chunkSelectState.test.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import {ChunkSelectState} from '../chunkSelectState';
b69ab319
b69ab3110describe('ChunkSelectState', () => {
b69ab3111 const a = 'aa\nbb\ncc\ndd\n';
b69ab3112 const b = 'cc\ndd\nee\nff\n';
b69ab3113
b69ab3114 describe('fromText()', () => {
b69ab3115 it('with none selected', () => {
b69ab3116 const state = ChunkSelectState.fromText(a, b, false);
b69ab3117 expect(renderLines(state)).toMatchObject([
b69ab3118 '[ ] - 1 aa',
b69ab3119 '[ ] - 2 bb',
b69ab3120 ' 3 1 cc',
b69ab3121 ' 4 2 dd',
b69ab3122 '[ ] + 3 ee',
b69ab3123 '[ ] + 4 ff',
b69ab3124 ]);
b69ab3125 expect(state.getSelectedText()).toBe(a);
b69ab3126 });
b69ab3127
b69ab3128 it('with all changes selected', () => {
b69ab3129 const state = ChunkSelectState.fromText(a, b, true);
b69ab3130 expect(renderLines(state)).toMatchObject([
b69ab3131 '[x] - 1 aa',
b69ab3132 '[x] - 2 bb',
b69ab3133 ' 3 1 cc',
b69ab3134 ' 4 2 dd',
b69ab3135 '[x] + 3 ee',
b69ab3136 '[x] + 4 ff',
b69ab3137 ]);
b69ab3138 expect(state.getSelectedText()).toBe(b);
b69ab3139 });
b69ab3140
b69ab3141 it('with free-form partial selection', () => {
b69ab3142 const text = 'aa\ncc\ndd\nee\n';
b69ab3143 const state = ChunkSelectState.fromText(a, b, text);
b69ab3144 expect(renderLines(state)).toMatchObject([
b69ab3145 '[ ] - 1 aa',
b69ab3146 '[x] - 2 bb',
b69ab3147 ' 3 1 cc',
b69ab3148 ' 4 2 dd',
b69ab3149 '[x] + 3 ee',
b69ab3150 '[ ] + 4 ff',
b69ab3151 ]);
b69ab3152 expect(state.getSelectedText()).toBe(text);
b69ab3153 });
b69ab3154
b69ab3155 it('with free-form extra deletions', () => {
b69ab3156 const text = '';
b69ab3157 const state = ChunkSelectState.fromText(a, b, text);
b69ab3158 expect(renderLines(state)).toMatchObject([
b69ab3159 '[x] - 1 aa',
b69ab3160 '[x] - 2 bb',
b69ab3161 ' !- 3 1 cc',
b69ab3162 ' !- 4 2 dd',
b69ab3163 '[ ] + 3 ee',
b69ab3164 '[ ] + 4 ff',
b69ab3165 ]);
b69ab3166 expect(state.getSelectedText()).toBe(text);
b69ab3167 });
b69ab3168
b69ab3169 it('with free-form extra insertions', () => {
b69ab3170 const text = 'aa\ncc\n<insertion>\ndd\nee\n';
b69ab3171 const state = ChunkSelectState.fromText(a, b, text);
b69ab3172 expect(renderLines(state)).toMatchObject([
b69ab3173 '[ ] - 1 aa',
b69ab3174 '[x] - 2 bb',
b69ab3175 ' 3 1 cc',
b69ab3176 ' !+ <insertion>',
b69ab3177 ' 4 2 dd',
b69ab3178 '[x] + 3 ee',
b69ab3179 '[ ] + 4 ff',
b69ab3180 ]);
b69ab3181 expect(state.getSelectedText()).toBe(text);
b69ab3182 });
b69ab3183
b69ab3184 it('sorts changes, deletion is before insertion', () => {
b69ab3185 const state = ChunkSelectState.fromText('aa\naa\n', 'bb\nbb\nbb\n', true);
b69ab3186 expect(renderLines(state)).toMatchObject([
b69ab3187 '[x] - 1 aa',
b69ab3188 '[x] - 2 aa',
b69ab3189 '[x] + 1 bb',
b69ab3190 '[x] + 2 bb',
b69ab3191 '[x] + 3 bb',
b69ab3192 ]);
b69ab3193 });
b69ab3194 });
b69ab3195
b69ab3196 describe('setSelectedLines()', () => {
b69ab3197 it('toggles deleted lines', () => {
b69ab3198 let state = ChunkSelectState.fromText(a, b, false);
b69ab3199 state = state.setSelectedLines([
b69ab31100 [0, true],
b69ab31101 [1, false],
b69ab31102 ]);
b69ab31103 expect(renderLines(state).slice(0, 2)).toMatchObject(['[x] - 1 aa', '[ ] - 2 bb']);
b69ab31104 state = state.setSelectedLines([
b69ab31105 [0, false],
b69ab31106 [1, true],
b69ab31107 ]);
b69ab31108 expect(renderLines(state).slice(0, 2)).toMatchObject(['[ ] - 1 aa', '[x] - 2 bb']);
b69ab31109 });
b69ab31110
b69ab31111 it('toggles added lines', () => {
b69ab31112 let state = ChunkSelectState.fromText(a, b, false);
b69ab31113 state = state.setSelectedLines([
b69ab31114 [4, true],
b69ab31115 [5, false],
b69ab31116 ]);
b69ab31117 expect(renderLines(state).slice(4, 6)).toMatchObject(['[x] + 3 ee', '[ ] + 4 ff']);
b69ab31118 state = state.setSelectedLines([
b69ab31119 [4, false],
b69ab31120 [5, true],
b69ab31121 ]);
b69ab31122 expect(renderLines(state).slice(4, 6)).toMatchObject(['[ ] + 3 ee', '[x] + 4 ff']);
b69ab31123 });
b69ab31124
b69ab31125 it('does nothing to other lines', () => {
b69ab31126 const text = 'aa\ncc\n<insertion>\ndd\nee\n';
b69ab31127 let state = ChunkSelectState.fromText(a, b, text);
b69ab31128 state = state.setSelectedLines([
b69ab31129 [2, false],
b69ab31130 [3, false],
b69ab31131 [4, true],
b69ab31132 ]);
b69ab31133 expect(renderLines(state)).toMatchObject([
b69ab31134 '[ ] - 1 aa',
b69ab31135 '[x] - 2 bb',
b69ab31136 ' 3 1 cc',
b69ab31137 ' !+ <insertion>',
b69ab31138 ' 4 2 dd',
b69ab31139 '[x] + 3 ee',
b69ab31140 '[ ] + 4 ff',
b69ab31141 ]);
b69ab31142 expect(state.getSelectedText()).toBe(text);
b69ab31143 });
b69ab31144 });
b69ab31145
b69ab31146 describe('setSelectedText()', () => {
b69ab31147 it('round-trips with getSelectedText()', () => {
b69ab31148 let state = ChunkSelectState.fromText(a, b, false);
b69ab31149 const lines = ['aa\n', 'bb\n', 'cc\n', 'ii\n', 'dd\n', 'ee\n', 'ff\n'];
b69ab31150 // eslint-disable-next-line no-bitwise
b69ab31151 const end = 1 << lines.length;
b69ab31152 for (let bits = 0; bits < end; ++bits) {
b69ab31153 // eslint-disable-next-line no-bitwise
b69ab31154 const text = lines.map((l, i) => ((bits & (1 << i)) === 0 ? '' : l)).join('');
b69ab31155 state = state.setSelectedText(text);
b69ab31156 expect(state.getSelectedText()).toBe(text);
b69ab31157 }
b69ab31158 });
b69ab31159 });
b69ab31160
b69ab31161 describe('getInverseText()', () => {
b69ab31162 it('produces changes with inverse selection', () => {
b69ab31163 const state = ChunkSelectState.fromText(a, b, a).setSelectedLines([
b69ab31164 [0, true],
b69ab31165 [4, true],
b69ab31166 ]);
b69ab31167 expect(renderLines(state)).toMatchObject([
b69ab31168 '[x] - 1 aa',
b69ab31169 '[ ] - 2 bb',
b69ab31170 ' 3 1 cc',
b69ab31171 ' 4 2 dd',
b69ab31172 '[x] + 3 ee',
b69ab31173 '[ ] + 4 ff',
b69ab31174 ]);
b69ab31175 expect(state.getSelectedText()).toBe('bb\ncc\ndd\nee\n');
b69ab31176 expect(state.getInverseText()).toBe('aa\ncc\ndd\nff\n');
b69ab31177 });
b69ab31178 });
b69ab31179
b69ab31180 describe('getLineRegions()', () => {
b69ab31181 it('produces a region when nothing is changed', () => {
b69ab31182 const state = ChunkSelectState.fromText(a, a, a);
b69ab31183 expect(state.getLineRegions()).toMatchObject([
b69ab31184 {
b69ab31185 lines: state.getLines(),
b69ab31186 same: true,
b69ab31187 collapsed: true,
b69ab31188 },
b69ab31189 ]);
b69ab31190 });
b69ab31191
b69ab31192 it('produces a region when everything is changed', () => {
b69ab31193 const a = 'a\na\na\n';
b69ab31194 const b = 'b\nb\nb\nb\n';
b69ab31195 [a, b].forEach(m => {
b69ab31196 const state = ChunkSelectState.fromText(a, b, m);
b69ab31197 expect(state.getLineRegions()).toMatchObject([
b69ab31198 {
b69ab31199 lines: state.getLines(),
b69ab31200 same: false,
b69ab31201 collapsed: false,
b69ab31202 },
b69ab31203 ]);
b69ab31204 });
b69ab31205 });
b69ab31206
b69ab31207 it('produces regions with complex changes', () => {
b69ab31208 const state = ChunkSelectState.fromText(
b69ab31209 '1\n2\n3\n4\n8\n9\n',
b69ab31210 '1\n2\n3\n5\n8\n9\n',
b69ab31211 '1\n2\n3\n5\n8\n9\n',
b69ab31212 );
b69ab31213 const lines = state.getLines();
b69ab31214 expect(state.getLineRegions()).toMatchObject([
b69ab31215 {
b69ab31216 lines: lines.slice(0, 1),
b69ab31217 collapsed: true,
b69ab31218 same: true,
b69ab31219 },
b69ab31220 {
b69ab31221 lines: lines.slice(1, 3),
b69ab31222 collapsed: false,
b69ab31223 same: true,
b69ab31224 },
b69ab31225 {
b69ab31226 lines: lines.slice(3, 5),
b69ab31227 collapsed: false,
b69ab31228 same: false,
b69ab31229 },
b69ab31230 {
b69ab31231 lines: lines.slice(5, 7),
b69ab31232 collapsed: false,
b69ab31233 same: true,
b69ab31234 },
b69ab31235 ]);
b69ab31236 });
b69ab31237 });
b69ab31238});
b69ab31239
b69ab31240/** Visualize line selections in ASCII. */
b69ab31241function renderLines(state: ChunkSelectState): string[] {
b69ab31242 return state.getLines().map(l => {
b69ab31243 const checkbox = {true: '[x]', false: '[ ]', null: ' '}[`${l.selected}`];
b69ab31244 const aLine = l.aLine === null ? '' : l.aLine.toString();
b69ab31245 const bLine = l.bLine === null ? '' : l.bLine.toString();
b69ab31246 return [
b69ab31247 checkbox.padStart(3),
b69ab31248 l.sign.padStart(2),
b69ab31249 aLine.padStart(3),
b69ab31250 bLine.padStart(3),
b69ab31251 ' ',
b69ab31252 l.data.trimEnd(),
b69ab31253 ].join('');
b69ab31254 });
b69ab31255}