Stage 7: regression tests for the polished UI (logic + behaviour)
Lock the polish branch's behaviour so a future UI edit surfaces as a failing
assertion to re-agree or fix.
Unit (vitest, node env):
- placement: recallIndex, cellOccupied/isBlankSlot, non-linear direction, the
single-tile submit default, and placementFromHint blank-fallback / rack-exhausted.
- banner: the marquee scroll-cycle repeat-then-advance, stop(), root-relative and
multiple links.
- client.GatewayError. Extract the check-word constraints out of Game.svelte into a
pure lib/checkword.ts (sanitize + canCheck) and cover them.
E2E (playwright mock, Chromium + WebKit):
- commit via the 🏁 control, history slide-down + close, the exchange dialog,
check-word input sanitising + verdict, resign-to-finished, and the Settings
board-label mode changing the on-board labels.
This commit is contained in:
@@ -12,6 +12,12 @@ describe('linkify', () => {
|
||||
expect(linkify('[x](ftp://evil)')).toBe('x');
|
||||
expect(linkify('[y](javascript:boom)')).toBe('y');
|
||||
});
|
||||
it('keeps root-relative links and renders several in one string', () => {
|
||||
expect(linkify('go [home](/lobby) or [docs](https://x.com)')).toBe(
|
||||
'go <a href="/lobby" target="_blank" rel="noopener noreferrer">home</a> or ' +
|
||||
'<a href="https://x.com" target="_blank" rel="noopener noreferrer">docs</a>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('banner rotator', () => {
|
||||
@@ -43,4 +49,42 @@ describe('banner rotator', () => {
|
||||
expect(scrolled).toBe(1);
|
||||
r.stop();
|
||||
});
|
||||
|
||||
it('repeats the scroll cycle while under holdMs, then advances', () => {
|
||||
vi.useFakeTimers();
|
||||
const cfg = { holdMs: 2500, edgePauseMs: 100, fadeMs: 10, scrollPxPerSec: 100 };
|
||||
const shown: number[] = [];
|
||||
let scrolls = 0;
|
||||
const r = createBannerRotator(
|
||||
[{ md: 'long' }, { md: 'short' }],
|
||||
{ overflowPx: (i) => (i === 0 ? 100 : 0), show: (i) => shown.push(i), scrollTo: () => scrolls++ },
|
||||
cfg,
|
||||
);
|
||||
r.start();
|
||||
vi.advanceTimersByTime(110); // fade(10) + edgePause(100) -> first scroll
|
||||
expect(scrolls).toBe(1);
|
||||
expect(shown).toEqual([0]);
|
||||
vi.advanceTimersByTime(1200); // scrollDur(1000) + edgePause + edgePause -> re-show + second scroll
|
||||
expect(scrolls).toBe(2);
|
||||
expect(shown).toEqual([0, 0]); // re-shown to reset scroll, still item 0 (under holdMs)
|
||||
vi.advanceTimersByTime(10_000);
|
||||
expect(shown).toContain(1); // eventually exceeds holdMs and advances to the fitting message
|
||||
r.stop();
|
||||
});
|
||||
|
||||
it('stop() halts further advancement', () => {
|
||||
vi.useFakeTimers();
|
||||
const cfg = { ...defaultBannerConfig, holdMs: 100, fadeMs: 5, edgePauseMs: 5 };
|
||||
const shown: number[] = [];
|
||||
const r = createBannerRotator(
|
||||
[{ md: 'a' }, { md: 'b' }],
|
||||
{ overflowPx: () => 0, show: (i) => shown.push(i), scrollTo: () => {} },
|
||||
cfg,
|
||||
);
|
||||
r.start();
|
||||
vi.advanceTimersByTime(cfg.fadeMs);
|
||||
r.stop();
|
||||
vi.advanceTimersByTime(10_000);
|
||||
expect(shown).toEqual([0]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user