Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/browser/controller/textAreaState.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
9
import { ITextAreaWrapper, TextAreaState } from '../../../browser/controller/editContext/textArea/textAreaEditContextState.js';
10
import { Range } from '../../../common/core/range.js';
11
import { Selection } from '../../../common/core/selection.js';
12
import { createTextModel } from '../../common/testTextModel.js';
13
import { SimplePagedScreenReaderStrategy } from '../../../browser/controller/editContext/screenReaderUtils.js';
14
15
class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper {
16
17
public _value: string;
18
public _selectionStart: number;
19
public _selectionEnd: number;
20
21
constructor() {
22
super();
23
this._value = '';
24
this._selectionStart = 0;
25
this._selectionEnd = 0;
26
}
27
28
public getValue(): string {
29
return this._value;
30
}
31
32
public setValue(reason: string, value: string): void {
33
this._value = value;
34
this._selectionStart = this._value.length;
35
this._selectionEnd = this._value.length;
36
}
37
38
public getSelectionStart(): number {
39
return this._selectionStart;
40
}
41
42
public getSelectionEnd(): number {
43
return this._selectionEnd;
44
}
45
46
public setSelectionRange(reason: string, selectionStart: number, selectionEnd: number): void {
47
if (selectionStart < 0) {
48
selectionStart = 0;
49
}
50
if (selectionStart > this._value.length) {
51
selectionStart = this._value.length;
52
}
53
if (selectionEnd < 0) {
54
selectionEnd = 0;
55
}
56
if (selectionEnd > this._value.length) {
57
selectionEnd = this._value.length;
58
}
59
this._selectionStart = selectionStart;
60
this._selectionEnd = selectionEnd;
61
}
62
}
63
64
function equalsTextAreaState(a: TextAreaState, b: TextAreaState): boolean {
65
return (
66
a.value === b.value
67
&& a.selectionStart === b.selectionStart
68
&& a.selectionEnd === b.selectionEnd
69
&& Range.equalsRange(a.selection, b.selection)
70
&& a.newlineCountBeforeSelection === b.newlineCountBeforeSelection
71
);
72
}
73
74
suite('TextAreaState', () => {
75
76
ensureNoDisposablesAreLeakedInTestSuite();
77
78
function assertTextAreaState(actual: TextAreaState, value: string, selectionStart: number, selectionEnd: number): void {
79
const desired = new TextAreaState(value, selectionStart, selectionEnd, null, undefined);
80
assert.ok(equalsTextAreaState(desired, actual), desired.toString() + ' == ' + actual.toString());
81
}
82
83
test('fromTextArea', () => {
84
const textArea = new MockTextAreaWrapper();
85
textArea._value = 'Hello world!';
86
textArea._selectionStart = 1;
87
textArea._selectionEnd = 12;
88
let actual = TextAreaState.readFromTextArea(textArea, null);
89
90
assertTextAreaState(actual, 'Hello world!', 1, 12);
91
assert.strictEqual(actual.value, 'Hello world!');
92
assert.strictEqual(actual.selectionStart, 1);
93
94
actual = actual.collapseSelection();
95
assertTextAreaState(actual, 'Hello world!', 12, 12);
96
97
textArea.dispose();
98
});
99
100
test('applyToTextArea', () => {
101
const textArea = new MockTextAreaWrapper();
102
textArea._value = 'Hello world!';
103
textArea._selectionStart = 1;
104
textArea._selectionEnd = 12;
105
106
let state = new TextAreaState('Hi world!', 2, 2, null, undefined);
107
state.writeToTextArea('test', textArea, false);
108
109
assert.strictEqual(textArea._value, 'Hi world!');
110
assert.strictEqual(textArea._selectionStart, 9);
111
assert.strictEqual(textArea._selectionEnd, 9);
112
113
state = new TextAreaState('Hi world!', 3, 3, null, undefined);
114
state.writeToTextArea('test', textArea, false);
115
116
assert.strictEqual(textArea._value, 'Hi world!');
117
assert.strictEqual(textArea._selectionStart, 9);
118
assert.strictEqual(textArea._selectionEnd, 9);
119
120
state = new TextAreaState('Hi world!', 0, 2, null, undefined);
121
state.writeToTextArea('test', textArea, true);
122
123
assert.strictEqual(textArea._value, 'Hi world!');
124
assert.strictEqual(textArea._selectionStart, 0);
125
assert.strictEqual(textArea._selectionEnd, 2);
126
127
textArea.dispose();
128
});
129
130
function testDeduceInput(prevState: TextAreaState | null, value: string, selectionStart: number, selectionEnd: number, couldBeEmojiInput: boolean, expected: string, expectedCharReplaceCnt: number): void {
131
prevState = prevState || TextAreaState.EMPTY;
132
133
const textArea = new MockTextAreaWrapper();
134
textArea._value = value;
135
textArea._selectionStart = selectionStart;
136
textArea._selectionEnd = selectionEnd;
137
138
const newState = TextAreaState.readFromTextArea(textArea, null);
139
const actual = TextAreaState.deduceInput(prevState, newState, couldBeEmojiInput);
140
141
assert.deepStrictEqual(actual, {
142
text: expected,
143
replacePrevCharCnt: expectedCharReplaceCnt,
144
replaceNextCharCnt: 0,
145
positionDelta: 0,
146
});
147
148
textArea.dispose();
149
}
150
151
test('extractNewText - no previous state with selection', () => {
152
testDeduceInput(
153
null,
154
'a',
155
0, 1, true,
156
'a', 0
157
);
158
});
159
160
test('issue #2586: Replacing selected end-of-line with newline locks up the document', () => {
161
testDeduceInput(
162
new TextAreaState(']\n', 1, 2, null, undefined),
163
']\n',
164
2, 2, true,
165
'\n', 0
166
);
167
});
168
169
test('extractNewText - no previous state without selection', () => {
170
testDeduceInput(
171
null,
172
'a',
173
1, 1, true,
174
'a', 0
175
);
176
});
177
178
test('extractNewText - typing does not cause a selection', () => {
179
testDeduceInput(
180
TextAreaState.EMPTY,
181
'a',
182
0, 1, true,
183
'a', 0
184
);
185
});
186
187
test('extractNewText - had the textarea empty', () => {
188
testDeduceInput(
189
TextAreaState.EMPTY,
190
'a',
191
1, 1, true,
192
'a', 0
193
);
194
});
195
196
test('extractNewText - had the entire line selected', () => {
197
testDeduceInput(
198
new TextAreaState('Hello world!', 0, 12, null, undefined),
199
'H',
200
1, 1, true,
201
'H', 0
202
);
203
});
204
205
test('extractNewText - had previous text 1', () => {
206
testDeduceInput(
207
new TextAreaState('Hello world!', 12, 12, null, undefined),
208
'Hello world!a',
209
13, 13, true,
210
'a', 0
211
);
212
});
213
214
test('extractNewText - had previous text 2', () => {
215
testDeduceInput(
216
new TextAreaState('Hello world!', 0, 0, null, undefined),
217
'aHello world!',
218
1, 1, true,
219
'a', 0
220
);
221
});
222
223
test('extractNewText - had previous text 3', () => {
224
testDeduceInput(
225
new TextAreaState('Hello world!', 6, 11, null, undefined),
226
'Hello other!',
227
11, 11, true,
228
'other', 0
229
);
230
});
231
232
test('extractNewText - IME', () => {
233
testDeduceInput(
234
TextAreaState.EMPTY,
235
'これは',
236
3, 3, true,
237
'これは', 0
238
);
239
});
240
241
test('extractNewText - isInOverwriteMode', () => {
242
testDeduceInput(
243
new TextAreaState('Hello world!', 0, 0, null, undefined),
244
'Aello world!',
245
1, 1, true,
246
'A', 0
247
);
248
});
249
250
test('extractMacReplacedText - does nothing if there is selection', () => {
251
testDeduceInput(
252
new TextAreaState('Hello world!', 5, 5, null, undefined),
253
'Hellö world!',
254
4, 5, true,
255
'ö', 0
256
);
257
});
258
259
test('extractMacReplacedText - does nothing if there is more than one extra char', () => {
260
testDeduceInput(
261
new TextAreaState('Hello world!', 5, 5, null, undefined),
262
'Hellöö world!',
263
5, 5, true,
264
'öö', 1
265
);
266
});
267
268
test('extractMacReplacedText - does nothing if there is more than one changed char', () => {
269
testDeduceInput(
270
new TextAreaState('Hello world!', 5, 5, null, undefined),
271
'Helöö world!',
272
5, 5, true,
273
'öö', 2
274
);
275
});
276
277
test('extractMacReplacedText', () => {
278
testDeduceInput(
279
new TextAreaState('Hello world!', 5, 5, null, undefined),
280
'Hellö world!',
281
5, 5, true,
282
'ö', 1
283
);
284
});
285
286
test('issue #25101 - First key press ignored', () => {
287
testDeduceInput(
288
new TextAreaState('a', 0, 1, null, undefined),
289
'a',
290
1, 1, true,
291
'a', 0
292
);
293
});
294
295
test('issue #16520 - Cmd-d of single character followed by typing same character as has no effect', () => {
296
testDeduceInput(
297
new TextAreaState('x x', 0, 1, null, undefined),
298
'x x',
299
1, 1, true,
300
'x', 0
301
);
302
});
303
304
function testDeduceAndroidCompositionInput(
305
prevState: TextAreaState | null,
306
value: string, selectionStart: number, selectionEnd: number,
307
expected: string, expectedReplacePrevCharCnt: number, expectedReplaceNextCharCnt: number, expectedPositionDelta: number): void {
308
prevState = prevState || TextAreaState.EMPTY;
309
310
const textArea = new MockTextAreaWrapper();
311
textArea._value = value;
312
textArea._selectionStart = selectionStart;
313
textArea._selectionEnd = selectionEnd;
314
315
const newState = TextAreaState.readFromTextArea(textArea, null);
316
const actual = TextAreaState.deduceAndroidCompositionInput(prevState, newState);
317
318
assert.deepStrictEqual(actual, {
319
text: expected,
320
replacePrevCharCnt: expectedReplacePrevCharCnt,
321
replaceNextCharCnt: expectedReplaceNextCharCnt,
322
positionDelta: expectedPositionDelta,
323
});
324
325
textArea.dispose();
326
}
327
328
test('Android composition input 1', () => {
329
testDeduceAndroidCompositionInput(
330
new TextAreaState('Microsoft', 4, 4, null, undefined),
331
'Microsoft',
332
4, 4,
333
'', 0, 0, 0,
334
);
335
});
336
337
test('Android composition input 2', () => {
338
testDeduceAndroidCompositionInput(
339
new TextAreaState('Microsoft', 4, 4, null, undefined),
340
'Microsoft',
341
0, 9,
342
'', 0, 0, 5,
343
);
344
});
345
346
test('Android composition input 3', () => {
347
testDeduceAndroidCompositionInput(
348
new TextAreaState('Microsoft', 0, 9, null, undefined),
349
'Microsoft\'s',
350
11, 11,
351
'\'s', 0, 0, 0,
352
);
353
});
354
355
test('Android backspace', () => {
356
testDeduceAndroidCompositionInput(
357
new TextAreaState('undefinedVariable', 2, 2, null, undefined),
358
'udefinedVariable',
359
1, 1,
360
'', 1, 0, 0,
361
);
362
});
363
364
suite('SimplePagedScreenReaderStrategy', () => {
365
366
function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void {
367
const model = createTextModel(lines.join('\n'));
368
const screenReaderStrategy = new SimplePagedScreenReaderStrategy();
369
const screenReaderContentState = screenReaderStrategy.fromEditorSelection(model, selection, 10, true);
370
const textAreaState = TextAreaState.fromScreenReaderContentState(screenReaderContentState);
371
assert.ok(equalsTextAreaState(textAreaState, expected));
372
model.dispose();
373
}
374
375
test('simple', () => {
376
testPagedScreenReaderStrategy(
377
[
378
'Hello world!'
379
],
380
new Selection(1, 13, 1, 13),
381
new TextAreaState('Hello world!', 12, 12, new Range(1, 13, 1, 13), 0)
382
);
383
384
testPagedScreenReaderStrategy(
385
[
386
'Hello world!'
387
],
388
new Selection(1, 1, 1, 1),
389
new TextAreaState('Hello world!', 0, 0, new Range(1, 1, 1, 1), 0)
390
);
391
392
testPagedScreenReaderStrategy(
393
[
394
'Hello world!'
395
],
396
new Selection(1, 1, 1, 6),
397
new TextAreaState('Hello world!', 0, 5, new Range(1, 1, 1, 6), 0)
398
);
399
});
400
401
test('multiline', () => {
402
testPagedScreenReaderStrategy(
403
[
404
'Hello world!',
405
'How are you?'
406
],
407
new Selection(1, 1, 1, 1),
408
new TextAreaState('Hello world!\nHow are you?', 0, 0, new Range(1, 1, 1, 1), 0)
409
);
410
411
testPagedScreenReaderStrategy(
412
[
413
'Hello world!',
414
'How are you?'
415
],
416
new Selection(2, 1, 2, 1),
417
new TextAreaState('Hello world!\nHow are you?', 13, 13, new Range(2, 1, 2, 1), 1)
418
);
419
});
420
421
test('page', () => {
422
testPagedScreenReaderStrategy(
423
[
424
'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'
425
],
426
new Selection(1, 1, 1, 1),
427
new TextAreaState('L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\n', 0, 0, new Range(1, 1, 1, 1), 0)
428
);
429
430
testPagedScreenReaderStrategy(
431
[
432
'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'
433
],
434
new Selection(11, 1, 11, 1),
435
new TextAreaState('L11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\n', 0, 0, new Range(11, 1, 11, 1), 0)
436
);
437
438
testPagedScreenReaderStrategy(
439
[
440
'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'
441
],
442
new Selection(12, 1, 12, 1),
443
new TextAreaState('L11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\n', 4, 4, new Range(12, 1, 12, 1), 1)
444
);
445
446
testPagedScreenReaderStrategy(
447
[
448
'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'
449
],
450
new Selection(21, 1, 21, 1),
451
new TextAreaState('L21', 0, 0, new Range(21, 1, 21, 1), 0)
452
);
453
});
454
455
});
456
});
457
458