Path: blob/main/src/vs/editor/test/browser/widget/observableCodeEditor.test.ts
5237 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 * as assert from 'assert';6import { DisposableStore } from '../../../../base/common/lifecycle.js';7import { IObservable, derivedHandleChanges } from '../../../../base/common/observable.js';8import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';9import { ICodeEditor } from '../../../browser/editorBrowser.js';10import { ObservableCodeEditor, observableCodeEditor } from '../../../browser/observableCodeEditor.js';11import { Position } from '../../../common/core/position.js';12import { Range } from '../../../common/core/range.js';13import { ViewModel } from '../../../common/viewModel/viewModelImpl.js';14import { withTestCodeEditor } from '../testCodeEditor.js';1516suite('CodeEditorWidget', () => {17ensureNoDisposablesAreLeakedInTestSuite();1819function withTestFixture(20cb: (args: { editor: ICodeEditor; viewModel: ViewModel; log: Log; derived: IObservable<string> }) => void21) {22withEditorSetupTestFixture(undefined, cb);23}2425function withEditorSetupTestFixture(26preSetupCallback:27| ((editor: ICodeEditor, disposables: DisposableStore) => void)28| undefined,29cb: (args: { editor: ICodeEditor; viewModel: ViewModel; log: Log; derived: IObservable<string> }) => void30) {31withTestCodeEditor('hello world', {}, (editor, viewModel) => {32const disposables = new DisposableStore();33preSetupCallback?.(editor, disposables);34const obsEditor = observableCodeEditor(editor);35const log = new Log();3637const derived = derivedHandleChanges(38{39changeTracker: {40createChangeSummary: () => undefined,41handleChange: (context) => {42const obsName = observableName(context.changedObservable, obsEditor);4344log.log(`handle change: ${obsName} ${formatChange(context.change)}`);45return true;46},47},48},49(reader) => {50const versionId = obsEditor.versionId.read(reader);51const selection = obsEditor.selections.read(reader)?.map((s) => s.toString()).join(', ');52obsEditor.onDidType.read(reader);5354const str = `running derived: selection: ${selection}, value: ${versionId}`;55log.log(str);56return str;57}58);5960derived.recomputeInitiallyAndOnChange(disposables);61assert.deepStrictEqual(log.getAndClearEntries(), [62'running derived: selection: [1,1 -> 1,1], value: 1',63]);6465cb({ editor, viewModel, log, derived });6667disposables.dispose();68});69}7071test('setPosition', () =>72withTestFixture(({ editor, log }) => {73editor.setPosition(new Position(1, 2));7475assert.deepStrictEqual(log.getAndClearEntries(), ([76'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":1,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"api","reason":0}',77'running derived: selection: [1,2 -> 1,2], value: 1'78]));79}));8081test('keyboard.type', () =>82withTestFixture(({ editor, log }) => {83editor.trigger('keyboard', 'type', { text: 'abc' });8485assert.deepStrictEqual(log.getAndClearEntries(), ([86'handle change: editor.onDidType "abc"',87'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',88'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',89'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',90'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',91'running derived: selection: [1,4 -> 1,4], value: 4'92]));93}));9495test('keyboard.type and set position', () =>96withTestFixture(({ editor, log }) => {97editor.trigger('keyboard', 'type', { text: 'abc' });9899assert.deepStrictEqual(log.getAndClearEntries(), ([100'handle change: editor.onDidType "abc"',101'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',102'handle change: editor.versionId {"changes":[{"range":"[1,2 -> 1,2]","rangeLength":0,"text":"b","rangeOffset":1}],"eol":"\\n","versionId":3,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',103'handle change: editor.versionId {"changes":[{"range":"[1,3 -> 1,3]","rangeLength":0,"text":"c","rangeOffset":2}],"eol":"\\n","versionId":4,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',104'handle change: editor.selections {"selection":"[1,4 -> 1,4]","modelVersionId":4,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',105'running derived: selection: [1,4 -> 1,4], value: 4'106]));107108editor.setPosition(new Position(1, 5), 'test');109110assert.deepStrictEqual(log.getAndClearEntries(), ([111'handle change: editor.selections {"selection":"[1,5 -> 1,5]","modelVersionId":4,"oldSelections":["[1,4 -> 1,4]"],"oldModelVersionId":4,"source":"test","reason":0}',112'running derived: selection: [1,5 -> 1,5], value: 4'113]));114}));115116test('listener interaction (unforced)', () => {117let derived: IObservable<string>;118let log: Log;119withEditorSetupTestFixture(120(editor, disposables) => {121disposables.add(122editor.onDidChangeModelContent(() => {123log.log('>>> before get');124derived.get();125log.log('<<< after get');126})127);128},129(args) => {130const editor = args.editor;131derived = args.derived;132log = args.log;133134editor.trigger('keyboard', 'type', { text: 'a' });135assert.deepStrictEqual(log.getAndClearEntries(), ([136'>>> before get',137'<<< after get',138'handle change: editor.onDidType "a"',139'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',140'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',141'running derived: selection: [1,2 -> 1,2], value: 2'142]));143}144);145});146147test('listener interaction ()', () => {148let derived: IObservable<string>;149let log: Log;150withEditorSetupTestFixture(151(editor, disposables) => {152disposables.add(153editor.onDidChangeModelContent(() => {154log.log('>>> before forceUpdate');155observableCodeEditor(editor).forceUpdate();156157log.log('>>> before get');158derived.get();159log.log('<<< after get');160})161);162},163(args) => {164const editor = args.editor;165derived = args.derived;166log = args.log;167168editor.trigger('keyboard', 'type', { text: 'a' });169170assert.deepStrictEqual(log.getAndClearEntries(), ([171'>>> before forceUpdate',172'>>> before get',173'handle change: editor.versionId undefined',174'running derived: selection: [1,2 -> 1,2], value: 2',175'<<< after get',176'handle change: editor.onDidType "a"',177'handle change: editor.versionId {"changes":[{"range":"[1,1 -> 1,1]","rangeLength":0,"text":"a","rangeOffset":0}],"eol":"\\n","versionId":2,"detailedReasons":[{"metadata":{"source":"cursor","kind":"type","detailedSource":"keyboard"}}],"detailedReasonsChangeLengths":[1]}',178'handle change: editor.selections {"selection":"[1,2 -> 1,2]","modelVersionId":2,"oldSelections":["[1,1 -> 1,1]"],"oldModelVersionId":1,"source":"keyboard","reason":0}',179'running derived: selection: [1,2 -> 1,2], value: 2'180]));181}182);183});184});185186class Log {187private readonly entries: string[] = [];188public log(message: string): void {189this.entries.push(message);190}191192public getAndClearEntries(): string[] {193const entries = [...this.entries];194this.entries.length = 0;195return entries;196}197}198199function formatChange(change: unknown) {200return JSON.stringify(201change,202(key, value) => {203if (value instanceof Range) {204return value.toString();205}206if (207value === false ||208(Array.isArray(value) && value.length === 0)209) {210return undefined;211}212return value;213}214);215}216217function observableName(obs: IObservable<any>, obsEditor: ObservableCodeEditor): string {218switch (obs) {219case obsEditor.selections:220return 'editor.selections';221case obsEditor.versionId:222return 'editor.versionId';223case obsEditor.onDidType:224return 'editor.onDidType';225default:226return 'unknown';227}228}229230231