Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Auto detect text files and perform LF normalization
* text=auto
* text=auto

# must stay byte-exact; autocrlf on Windows CI breaks the file-encoding tests
test/spec/encoding-test-files/* -text
14 changes: 8 additions & 6 deletions src/extensions/default/HTMLCodeHints/unittests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
170 changes: 89 additions & 81 deletions src/extensions/default/TypeScriptSupport/unittests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <script> tags -----------------------------------------
Expand Down Expand Up @@ -516,27 +522,29 @@ define(function (require, exports, module) {
path.join(projectPath, "fixable.ts"), "consol.log(1);\n", FileSystem));
await SpecRunnerUtils.loadProjectInTestWindow(projectPath);
await awaitsForDone(SpecRunnerUtils.openProjectFiles(["fixable.ts"]), "open fixable.ts");
try {
await awaitsFor(function () {
return $("#problems-panel").text().indexOf("Cannot find name 'consol'") !== -1;
}, "the spelling diagnostic to appear in the problems panel", 30000);

await awaitsFor(function () {
return $("#problems-panel").text().indexOf("Cannot find name 'consol'") !== -1;
}, "the spelling diagnostic to appear in the problems panel", 30000);

// The idle codeAction fetch (~800ms after diagnostics settle) attaches the fix and
// nudges a re-run - the Fix All button becoming visible proves the whole chain.
await awaitsFor(function () {
const $btn = $("#problems-panel .problems-fix-all-btn");
return $btn.length && !$btn.hasClass("forced-hidden");
}, "the Fix All button to appear once quickfixes are fetched", 30000);

const editor = EditorManager.getCurrentFullEditor();
$("#problems-panel .problems-fix-all-btn").click();
await awaitsFor(function () {
return editor.document.getText().indexOf("console.log(1);") !== -1;
}, "the quickfix to change consol -> console", 10000);
// The idle codeAction fetch (~800ms after diagnostics settle) attaches the fix and
// nudges a re-run - the Fix All button becoming visible proves the whole chain.
await awaitsFor(function () {
const $btn = $("#problems-panel .problems-fix-all-btn");
return $btn.length && !$btn.hasClass("forced-hidden");
}, "the Fix All button to appear once quickfixes are fetched", 30000);

await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }),
"close fixable.ts");
await SpecRunnerUtils.removeTempDirectory();
const editor = EditorManager.getCurrentFullEditor();
$("#problems-panel .problems-fix-all-btn").click();
await awaitsFor(function () {
return editor.document.getText().indexOf("console.log(1);") !== -1;
}, "the quickfix to change consol -> console", 10000);
} finally {
// see the incremental-edits test above
await jsPromise(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }))
.catch(function (e) { console.error("Failed closing fixable.ts", e); });
await SpecRunnerUtils.removeTempDirectory();
}
}, 90000);

// ----- hover quick-actions (Go to Definition / Find Usages) -------------------------------
Expand Down
49 changes: 32 additions & 17 deletions test/spec/EditorCommandHandlers-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,24 @@ define(function (require, exports, module) {
"Open into working set");
myEditor = EditorManager.getCurrentFullEditor();

// one jump attempt capped at 3s, so a hung request just counts
// as a failed attempt and the caller can retry
function attemptJumpToDefinition() {
return new Promise(function (resolve) {
let settled = false;
function settle(val) {
if (!settled) {
settled = true;
resolve(val);
}
}
CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION)
.done(function () { settle(true); })
.fail(function () { settle(false); });
setTimeout(function () { settle(false); }, 3000);
});
}

if (window.Phoenix.isNativeApp) {
// Desktop has the LSP server (vtsls), which provides jump-to-definition for
// JavaScript at a higher priority than the built-in Tern provider. vtsls returns
Expand All @@ -230,23 +248,11 @@ define(function (require, exports, module) {
// awaitsFor aborts outright on a pollFn exception, so the promise is absorbed.
await awaitsFor(async function () {
myEditor.setCursorPos({line: 5, ch: 8});
var landed = await new Promise(function (resolve) {
var settled = false;
function settle(val) {
if (!settled) {
settled = true;
resolve(val);
}
}
CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION)
.done(function () { settle(true); })
.fail(function () { settle(false); });
setTimeout(function () { settle(false); }, 3000);
});
const landed = await attemptJumpToDefinition();
if (!landed) {
return false;
}
var sel = myEditor.getSelection();
const sel = myEditor.getSelection();
return sel.start.line === 0 && sel.start.ch === 0 &&
sel.end.line === 0 && sel.end.ch === 0;
}, "LSP jump-to-definition to land on the testMe declaration", 15000, 500);
Expand All @@ -256,9 +262,18 @@ define(function (require, exports, module) {
end: {line: 0, ch: 0}
}));
} else {
myEditor.setCursorPos({line: 5, ch: 8});
await awaitsForDone(CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION),
"Jump To Definition");
// Tern can stall on a slow CI runner while it warms up, so
// retry with capped attempts, same as the LSP path above
await awaitsFor(async function () {
myEditor.setCursorPos({line: 5, ch: 8});
const landed = await attemptJumpToDefinition();
if (!landed) {
return false;
}
const sel = myEditor.getSelection();
return sel.start.line === 0 && sel.start.ch === 9 &&
sel.end.line === 0 && sel.end.ch === 15;
}, "Tern jump-to-definition to select the testMe identifier", 30000, 500);
selection = myEditor.getSelection();
expect(fixSel(selection)).toEql(fixSel({
start: {line: 0, ch: 9},
Expand Down
22 changes: 18 additions & 4 deletions test/spec/Extn-ESLint-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -452,13 +452,27 @@ define(function (require, exports, module) {

it("should be able to fix all errors", async function () {
await _openAndVerifyInitial();
const editor = EditorManager.getCurrentFullEditor();
let editor = EditorManager.getCurrentFullEditor();
editor.setCursorPos(0, 0); // resent any saved selections from previous run
// click on fix : Expected indentation of 4 spaces but found 9. ESLint (indent)
// a late doc reload can stale the fixes and pop the "Failed to Apply Fix" dialog
// on slow CI; dismiss it, re-lint and retry so it doesn't leak into later suites
$($("#problems-panel").find(".problems-fix-all-btn")).click();
await awaitsFor(()=>{
await awaitsFor(async ()=>{
if($(".error-dialog.instance").length){
testWindow.brackets.test.Dialogs.cancelModalDialogIfOpen(
testWindow.brackets.test.DefaultDialogs.DIALOG_ID_ERROR);
await _triggerLint();
await awaitsFor(()=>{
return $("#problems-panel").find(".ph-fix-problem").length === 2;
}, "fix buttons to reappear after re-lint", 15000);
editor = EditorManager.getCurrentFullEditor();
editor.setCursorPos(0, 0);
$($("#problems-panel").find(".problems-fix-all-btn")).click();
return false;
}
return $("#problems-panel").find(".ph-fix-problem").length === 0;
}, "no problems should remain as all is now fixed", 15000);
}, "no problems should remain as all is now fixed", 30000);

// fixing multiple should place the cursor on first fix
expect(editor.hasSelection()).toBeFalse();
Expand All @@ -472,7 +486,7 @@ define(function (require, exports, module) {
await awaitsFor(()=>{
return $("#problems-panel").find(".ph-fix-problem").length === 2;
}, "2 problem should be there", 15000);
}, 30000);
}, 60000);
});
});
});
Loading
Loading