Checks / web (push) Canceled after 0s
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>
81 lines
3.7 KiB
TypeScript
81 lines
3.7 KiB
TypeScript
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 }]);
|
||
});
|