Skip to content

Commit d43e24c

Browse files
authored
Merge branch 'main' into chore/update-eslint-dependencies
2 parents d17df76 + 60aadf3 commit d43e24c

5 files changed

Lines changed: 372 additions & 35 deletions

File tree

e2e-tests/options.test.ts

Lines changed: 128 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,132 @@ test("should switch language and show options for each", async ({ page }) => {
1616

1717
const languageSelect = page.getByRole("combobox", { name: "Language" });
1818

19-
await languageSelect.click();
20-
await page.getByRole("option", { name: "JSON" }).click();
21-
await expect(
22-
page.getByRole("switch", { name: "Allow Trailing Commas" }),
23-
).toBeVisible();
24-
25-
await languageSelect.click();
26-
await page.getByRole("option", { name: "Markdown" }).click();
27-
await expect(
28-
page.getByRole("combobox", { name: "Front Matter" }),
29-
).toBeVisible();
30-
31-
await languageSelect.click();
32-
await page.getByRole("option", { name: "CSS" }).click();
33-
await expect(
34-
page.getByRole("switch", { name: "Tolerant Parsing" }),
35-
).toBeVisible();
36-
37-
await languageSelect.click();
38-
await page.getByRole("option", { name: "HTML" }).click();
39-
await expect(
40-
page.getByRole("combobox", { name: "Template Engine Syntax" }),
41-
).toBeVisible();
19+
// JavaScript
20+
await test.step("should show JavaScript options when JavaScript is selected", async () => {
21+
await languageSelect.click();
22+
await page
23+
.getByRole("option", { exact: true, name: "JavaScript" })
24+
.click();
25+
26+
// `Language` Combobox
27+
await expect(
28+
page.getByRole("combobox", { exact: true, name: "Language" }),
29+
).toHaveText("JavaScript");
30+
31+
// `Parser` Combobox
32+
await expect(
33+
page.getByRole("combobox", { exact: true, name: "Parser" }),
34+
).toHaveText("Espree");
35+
36+
// `Source Type` Combobox
37+
await expect(
38+
page.getByRole("combobox", { exact: true, name: "Source Type" }),
39+
).toHaveText("Module");
40+
41+
// `ECMAScript Version` Combobox
42+
await expect(
43+
page.getByRole("combobox", {
44+
exact: true,
45+
name: "ECMAScript Version",
46+
}),
47+
).toHaveText("Latest");
48+
49+
// `JSX` Switch
50+
await expect(
51+
page.getByRole("switch", {
52+
exact: true,
53+
name: "JSX",
54+
}),
55+
).toBeVisible();
56+
});
57+
58+
// JSON
59+
await test.step("should show JSON options when JSON is selected", async () => {
60+
await languageSelect.click();
61+
await page.getByRole("option", { exact: true, name: "JSON" }).click();
62+
63+
// `Language` Combobox
64+
await expect(
65+
page.getByRole("combobox", { exact: true, name: "Language" }),
66+
).toHaveText("JSON");
67+
68+
// `Mode` Combobox
69+
await expect(
70+
page.getByRole("combobox", { exact: true, name: "Mode" }),
71+
).toHaveText("jsonc");
72+
73+
// `Allow Trailing Commas` Switch
74+
await expect(
75+
page.getByRole("switch", {
76+
exact: true,
77+
name: "Allow Trailing Commas",
78+
}),
79+
).toBeVisible();
80+
});
81+
82+
// Markdown
83+
await test.step("should show Markdown options when Markdown is selected", async () => {
84+
await languageSelect.click();
85+
await page
86+
.getByRole("option", { exact: true, name: "Markdown" })
87+
.click();
88+
89+
// `Language` Combobox
90+
await expect(
91+
page.getByRole("combobox", { exact: true, name: "Language" }),
92+
).toHaveText("Markdown");
93+
94+
// `Mode` Combobox
95+
await expect(
96+
page.getByRole("combobox", { exact: true, name: "Mode" }),
97+
).toHaveText("CommonMark");
98+
99+
// `Front Matter` Combobox
100+
await expect(
101+
page.getByRole("combobox", { exact: true, name: "Front Matter" }),
102+
).toBeVisible();
103+
});
104+
105+
// CSS
106+
await test.step("should show CSS options when CSS is selected", async () => {
107+
await languageSelect.click();
108+
await page.getByRole("option", { exact: true, name: "CSS" }).click();
109+
110+
// `Language` Combobox
111+
await expect(
112+
page.getByRole("combobox", { exact: true, name: "Language" }),
113+
).toHaveText("CSS");
114+
115+
// `Tolerant Parsing` Switch
116+
await expect(
117+
page.getByRole("switch", { exact: true, name: "Tolerant Parsing" }),
118+
).toBeVisible();
119+
});
120+
121+
// HTML
122+
await test.step("should show HTML options when HTML is selected", async () => {
123+
await languageSelect.click();
124+
await page.getByRole("option", { exact: true, name: "HTML" }).click();
125+
126+
// `Language` Combobox
127+
await expect(
128+
page.getByRole("combobox", { exact: true, name: "Language" }),
129+
).toHaveText("HTML");
130+
131+
// `Template Engine Syntax` Combobox
132+
await expect(
133+
page.getByRole("combobox", {
134+
exact: true,
135+
name: "Template Engine Syntax",
136+
}),
137+
).toBeVisible();
138+
139+
// `Front Matter` Switch
140+
await expect(
141+
page.getByRole("switch", {
142+
exact: true,
143+
name: "Front Matter",
144+
}),
145+
).toBeVisible();
146+
});
42147
});

e2e-tests/persistence.test.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { expect, test, type Page } from "@playwright/test";
2+
3+
const storageKey = "eslint-explorer";
4+
5+
async function getPersistedJavaScriptCode(page: Page): Promise<string> {
6+
return page.evaluate(key => {
7+
const storedValue = window.localStorage.getItem(key);
8+
9+
if (!storedValue) {
10+
return "";
11+
}
12+
13+
return JSON.parse(storedValue).state.code.javascript;
14+
}, storageKey);
15+
}
16+
17+
async function getPersistedExplorerState(page: Page): Promise<string> {
18+
const persistedValue = await page.evaluate(
19+
key => window.localStorage.getItem(key),
20+
storageKey,
21+
);
22+
23+
if (!persistedValue) {
24+
throw new Error("Expected explorer state to be persisted");
25+
}
26+
27+
return persistedValue;
28+
}
29+
30+
async function getStoredHashValue(page: Page): Promise<string> {
31+
return page.evaluate(key => {
32+
return (
33+
new URLSearchParams(window.location.hash.slice(1)).get(key) ?? ""
34+
);
35+
}, storageKey);
36+
}
37+
38+
async function replaceEditorValue(page: Page, value: string) {
39+
const codeEditor = page
40+
.getByRole("region", { name: "Code Editor Panel" })
41+
.getByRole("textbox")
42+
.nth(1);
43+
44+
await codeEditor.click();
45+
await codeEditor.press("ControlOrMeta+KeyA");
46+
await codeEditor.press("Backspace");
47+
await codeEditor.pressSequentially(value);
48+
}
49+
50+
test("should persist unicode code safely in the URL hash", async ({ page }) => {
51+
await page.addInitScript(key => {
52+
window.localStorage.removeItem(key);
53+
}, storageKey);
54+
await page.goto("/");
55+
56+
const unicodeCode = 'const \u03C0 = "\u{1F600}";';
57+
58+
await replaceEditorValue(page, unicodeCode);
59+
60+
await expect.poll(() => getPersistedJavaScriptCode(page)).toBe(unicodeCode);
61+
await expect.poll(() => getStoredHashValue(page)).toContain("v2.");
62+
63+
const persistedHash = await page.evaluate(() => window.location.hash);
64+
65+
await page.evaluate(key => {
66+
window.localStorage.removeItem(key);
67+
}, storageKey);
68+
await page.goto(`/${persistedHash}`);
69+
70+
await expect(page.locator(".cm-content")).toContainText(unicodeCode);
71+
});
72+
73+
test("should still load state from legacy hash links", async ({ page }) => {
74+
await page.addInitScript(key => {
75+
window.localStorage.removeItem(key);
76+
}, storageKey);
77+
await page.goto("/");
78+
79+
const legacyCode = "console.log('legacy hash');";
80+
81+
await replaceEditorValue(page, legacyCode);
82+
83+
await expect.poll(() => getPersistedJavaScriptCode(page)).toBe(legacyCode);
84+
85+
const persistedValue = await getPersistedExplorerState(page);
86+
87+
const legacyHash = await page.evaluate(
88+
([key, value]) => {
89+
const searchParams = new URLSearchParams();
90+
searchParams.set(key, btoa(JSON.stringify(value)));
91+
return searchParams.toString();
92+
},
93+
[storageKey, persistedValue],
94+
);
95+
96+
await page.evaluate(key => {
97+
window.localStorage.removeItem(key);
98+
}, storageKey);
99+
await page.goto(`/#${legacyHash}`);
100+
101+
await expect(page.locator(".cm-content")).toContainText(legacyCode);
102+
});
103+
104+
test("should fall back to localStorage when a v2 hash is malformed", async ({
105+
page,
106+
}) => {
107+
await page.addInitScript(key => {
108+
window.localStorage.removeItem(key);
109+
}, storageKey);
110+
await page.goto("/");
111+
112+
const fallbackCode = "console.log('localStorage fallback');";
113+
114+
await replaceEditorValue(page, fallbackCode);
115+
116+
await expect
117+
.poll(() => getPersistedJavaScriptCode(page))
118+
.toBe(fallbackCode);
119+
120+
const persistedValue = await getPersistedExplorerState(page);
121+
122+
const malformedHash = await page.evaluate(key => {
123+
const bytes = new TextEncoder().encode("not valid persisted state");
124+
let binary = "";
125+
126+
for (const byte of bytes) {
127+
binary += String.fromCharCode(byte);
128+
}
129+
130+
const base64Url = btoa(binary)
131+
.replace(/\+/g, "-")
132+
.replace(/\//g, "_")
133+
.replace(/=+$/, "");
134+
135+
const searchParams = new URLSearchParams();
136+
searchParams.set(key, `v2.${base64Url}`);
137+
return searchParams.toString();
138+
}, storageKey);
139+
140+
await page.evaluate(
141+
([key, value]) => {
142+
window.localStorage.setItem(key, value);
143+
},
144+
[storageKey, persistedValue],
145+
);
146+
await page.goto(`/#${malformedHash}`);
147+
148+
await expect(page.locator(".cm-content")).toContainText(fallbackCode);
149+
});

package-lock.json

Lines changed: 13 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@codemirror/lang-json": "^6.0.2",
3838
"@codemirror/lang-markdown": "^6.5.0",
3939
"@codemirror/language": "^6.12.3",
40-
"@codemirror/view": "^6.40.0",
40+
"@codemirror/view": "^6.41.0",
4141
"@eslint/css": "^1.1.0",
4242
"@eslint/js": "^9.39.4",
4343
"@eslint/json": "^1.2.0",
@@ -95,7 +95,7 @@
9595
"react-resizable-panels": "^3.0.6",
9696
"tailwindcss": "^3.4.19",
9797
"typescript": "^5.9.3",
98-
"typescript-eslint": "^8.57.2",
98+
"typescript-eslint": "^8.58.0",
9999
"vite": "^8.0.3",
100100
"yorkie": "^2.0.0"
101101
},

0 commit comments

Comments
 (0)