diff --git a/.gitattributes b/.gitattributes index eba1110b57..f1b23ccf15 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ # Auto detect text files and perform LF normalization -* text=auto \ No newline at end of file +* text=auto + +# must stay byte-exact; autocrlf on Windows CI breaks the file-encoding tests +test/spec/encoding-test-files/* -text diff --git a/src/extensions/default/HTMLCodeHints/unittests.js b/src/extensions/default/HTMLCodeHints/unittests.js index 73f4afbb06..8275e4c123 100644 --- a/src/extensions/default/HTMLCodeHints/unittests.js +++ b/src/extensions/default/HTMLCodeHints/unittests.js @@ -852,12 +852,14 @@ define(function (require, exports, module) { HTMLCodeHints.emmetHintProvider.insertHint(hints[0]); - let text = testDocument.getText().toLowerCase(); - text = text.split(" ")[0]; - // add an extra space at the end to make sure that content after that is also present - // we cannot check with exact text because it generates randomly - text += ' '; - expect(text).toBe("lorem "); + const fullText = testDocument.getText().toLowerCase(); + expect(fullText.length).toBeGreaterThan("lorem".length); + let text = fullText.split(" ")[0]; + // generated text is random and may punctuate the first word ("lorem, ipsum") + while (text.length && (text[text.length - 1] < "a" || text[text.length - 1] > "z")) { + text = text.slice(0, -1); + } + expect(text).toBe("lorem"); }); it("should show emmet hint when lorem is typed along with some number and expand it", function () { diff --git a/src/extensions/default/TypeScriptSupport/unittests.js b/src/extensions/default/TypeScriptSupport/unittests.js index 59e2f73184..26a0a7e4c8 100644 --- a/src/extensions/default/TypeScriptSupport/unittests.js +++ b/src/extensions/default/TypeScriptSupport/unittests.js @@ -167,73 +167,79 @@ define(function (require, exports, module) { it("keeps the server in sync across many incremental edits", async function () { await _openInProject("ts/", "incremental.ts"); - const editor = EditorManager.getCurrentFullEditor(); - const doc = editor.document; - await awaitsFor(inspectionClean, "incremental.ts to start clean", 30000); - - // 1) Many single-character inserts - each a separate change record - appending a valid - // statement. Fed only these incremental edits, the server must still parse it as clean. - const insert = "\nlet d: number = 4;"; - for (let i = 0; i < insert.length; i++) { - const line = editor.lineCount() - 1; - doc.replaceRange(insert[i], { line: line, ch: editor.getLine(line).length }); - } - await awaitsFor(inspectionClean, "still clean after many single-char inserts", 30000); - - // 2) A whole-line replacement (non-empty range) that introduces a type error on line 0. - doc.replaceRange('let a: number = "oops";', - { line: 0, ch: 0 }, { line: 0, ch: editor.getLine(0).length }); - await awaitsFor(function () { - return panelText().includes("not assignable"); - }, "type error after a mid-document line replacement", 30000); - - // 3) Fix it with another replacement -> the error must clear. - doc.replaceRange("let a: number = 0;", - { line: 0, ch: 0 }, { line: 0, ch: editor.getLine(0).length }); - await awaitsFor(inspectionClean, "error clears after the fix", 30000); - - // 4) A multi-line deletion (non-empty range, empty text): drop line 1 entirely. Still valid. - doc.replaceRange("", { line: 1, ch: 0 }, { line: 2, ch: 0 }); - await awaitsFor(inspectionClean, "still clean after a deletion", 30000); + try { + const editor = EditorManager.getCurrentFullEditor(); + const doc = editor.document; + await awaitsFor(inspectionClean, "incremental.ts to start clean", 30000); + + // 1) Many single-character inserts - each a separate change record - appending a valid + // statement. Fed only these incremental edits, the server must still parse it as clean. + const insert = "\nlet d: number = 4;"; + for (let i = 0; i < insert.length; i++) { + const line = editor.lineCount() - 1; + doc.replaceRange(insert[i], { line: line, ch: editor.getLine(line).length }); + } + await awaitsFor(inspectionClean, "still clean after many single-char inserts", 30000); - // Discard the in-memory edits so the fixture is pristine for re-runs / other tests. - await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }), - "close incremental.ts"); + // 2) A whole-line replacement (non-empty range) that introduces a type error on line 0. + doc.replaceRange('let a: number = "oops";', + { line: 0, ch: 0 }, { line: 0, ch: editor.getLine(0).length }); + await awaitsFor(function () { + return panelText().includes("not assignable"); + }, "type error after a mid-document line replacement", 30000); + + // 3) Fix it with another replacement -> the error must clear. + doc.replaceRange("let a: number = 0;", + { line: 0, ch: 0 }, { line: 0, ch: editor.getLine(0).length }); + await awaitsFor(inspectionClean, "error clears after the fix", 30000); + + // 4) A multi-line deletion (non-empty range, empty text): drop line 1 entirely. Still valid. + doc.replaceRange("", { line: 1, ch: 0 }, { line: 2, ch: 0 }); + await awaitsFor(inspectionClean, "still clean after a deletion", 30000); + } finally { + // must run even on failure - a dirty file left open hangs every later + // project switch on the save prompt, cascading into unrelated suites + await jsPromise(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true })) + .catch(function (e) { console.error("Failed closing incremental.ts", e); }); + } }, 90000); it("keeps the server in sync for a batched multi-cursor edit", async function () { await _openInProject("ts/", "incremental.ts"); - const editor = EditorManager.getCurrentFullEditor(); - await awaitsFor(inspectionClean, "incremental.ts to start clean", 30000); - - // Two cursors on the SAME line, edited in one operation - the order-sensitive case, since - // the first replacement shifts the columns of the second. CodeMirror applies and reports - // the batch so that replaying the records in array order reproduces the result; this - // confirms our 1:1 change-record -> LSP-range map honours that ordering. - // "let a: number = 0;" -> "let abc: number = \"hello\";" - editor.setSelections([ - { start: { line: 0, ch: 4 }, end: { line: 0, ch: 5 } }, // the identifier `a` - { start: { line: 0, ch: 16 }, end: { line: 0, ch: 17 } } // the literal `0` - ]); - editor._codeMirror.replaceSelections(["abc", '"hello"']); - // Editor side is correct by construction; the assertion that matters is the server agreeing - // - it can only report the error if it received the same text. - expect(editor.getLine(0)).toBe('let abc: number = "hello";'); - await awaitsFor(function () { - return panelText().includes("not assignable"); - }, "type error after the multi-cursor edit", 30000); - - // Revert both cursors in one operation -> the error must clear (sync holds the other way). - editor.setSelections([ - { start: { line: 0, ch: 4 }, end: { line: 0, ch: 7 } }, // `abc` - { start: { line: 0, ch: 18 }, end: { line: 0, ch: 25 } } // `"hello"` - ]); - editor._codeMirror.replaceSelections(["a", "0"]); - expect(editor.getLine(0)).toBe("let a: number = 0;"); - await awaitsFor(inspectionClean, "error clears after reverting the multi-cursor edit", 30000); - - await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }), - "close incremental.ts"); + try { + const editor = EditorManager.getCurrentFullEditor(); + await awaitsFor(inspectionClean, "incremental.ts to start clean", 30000); + + // Two cursors on the SAME line, edited in one operation - the order-sensitive case, since + // the first replacement shifts the columns of the second. CodeMirror applies and reports + // the batch so that replaying the records in array order reproduces the result; this + // confirms our 1:1 change-record -> LSP-range map honours that ordering. + // "let a: number = 0;" -> "let abc: number = \"hello\";" + editor.setSelections([ + { start: { line: 0, ch: 4 }, end: { line: 0, ch: 5 } }, // the identifier `a` + { start: { line: 0, ch: 16 }, end: { line: 0, ch: 17 } } // the literal `0` + ]); + editor._codeMirror.replaceSelections(["abc", '"hello"']); + // Editor side is correct by construction; the assertion that matters is the server agreeing + // - it can only report the error if it received the same text. + expect(editor.getLine(0)).toBe('let abc: number = "hello";'); + await awaitsFor(function () { + return panelText().includes("not assignable"); + }, "type error after the multi-cursor edit", 30000); + + // Revert both cursors in one operation -> the error must clear (sync holds the other way). + editor.setSelections([ + { start: { line: 0, ch: 4 }, end: { line: 0, ch: 7 } }, // `abc` + { start: { line: 0, ch: 18 }, end: { line: 0, ch: 25 } } // `"hello"` + ]); + editor._codeMirror.replaceSelections(["a", "0"]); + expect(editor.getLine(0)).toBe("let a: number = 0;"); + await awaitsFor(inspectionClean, "error clears after reverting the multi-cursor edit", 30000); + } finally { + // see the incremental-edits test above + await jsPromise(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true })) + .catch(function (e) { console.error("Failed closing incremental.ts", e); }); + } }, 90000); // ----- embedded JavaScript in HTML