Stage 7 polish: game rework + board zoom + tests (Parts D/E/F/I)
- Board: fixed-viewport transform-scale zoom (animated) with counter-scaled cqw labels, corner letters, bonus-label modes (boardlabels), contrasting grid lines
- Game: Screen shell + game tab-bar (Draw/Skip/Hint/Shuffle) via HoldConfirm popovers; MakeMove 🏁 + compact popup; rack collapses used slots; hint places tiles on board (placementFromHint) + no_hint_available toast; Scores:N replaces Hints; history slide-down (swipe/click, scroll-locked); check-word alphabet/length limit + in-memory cache + 5s throttle
- backend: no_hint_available result code split + test
- vitest: banner rotator + linkify, resultBadge, boardlabels, placementFromHint (29 tests); Playwright smoke updated; prod bundle ~74 KB gzip
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createBannerRotator, defaultBannerConfig, linkify } from './banner';
|
||||
|
||||
describe('linkify', () => {
|
||||
it('escapes html and renders markdown links', () => {
|
||||
expect(linkify('a < b & c')).toBe('a < b & c');
|
||||
expect(linkify('see [docs](https://x.com) now')).toBe(
|
||||
'see <a href="https://x.com" target="_blank" rel="noopener noreferrer">docs</a> now',
|
||||
);
|
||||
});
|
||||
it('drops a non-http(s) link target (keeps the label)', () => {
|
||||
expect(linkify('[x](ftp://evil)')).toBe('x');
|
||||
expect(linkify('[y](javascript:boom)')).toBe('y');
|
||||
});
|
||||
});
|
||||
|
||||
describe('banner rotator', () => {
|
||||
afterEach(() => vi.useRealTimers());
|
||||
|
||||
it('holds a fitting message then advances, and scrolls an overflowing one', () => {
|
||||
vi.useFakeTimers();
|
||||
const cfg = { ...defaultBannerConfig, holdMs: 1000, edgePauseMs: 100, fadeMs: 10, scrollPxPerSec: 50 };
|
||||
const shown: number[] = [];
|
||||
let scrolled = 0;
|
||||
const overflow = [0, 200]; // item 0 fits, item 1 overflows
|
||||
const r = createBannerRotator(
|
||||
[{ md: 'a' }, { md: 'b' }],
|
||||
{
|
||||
overflowPx: (i) => overflow[i],
|
||||
show: (i) => shown.push(i),
|
||||
scrollTo: () => scrolled++,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
|
||||
r.start();
|
||||
expect(shown).toEqual([0]);
|
||||
vi.advanceTimersByTime(cfg.fadeMs); // settle + measure item 0
|
||||
vi.advanceTimersByTime(cfg.holdMs); // advance to item 1
|
||||
expect(shown).toEqual([0, 1]);
|
||||
vi.advanceTimersByTime(cfg.fadeMs); // settle + measure item 1 (overflows)
|
||||
vi.advanceTimersByTime(cfg.edgePauseMs); // edge pause -> scrollTo
|
||||
expect(scrolled).toBe(1);
|
||||
r.stop();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user