Add text size and keyboard settings (#38)
Checks / web (push) Failing after 18s
Private ROM checks / private-rom (push) Successful in 14m40s

Settings sets the text size (100, 125 or 150 %) and the key of each
button, kept in this browser's storage and validated on load. Every font
size scales with --text-scale, and the dialogue box scrolls instead of
growing past the screen. The game screen's label, the Play message and
Help's control list name the current keys.

Screen reader announcements for every native screen and touch control
remapping remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
samyandClaude Opus 5.5 committed 2026-10-03 20:42:59 +02:00
1 parent 29248172bd
commit f289615bd2
10 files changed
+588 -41

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
import { test, expect, type Page } from "@playwright/test";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
const romPath = resolve(
process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc",
);
test.beforeEach(() => test.skip(!existsSync(romPath), "Requires the local user ROM."));
async function start(page: Page) {
await page.route("**/src/game/runtime.ts", async (route) => {
const response = await route.fetch(),
source = await response.text();
const needle = "const world = new GameWorld(assets);";
expect(source).toContain(needle);
// Fixture worlds stand in for a saved session, so no introduction runs.
expect(source).toContain("this.world?.beginIntro();");
await route.fulfill({
response,
body: source.replace("this.world?.beginIntro();", "").replace(
needle,
`const { pokedexFixtureWorld } = await import('/tests/pokedex-fixture.ts'); const world = pokedexFixtureWorld(assets);`,
),
});
});
await page.clock.install({ time: new Date("2026-09-22T12:00:00") });
await page.goto("/");
await page.locator("#rom-file").setInputFiles(romPath);
await expect(page.locator("#status")).toContainText("verified");
await page.clock.pauseAt(new Date("2026-09-22T12:05:00"));
await page.locator("#play").click();
}
test("settings rebind a key, scale the text and survive a reload", async ({ page }) => {
await start(page);
const dialog = page.locator("#settings-dialog");
await page.locator("#settings").click();
await expect(dialog).toBeVisible();
const startRow = dialog.locator("#key-table tr", { hasText: "START" });
await expect(startRow).toContainText("Enter");
// Escape cancels a key change without closing the dialog.
await startRow.getByRole("button", { name: /Change the key for START/ }).click();
await expect(startRow).toContainText("Press a key");
await page.keyboard.press("Escape");
await expect(dialog).toBeVisible();
await expect(page.locator("#settings-status")).toHaveText("Key change cancelled.");
// Space is reserved; Q becomes START.
await startRow.getByRole("button", { name: /Change the key for START/ }).click();
await page.keyboard.press("Space");
await expect(page.locator("#settings-status")).toContainText("keeps its usual role");
await startRow.getByRole("button", { name: /Change the key for START/ }).click();
await page.keyboard.press("KeyQ");
await expect(startRow).toContainText("Q");
await expect(page.locator("#settings-status")).toHaveText("START is now Q.");
await dialog.getByLabel("Large", { exact: true }).check();
await expect(page.locator("html")).toHaveAttribute("style", /--text-scale: 1.25/);
await page.locator("#close-settings").click();
await expect(page.locator("#game-screen")).toHaveAttribute("aria-label", /Q is START/);
// Enter no longer opens the start menu; Q does.
await page.locator("#play").click();
await page.locator("#game-screen").focus();
await page.keyboard.press("Enter");
await page.clock.runFor(200);
await expect(page.locator("#dialogue-choice")).toBeHidden();
await page.keyboard.press("KeyQ");
await expect(page.locator("#dialogue-choice").getByRole("option", { name: /^POKéDEX/ })).toBeVisible();
// The choices stay after a reload.
await page.reload();
await expect(page.locator("html")).toHaveAttribute("style", /--text-scale: 1.25/);
await page.locator("#settings").click();
await expect(page.locator("#key-table tr", { hasText: "START" })).toContainText("Q");
await page.locator("#reset-keys").click();
await expect(page.locator("#key-table tr", { hasText: "START" })).toContainText("Enter");
});
test("help lists the current keys", async ({ page }) => {
await start(page);
await page.locator("#help").click();
const keys = page.locator("#help-keys");
await expect(keys).toContainText("Interact / Continue");
await expect(keys).toContainText("X (A)");
await expect(keys).toContainText("Enter (START)");
});