Files
Crystal/tests/mart-browser.spec.ts
T
Sébastien QUEROLandClaude Opus 5.5 b13fd20be4
Checks / web (push) Canceled after 0s
Port the introduction's naming and clock screens
A new game now runs NewGame's InitGender and OakSpeech natively before
the bedroom: Boy or Girl, InitClock's hour and minute steppers with
their YES/NO confirmations, Oak's speech with Wooper, and NamePlayer's
preset names or naming field (a blank name takes CHRIS or KRIS). Texts,
menus and time strings come from a new INTRO profile table verified by
check:reference; Oak's and the player's pictures decode from
TrainerPicPointers, ChrisPic/KrisPic and TrainerPalettes.

The chosen time is kept as a minute offset from the device clock, which
time of day, the weekday, NPC hours and the daily reset now read. The
intro runs only when nothing is saved; a save made during it starts it
again. Saves gain intro and clock.minuteOffset, migrated by absence. The
shell's name and character fields are gone. Intentional change: the
steppers start at the device time rather than 10:00.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:53:52 +02:00

81 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect, type Page } from "@playwright/test";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
const romPath = resolve(process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc");
async function start(page: Page) {
// Replace only this test's runtime module response, the same way
// battle-browser.spec.ts substitutes an encounter fixture: no fixture
// import or map override exists in the application or production build.
await page.route("**/src/game/runtime.ts", async (route) => {
const response = await route.fetch();
const 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 { martFixtureWorld } = await import('/tests/mart-fixture.ts');
const world = martFixtureWorld(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();
}
async function exportState(page: Page) {
await page.locator("#tab-saves").click();
const download = page.waitForEvent("download");
await page.locator("#backup").click();
const path = (await (await download).path())!;
const record = JSON.parse(readFileSync(path, "utf8"));
await page.locator("#tab-game").click();
return JSON.parse(Buffer.from(record.state, "base64").toString("utf8"));
}
test.beforeEach(() => test.skip(!existsSync(romPath), "Requires the local user ROM; only map-enabling is a test fixture."));
test("buys a Potion from the Cherrygrove clerk with the keyboard", async ({ page }) => {
await start(page);
// The fixture places the player at the counter, already facing the clerk.
await page.keyboard.press("KeyX");
await expect(page.locator("#mart")).toBeVisible();
await expect(page.locator("#mart-menu")).toContainText("BUY");
await page.keyboard.press("KeyX"); // BUY is already highlighted
await expect(page.locator("#mart-menu")).toContainText("POTION");
await page.keyboard.press("KeyX"); // the Potion is first and already highlighted
await expect(page.locator(".mart-quantity output")).toHaveText(/×1/);
await page.keyboard.press("ArrowUp"); // quantity 2, exercising the stepper
await expect(page.locator(".mart-quantity output")).toHaveText(/×2/);
await page.keyboard.press("KeyX"); // confirm the quantity
await expect(page.locator("#dialogue-text")).toContainText("will be");
await expect(page.locator("#dialogue-choice")).toContainText("YES");
await page.keyboard.press("KeyX"); // YES is already highlighted
await expect(page.locator("#dialogue-text")).toContainText("Here you are");
await page.keyboard.press("KeyX"); // continue
// Back at the item list (BuyMenu loops for another purchase); leave.
await expect(page.locator("#mart-menu")).toContainText("POTION");
await page.keyboard.press("KeyZ"); // back out of the list
await page.keyboard.press("KeyZ"); // quit the BUY/SELL/QUIT menu
await expect(page.locator("#dialogue-text")).toContainText("Please come again");
await page.keyboard.press("KeyX");
await expect(page.locator("#mart")).toBeHidden();
await expect(page.locator("#game-notice")).toHaveText("");
const state = await exportState(page);
expect(state.money).toBe(3000 - 300 * 2);
expect(state.bag.items).toEqual([{ item: 18, quantity: 2 }]);
});