Path: blob/main/src/vs/editor/test/common/services/modelService.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 { CharCode } from '../../../../base/common/charCode.js';7import * as platform from '../../../../base/common/platform.js';8import { URI } from '../../../../base/common/uri.js';9import { EditOperation } from '../../../common/core/editOperation.js';10import { Range } from '../../../common/core/range.js';11import { Selection } from '../../../common/core/selection.js';12import { StringBuilder } from '../../../common/core/stringBuilder.js';13import { DefaultEndOfLine, ITextBuffer, ITextBufferFactory, ITextSnapshot } from '../../../common/model.js';14import { createTextBuffer } from '../../../common/model/textModel.js';15import { ModelService } from '../../../common/services/modelService.js';16import { TestConfigurationService } from '../../../../platform/configuration/test/common/testConfigurationService.js';17import { createModelServices, createTextModel } from '../testTextModel.js';18import { DisposableStore } from '../../../../base/common/lifecycle.js';19import { IModelService } from '../../../common/services/model.js';20import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';21import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js';22import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';2324const GENERATE_TESTS = false;2526suite('ModelService', () => {27let disposables: DisposableStore;28let modelService: IModelService;29let instantiationService: TestInstantiationService;3031setup(() => {32disposables = new DisposableStore();3334const configService = new TestConfigurationService();35configService.setUserConfiguration('files', { 'eol': '\n' });36configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot'));3738instantiationService = createModelServices(disposables, [39[IConfigurationService, configService]40]);41modelService = instantiationService.get(IModelService);42});4344teardown(() => {45disposables.dispose();46});4748ensureNoDisposablesAreLeakedInTestSuite();4950test('EOL setting respected depending on root', () => {51const model1 = modelService.createModel('farboo', null);52const model2 = modelService.createModel('farboo', null, URI.file(platform.isWindows ? 'c:\\myroot\\myfile.txt' : '/myroot/myfile.txt'));53const model3 = modelService.createModel('farboo', null, URI.file(platform.isWindows ? 'c:\\other\\myfile.txt' : '/other/myfile.txt'));5455assert.strictEqual(model1.getOptions().defaultEOL, DefaultEndOfLine.LF);56assert.strictEqual(model2.getOptions().defaultEOL, DefaultEndOfLine.CRLF);57assert.strictEqual(model3.getOptions().defaultEOL, DefaultEndOfLine.LF);5859model1.dispose();60model2.dispose();61model3.dispose();62});6364test('_computeEdits no change', function () {6566const model = disposables.add(createTextModel(67[68'This is line one', //1669'and this is line number two', //2770'it is followed by #3', //2071'and finished with the fourth.', //2972].join('\n')73));7475const textBuffer = createAndRegisterTextBuffer(76disposables,77[78'This is line one', //1679'and this is line number two', //2780'it is followed by #3', //2081'and finished with the fourth.', //2982].join('\n'),83DefaultEndOfLine.LF84);8586const actual = ModelService._computeEdits(model, textBuffer);8788assert.deepStrictEqual(actual, []);89});9091test('_computeEdits first line changed', function () {9293const model = disposables.add(createTextModel(94[95'This is line one', //1696'and this is line number two', //2797'it is followed by #3', //2098'and finished with the fourth.', //2999].join('\n')100));101102const textBuffer = createAndRegisterTextBuffer(103disposables,104[105'This is line One', //16106'and this is line number two', //27107'it is followed by #3', //20108'and finished with the fourth.', //29109].join('\n'),110DefaultEndOfLine.LF111);112113const actual = ModelService._computeEdits(model, textBuffer);114115assert.deepStrictEqual(actual, [116EditOperation.replaceMove(new Range(1, 1, 2, 1), 'This is line One\n')117]);118});119120test('_computeEdits EOL changed', function () {121122const model = disposables.add(createTextModel(123[124'This is line one', //16125'and this is line number two', //27126'it is followed by #3', //20127'and finished with the fourth.', //29128].join('\n')129));130131const textBuffer = createAndRegisterTextBuffer(132disposables,133[134'This is line one', //16135'and this is line number two', //27136'it is followed by #3', //20137'and finished with the fourth.', //29138].join('\r\n'),139DefaultEndOfLine.LF140);141142const actual = ModelService._computeEdits(model, textBuffer);143144assert.deepStrictEqual(actual, []);145});146147test('_computeEdits EOL and other change 1', function () {148149const model = disposables.add(createTextModel(150[151'This is line one', //16152'and this is line number two', //27153'it is followed by #3', //20154'and finished with the fourth.', //29155].join('\n')156));157158const textBuffer = createAndRegisterTextBuffer(159disposables,160[161'This is line One', //16162'and this is line number two', //27163'It is followed by #3', //20164'and finished with the fourth.', //29165].join('\r\n'),166DefaultEndOfLine.LF167);168169const actual = ModelService._computeEdits(model, textBuffer);170171assert.deepStrictEqual(actual, [172EditOperation.replaceMove(173new Range(1, 1, 4, 1),174[175'This is line One',176'and this is line number two',177'It is followed by #3',178''179].join('\r\n')180)181]);182});183184test('_computeEdits EOL and other change 2', function () {185186const model = disposables.add(createTextModel(187[188'package main', // 1189'func foo() {', // 2190'}' // 3191].join('\n')192));193194const textBuffer = createAndRegisterTextBuffer(195disposables,196[197'package main', // 1198'func foo() {', // 2199'}', // 3200''201].join('\r\n'),202DefaultEndOfLine.LF203);204205const actual = ModelService._computeEdits(model, textBuffer);206207assert.deepStrictEqual(actual, [208EditOperation.replaceMove(new Range(3, 2, 3, 2), '\r\n')209]);210});211212test('generated1', () => {213const file1 = ['pram', 'okctibad', 'pjuwtemued', 'knnnm', 'u', ''];214const file2 = ['tcnr', 'rxwlicro', 'vnzy', '', '', 'pjzcogzur', 'ptmxyp', 'dfyshia', 'pee', 'ygg'];215assertComputeEdits(file1, file2);216});217218test('generated2', () => {219const file1 = ['', 'itls', 'hrilyhesv', ''];220const file2 = ['vdl', '', 'tchgz', 'bhx', 'nyl'];221assertComputeEdits(file1, file2);222});223224test('generated3', () => {225const file1 = ['ubrbrcv', 'wv', 'xodspybszt', 's', 'wednjxm', 'fklajt', 'fyfc', 'lvejgge', 'rtpjlodmmk', 'arivtgmjdm'];226const file2 = ['s', 'qj', 'tu', 'ur', 'qerhjjhyvx', 't'];227assertComputeEdits(file1, file2);228});229230test('generated4', () => {231const file1 = ['ig', 'kh', 'hxegci', 'smvker', 'pkdmjjdqnv', 'vgkkqqx', '', 'jrzeb'];232const file2 = ['yk', ''];233assertComputeEdits(file1, file2);234});235236test('does insertions in the middle of the document', () => {237const file1 = [238'line 1',239'line 2',240'line 3'241];242const file2 = [243'line 1',244'line 2',245'line 5',246'line 3'247];248assertComputeEdits(file1, file2);249});250251test('does insertions at the end of the document', () => {252const file1 = [253'line 1',254'line 2',255'line 3'256];257const file2 = [258'line 1',259'line 2',260'line 3',261'line 4'262];263assertComputeEdits(file1, file2);264});265266test('does insertions at the beginning of the document', () => {267const file1 = [268'line 1',269'line 2',270'line 3'271];272const file2 = [273'line 0',274'line 1',275'line 2',276'line 3'277];278assertComputeEdits(file1, file2);279});280281test('does replacements', () => {282const file1 = [283'line 1',284'line 2',285'line 3'286];287const file2 = [288'line 1',289'line 7',290'line 3'291];292assertComputeEdits(file1, file2);293});294295test('does deletions', () => {296const file1 = [297'line 1',298'line 2',299'line 3'300];301const file2 = [302'line 1',303'line 3'304];305assertComputeEdits(file1, file2);306});307308test('does insert, replace, and delete', () => {309const file1 = [310'line 1',311'line 2',312'line 3',313'line 4',314'line 5',315];316const file2 = [317'line 0', // insert line 0318'line 1',319'replace line 2', // replace line 2320'line 3',321// delete line 4322'line 5',323];324assertComputeEdits(file1, file2);325});326327test('maintains undo for same resource and same content', () => {328const resource = URI.parse('file://test.txt');329330// create a model331const model1 = modelService.createModel('text', null, resource);332// make an edit333model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);334assert.strictEqual(model1.getValue(), 'text1');335// dispose it336modelService.destroyModel(resource);337338// create a new model with the same content339const model2 = modelService.createModel('text1', null, resource);340// undo341model2.undo();342assert.strictEqual(model2.getValue(), 'text');343// dispose it344modelService.destroyModel(resource);345});346347test('maintains version id and alternative version id for same resource and same content', () => {348const resource = URI.parse('file://test.txt');349350// create a model351const model1 = modelService.createModel('text', null, resource);352// make an edit353model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);354assert.strictEqual(model1.getValue(), 'text1');355const versionId = model1.getVersionId();356const alternativeVersionId = model1.getAlternativeVersionId();357// dispose it358modelService.destroyModel(resource);359360// create a new model with the same content361const model2 = modelService.createModel('text1', null, resource);362assert.strictEqual(model2.getVersionId(), versionId);363assert.strictEqual(model2.getAlternativeVersionId(), alternativeVersionId);364// dispose it365modelService.destroyModel(resource);366});367368test('does not maintain undo for same resource and different content', () => {369const resource = URI.parse('file://test.txt');370371// create a model372const model1 = modelService.createModel('text', null, resource);373// make an edit374model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);375assert.strictEqual(model1.getValue(), 'text1');376// dispose it377modelService.destroyModel(resource);378379// create a new model with the same content380const model2 = modelService.createModel('text2', null, resource);381// undo382model2.undo();383assert.strictEqual(model2.getValue(), 'text2');384// dispose it385modelService.destroyModel(resource);386});387388test('setValue should clear undo stack', () => {389const resource = URI.parse('file://test.txt');390391const model = modelService.createModel('text', null, resource);392model.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]);393assert.strictEqual(model.getValue(), 'text1');394395model.setValue('text2');396model.undo();397assert.strictEqual(model.getValue(), 'text2');398// dispose it399modelService.destroyModel(resource);400});401});402403function assertComputeEdits(lines1: string[], lines2: string[]): void {404const model = createTextModel(lines1.join('\n'));405const { disposable, textBuffer } = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF);406407// compute required edits408// let start = Date.now();409const edits = ModelService._computeEdits(model, textBuffer);410// console.log(`took ${Date.now() - start} ms.`);411412// apply edits413model.pushEditOperations([], edits, null);414415assert.strictEqual(model.getValue(), lines2.join('\n'));416disposable.dispose();417model.dispose();418}419420function getRandomInt(min: number, max: number): number {421return Math.floor(Math.random() * (max - min + 1)) + min;422}423424function getRandomString(minLength: number, maxLength: number): string {425const length = getRandomInt(minLength, maxLength);426const t = new StringBuilder(length);427for (let i = 0; i < length; i++) {428t.appendASCIICharCode(getRandomInt(CharCode.a, CharCode.z));429}430return t.build();431}432433function generateFile(small: boolean): string[] {434const lineCount = getRandomInt(1, small ? 3 : 10000);435const lines: string[] = [];436for (let i = 0; i < lineCount; i++) {437lines.push(getRandomString(0, small ? 3 : 10000));438}439return lines;440}441442if (GENERATE_TESTS) {443let number = 1;444while (true) {445446console.log('------TEST: ' + number++);447448const file1 = generateFile(true);449const file2 = generateFile(true);450451console.log('------TEST GENERATED');452453try {454assertComputeEdits(file1, file2);455} catch (err) {456console.log(err);457console.log(`458const file1 = ${JSON.stringify(file1).replace(/"/g, '\'')};459const file2 = ${JSON.stringify(file2).replace(/"/g, '\'')};460assertComputeEdits(file1, file2);461`);462break;463}464}465}466467function createAndRegisterTextBuffer(store: DisposableStore, value: string | ITextBufferFactory | ITextSnapshot, defaultEOL: DefaultEndOfLine): ITextBuffer {468const { disposable, textBuffer } = createTextBuffer(value, defaultEOL);469store.add(disposable);470return textBuffer;471}472473474