6636d7c309
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
A one-time coachmark overlay walks a new player through the lobby and their first game board: a light dimmed layer draws one tail-pointed hint bubble at a time, advancing on a tap anywhere and removing itself for good after the last hint. Two independent series (lobby: settings/stats/new game; game: header/pass-exchange/hints/shuffle/rack), gated by a per-device persisted flag and marked done only after the last hint, so an interrupted run replays from the start. A deep-link into Settings -> Friends still triggers the lobby series on the first trip back to the lobby. Targets carry a data-coach attribute, so one positioning engine anchors the bubble in both portrait and landscape, re-measuring each frame until the geometry settles (route slide, hidden-banner reflow, fonts). The promo banner hides while the overlay is up (app.coachActive); a hidden DebugPanel "Reset visited" control replays the walk-through. Off by default in the mock build so the Playwright smoke is unaffected; ?coach forces it on for the dedicated e2e. Pure geometry (step lists, nextVisibleStep, placeBubble) in lib/coachmark.ts (unit-tested); Coachmark.svelte renders. Docs: FUNCTIONAL(+ru) onboarding story, UI_DESIGN coachmark section.
49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
import { expect, test, type Page } from './fixtures';
|
|
|
|
// The first-run onboarding coachmarks. `?coach` forces the overlay on in the mock build (where it
|
|
// is otherwise off so it cannot eat the smoke's taps) and bypasses the persisted "seen" flag, so
|
|
// the series replay deterministically here. A tap anywhere on the overlay advances; after the last
|
|
// hint the overlay removes itself.
|
|
|
|
// Tap the overlay near a corner (clear of the bubble) to advance to the next hint.
|
|
async function advance(page: Page): Promise<void> {
|
|
await page.locator('[data-coach-overlay]').click({ position: { x: 5, y: 5 } });
|
|
}
|
|
|
|
// Drive both series end to end: guest login -> lobby coachmarks (3) -> open the seeded game ->
|
|
// game coachmarks (5) -> gone. Shared by the portrait and landscape projects/cases.
|
|
async function runOnboarding(page: Page): Promise<void> {
|
|
const overlay = page.locator('[data-coach-overlay]');
|
|
|
|
await page.goto('/?coach=1');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
|
|
// Lobby series: settings -> stats -> new game.
|
|
await expect(overlay).toBeVisible({ timeout: 10000 });
|
|
await advance(page);
|
|
await advance(page);
|
|
await advance(page);
|
|
await expect(overlay).toBeHidden();
|
|
|
|
// The lobby is interactive again: open the seeded active game.
|
|
await page.getByRole('button', { name: /Ann/ }).click();
|
|
await expect(page.locator('[data-cell]').first()).toBeVisible();
|
|
|
|
// Game series: header -> pass/exchange -> hints -> shuffle -> rack.
|
|
await expect(overlay).toBeVisible({ timeout: 10000 });
|
|
for (let i = 0; i < 5; i++) await advance(page);
|
|
await expect(overlay).toBeHidden();
|
|
}
|
|
|
|
test('onboarding walks the lobby then the first game (portrait)', async ({ page }) => {
|
|
await runOnboarding(page);
|
|
});
|
|
|
|
test.describe('landscape', () => {
|
|
test.use({ viewport: { width: 1100, height: 640 } });
|
|
|
|
test('onboarding anchors and advances in the wide layout', async ({ page }) => {
|
|
await runOnboarding(page);
|
|
});
|
|
});
|