Path: blob/main/src/vs/editor/test/common/model/textChange.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 { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';7import { compressConsecutiveTextChanges, TextChange } from '../../../common/core/textChange.js';89const GENERATE_TESTS = false;1011interface IGeneratedEdit {12offset: number;13length: number;14text: string;15}1617suite('TextChangeCompressor', () => {1819ensureNoDisposablesAreLeakedInTestSuite();2021function getResultingContent(initialContent: string, edits: IGeneratedEdit[]): string {22let content = initialContent;23for (let i = edits.length - 1; i >= 0; i--) {24content = (25content.substring(0, edits[i].offset) +26edits[i].text +27content.substring(edits[i].offset + edits[i].length)28);29}30return content;31}3233function getTextChanges(initialContent: string, edits: IGeneratedEdit[]): TextChange[] {34let content = initialContent;35const changes: TextChange[] = new Array<TextChange>(edits.length);36let deltaOffset = 0;3738for (let i = 0; i < edits.length; i++) {39const edit = edits[i];4041const position = edit.offset + deltaOffset;42const length = edit.length;43const text = edit.text;4445const oldText = content.substr(position, length);4647content = (48content.substr(0, position) +49text +50content.substr(position + length)51);5253changes[i] = new TextChange(edit.offset, oldText, position, text);5455deltaOffset += text.length - length;56}5758return changes;59}6061function assertCompression(initialText: string, edit1: IGeneratedEdit[], edit2: IGeneratedEdit[]): void {6263const tmpText = getResultingContent(initialText, edit1);64const chg1 = getTextChanges(initialText, edit1);6566const finalText = getResultingContent(tmpText, edit2);67const chg2 = getTextChanges(tmpText, edit2);6869const compressedTextChanges = compressConsecutiveTextChanges(chg1, chg2);7071// Check that the compression was correct72const compressedDoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => {73return {74offset: change.oldPosition,75length: change.oldLength,76text: change.newText77};78});79const actualDoResult = getResultingContent(initialText, compressedDoTextEdits);80assert.strictEqual(actualDoResult, finalText);8182const compressedUndoTextEdits: IGeneratedEdit[] = compressedTextChanges.map((change) => {83return {84offset: change.newPosition,85length: change.newLength,86text: change.oldText87};88});89const actualUndoResult = getResultingContent(finalText, compressedUndoTextEdits);90assert.strictEqual(actualUndoResult, initialText);91}9293test('simple 1', () => {94assertCompression(95'',96[{ offset: 0, length: 0, text: 'h' }],97[{ offset: 1, length: 0, text: 'e' }]98);99});100101test('simple 2', () => {102assertCompression(103'|',104[{ offset: 0, length: 0, text: 'h' }],105[{ offset: 2, length: 0, text: 'e' }]106);107});108109test('complex1', () => {110assertCompression(111'abcdefghij',112[113{ offset: 0, length: 3, text: 'qh' },114{ offset: 5, length: 0, text: '1' },115{ offset: 8, length: 2, text: 'X' }116],117[118{ offset: 1, length: 0, text: 'Z' },119{ offset: 3, length: 3, text: 'Y' },120]121);122});123124// test('issue #118041', () => {125// assertCompression(126// '',127// [128// { offset: 0, length: 1, text: '' },129// ],130// [131// { offset: 1, length: 0, text: 'Z' },132// { offset: 3, length: 3, text: 'Y' },133// ]134// );135// })136137test('gen1', () => {138assertCompression(139'kxm',140[{ offset: 0, length: 1, text: 'tod_neu' }],141[{ offset: 1, length: 2, text: 'sag_e' }]142);143});144145test('gen2', () => {146assertCompression(147'kpb_r_v',148[{ offset: 5, length: 2, text: 'a_jvf_l' }],149[{ offset: 10, length: 2, text: 'w' }]150);151});152153test('gen3', () => {154assertCompression(155'slu_w',156[{ offset: 4, length: 1, text: '_wfw' }],157[{ offset: 3, length: 5, text: '' }]158);159});160161test('gen4', () => {162assertCompression(163'_e',164[{ offset: 2, length: 0, text: 'zo_b' }],165[{ offset: 1, length: 3, text: 'tra' }]166);167});168169test('gen5', () => {170assertCompression(171'ssn_',172[{ offset: 0, length: 2, text: 'tat_nwe' }],173[{ offset: 2, length: 6, text: 'jm' }]174);175});176177test('gen6', () => {178assertCompression(179'kl_nru',180[{ offset: 4, length: 1, text: '' }],181[{ offset: 1, length: 4, text: '__ut' }]182);183});184185const _a = 'a'.charCodeAt(0);186const _z = 'z'.charCodeAt(0);187188function getRandomInt(min: number, max: number): number {189return Math.floor(Math.random() * (max - min + 1)) + min;190}191192function getRandomString(minLength: number, maxLength: number): string {193const length = getRandomInt(minLength, maxLength);194let r = '';195for (let i = 0; i < length; i++) {196r += String.fromCharCode(getRandomInt(_a, _z));197}198return r;199}200201function getRandomEOL(): string {202switch (getRandomInt(1, 3)) {203case 1: return '\r';204case 2: return '\n';205case 3: return '\r\n';206}207throw new Error(`not possible`);208}209210function getRandomBuffer(small: boolean): string {211const lineCount = getRandomInt(1, small ? 3 : 10);212const lines: string[] = [];213for (let i = 0; i < lineCount; i++) {214lines.push(getRandomString(0, small ? 3 : 10) + getRandomEOL());215}216return lines.join('');217}218219function getRandomEdits(content: string, min: number = 1, max: number = 5): IGeneratedEdit[] {220221const result: IGeneratedEdit[] = [];222let cnt = getRandomInt(min, max);223224let maxOffset = content.length;225226while (cnt > 0 && maxOffset > 0) {227228const offset = getRandomInt(0, maxOffset);229const length = getRandomInt(0, maxOffset - offset);230const text = getRandomBuffer(true);231232result.push({233offset: offset,234length: length,235text: text236});237238maxOffset = offset;239cnt--;240}241242result.reverse();243244return result;245}246247class GeneratedTest {248249private readonly _content: string;250private readonly _edits1: IGeneratedEdit[];251private readonly _edits2: IGeneratedEdit[];252253constructor() {254this._content = getRandomBuffer(false).replace(/\n/g, '_');255this._edits1 = getRandomEdits(this._content, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; });256const tmp = getResultingContent(this._content, this._edits1);257this._edits2 = getRandomEdits(tmp, 1, 5).map((e) => { return { offset: e.offset, length: e.length, text: e.text.replace(/\n/g, '_') }; });258}259260public print(): void {261console.log(`assertCompression(${JSON.stringify(this._content)}, ${JSON.stringify(this._edits1)}, ${JSON.stringify(this._edits2)});`);262}263264public assert(): void {265assertCompression(this._content, this._edits1, this._edits2);266}267}268269if (GENERATE_TESTS) {270let testNumber = 0;271while (true) {272testNumber++;273console.log(`------RUNNING TextChangeCompressor TEST ${testNumber}`);274const test = new GeneratedTest();275try {276test.assert();277} catch (err) {278console.log(err);279test.print();280break;281}282}283}284});285286suite('TextChange', () => {287288ensureNoDisposablesAreLeakedInTestSuite();289290test('issue #118041: unicode character undo bug', () => {291const textChange = new TextChange(428, '', 428, '');292const buff = new Uint8Array(textChange.writeSize());293textChange.write(buff, 0);294const actual: TextChange[] = [];295TextChange.read(buff, 0, actual);296assert.deepStrictEqual(actual[0], textChange);297});298299});300301302