Path: blob/main/src/vs/editor/test/browser/controller/textAreaState.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { Disposable } from '../../../../base/common/lifecycle.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';8import { ITextAreaWrapper, TextAreaState } from '../../../browser/controller/editContext/textArea/textAreaEditContextState.js';9import { Range } from '../../../common/core/range.js';10import { Selection } from '../../../common/core/selection.js';11import { createTextModel } from '../../common/testTextModel.js';12import { SimplePagedScreenReaderStrategy } from '../../../browser/controller/editContext/screenReaderUtils.js';1314class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper {1516public _value: string;17public _selectionStart: number;18public _selectionEnd: number;1920constructor() {21super();22this._value = '';23this._selectionStart = 0;24this._selectionEnd = 0;25}2627public getValue(): string {28return this._value;29}3031public setValue(reason: string, value: string): void {32this._value = value;33this._selectionStart = this._value.length;34this._selectionEnd = this._value.length;35}3637public getSelectionStart(): number {38return this._selectionStart;39}4041public getSelectionEnd(): number {42return this._selectionEnd;43}4445public setSelectionRange(reason: string, selectionStart: number, selectionEnd: number): void {46if (selectionStart < 0) {47selectionStart = 0;48}49if (selectionStart > this._value.length) {50selectionStart = this._value.length;51}52if (selectionEnd < 0) {53selectionEnd = 0;54}55if (selectionEnd > this._value.length) {56selectionEnd = this._value.length;57}58this._selectionStart = selectionStart;59this._selectionEnd = selectionEnd;60}61}6263function equalsTextAreaState(a: TextAreaState, b: TextAreaState): boolean {64return (65a.value === b.value66&& a.selectionStart === b.selectionStart67&& a.selectionEnd === b.selectionEnd68&& Range.equalsRange(a.selection, b.selection)69&& a.newlineCountBeforeSelection === b.newlineCountBeforeSelection70);71}7273suite('TextAreaState', () => {7475ensureNoDisposablesAreLeakedInTestSuite();7677function assertTextAreaState(actual: TextAreaState, value: string, selectionStart: number, selectionEnd: number): void {78const desired = new TextAreaState(value, selectionStart, selectionEnd, null, undefined);79assert.ok(equalsTextAreaState(desired, actual), desired.toString() + ' == ' + actual.toString());80}8182test('fromTextArea', () => {83const textArea = new MockTextAreaWrapper();84textArea._value = 'Hello world!';85textArea._selectionStart = 1;86textArea._selectionEnd = 12;87let actual = TextAreaState.readFromTextArea(textArea, null);8889assertTextAreaState(actual, 'Hello world!', 1, 12);90assert.strictEqual(actual.value, 'Hello world!');91assert.strictEqual(actual.selectionStart, 1);9293actual = actual.collapseSelection();94assertTextAreaState(actual, 'Hello world!', 12, 12);9596textArea.dispose();97});9899test('applyToTextArea', () => {100const textArea = new MockTextAreaWrapper();101textArea._value = 'Hello world!';102textArea._selectionStart = 1;103textArea._selectionEnd = 12;104105let state = new TextAreaState('Hi world!', 2, 2, null, undefined);106state.writeToTextArea('test', textArea, false);107108assert.strictEqual(textArea._value, 'Hi world!');109assert.strictEqual(textArea._selectionStart, 9);110assert.strictEqual(textArea._selectionEnd, 9);111112state = new TextAreaState('Hi world!', 3, 3, null, undefined);113state.writeToTextArea('test', textArea, false);114115assert.strictEqual(textArea._value, 'Hi world!');116assert.strictEqual(textArea._selectionStart, 9);117assert.strictEqual(textArea._selectionEnd, 9);118119state = new TextAreaState('Hi world!', 0, 2, null, undefined);120state.writeToTextArea('test', textArea, true);121122assert.strictEqual(textArea._value, 'Hi world!');123assert.strictEqual(textArea._selectionStart, 0);124assert.strictEqual(textArea._selectionEnd, 2);125126textArea.dispose();127});128129function testDeduceInput(prevState: TextAreaState | null, value: string, selectionStart: number, selectionEnd: number, couldBeEmojiInput: boolean, expected: string, expectedCharReplaceCnt: number): void {130prevState = prevState || TextAreaState.EMPTY;131132const textArea = new MockTextAreaWrapper();133textArea._value = value;134textArea._selectionStart = selectionStart;135textArea._selectionEnd = selectionEnd;136137const newState = TextAreaState.readFromTextArea(textArea, null);138const actual = TextAreaState.deduceInput(prevState, newState, couldBeEmojiInput);139140assert.deepStrictEqual(actual, {141text: expected,142replacePrevCharCnt: expectedCharReplaceCnt,143replaceNextCharCnt: 0,144positionDelta: 0,145});146147textArea.dispose();148}149150test('extractNewText - no previous state with selection', () => {151testDeduceInput(152null,153'a',1540, 1, true,155'a', 0156);157});158159test('issue #2586: Replacing selected end-of-line with newline locks up the document', () => {160testDeduceInput(161new TextAreaState(']\n', 1, 2, null, undefined),162']\n',1632, 2, true,164'\n', 0165);166});167168test('extractNewText - no previous state without selection', () => {169testDeduceInput(170null,171'a',1721, 1, true,173'a', 0174);175});176177test('extractNewText - typing does not cause a selection', () => {178testDeduceInput(179TextAreaState.EMPTY,180'a',1810, 1, true,182'a', 0183);184});185186test('extractNewText - had the textarea empty', () => {187testDeduceInput(188TextAreaState.EMPTY,189'a',1901, 1, true,191'a', 0192);193});194195test('extractNewText - had the entire line selected', () => {196testDeduceInput(197new TextAreaState('Hello world!', 0, 12, null, undefined),198'H',1991, 1, true,200'H', 0201);202});203204test('extractNewText - had previous text 1', () => {205testDeduceInput(206new TextAreaState('Hello world!', 12, 12, null, undefined),207'Hello world!a',20813, 13, true,209'a', 0210);211});212213test('extractNewText - had previous text 2', () => {214testDeduceInput(215new TextAreaState('Hello world!', 0, 0, null, undefined),216'aHello world!',2171, 1, true,218'a', 0219);220});221222test('extractNewText - had previous text 3', () => {223testDeduceInput(224new TextAreaState('Hello world!', 6, 11, null, undefined),225'Hello other!',22611, 11, true,227'other', 0228);229});230231test('extractNewText - IME', () => {232testDeduceInput(233TextAreaState.EMPTY,234'これは',2353, 3, true,236'これは', 0237);238});239240test('extractNewText - isInOverwriteMode', () => {241testDeduceInput(242new TextAreaState('Hello world!', 0, 0, null, undefined),243'Aello world!',2441, 1, true,245'A', 0246);247});248249test('extractMacReplacedText - does nothing if there is selection', () => {250testDeduceInput(251new TextAreaState('Hello world!', 5, 5, null, undefined),252'Hellö world!',2534, 5, true,254'ö', 0255);256});257258test('extractMacReplacedText - does nothing if there is more than one extra char', () => {259testDeduceInput(260new TextAreaState('Hello world!', 5, 5, null, undefined),261'Hellöö world!',2625, 5, true,263'öö', 1264);265});266267test('extractMacReplacedText - does nothing if there is more than one changed char', () => {268testDeduceInput(269new TextAreaState('Hello world!', 5, 5, null, undefined),270'Helöö world!',2715, 5, true,272'öö', 2273);274});275276test('extractMacReplacedText', () => {277testDeduceInput(278new TextAreaState('Hello world!', 5, 5, null, undefined),279'Hellö world!',2805, 5, true,281'ö', 1282);283});284285test('issue #25101 - First key press ignored', () => {286testDeduceInput(287new TextAreaState('a', 0, 1, null, undefined),288'a',2891, 1, true,290'a', 0291);292});293294test('issue #16520 - Cmd-d of single character followed by typing same character as has no effect', () => {295testDeduceInput(296new TextAreaState('x x', 0, 1, null, undefined),297'x x',2981, 1, true,299'x', 0300);301});302303function testDeduceAndroidCompositionInput(304prevState: TextAreaState | null,305value: string, selectionStart: number, selectionEnd: number,306expected: string, expectedReplacePrevCharCnt: number, expectedReplaceNextCharCnt: number, expectedPositionDelta: number): void {307prevState = prevState || TextAreaState.EMPTY;308309const textArea = new MockTextAreaWrapper();310textArea._value = value;311textArea._selectionStart = selectionStart;312textArea._selectionEnd = selectionEnd;313314const newState = TextAreaState.readFromTextArea(textArea, null);315const actual = TextAreaState.deduceAndroidCompositionInput(prevState, newState);316317assert.deepStrictEqual(actual, {318text: expected,319replacePrevCharCnt: expectedReplacePrevCharCnt,320replaceNextCharCnt: expectedReplaceNextCharCnt,321positionDelta: expectedPositionDelta,322});323324textArea.dispose();325}326327test('Android composition input 1', () => {328testDeduceAndroidCompositionInput(329new TextAreaState('Microsoft', 4, 4, null, undefined),330'Microsoft',3314, 4,332'', 0, 0, 0,333);334});335336test('Android composition input 2', () => {337testDeduceAndroidCompositionInput(338new TextAreaState('Microsoft', 4, 4, null, undefined),339'Microsoft',3400, 9,341'', 0, 0, 5,342);343});344345test('Android composition input 3', () => {346testDeduceAndroidCompositionInput(347new TextAreaState('Microsoft', 0, 9, null, undefined),348'Microsoft\'s',34911, 11,350'\'s', 0, 0, 0,351);352});353354test('Android backspace', () => {355testDeduceAndroidCompositionInput(356new TextAreaState('undefinedVariable', 2, 2, null, undefined),357'udefinedVariable',3581, 1,359'', 1, 0, 0,360);361});362363suite('SimplePagedScreenReaderStrategy', () => {364365function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void {366const model = createTextModel(lines.join('\n'));367const screenReaderStrategy = new SimplePagedScreenReaderStrategy();368const screenReaderContentState = screenReaderStrategy.fromEditorSelection(model, selection, 10, true);369const textAreaState = TextAreaState.fromScreenReaderContentState(screenReaderContentState);370assert.ok(equalsTextAreaState(textAreaState, expected));371model.dispose();372}373374test('simple', () => {375testPagedScreenReaderStrategy(376[377'Hello world!'378],379new Selection(1, 13, 1, 13),380new TextAreaState('Hello world!', 12, 12, new Range(1, 13, 1, 13), 0)381);382383testPagedScreenReaderStrategy(384[385'Hello world!'386],387new Selection(1, 1, 1, 1),388new TextAreaState('Hello world!', 0, 0, new Range(1, 1, 1, 1), 0)389);390391testPagedScreenReaderStrategy(392[393'Hello world!'394],395new Selection(1, 1, 1, 6),396new TextAreaState('Hello world!', 0, 5, new Range(1, 1, 1, 6), 0)397);398});399400test('multiline', () => {401testPagedScreenReaderStrategy(402[403'Hello world!',404'How are you?'405],406new Selection(1, 1, 1, 1),407new TextAreaState('Hello world!\nHow are you?', 0, 0, new Range(1, 1, 1, 1), 0)408);409410testPagedScreenReaderStrategy(411[412'Hello world!',413'How are you?'414],415new Selection(2, 1, 2, 1),416new TextAreaState('Hello world!\nHow are you?', 13, 13, new Range(2, 1, 2, 1), 1)417);418});419420test('page', () => {421testPagedScreenReaderStrategy(422[423'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'424],425new Selection(1, 1, 1, 1),426new TextAreaState('L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\n', 0, 0, new Range(1, 1, 1, 1), 0)427);428429testPagedScreenReaderStrategy(430[431'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'432],433new Selection(11, 1, 11, 1),434new TextAreaState('L11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\n', 0, 0, new Range(11, 1, 11, 1), 0)435);436437testPagedScreenReaderStrategy(438[439'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'440],441new Selection(12, 1, 12, 1),442new TextAreaState('L11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\n', 4, 4, new Range(12, 1, 12, 1), 1)443);444445testPagedScreenReaderStrategy(446[447'L1\nL2\nL3\nL4\nL5\nL6\nL7\nL8\nL9\nL10\nL11\nL12\nL13\nL14\nL15\nL16\nL17\nL18\nL19\nL20\nL21'448],449new Selection(21, 1, 21, 1),450new TextAreaState('L21', 0, 0, new Range(21, 1, 21, 1), 0)451);452});453454});455});456457458