summaryrefslogtreecommitdiff
path: root/_tools/e2e
diff options
context:
space:
mode:
authorHizbu Azka <hanifan.azka@gmail.com>2026-10-23 10:45:25 +0700
committerHizbu Azka <hanifan.azka@gmail.com>2026-10-23 10:46:41 +0700
commit272b2b94a268e1e342dab2daae4031f4eabdbb8e (patch)
tree8e5c49b6e528e5a1a7aa4190a21fbffeebdde165 /_tools/e2e
parent2bd366b984bb0ac3ac98691a51f837731af67b2a (diff)
run prettier
Diffstat (limited to '_tools/e2e')
-rw-r--r--_tools/e2e/playwright.config.ts22
-rw-r--r--_tools/e2e/tests/regex-editor.spec.ts695
2 files changed, 420 insertions, 297 deletions
diff --git a/_tools/e2e/playwright.config.ts b/_tools/e2e/playwright.config.ts
index 1e97f70..fdd7826 100644
--- a/_tools/e2e/playwright.config.ts
+++ b/_tools/e2e/playwright.config.ts
@@ -1,4 +1,4 @@
-import { defineConfig, devices } from '@playwright/test';
+import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
@@ -12,7 +12,7 @@ import { defineConfig, devices } from '@playwright/test';
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
- testDir: './tests',
+ testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -22,26 +22,26 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
- reporter: 'html',
+ reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: 'on-first-retry',
+ trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
- name: 'chromium',
- use: { ...devices['Desktop Chrome'] },
+ name: "chromium",
+ use: { ...devices["Desktop Chrome"] },
},
{
- name: 'firefox',
- use: { ...devices['Desktop Firefox'] },
+ name: "firefox",
+ use: { ...devices["Desktop Firefox"] },
},
// {
@@ -72,9 +72,9 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
- command: 'jekyll serve --source .. --destination ../_site',
- url: 'http://localhost:4000',
+ command: "jekyll serve --source .. --destination ../_site",
+ url: "http://localhost:4000",
reuseExistingServer: !process.env.CI,
timeout: 60_000,
},
-})
+});
diff --git a/_tools/e2e/tests/regex-editor.spec.ts b/_tools/e2e/tests/regex-editor.spec.ts
index 1f3c0d3..fd0a04f 100644
--- a/_tools/e2e/tests/regex-editor.spec.ts
+++ b/_tools/e2e/tests/regex-editor.spec.ts
@@ -1,4 +1,4 @@
-import { test, expect, Page, Locator, Dialog } from '@playwright/test';
+import { test, expect, Page, Locator, Dialog } from "@playwright/test";
/**
* Playwright spec for https://hizbu.com/regex/
@@ -11,33 +11,39 @@ import { test, expect, Page, Locator, Dialog } from '@playwright/test';
* DOM node (`cmView` / `cmTile` property). If a CodeMirror upgrade ever moves that
* property, only the `__cmView` init script below needs updating.
*/
-const URL = 'http://localhost:4000/regex/';
+const URL = "http://localhost:4000/regex/";
const KEY = {
- patterns: 'regex_patterns',
- docs: 'regex_documents',
- titles: 'regex_tab_titles',
- active: 'regex_active_tab',
+ patterns: "regex_patterns",
+ docs: "regex_documents",
+ titles: "regex_tab_titles",
+ active: "regex_active_tab",
};
-const DEFAULT_TITLES = ['Emails & IDs', 'Code Snippet', 'Server Logs'];
-const NEW_TAB_TEXT = '// Type or paste text here...\n';
+const DEFAULT_TITLES = ["Emails & IDs", "Code Snippet", "Server Logs"];
+const NEW_TAB_TEXT = "// Type or paste text here...\n";
// ---------- locators ----------
-const topEditor = (p: Page) => p.locator('#regex-editor .cm-content');
-const mainEditor = (p: Page) => p.locator('#cm6-editor .cm-content');
+const topEditor = (p: Page) => p.locator("#regex-editor .cm-content");
+const mainEditor = (p: Page) => p.locator("#cm6-editor .cm-content");
const tabs = (p: Page) => p.locator('#tab-bar [role="tab"]');
-const tabLabel = (p: Page, i: number) => tabs(p).nth(i).locator('span[title="Double-click to rename"]');
-const tabClose = (p: Page, i: number) => tabs(p).nth(i).locator('.tab-close');
-const addTabBtn = (p: Page) => p.locator('#add-tab-btn');
-const fullscreen = (p: Page) => p.locator('#fullscreen');
+const tabLabel = (p: Page, i: number) =>
+ tabs(p).nth(i).locator('span[title="Double-click to rename"]');
+const tabClose = (p: Page, i: number) => tabs(p).nth(i).locator(".tab-close");
+const addTabBtn = (p: Page) => p.locator("#add-tab-btn");
+const fullscreen = (p: Page) => p.locator("#fullscreen");
const matchTexts = (p: Page, n: number) =>
p.locator(`#cm6-editor .cm-regex-match-${n}`).allTextContents();
// ---------- helpers ----------
const docText = (l: Locator): Promise<string> =>
l.evaluate((el: any) => {
- try { return (window as any).__cmView(el).state.doc.toString(); }
- catch { return [...el.querySelectorAll('.cm-line')].map((x: any) => x.textContent).join('\n'); }
+ try {
+ return (window as any).__cmView(el).state.doc.toString();
+ } catch {
+ return [...el.querySelectorAll(".cm-line")]
+ .map((x: any) => x.textContent)
+ .join("\n");
+ }
});
const selection = (l: Locator): Promise<{ anchor: number; head: number }> =>
@@ -47,13 +53,16 @@ const selection = (l: Locator): Promise<{ anchor: number; head: number }> =>
});
/** Focus the editor WITHOUT changing its selection (unlike click). */
-const focusEditor = (l: Locator) => l.evaluate((el: any) => (window as any).__cmView(el).focus());
+const focusEditor = (l: Locator) =>
+ l.evaluate((el: any) => (window as any).__cmView(el).focus());
-const storage = (p: Page, key: string) => p.evaluate((k) => localStorage.getItem(k), key);
-const storageJson = async (p: Page, key: string) => JSON.parse((await storage(p, key)) ?? 'null');
+const storage = (p: Page, key: string) =>
+ p.evaluate((k) => localStorage.getItem(k), key);
+const storageJson = async (p: Page, key: string) =>
+ JSON.parse((await storage(p, key)) ?? "null");
async function waitReady(p: Page) {
- await expect(p.locator('#loading')).toHaveCount(0, { timeout: 30_000 });
+ await expect(p.locator("#loading")).toHaveCount(0, { timeout: 30_000 });
await expect(topEditor(p)).toBeVisible();
await expect(mainEditor(p)).toBeVisible();
}
@@ -66,10 +75,10 @@ async function reload(p: Page) {
/** Replace the whole content of a CodeMirror editor by typing it. */
async function setDoc(p: Page, editor: Locator, lines: string[]) {
await editor.click();
- await p.keyboard.press('ControlOrMeta+a');
- await p.keyboard.press('Backspace');
+ await p.keyboard.press("ControlOrMeta+a");
+ await p.keyboard.press("Backspace");
for (let i = 0; i < lines.length; i++) {
- if (i > 0) await p.keyboard.press('Enter');
+ if (i > 0) await p.keyboard.press("Enter");
if (lines[i]) await p.keyboard.type(lines[i]);
}
}
@@ -81,11 +90,16 @@ async function redo(p: Page) {
// CodeMirror historyKeymap: Mod-y = redo (Win/Linux), Mod-Shift-z = redo (Mac).
// Ctrl+Shift+Z is avoided on purpose: on Linux Chromium it was observed to act as undo.
const isMac = await p.evaluate(() => /Mac/i.test(navigator.platform));
- await p.keyboard.press(isMac ? 'Meta+Shift+z' : 'Control+y');
+ await p.keyboard.press(isMac ? "Meta+Shift+z" : "Control+y");
}
/** Close a tab; asserts the native confirm() text, then accepts or dismisses it. */
-async function closeTab(p: Page, index: number, accept: boolean, expectedName?: string) {
+async function closeTab(
+ p: Page,
+ index: number,
+ accept: boolean,
+ expectedName?: string,
+) {
// window.confirm() blocks the page, so click() cannot resolve until the dialog is answered.
// The handler therefore must be registered BEFORE the click and answer immediately.
const seen: { type: string; message: string }[] = [];
@@ -93,16 +107,19 @@ async function closeTab(p: Page, index: number, accept: boolean, expectedName?:
seen.push({ type: d.type(), message: d.message() });
await (accept ? d.accept() : d.dismiss());
};
- p.on('dialog', handler);
+ p.on("dialog", handler);
try {
await tabClose(p, index).click();
} finally {
- p.off('dialog', handler);
+ p.off("dialog", handler);
}
- expect(seen, 'expected exactly one confirm() dialog').toHaveLength(1);
- expect(seen[0].type).toBe('confirm');
- if (expectedName) expect(seen[0].message).toBe(`Are you sure you want to close "${expectedName}"?`);
+ expect(seen, "expected exactly one confirm() dialog").toHaveLength(1);
+ expect(seen[0].type).toBe("confirm");
+ if (expectedName)
+ expect(seen[0].message).toBe(
+ `Are you sure you want to close "${expectedName}"?`,
+ );
}
test.beforeEach(async ({ page }) => {
@@ -112,7 +129,10 @@ test.beforeEach(async ({ page }) => {
(window as any).__cmView = (el: any) => {
const t = el.cmView ?? el.cmTile;
const v = t?.view ?? t?.root?.view ?? t?.rootView?.view;
- if (!v) throw new Error('Could not locate CodeMirror EditorView on .cm-content');
+ if (!v)
+ throw new Error(
+ "Could not locate CodeMirror EditorView on .cm-content",
+ );
return v;
};
});
@@ -124,260 +144,312 @@ test.beforeEach(async ({ page }) => {
// =====================================================================
// 0. initial state
// =====================================================================
-test.describe('initial state', () => {
- test('loads default tabs, regexes and highlights', async ({ page }) => {
+test.describe("initial state", () => {
+ test("loads default tabs, regexes and highlights", async ({ page }) => {
await expect(tabs(page)).toHaveCount(3);
await expect(tabs(page).nth(0)).toHaveClass(/active/);
- for (let i = 0; i < 3; i++) await expect(tabLabel(page, i)).toHaveText(DEFAULT_TITLES[i]);
+ for (let i = 0; i < 3; i++)
+ await expect(tabLabel(page, i)).toHaveText(DEFAULT_TITLES[i]);
- await expect(topEditor(page)).toContainText('[0-9]{4}-[0-9]{2}-[0-9]{2}');
- await expect.poll(() => matchTexts(page, 0)).toContain('support@example.com');
- await expect.poll(() => matchTexts(page, 1)).toContain('2026-10-17');
+ await expect(topEditor(page)).toContainText("[0-9]{4}-[0-9]{2}-[0-9]{2}");
+ await expect
+ .poll(() => matchTexts(page, 0))
+ .toContain("support@example.com");
+ await expect.poll(() => matchTexts(page, 1)).toContain("2026-10-17");
});
});
// =====================================================================
// 1. every regex line is reflected in the lower editor
// =====================================================================
-test.describe('regex lines -> lower editor highlights', () => {
- test('each line is reflected (highlight class N = line index % 5) as it is typed', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['abc 123 DEF 456']);
+test.describe("regex lines -> lower editor highlights", () => {
+ test("each line is reflected (highlight class N = line index % 5) as it is typed", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["abc 123 DEF 456"]);
await setDoc(page, topEditor(page), []); // clear default regexes, leaves top editor focused
const lines = [
- { text: '/[a-z]+/', expected: ['abc'] },
- { text: '/[0-9]+/', expected: ['123', '456'] },
- { text: '/[A-Z]+/', expected: ['DEF'] },
- { text: '/zzz/', expected: [] },
- { text: '/ /', expected: [' ', ' ', ' '] },
+ { text: "/[a-z]+/", expected: ["abc"] },
+ { text: "/[0-9]+/", expected: ["123", "456"] },
+ { text: "/[A-Z]+/", expected: ["DEF"] },
+ { text: "/zzz/", expected: [] },
+ { text: "/ /", expected: [" ", " ", " "] },
];
for (let i = 0; i < lines.length; i++) {
- if (i > 0) await page.keyboard.press('Enter');
+ if (i > 0) await page.keyboard.press("Enter");
await page.keyboard.type(lines[i].text);
// lower editor shows this line's matches with its colour class
await expect.poll(() => matchTexts(page, i)).toEqual(lines[i].expected);
// top editor line fringe uses the same colour index
- await expect(page.locator('#regex-editor .cm-line').nth(i))
- .toHaveClass(new RegExp(`cm-regex-line-${i % 5}`));
+ await expect(page.locator("#regex-editor .cm-line").nth(i)).toHaveClass(
+ new RegExp(`cm-regex-line-${i % 5}`),
+ );
// all earlier lines are still reflected
- for (let j = 0; j < i; j++) expect(await matchTexts(page, j)).toEqual(lines[j].expected);
+ for (let j = 0; j < i; j++)
+ expect(await matchTexts(page, j)).toEqual(lines[j].expected);
}
- await expect.poll(() => storage(page, KEY.patterns)).toBe(lines.map((l) => l.text).join('\n'));
- });
-
- test('colour index wraps after 5 lines', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['w0 w1 w2 w3 w4 w5']);
- await setDoc(page, topEditor(page), [0, 1, 2, 3, 4, 5].map((i) => `/w${i}/`));
-
- await expect.poll(() => matchTexts(page, 0)).toEqual(['w0', 'w5']);
- for (const n of [1, 2, 3, 4]) expect(await matchTexts(page, n)).toEqual([`w${n}`]);
- await expect(page.locator('#regex-editor .cm-line').nth(5)).toHaveClass(/cm-regex-line-0/);
- });
-
- test('blank lines are skipped but still consume a line index', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['abc 123']);
- await setDoc(page, topEditor(page), ['/abc/', '', '/123/']);
-
- await expect.poll(() => matchTexts(page, 0)).toEqual(['abc']);
+ await expect
+ .poll(() => storage(page, KEY.patterns))
+ .toBe(lines.map((l) => l.text).join("\n"));
+ });
+
+ test("colour index wraps after 5 lines", async ({ page }) => {
+ await setDoc(page, mainEditor(page), ["w0 w1 w2 w3 w4 w5"]);
+ await setDoc(
+ page,
+ topEditor(page),
+ [0, 1, 2, 3, 4, 5].map((i) => `/w${i}/`),
+ );
+
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["w0", "w5"]);
+ for (const n of [1, 2, 3, 4])
+ expect(await matchTexts(page, n)).toEqual([`w${n}`]);
+ await expect(page.locator("#regex-editor .cm-line").nth(5)).toHaveClass(
+ /cm-regex-line-0/,
+ );
+ });
+
+ test("blank lines are skipped but still consume a line index", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["abc 123"]);
+ await setDoc(page, topEditor(page), ["/abc/", "", "/123/"]);
+
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["abc"]);
expect(await matchTexts(page, 1)).toEqual([]);
- expect(await matchTexts(page, 2)).toEqual(['123']);
+ expect(await matchTexts(page, 2)).toEqual(["123"]);
});
- test('editing a line updates highlights; deleting all lines removes them', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['cat dog bird']);
- await setDoc(page, topEditor(page), ['/cat/']);
- await expect.poll(() => matchTexts(page, 0)).toEqual(['cat']);
+ test("editing a line updates highlights; deleting all lines removes them", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["cat dog bird"]);
+ await setDoc(page, topEditor(page), ["/cat/"]);
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["cat"]);
// replace line 0
- await page.keyboard.press('ControlOrMeta+Home');
- await page.keyboard.press('Shift+End');
- await page.keyboard.type('/dog/');
- await expect.poll(() => matchTexts(page, 0)).toEqual(['dog']);
+ await page.keyboard.press("ControlOrMeta+Home");
+ await page.keyboard.press("Shift+End");
+ await page.keyboard.type("/dog/");
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["dog"]);
// delete everything
- await page.keyboard.press('ControlOrMeta+a');
- await page.keyboard.press('Backspace');
- await expect(page.locator('#cm6-editor [class*="cm-regex-match-"]')).toHaveCount(0);
+ await page.keyboard.press("ControlOrMeta+a");
+ await page.keyboard.press("Backspace");
+ await expect(
+ page.locator('#cm6-editor [class*="cm-regex-match-"]'),
+ ).toHaveCount(0);
});
- test('editing the lower editor text refreshes highlights', async ({ page }) => {
- await setDoc(page, topEditor(page), ['/foo/']);
- await setDoc(page, mainEditor(page), ['nothing here']);
- await expect(page.locator('#cm6-editor .cm-regex-match-0')).toHaveCount(0);
+ test("editing the lower editor text refreshes highlights", async ({
+ page,
+ }) => {
+ await setDoc(page, topEditor(page), ["/foo/"]);
+ await setDoc(page, mainEditor(page), ["nothing here"]);
+ await expect(page.locator("#cm6-editor .cm-regex-match-0")).toHaveCount(0);
- await page.keyboard.type(' foo foo');
- await expect.poll(() => matchTexts(page, 0)).toEqual(['foo', 'foo']);
+ await page.keyboard.type(" foo foo");
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["foo", "foo"]);
});
- test('flags: g default, explicit flags honoured, no-g matches only once', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['abc ABC abc']);
+ test("flags: g default, explicit flags honoured, no-g matches only once", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["abc ABC abc"]);
- await setDoc(page, topEditor(page), ['/abc/gi']);
- await expect.poll(() => matchTexts(page, 0)).toEqual(['abc', 'ABC', 'abc']);
+ await setDoc(page, topEditor(page), ["/abc/gi"]);
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["abc", "ABC", "abc"]);
- await setDoc(page, topEditor(page), ['/abc/i']); // no g -> first match only
- await expect.poll(() => matchTexts(page, 0)).toEqual(['abc']);
+ await setDoc(page, topEditor(page), ["/abc/i"]); // no g -> first match only
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["abc"]);
- await setDoc(page, topEditor(page), ['abc']); // no slashes -> raw pattern, g flag
- await expect.poll(() => matchTexts(page, 0)).toEqual(['abc', 'abc']);
+ await setDoc(page, topEditor(page), ["abc"]); // no slashes -> raw pattern, g flag
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["abc", "abc"]);
});
- test('invalid regex line is ignored without breaking other lines', async ({ page }) => {
+ test("invalid regex line is ignored without breaking other lines", async ({
+ page,
+ }) => {
const errors: string[] = [];
- page.on('pageerror', (e) => errors.push(e.message));
+ page.on("pageerror", (e) => errors.push(e.message));
- await setDoc(page, mainEditor(page), ['abc 123']);
- await setDoc(page, topEditor(page), ['/(/', '/123/']);
+ await setDoc(page, mainEditor(page), ["abc 123"]);
+ await setDoc(page, topEditor(page), ["/(/", "/123/"]);
- await expect.poll(() => matchTexts(page, 1)).toEqual(['123']);
+ await expect.poll(() => matchTexts(page, 1)).toEqual(["123"]);
expect(await matchTexts(page, 0)).toEqual([]);
expect(errors).toEqual([]);
});
- test('top editor gets one fringe-coloured line per regex line', async ({ page }) => {
- await setDoc(page, topEditor(page), ['a', 'b', 'c', 'd', 'e', 'f']);
- const lines = page.locator('#regex-editor .cm-line');
+ test("top editor gets one fringe-coloured line per regex line", async ({
+ page,
+ }) => {
+ await setDoc(page, topEditor(page), ["a", "b", "c", "d", "e", "f"]);
+ const lines = page.locator("#regex-editor .cm-line");
await expect(lines).toHaveCount(6);
- for (let i = 0; i < 6; i++) await expect(lines.nth(i)).toHaveClass(new RegExp(`cm-regex-line-${i % 5}`));
+ for (let i = 0; i < 6; i++)
+ await expect(lines.nth(i)).toHaveClass(
+ new RegExp(`cm-regex-line-${i % 5}`),
+ );
});
});
// =====================================================================
// 2. localStorage
// =====================================================================
-test.describe('localStorage', () => {
- test('nothing is written until the first change, then all keys exist', async ({ page }) => {
+test.describe("localStorage", () => {
+ test("nothing is written until the first change, then all keys exist", async ({
+ page,
+ }) => {
expect(await storage(page, KEY.docs)).toBeNull();
- await setDoc(page, topEditor(page), ['/x/']);
- await expect.poll(() => storage(page, KEY.patterns)).toBe('/x/');
+ await setDoc(page, topEditor(page), ["/x/"]);
+ await expect.poll(() => storage(page, KEY.patterns)).toBe("/x/");
expect(await storageJson(page, KEY.titles)).toEqual(DEFAULT_TITLES);
- expect(await storage(page, KEY.active)).toBe('0');
+ expect(await storage(page, KEY.active)).toBe("0");
expect((await storageJson(page, KEY.docs)).length).toBe(3);
});
- test('regex editor text is stored and restored', async ({ page }) => {
- await setDoc(page, topEditor(page), ['/one/', '/two/g']);
- await expect.poll(() => storage(page, KEY.patterns)).toBe('/one/\n/two/g');
+ test("regex editor text is stored and restored", async ({ page }) => {
+ await setDoc(page, topEditor(page), ["/one/", "/two/g"]);
+ await expect.poll(() => storage(page, KEY.patterns)).toBe("/one/\n/two/g");
await reload(page);
- expect(await docText(topEditor(page))).toBe('/one/\n/two/g');
+ expect(await docText(topEditor(page))).toBe("/one/\n/two/g");
});
- test('lower editor text is stored per tab and restored', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['tab zero text']);
+ test("lower editor text is stored per tab and restored", async ({ page }) => {
+ await setDoc(page, mainEditor(page), ["tab zero text"]);
await switchTo(page, 1);
- await setDoc(page, mainEditor(page), ['tab one text']);
+ await setDoc(page, mainEditor(page), ["tab one text"]);
- await expect.poll(async () => (await storageJson(page, KEY.docs))?.[0]).toBe('tab zero text');
- expect((await storageJson(page, KEY.docs))[1]).toBe('tab one text');
- expect(await storage(page, KEY.active)).toBe('1');
+ await expect
+ .poll(async () => (await storageJson(page, KEY.docs))?.[0])
+ .toBe("tab zero text");
+ expect((await storageJson(page, KEY.docs))[1]).toBe("tab one text");
+ expect(await storage(page, KEY.active)).toBe("1");
await reload(page);
await expect(tabs(page).nth(1)).toHaveClass(/active/); // active tab restored
- expect(await docText(mainEditor(page))).toBe('tab one text');
+ expect(await docText(mainEditor(page))).toBe("tab one text");
await switchTo(page, 0);
- expect(await docText(mainEditor(page))).toBe('tab zero text');
+ expect(await docText(mainEditor(page))).toBe("tab zero text");
});
- test('tab titles and tab count are stored and restored', async ({ page }) => {
+ test("tab titles and tab count are stored and restored", async ({ page }) => {
await addTabBtn(page).click();
await tabLabel(page, 0).dblclick();
- await page.locator('#tab-bar input').fill('Renamed');
- await page.keyboard.press('Enter');
+ await page.locator("#tab-bar input").fill("Renamed");
+ await page.keyboard.press("Enter");
- await expect.poll(() => storageJson(page, KEY.titles)).toEqual(['Renamed', 'Code Snippet', 'Server Logs', 'Dataset 4']);
+ await expect
+ .poll(() => storageJson(page, KEY.titles))
+ .toEqual(["Renamed", "Code Snippet", "Server Logs", "Dataset 4"]);
await reload(page);
await expect(tabs(page)).toHaveCount(4);
- await expect(tabLabel(page, 0)).toHaveText('Renamed');
- await expect(tabLabel(page, 3)).toHaveText('Dataset 4');
+ await expect(tabLabel(page, 0)).toHaveText("Renamed");
+ await expect(tabLabel(page, 3)).toHaveText("Dataset 4");
});
- test('highlights are re-applied after reload', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['needle hay needle']);
- await setDoc(page, topEditor(page), ['/needle/']);
- await expect.poll(() => matchTexts(page, 0)).toEqual(['needle', 'needle']);
+ test("highlights are re-applied after reload", async ({ page }) => {
+ await setDoc(page, mainEditor(page), ["needle hay needle"]);
+ await setDoc(page, topEditor(page), ["/needle/"]);
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["needle", "needle"]);
await reload(page);
- await expect.poll(() => matchTexts(page, 0)).toEqual(['needle', 'needle']);
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["needle", "needle"]);
});
- test('clearing localStorage resets to defaults', async ({ page }) => {
- await setDoc(page, topEditor(page), ['/wiped/']);
- await expect.poll(() => storage(page, KEY.patterns)).toBe('/wiped/');
+ test("clearing localStorage resets to defaults", async ({ page }) => {
+ await setDoc(page, topEditor(page), ["/wiped/"]);
+ await expect.poll(() => storage(page, KEY.patterns)).toBe("/wiped/");
await page.evaluate(() => localStorage.clear());
await reload(page);
await expect(tabs(page)).toHaveCount(3);
- expect(await docText(topEditor(page))).not.toContain('wiped');
+ expect(await docText(topEditor(page))).not.toContain("wiped");
});
});
// =====================================================================
// 3. rename tab on double click
// =====================================================================
-test.describe('rename tab', () => {
- const input = (p: Page) => p.locator('#tab-bar input');
+test.describe("rename tab", () => {
+ const input = (p: Page) => p.locator("#tab-bar input");
- test('double click shows a focused input with the current name selected', async ({ page }) => {
+ test("double click shows a focused input with the current name selected", async ({
+ page,
+ }) => {
await tabLabel(page, 0).dblclick();
await expect(input(page)).toBeVisible();
await expect(input(page)).toBeFocused();
await expect(input(page)).toHaveValue(DEFAULT_TITLES[0]);
- expect(await input(page).evaluate((el: HTMLInputElement) => el.selectionEnd! - el.selectionStart!))
- .toBe(DEFAULT_TITLES[0].length);
+ expect(
+ await input(page).evaluate(
+ (el: HTMLInputElement) => el.selectionEnd! - el.selectionStart!,
+ ),
+ ).toBe(DEFAULT_TITLES[0].length);
});
- test('Enter commits, trims, and persists across reload', async ({ page }) => {
+ test("Enter commits, trims, and persists across reload", async ({ page }) => {
await tabLabel(page, 0).dblclick();
- await input(page).fill(' My Tab ');
- await page.keyboard.press('Enter');
+ await input(page).fill(" My Tab ");
+ await page.keyboard.press("Enter");
await expect(input(page)).toHaveCount(0);
- await expect(tabLabel(page, 0)).toHaveText('My Tab');
- await expect.poll(async () => (await storageJson(page, KEY.titles))[0]).toBe('My Tab');
+ await expect(tabLabel(page, 0)).toHaveText("My Tab");
+ await expect
+ .poll(async () => (await storageJson(page, KEY.titles))[0])
+ .toBe("My Tab");
await reload(page);
- await expect(tabLabel(page, 0)).toHaveText('My Tab');
+ await expect(tabLabel(page, 0)).toHaveText("My Tab");
});
- test('Escape cancels', async ({ page }) => {
+ test("Escape cancels", async ({ page }) => {
await tabLabel(page, 0).dblclick();
- await input(page).fill('nope');
- await page.keyboard.press('Escape');
+ await input(page).fill("nope");
+ await page.keyboard.press("Escape");
await expect(tabLabel(page, 0)).toHaveText(DEFAULT_TITLES[0]);
});
- test('blur commits', async ({ page }) => {
+ test("blur commits", async ({ page }) => {
await tabLabel(page, 0).dblclick();
- await input(page).fill('Blurred');
+ await input(page).fill("Blurred");
await mainEditor(page).click();
- await expect(tabLabel(page, 0)).toHaveText('Blurred');
+ await expect(tabLabel(page, 0)).toHaveText("Blurred");
});
- test('empty / whitespace-only name keeps the old name', async ({ page }) => {
+ test("empty / whitespace-only name keeps the old name", async ({ page }) => {
await tabLabel(page, 0).dblclick();
- await input(page).fill(' ');
- await page.keyboard.press('Enter');
+ await input(page).fill(" ");
+ await page.keyboard.press("Enter");
await expect(tabLabel(page, 0)).toHaveText(DEFAULT_TITLES[0]);
});
- test('typing in the rename input (incl. Enter) never edits the editors', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['keep me']);
- await setDoc(page, topEditor(page), ['/keep/']);
+ test("typing in the rename input (incl. Enter) never edits the editors", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["keep me"]);
+ await setDoc(page, topEditor(page), ["/keep/"]);
await tabLabel(page, 0).dblclick();
- await input(page).fill('abc');
- await page.keyboard.press('Enter');
- expect(await docText(mainEditor(page))).toBe('keep me');
- expect(await docText(topEditor(page))).toBe('/keep/');
+ await input(page).fill("abc");
+ await page.keyboard.press("Enter");
+ expect(await docText(mainEditor(page))).toBe("keep me");
+ expect(await docText(topEditor(page))).toBe("/keep/");
});
- test('renaming a non-active tab also works (and activates it)', async ({ page }) => {
+ test("renaming a non-active tab also works (and activates it)", async ({
+ page,
+ }) => {
await tabLabel(page, 2).dblclick();
- await input(page).fill('Logs!');
- await page.keyboard.press('Enter');
- await expect(tabLabel(page, 2)).toHaveText('Logs!');
+ await input(page).fill("Logs!");
+ await page.keyboard.press("Enter");
+ await expect(tabLabel(page, 2)).toHaveText("Logs!");
await expect(tabs(page).nth(2)).toHaveClass(/active/);
});
- test('clicking inside the rename input does not switch tabs', async ({ page }) => {
+ test("clicking inside the rename input does not switch tabs", async ({
+ page,
+ }) => {
await tabLabel(page, 0).dblclick();
await input(page).click();
await expect(input(page)).toBeVisible();
@@ -388,86 +460,104 @@ test.describe('rename tab', () => {
// =====================================================================
// 4. add / delete tab (with confirmation)
// =====================================================================
-test.describe('add and delete tabs', () => {
- test('add creates "Dataset N" with starter text and activates it', async ({ page }) => {
+test.describe("add and delete tabs", () => {
+ test('add creates "Dataset N" with starter text and activates it', async ({
+ page,
+ }) => {
await addTabBtn(page).click();
await expect(tabs(page)).toHaveCount(4);
- await expect(tabLabel(page, 3)).toHaveText('Dataset 4');
+ await expect(tabLabel(page, 3)).toHaveText("Dataset 4");
await expect(tabs(page).nth(3)).toHaveClass(/active/);
expect(await docText(mainEditor(page))).toBe(NEW_TAB_TEXT);
await addTabBtn(page).click();
- await expect(tabLabel(page, 4)).toHaveText('Dataset 5');
+ await expect(tabLabel(page, 4)).toHaveText("Dataset 5");
});
- test('add keeps the previous tab content intact', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['original']);
+ test("add keeps the previous tab content intact", async ({ page }) => {
+ await setDoc(page, mainEditor(page), ["original"]);
await addTabBtn(page).click();
await switchTo(page, 0);
- expect(await docText(mainEditor(page))).toBe('original');
+ expect(await docText(mainEditor(page))).toBe("original");
});
- test('delete shows confirm() with the tab name; cancelling keeps the tab', async ({ page }) => {
- await closeTab(page, 1, false, 'Code Snippet');
+ test("delete shows confirm() with the tab name; cancelling keeps the tab", async ({
+ page,
+ }) => {
+ await closeTab(page, 1, false, "Code Snippet");
await expect(tabs(page)).toHaveCount(3);
- await expect(tabLabel(page, 1)).toHaveText('Code Snippet');
+ await expect(tabLabel(page, 1)).toHaveText("Code Snippet");
});
- test('accepting removes the tab and persists', async ({ page }) => {
- await closeTab(page, 1, true, 'Code Snippet');
+ test("accepting removes the tab and persists", async ({ page }) => {
+ await closeTab(page, 1, true, "Code Snippet");
await expect(tabs(page)).toHaveCount(2);
- await expect(tabLabel(page, 0)).toHaveText('Emails & IDs');
- await expect(tabLabel(page, 1)).toHaveText('Server Logs');
+ await expect(tabLabel(page, 0)).toHaveText("Emails & IDs");
+ await expect(tabLabel(page, 1)).toHaveText("Server Logs");
- await expect.poll(() => storageJson(page, KEY.titles)).toEqual(['Emails & IDs', 'Server Logs']);
+ await expect
+ .poll(() => storageJson(page, KEY.titles))
+ .toEqual(["Emails & IDs", "Server Logs"]);
expect((await storageJson(page, KEY.docs)).length).toBe(2);
await reload(page);
await expect(tabs(page)).toHaveCount(2);
});
- test('close click does not switch to the tab being closed when cancelled', async ({ page }) => {
+ test("close click does not switch to the tab being closed when cancelled", async ({
+ page,
+ }) => {
await closeTab(page, 2, false);
await expect(tabs(page).nth(0)).toHaveClass(/active/);
});
- test('deleting a tab BEFORE the active one keeps the same tab and content active', async ({ page }) => {
+ test("deleting a tab BEFORE the active one keeps the same tab and content active", async ({
+ page,
+ }) => {
await switchTo(page, 2);
const before = await docText(mainEditor(page));
await closeTab(page, 0, true);
await expect(tabs(page)).toHaveCount(2);
await expect(tabs(page).nth(1)).toHaveClass(/active/);
- await expect(tabLabel(page, 1)).toHaveText('Server Logs');
+ await expect(tabLabel(page, 1)).toHaveText("Server Logs");
expect(await docText(mainEditor(page))).toBe(before);
});
- test('deleting the active LAST tab activates the new last tab', async ({ page }) => {
+ test("deleting the active LAST tab activates the new last tab", async ({
+ page,
+ }) => {
await switchTo(page, 2);
await closeTab(page, 2, true);
await expect(tabs(page)).toHaveCount(2);
await expect(tabs(page).nth(1)).toHaveClass(/active/);
- await expect(tabLabel(page, 1)).toHaveText('Code Snippet');
+ await expect(tabLabel(page, 1)).toHaveText("Code Snippet");
});
- test('deleting the active FIRST tab activates the tab that took its place', async ({ page }) => {
+ test("deleting the active FIRST tab activates the tab that took its place", async ({
+ page,
+ }) => {
await closeTab(page, 0, true);
await expect(tabs(page).nth(0)).toHaveClass(/active/);
- await expect(tabLabel(page, 0)).toHaveText('Code Snippet');
+ await expect(tabLabel(page, 0)).toHaveText("Code Snippet");
});
- test('last remaining tab cannot be closed (no close button)', async ({ page }) => {
+ test("last remaining tab cannot be closed (no close button)", async ({
+ page,
+ }) => {
await closeTab(page, 0, true);
await closeTab(page, 0, true);
await expect(tabs(page)).toHaveCount(1);
- await expect(page.locator('#tab-bar .tab-close')).toHaveCount(0);
+ await expect(page.locator("#tab-bar .tab-close")).toHaveCount(0);
});
- test('deleted tab edits do not leak into remaining tabs', async ({ page }) => {
+ test("deleted tab edits do not leak into remaining tabs", async ({
+ page,
+ }) => {
await switchTo(page, 1);
- await setDoc(page, mainEditor(page), ['doomed']);
+ await setDoc(page, mainEditor(page), ["doomed"]);
await closeTab(page, 1, true);
for (let i = 0; i < 2; i++) {
await switchTo(page, i);
- expect(await docText(mainEditor(page))).not.toBe('doomed');
+ expect(await docText(mainEditor(page))).not.toBe("doomed");
}
});
});
@@ -475,59 +565,74 @@ test.describe('add and delete tabs', () => {
// =====================================================================
// 5. fullscreen (body margin / padding / max-width)
// =====================================================================
-test.describe('fullscreen', () => {
+test.describe("fullscreen", () => {
const bodyMetrics = (p: Page) =>
p.evaluate(() => {
const cs = getComputedStyle(document.body);
return {
margin: [cs.marginTop, cs.marginRight, cs.marginBottom, cs.marginLeft],
- padding: [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft],
+ padding: [
+ cs.paddingTop,
+ cs.paddingRight,
+ cs.paddingBottom,
+ cs.paddingLeft,
+ ],
maxWidth: cs.maxWidth,
- inline: ['margin', 'padding', 'max-width'].map((k) => document.body.style.getPropertyValue(k)),
+ inline: ["margin", "padding", "max-width"].map((k) =>
+ document.body.style.getPropertyValue(k),
+ ),
};
});
- const ZERO = ['0px', '0px', '0px', '0px'];
+ const ZERO = ["0px", "0px", "0px", "0px"];
- test('checkbox is unchecked by default', async ({ page }) => {
+ test("checkbox is unchecked by default", async ({ page }) => {
await expect(fullscreen(page)).not.toBeChecked();
});
- test('checking removes body margin & padding and lifts max-width', async ({ page }) => {
+ test("checking removes body margin & padding and lifts max-width", async ({
+ page,
+ }) => {
await fullscreen(page).check();
const m = await bodyMetrics(page);
expect(m.margin).toEqual(ZERO);
expect(m.padding).toEqual(ZERO);
- expect(m.maxWidth).toBe('none');
- expect(m.inline).toEqual(['0px', '0px', 'none']);
+ expect(m.maxWidth).toBe("none");
+ expect(m.inline).toEqual(["0px", "0px", "none"]);
- const box = (await page.locator('body').boundingBox())!;
+ const box = (await page.locator("body").boundingBox())!;
expect(box.x).toBe(0);
- const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
+ const clientWidth = await page.evaluate(
+ () => document.documentElement.clientWidth,
+ );
expect(Math.round(box.width)).toBe(clientWidth);
});
- test('unchecking restores the original body styles exactly', async ({ page }) => {
+ test("unchecking restores the original body styles exactly", async ({
+ page,
+ }) => {
const before = await bodyMetrics(page);
await fullscreen(page).check();
await fullscreen(page).uncheck();
expect(await bodyMetrics(page)).toEqual(before);
});
- test('editors become at least as wide in fullscreen', async ({ page }) => {
- const wBefore = (await page.locator('#regex-editor').boundingBox())!.width;
+ test("editors become at least as wide in fullscreen", async ({ page }) => {
+ const wBefore = (await page.locator("#regex-editor").boundingBox())!.width;
await fullscreen(page).check();
- const wAfter = (await page.locator('#regex-editor').boundingBox())!.width;
+ const wAfter = (await page.locator("#regex-editor").boundingBox())!.width;
expect(wAfter).toBeGreaterThanOrEqual(wBefore);
});
- test('fullscreen survives tab switching, add/rename and typing', async ({ page }) => {
+ test("fullscreen survives tab switching, add/rename and typing", async ({
+ page,
+ }) => {
await fullscreen(page).check();
await switchTo(page, 1);
await addTabBtn(page).click();
- await setDoc(page, topEditor(page), ['/x/']);
+ await setDoc(page, topEditor(page), ["/x/"]);
await tabLabel(page, 0).dblclick();
- await page.locator('#tab-bar input').fill('FS');
- await page.keyboard.press('Enter');
+ await page.locator("#tab-bar input").fill("FS");
+ await page.keyboard.press("Enter");
const m = await bodyMetrics(page);
expect(m.margin).toEqual(ZERO);
@@ -535,7 +640,7 @@ test.describe('fullscreen', () => {
await expect(fullscreen(page)).toBeChecked();
});
- test('toggling repeatedly is stable', async ({ page }) => {
+ test("toggling repeatedly is stable", async ({ page }) => {
const before = await bodyMetrics(page);
for (let i = 0; i < 3; i++) {
await fullscreen(page).check();
@@ -550,21 +655,25 @@ test.describe('fullscreen', () => {
// 6. undo / redo / selection / cursor preserved across tab movement
// (each tab has its own EditorState in the lower editor)
// =====================================================================
-test.describe('per-tab editor state', () => {
+test.describe("per-tab editor state", () => {
const HISTORY_GAP = 700; // CodeMirror groups edits within 500ms into one undo step
- test('cursor position is preserved per tab', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['line one', 'line two', 'line three']);
- await page.keyboard.press('ControlOrMeta+Home');
- await page.keyboard.press('ArrowDown');
- for (let i = 0; i < 4; i++) await page.keyboard.press('ArrowRight');
+ test("cursor position is preserved per tab", async ({ page }) => {
+ await setDoc(page, mainEditor(page), [
+ "line one",
+ "line two",
+ "line three",
+ ]);
+ await page.keyboard.press("ControlOrMeta+Home");
+ await page.keyboard.press("ArrowDown");
+ for (let i = 0; i < 4; i++) await page.keyboard.press("ArrowRight");
const cursor0 = await selection(mainEditor(page));
expect(cursor0.head).toBe(9 + 4);
expect(cursor0.anchor).toBe(cursor0.head);
await switchTo(page, 1);
await focusEditor(mainEditor(page));
- await page.keyboard.press('ControlOrMeta+End');
+ await page.keyboard.press("ControlOrMeta+End");
const len1 = (await docText(mainEditor(page))).length;
expect((await selection(mainEditor(page))).head).toBe(len1);
@@ -573,23 +682,25 @@ test.describe('per-tab editor state', () => {
// typing at the restored position lands exactly there
await focusEditor(mainEditor(page));
- await page.keyboard.type('X');
- expect(await docText(mainEditor(page))).toBe('line one\nlineX two\nline three');
+ await page.keyboard.type("X");
+ expect(await docText(mainEditor(page))).toBe(
+ "line one\nlineX two\nline three",
+ );
await switchTo(page, 1);
expect((await selection(mainEditor(page))).head).toBe(len1);
});
- test('selection range is preserved per tab', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['hello brave new world']);
- await page.keyboard.press('ControlOrMeta+Home');
- for (let i = 0; i < 6; i++) await page.keyboard.press('ArrowRight');
- for (let i = 0; i < 5; i++) await page.keyboard.press('Shift+ArrowRight'); // "brave"
+ test("selection range is preserved per tab", async ({ page }) => {
+ await setDoc(page, mainEditor(page), ["hello brave new world"]);
+ await page.keyboard.press("ControlOrMeta+Home");
+ for (let i = 0; i < 6; i++) await page.keyboard.press("ArrowRight");
+ for (let i = 0; i < 5; i++) await page.keyboard.press("Shift+ArrowRight"); // "brave"
expect(await selection(mainEditor(page))).toEqual({ anchor: 6, head: 11 });
await switchTo(page, 1);
await focusEditor(mainEditor(page));
- await page.keyboard.press('ControlOrMeta+a');
+ await page.keyboard.press("ControlOrMeta+a");
const all1 = await selection(mainEditor(page));
expect(all1.anchor).toBe(0);
expect(all1.head).toBeGreaterThan(0);
@@ -602,14 +713,16 @@ test.describe('per-tab editor state', () => {
// selection is real: typing replaces exactly the preserved range
await switchTo(page, 0);
await focusEditor(mainEditor(page));
- await page.keyboard.type('BRAVE');
- expect(await docText(mainEditor(page))).toBe('hello BRAVE new world');
+ await page.keyboard.type("BRAVE");
+ expect(await docText(mainEditor(page))).toBe("hello BRAVE new world");
});
- test('selection direction (anchor vs head) is preserved', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['abcdefghij']);
- await page.keyboard.press('ControlOrMeta+End');
- for (let i = 0; i < 4; i++) await page.keyboard.press('Shift+ArrowLeft'); // backwards
+ test("selection direction (anchor vs head) is preserved", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["abcdefghij"]);
+ await page.keyboard.press("ControlOrMeta+End");
+ for (let i = 0; i < 4; i++) await page.keyboard.press("Shift+ArrowLeft"); // backwards
const sel = await selection(mainEditor(page));
expect(sel.head).toBeLessThan(sel.anchor);
@@ -618,76 +731,80 @@ test.describe('per-tab editor state', () => {
expect(await selection(mainEditor(page))).toEqual(sel);
});
- test('undo history is preserved per tab and does not leak between tabs', async ({ page }) => {
+ test("undo history is preserved per tab and does not leak between tabs", async ({
+ page,
+ }) => {
// tab 0: three separate undo steps
- await setDoc(page, mainEditor(page), ['base']);
+ await setDoc(page, mainEditor(page), ["base"]);
await page.waitForTimeout(HISTORY_GAP);
- await page.keyboard.type(' one');
+ await page.keyboard.type(" one");
await page.waitForTimeout(HISTORY_GAP);
- await page.keyboard.type(' two');
+ await page.keyboard.type(" two");
await page.waitForTimeout(HISTORY_GAP);
- await page.keyboard.type(' three');
- expect(await docText(mainEditor(page))).toBe('base one two three');
+ await page.keyboard.type(" three");
+ expect(await docText(mainEditor(page))).toBe("base one two three");
// tab 1: its own edit
await switchTo(page, 1);
const tab1Original = await docText(mainEditor(page));
await focusEditor(mainEditor(page));
- await page.keyboard.press('ControlOrMeta+End');
- await page.keyboard.type(' T1');
+ await page.keyboard.press("ControlOrMeta+End");
+ await page.keyboard.type(" T1");
await page.waitForTimeout(HISTORY_GAP);
- expect(await docText(mainEditor(page))).toBe(tab1Original + ' T1');
+ expect(await docText(mainEditor(page))).toBe(tab1Original + " T1");
// back to tab 0: undo works step by step, with no tab-1 text involved
await switchTo(page, 0);
await focusEditor(mainEditor(page));
- await page.keyboard.press('ControlOrMeta+z');
- expect(await docText(mainEditor(page))).toBe('base one two');
- await page.keyboard.press('ControlOrMeta+z');
- expect(await docText(mainEditor(page))).toBe('base one');
+ await page.keyboard.press("ControlOrMeta+z");
+ expect(await docText(mainEditor(page))).toBe("base one two");
+ await page.keyboard.press("ControlOrMeta+z");
+ expect(await docText(mainEditor(page))).toBe("base one");
// tab 1: its undo only removes its own edit
await switchTo(page, 1);
await focusEditor(mainEditor(page));
- await page.keyboard.press('ControlOrMeta+z');
+ await page.keyboard.press("ControlOrMeta+z");
expect(await docText(mainEditor(page))).toBe(tab1Original);
- await page.keyboard.press('ControlOrMeta+z'); // nothing more to undo
+ await page.keyboard.press("ControlOrMeta+z"); // nothing more to undo
expect(await docText(mainEditor(page))).toBe(tab1Original);
// tab 0 untouched by tab 1's undo
await switchTo(page, 0);
- expect(await docText(mainEditor(page))).toBe('base one');
+ expect(await docText(mainEditor(page))).toBe("base one");
});
- test('redo history is preserved across tab switches', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['alpha']);
+ test("redo history is preserved across tab switches", async ({ page }) => {
+ await setDoc(page, mainEditor(page), ["alpha"]);
await page.waitForTimeout(HISTORY_GAP);
- await page.keyboard.type(' beta');
+ await page.keyboard.type(" beta");
await page.waitForTimeout(HISTORY_GAP);
- await page.keyboard.type(' gamma');
- await page.keyboard.press('ControlOrMeta+z');
- await page.keyboard.press('ControlOrMeta+z');
- expect(await docText(mainEditor(page))).toBe('alpha');
+ await page.keyboard.type(" gamma");
+ await page.keyboard.press("ControlOrMeta+z");
+ await page.keyboard.press("ControlOrMeta+z");
+ expect(await docText(mainEditor(page))).toBe("alpha");
await switchTo(page, 2);
await focusEditor(mainEditor(page));
- await page.keyboard.type('noise');
+ await page.keyboard.type("noise");
await page.waitForTimeout(HISTORY_GAP);
await switchTo(page, 0);
- expect(await docText(mainEditor(page))).toBe('alpha');
+ expect(await docText(mainEditor(page))).toBe("alpha");
await focusEditor(mainEditor(page));
await redo(page);
- expect(await docText(mainEditor(page))).toBe('alpha beta');
+ expect(await docText(mainEditor(page))).toBe("alpha beta");
await redo(page);
- expect(await docText(mainEditor(page))).toBe('alpha beta gamma');
+ expect(await docText(mainEditor(page))).toBe("alpha beta gamma");
});
- test('undo restores selection/cursor consistently after returning to a tab', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['12345']);
+ test("undo restores selection/cursor consistently after returning to a tab", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["12345"]);
await page.waitForTimeout(HISTORY_GAP);
- await page.keyboard.type('678');
- await page.keyboard.press('Home');
+ await page.keyboard.type("678");
+ await page.keyboard.press("Home");
const cursor = await selection(mainEditor(page));
await switchTo(page, 1);
@@ -695,30 +812,32 @@ test.describe('per-tab editor state', () => {
expect(await selection(mainEditor(page))).toEqual(cursor);
await focusEditor(mainEditor(page));
- await page.keyboard.press('ControlOrMeta+z');
- expect(await docText(mainEditor(page))).toBe('12345');
+ await page.keyboard.press("ControlOrMeta+z");
+ expect(await docText(mainEditor(page))).toBe("12345");
});
- test('a newly added tab has a fresh history (undo is a no-op) and cursor at start', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['something']);
+ test("a newly added tab has a fresh history (undo is a no-op) and cursor at start", async ({
+ page,
+ }) => {
+ await setDoc(page, mainEditor(page), ["something"]);
await addTabBtn(page).click();
await focusEditor(mainEditor(page));
expect(await selection(mainEditor(page))).toEqual({ anchor: 0, head: 0 });
- await page.keyboard.press('ControlOrMeta+z');
+ await page.keyboard.press("ControlOrMeta+z");
expect(await docText(mainEditor(page))).toBe(NEW_TAB_TEXT);
});
- test('state survives many rapid tab switches', async ({ page }) => {
- await setDoc(page, mainEditor(page), ['AAAAAAAAAA']);
- await page.keyboard.press('ControlOrMeta+Home');
- for (let i = 0; i < 2; i++) await page.keyboard.press('ArrowRight');
- for (let i = 0; i < 3; i++) await page.keyboard.press('Shift+ArrowRight');
+ test("state survives many rapid tab switches", async ({ page }) => {
+ await setDoc(page, mainEditor(page), ["AAAAAAAAAA"]);
+ await page.keyboard.press("ControlOrMeta+Home");
+ for (let i = 0; i < 2; i++) await page.keyboard.press("ArrowRight");
+ for (let i = 0; i < 3; i++) await page.keyboard.press("Shift+ArrowRight");
const selA = await selection(mainEditor(page));
await switchTo(page, 1);
- await setDoc(page, mainEditor(page), ['BBBBBBBBBB']);
- await page.keyboard.press('ControlOrMeta+Home');
- for (let i = 0; i < 6; i++) await page.keyboard.press('ArrowRight');
+ await setDoc(page, mainEditor(page), ["BBBBBBBBBB"]);
+ await page.keyboard.press("ControlOrMeta+Home");
+ for (let i = 0; i < 6; i++) await page.keyboard.press("ArrowRight");
const selB = await selection(mainEditor(page));
for (let i = 0; i < 5; i++) {
@@ -730,24 +849,28 @@ test.describe('per-tab editor state', () => {
expect(await selection(mainEditor(page))).toEqual(selA);
});
- test('highlights are refreshed per tab when switching (stale regex queries)', async ({ page }) => {
- await setDoc(page, topEditor(page), ['/zqx/']);
- await setDoc(page, mainEditor(page), ['zqx qqq']);
- await expect.poll(() => matchTexts(page, 0)).toEqual(['zqx']);
+ test("highlights are refreshed per tab when switching (stale regex queries)", async ({
+ page,
+ }) => {
+ await setDoc(page, topEditor(page), ["/zqx/"]);
+ await setDoc(page, mainEditor(page), ["zqx qqq"]);
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["zqx"]);
await switchTo(page, 1);
- await expect(page.locator('#cm6-editor .cm-regex-match-0')).toHaveCount(0);
+ await expect(page.locator("#cm6-editor .cm-regex-match-0")).toHaveCount(0);
// change the regex while on tab 1, then return to tab 0: must use the NEW regex
- await setDoc(page, topEditor(page), ['/qqq/']);
+ await setDoc(page, topEditor(page), ["/qqq/"]);
await switchTo(page, 0);
- await expect.poll(() => matchTexts(page, 0)).toEqual(['qqq']);
+ await expect.poll(() => matchTexts(page, 0)).toEqual(["qqq"]);
});
- test('switching tabs does not alter the regex (top) editor', async ({ page }) => {
- await setDoc(page, topEditor(page), ['/keep/', '/me/']);
+ test("switching tabs does not alter the regex (top) editor", async ({
+ page,
+ }) => {
+ await setDoc(page, topEditor(page), ["/keep/", "/me/"]);
await switchTo(page, 1);
await switchTo(page, 2);
- expect(await docText(topEditor(page))).toBe('/keep/\n/me/');
+ expect(await docText(topEditor(page))).toBe("/keep/\n/me/");
});
});