{ "summary": { "unit": { "passed": 13, "failed": 0 }, "api": { "passed": 2, "failed": 0 }, "socket": { "passed": 31, "failed": 0 }, "e2e": { "passed": 3, "failed": 0 } }, "unit_tests": [ { "name": "normalize: lowercases and trims", "status": "pass" }, { "name": "maskWord: blanks letters but preserves spaces", "status": "pass" }, { "name": "maskWord: with all indices revealed produces lowercased word", "status": "pass" }, { "name": "maskWord: partial reveal mixes letters and underscores", "status": "pass" }, { "name": "pickRevealIndex: picks unrevealed non-space index", "status": "pass" }, { "name": "pickRevealIndex: returns null when all letters revealed", "status": "pass" }, { "name": "revealHint flow: applying picked index reveals exactly N more letters", "status": "pass" }, { "name": "revealHint idempotence: re-applying same revealed set yields same mask", "status": "pass" }, { "name": "isCloseGuess: edit distance 1-2 returns true", "status": "pass" }, { "name": "genCode: returns a unique 6-char alphanumeric code", "status": "pass" }, { "name": "setRoom + getRoom: returns same instance, case-insensitive lookup", "status": "pass" }, { "name": "makeId: returns a non-empty string id", "status": "pass" }, { "name": "allRooms: exposes the underlying Map", "status": "pass" } ], "api_tests": [ { "endpoint": "GET /api/health", "status": "pass", "code": 200, "notes": "Returns {status:'ok', uptime:N, service:'skribbl-gartic-color', ts:N}; matches PRD contract." }, { "endpoint": "GET /api/room/NOPE12/exists", "status": "pass", "code": 200, "notes": "Returns {exists:false, mode:null, playerCount:0} for non-existent room as required." } ], "socket_tests": { "skribbl": [ { "name": "clients A and B connected", "status": "pass" }, { "name": "A: room:create -> ok, returns code+sessionToken+playerId", "status": "pass" }, { "name": "B: room:join -> ok", "status": "pass" }, { "name": "Both clients receive room:state with both players", "status": "pass" }, { "name": "chat:send broadcasts to both clients", "status": "pass" }, { "name": "game:start ack ok (host)", "status": "pass" }, { "name": "drawer received skribbl:wordChoices (3 choices)", "status": "pass" }, { "name": "skribbl:pickWord ack ok with chosen word", "status": "pass" }, { "name": "Both clients received skribbl:roundStart", "status": "pass" }, { "name": "Non-drawer received skribbl:stroke from drawer", "status": "pass" }, { "name": "Correct guess -> chat:msg kind=correct on both clients", "status": "pass" }, { "name": "room:state shows guesser score > 0 after correct guess (149 pts)", "status": "pass" }, { "name": "skribbl:roundEnd fires with the word", "status": "pass" } ], "gartic": [ { "name": "Both clients connected", "status": "pass" }, { "name": "room:create (mode=gartic) ok", "status": "pass" }, { "name": "B joined", "status": "pass" }, { "name": "game:start ok", "status": "pass" }, { "name": "Both clients got gartic:turn task=prompt", "status": "pass" }, { "name": "Both prompts submitted (acks ok)", "status": "pass" }, { "name": "Both got next turn task=drawing with content from prior prompt", "status": "pass" }, { "name": "Drawing turn includes prior prompt as content", "status": "pass" }, { "name": "Drawings submitted (acks ok)", "status": "pass" }, { "name": "gartic:bookComplete fired with 2 books (2-player chain)", "status": "pass" } ], "color": [ { "name": "Clients A,B connected", "status": "pass" }, { "name": "room:create (mode=color, canvasType=blank) ok", "status": "pass" }, { "name": "B joined", "status": "pass" }, { "name": "game:start ok", "status": "pass" }, { "name": "room:state transitioned to phase=playing with color object", "status": "pass" }, { "name": "B received color:strokeBroadcast with same payload", "status": "pass" }, { "name": "C joined mid-game", "status": "pass" }, { "name": "New joiner C received color:state with existing stroke", "status": "pass" } ] }, "e2e_tests": [ { "flow": "A", "status": "pass", "screenshots": [ "test-results/e2e-skribbl-01-landing.png", "test-results/e2e-skribbl-02-create-form.png", "test-results/e2e-skribbl-03-lobby-alice.png", "test-results/e2e-skribbl-04-lobby-bob.png", "test-results/e2e-skribbl-05-play-alice.png", "test-results/e2e-skribbl-06-play-bob.png" ], "notes": "Landing renders DrawTogether brand. Create CTA navigates to /create. Skribbl mode + nickname submit creates room, lands on /room/{code} with [data-testid=room-code] visible. Bob joins via /join with code. Alice clicks start-game, both Alice and Bob auto-navigate to /play." }, { "flow": "B", "status": "pass", "screenshots": ["test-results/e2e-join-bad-01.png"], "notes": "Submitting code 'ZZZZZZ' from /join keeps user on /join and renders error message ('couldn't join'/'room not found'). No incorrect redirect to a room page." }, { "flow": "C", "status": "pass", "screenshots": [ "test-results/e2e-color-01-create.png", "test-results/e2e-color-02-lobby.png", "test-results/e2e-color-03-play.png" ], "notes": "Color mode room created (single-player allowed by server). Start-game transitions to /play; [data-testid=color-canvas] renders and 16 color palette swatches detected on the page." } ], "issues_found": [ { "severity": "minor", "area": "skribbl", "description": "On the drawer's play screen during the 'choosing a word' phase, the secret-word area still shows underscores (`_ _ _ _ _`) of arbitrary length even though no word is yet picked. The drawer hasn't selected a word, so showing a placeholder mask of unrelated length is confusing. Likely the UI keeps a stale wordMask from an earlier state or shows a default placeholder.", "reproduce": "Create a skribbl room with 2 players, start game, observe the drawer's play screen during the 15-second word-choice window — you'll see 5 underscores under 'YOUR SECRET WORD' before any word is chosen." }, { "severity": "minor", "area": "color", "description": "On the color play screen at viewport 1280x800, the canvas occupies the full viewport so the color palette + tool buttons (brush/eraser/bucket) sit below the fold; the user has to scroll to access them. This is not a feature break (selectors exist in DOM and respond) but at common laptop viewports the toolbar is not immediately visible.", "reproduce": "Open a color room as host, click Start Game, observe at 1280x800: header + canvas fill the screen, palette/tools require scrolling. See screenshot test-results/e2e-color-03-play.png." }, { "severity": "minor", "area": "gartic", "description": "For 2-player gartic, the chain only goes prompt -> drawing -> done (totalTurns = players.length = 2). There's no 'guess' turn ever produced because turnIndex 1 is drawing and the loop ends. This is consistent with the algorithm (totalTurns = players.length) but means with 2 players you never see a guess phase. PRD/test spec mentioned a guess turn — verify if 3+ players is required for the full prompt->draw->guess cycle.", "reproduce": "Run test/socket-gartic.js — totalTurns=2, after drawing submission, gartic:bookComplete fires immediately with no guess turn." } ], "recommendations": [ "Consider hiding/blanking the word-mask placeholder on the drawer's play screen during the 'choosing' phase so users don't see meaningless underscores before picking a word.", "Color play screen: anchor the palette/toolbar to the bottom edge of the viewport (sticky/fixed) or shrink the canvas height so the tools are visible without scrolling on 1280x800 and smaller laptop viewports — this will likely come up again in Phase 7 (UI/UX) and Phase 8 (responsive).", "Gartic: for 2-player rooms the book never reaches a guess phase. Either require >=3 players for gartic mode, or extend totalTurns to players.length+1 to ensure at least one guess turn even at 2 players. Document expected behaviour either way." ] }