diff --git a/tests/browser/editor-pipeline.test.js b/tests/browser/editor-pipeline.test.js index 689911d8..ba087f04 100644 --- a/tests/browser/editor-pipeline.test.js +++ b/tests/browser/editor-pipeline.test.js @@ -653,6 +653,63 @@ test("disabled auto indentation uses native Enter and retains manual indentation } }); +test("a click back into the editor after Escape lets Tab indent again", async (t) => { + const ctx = await editorPage(t); + if (!ctx) return; + const { page, errors } = ctx; + try { + await setCode(page, "x"); + await page.keyboard.press("Escape"); + await page.locator("#editor").click(); + // The click leaves the caret wherever it landed; put it back at the end. + await setCode(page, "x"); + await page.keyboard.press("Tab"); + assert.deepEqual(await caretState(page), { + value: "x ", + start: 5, + end: 5, + }); + assert.equal( + await page + .locator("#editor") + .evaluate((editor) => document.activeElement === editor), + true, + ); + assert.deepEqual(errors, []); + } finally { + await page.close(); + } +}); + +test("leaving the editor after Escape disarms its Tab exit", async (t) => { + const ctx = await editorPage(t); + if (!ctx) return; + const { page, errors } = ctx; + try { + await setCode(page, "x"); + await page.keyboard.press("Escape"); + // Focus goes elsewhere without that Tab, as a click on another control + // sends it, and returns with no click in the editor, as Shift+Tab does. + await page.locator("#editor").evaluate((editor) => editor.blur()); + await setCode(page, "x"); + await page.keyboard.press("Tab"); + assert.deepEqual(await caretState(page), { + value: "x ", + start: 5, + end: 5, + }); + assert.equal( + await page + .locator("#editor") + .evaluate((editor) => document.activeElement === editor), + true, + ); + assert.deepEqual(errors, []); + } finally { + await page.close(); + } +}); + for (const direction of ["forward", "backward"]) { for (const { command, code, end, edited, start, finish } of [ { diff --git a/web/interview.js b/web/interview.js index 6b6bc289..c635be68 100644 --- a/web/interview.js +++ b/web/interview.js @@ -1337,6 +1337,14 @@ function bindEvents() { // that instead, or a candidate driving the page from the keyboard is stuck // in the textarea with no way out. let tabLeavesEditor = false; + // Escape arms only the keyboard's next move. A click in the editor is the + // candidate editing again, and a blur means they left some other way, so + // neither may leave the next Tab armed. + for (const type of ["pointerdown", "blur"]) { + nodes.editor.addEventListener(type, () => { + tabLeavesEditor = false; + }); + } nodes.editor.addEventListener("keydown", (event) => { if (event.key === "Escape") { tabLeavesEditor = true;