AT Mot — a bilingual (EN/FR) daily word game native to the AT Protocol.
1// @vitest-environment happy-dom
2import { beforeEach, describe, expect, it } from 'vitest';
3import { renderPlayView } from '../src/ui/playView.js';
4import type { Ctx } from '../src/ui/context.js';
5
6function makeCtx(session: Ctx['session'] = null): Ctx {
7 return {
8 lang: 'en',
9 session,
10 handle: null,
11 pendingRecord: null,
12 setLang() {},
13 navigate() {},
14 rerender() {},
15 async signInWith() {},
16 async signOut() {},
17 };
18}
19
20/** Index of the first top-level child of <main> matching `selector`. */
21function childIndex(main: Element, selector: string): number {
22 const children = [...main.children];
23 return children.findIndex((c) => c.matches(selector) || c.querySelector(selector) !== null);
24}
25
26describe('renderPlayView layout order', () => {
27 beforeEach(() => {
28 localStorage.clear();
29 });
30
31 // Regression for issue #2: the sign-in prompt used to sit between the board and
32 // the keyboard, pushing the keys off-screen on mobile. Tiles → keyboard → prompt.
33 it('renders the keyboard before the sign-in prompt while signed out', () => {
34 const root = document.createElement('div');
35 renderPlayView(makeCtx(null), root);
36
37 const main = root.querySelector('main');
38 expect(main).not.toBeNull();
39
40 const boardIdx = childIndex(main!, '.board');
41 const keyboardIdx = childIndex(main!, '.keyboard');
42 const signInIdx = childIndex(main!, '.signin');
43
44 expect(boardIdx).toBeGreaterThanOrEqual(0);
45 expect(keyboardIdx).toBeGreaterThan(boardIdx);
46 expect(signInIdx).toBeGreaterThan(keyboardIdx);
47 });
48
49 // The header sign-in shortcut jumps to the inline bar so mobile players who
50 // miss the prompt below the keyboard can still reach it.
51 it('focuses the inline sign-in input when the header Sign in button is clicked', () => {
52 const root = document.createElement('div');
53 document.body.replaceChildren(root);
54 renderPlayView(makeCtx(null), root);
55
56 const headerSignIn = [...root.querySelectorAll('header button')].find(
57 (b) => b.textContent === 'Sign in',
58 ) as HTMLButtonElement | undefined;
59 expect(headerSignIn).toBeDefined();
60
61 headerSignIn!.click();
62 expect(document.activeElement?.id).toBe('atmot-handle');
63 });
64});