Files
python-tdd/src/static/tests/MySeaSeatsSpec.js

127 lines
5.1 KiB
JavaScript
Raw Normal View History

my-sea spectator Phase B: seat-2C occupancy + visitor token gate + one-shot seated glow + gear BYE — TDD Phase B of the my-sea invite → spectator → voice blueprint. An ACCEPTED invitee can watch the owner's my-sea read-only, deposit a token to occupy seat 2C (opening a 24h voice window for Phase C), and BYE out. Owner's my_sea.html is left structurally intact — the spectator gets a dedicated, simpler my_sea_visit.html; the read-only draw reuses the existing `latest_draw_slots` payload (no picker surgery). - B1: my_sea_visit(owner_id) spectator view — 403 unless an ACCEPTED SeaInvite(owner, request.user); owner bounced to their own my_sea. Context forces owner-only controls off (sea_btn_active=False, read_only=True); renders the table hex (1C owner / 2C visitor) + owner draw read-only. - B2: visitor gate — my_sea_visit_gate reuses my_sea_gate.html w. a spectator branch (titles the OWNER's Sea, INSERT posts to the visitor endpoint, bud-panel suppressed, gear NVM→visit + BYE). Single-step my_sea_visit_insert_token selects+debits the visitor's token (same priority chain) and records token_deposited_at + a 24h voice_until on the SeaInvite → seat 2C present. Center btn flips GATE VIEW → VIEW DRAW. - B3: spectator gear BYE — my_sea_visit_leave sets status=LEFT, left_at, clears voice_until (frees 2C, ends voice), redirects /gameboard/. _my_sea_gear.html gains a `leave_url`-gated BYE below NVM (owner pages pass no leave_url, so unchanged). - B-seat: one-shot "seated" glow per user-spec 2026-05-27 — new shared apps/gameboard/my-sea-seats.js: on first view (localStorage-gated by a per-occupancy data-seat-token) an occupied seat flares --terUser + --ninUser glow ~1.5s then settles to full-opacity --secUser (.fa-ban already swapped to .fa-circle-check). _room.scss adds .seated / .seat-just-seated + the my-sea-seat-flare keyframes (mirrors the room's .active→.role-confirmed handoff). Wired on BOTH the spectator page (load) and the owner page (load + on the FREE DRAW seat-1 transition). MySeaSeatsSpec.js Jasmine spec covers the gating + timed class removal. - B5: MySeaSpectatorFlowTest FT — accept → visit → GATE VIEW → deposit → VIEW DRAW + seat 2C seated. URLs: my-sea/visit/<uuid:owner_id>/ (+ /gate/, /insert, /leave). 470 IT/UT green; spectator FT + full Jasmine suite green. Phase C (WebRTC mesh voice + coturn droplet) next — the 24h voice_until window set here drives it. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:35:00 -04:00
// Jasmine spec for my-sea-seats.js — the one-shot "seated" glow module
// (Phase B of the my-sea invite/voice sprint). Verifies the localStorage-
// gated first-view behaviour + the timed flare-class removal.
describe('my-sea-seats one-shot seated glow', function () {
var seat;
beforeEach(function () {
window.localStorage.clear();
seat = document.createElement('div');
seat.className = 'table-seat seated';
document.body.appendChild(seat);
});
afterEach(function () {
if (seat && seat.parentNode) seat.parentNode.removeChild(seat);
window.localStorage.clear();
});
it('exposes playSeatGlow globally', function () {
expect(typeof window.playSeatGlow).toBe('function');
});
it('adds the seat-just-seated flare class', function () {
window.playSeatGlow(seat);
expect(seat.classList.contains('seat-just-seated')).toBe(true);
});
it('marks a tokened seat seen in localStorage', function () {
seat.setAttribute('data-seat-token', 'visit-42');
window.playSeatGlow(seat);
expect(window.localStorage.getItem('mysea-seat-seen:visit-42')).toBe('1');
});
it('does not replay the flare for an already-seen token', function () {
seat.setAttribute('data-seat-token', 'visit-42');
window.localStorage.setItem('mysea-seat-seen:visit-42', '1');
window.playSeatGlow(seat);
expect(seat.classList.contains('seat-just-seated')).toBe(false);
});
it('always animates a tokenless seat (no persistence)', function () {
window.playSeatGlow(seat);
expect(seat.classList.contains('seat-just-seated')).toBe(true);
});
my-sea voice: voice-btn glow/pulse state machine (sea-precedence, pulse-while-alone, 2x on 2nd party) — TDD Phase 3 of the my-sea voice batch (user-spec 2026-05-29). A --quaUser/--ninUser glow + pulse machine for the burger btn + its voice sub-btn, driven by the voice sub-btn availability (voice_active) + the live mesh state. - voice-mesh.js: VoiceRoom gains a state-change hook — setOnStateChange(cb) + peerCount() + _notify({inCall, peerCount, muted}), fired on join, every peer add/drop, mute toggle, and teardown. No behaviour change without a subscriber (VoiceMeshSpec stays green). - voice-glow.js (new): the glow machine. PRE-JOIN nudge — burger glows when the fan is closed (sea draw-nudge keeps burger precedence; voice reclaims it once the sea glow clears), voice sub-btn glows when the fan opens. LIVE — the glow PULSES on whichever surface shows (voice sub-btn fan-open, burger fan-closed): base 2s cadence while alone, doubled (.voice-pulse--fast) once a 2nd party connects (equalizer stand-in; a true volume-reactive equalizer is a live-only enhancement). Class writes are reconciled (idempotent) so the burger-class MutationObserver doesn't feed back on itself. - _burger.scss: .voice-glow + @keyframes voice-pulse + .voice-pulse(--fast). - loaded on my_sea.html + my_sea_visit.html (after burger-btn.js). - VoiceGlowSpec.js (18 specs) + registered in SpecRunner; MySeaSeatsSpec flare window updated 1.5s → 2s (Phase 2 bump). 428 Jasmine specs green. Live-verify on staging: the actual glow colours/cadence + the equalizer upgrade (item 5) and disconnect states (item 7) land in later phases. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 21:06:02 -04:00
it('removes the flare class after the 2s glow window', function () {
my-sea spectator Phase B: seat-2C occupancy + visitor token gate + one-shot seated glow + gear BYE — TDD Phase B of the my-sea invite → spectator → voice blueprint. An ACCEPTED invitee can watch the owner's my-sea read-only, deposit a token to occupy seat 2C (opening a 24h voice window for Phase C), and BYE out. Owner's my_sea.html is left structurally intact — the spectator gets a dedicated, simpler my_sea_visit.html; the read-only draw reuses the existing `latest_draw_slots` payload (no picker surgery). - B1: my_sea_visit(owner_id) spectator view — 403 unless an ACCEPTED SeaInvite(owner, request.user); owner bounced to their own my_sea. Context forces owner-only controls off (sea_btn_active=False, read_only=True); renders the table hex (1C owner / 2C visitor) + owner draw read-only. - B2: visitor gate — my_sea_visit_gate reuses my_sea_gate.html w. a spectator branch (titles the OWNER's Sea, INSERT posts to the visitor endpoint, bud-panel suppressed, gear NVM→visit + BYE). Single-step my_sea_visit_insert_token selects+debits the visitor's token (same priority chain) and records token_deposited_at + a 24h voice_until on the SeaInvite → seat 2C present. Center btn flips GATE VIEW → VIEW DRAW. - B3: spectator gear BYE — my_sea_visit_leave sets status=LEFT, left_at, clears voice_until (frees 2C, ends voice), redirects /gameboard/. _my_sea_gear.html gains a `leave_url`-gated BYE below NVM (owner pages pass no leave_url, so unchanged). - B-seat: one-shot "seated" glow per user-spec 2026-05-27 — new shared apps/gameboard/my-sea-seats.js: on first view (localStorage-gated by a per-occupancy data-seat-token) an occupied seat flares --terUser + --ninUser glow ~1.5s then settles to full-opacity --secUser (.fa-ban already swapped to .fa-circle-check). _room.scss adds .seated / .seat-just-seated + the my-sea-seat-flare keyframes (mirrors the room's .active→.role-confirmed handoff). Wired on BOTH the spectator page (load) and the owner page (load + on the FREE DRAW seat-1 transition). MySeaSeatsSpec.js Jasmine spec covers the gating + timed class removal. - B5: MySeaSpectatorFlowTest FT — accept → visit → GATE VIEW → deposit → VIEW DRAW + seat 2C seated. URLs: my-sea/visit/<uuid:owner_id>/ (+ /gate/, /insert, /leave). 470 IT/UT green; spectator FT + full Jasmine suite green. Phase C (WebRTC mesh voice + coturn droplet) next — the 24h voice_until window set here drives it. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:35:00 -04:00
jasmine.clock().install();
window.playSeatGlow(seat);
expect(seat.classList.contains('seat-just-seated')).toBe(true);
my-sea voice: voice-btn glow/pulse state machine (sea-precedence, pulse-while-alone, 2x on 2nd party) — TDD Phase 3 of the my-sea voice batch (user-spec 2026-05-29). A --quaUser/--ninUser glow + pulse machine for the burger btn + its voice sub-btn, driven by the voice sub-btn availability (voice_active) + the live mesh state. - voice-mesh.js: VoiceRoom gains a state-change hook — setOnStateChange(cb) + peerCount() + _notify({inCall, peerCount, muted}), fired on join, every peer add/drop, mute toggle, and teardown. No behaviour change without a subscriber (VoiceMeshSpec stays green). - voice-glow.js (new): the glow machine. PRE-JOIN nudge — burger glows when the fan is closed (sea draw-nudge keeps burger precedence; voice reclaims it once the sea glow clears), voice sub-btn glows when the fan opens. LIVE — the glow PULSES on whichever surface shows (voice sub-btn fan-open, burger fan-closed): base 2s cadence while alone, doubled (.voice-pulse--fast) once a 2nd party connects (equalizer stand-in; a true volume-reactive equalizer is a live-only enhancement). Class writes are reconciled (idempotent) so the burger-class MutationObserver doesn't feed back on itself. - _burger.scss: .voice-glow + @keyframes voice-pulse + .voice-pulse(--fast). - loaded on my_sea.html + my_sea_visit.html (after burger-btn.js). - VoiceGlowSpec.js (18 specs) + registered in SpecRunner; MySeaSeatsSpec flare window updated 1.5s → 2s (Phase 2 bump). 428 Jasmine specs green. Live-verify on staging: the actual glow colours/cadence + the equalizer upgrade (item 5) and disconnect states (item 7) land in later phases. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 21:06:02 -04:00
// Still flaring mid-window (bumped 1.5s → 2s, user-spec 2026-05-29).
my-sea spectator Phase B: seat-2C occupancy + visitor token gate + one-shot seated glow + gear BYE — TDD Phase B of the my-sea invite → spectator → voice blueprint. An ACCEPTED invitee can watch the owner's my-sea read-only, deposit a token to occupy seat 2C (opening a 24h voice window for Phase C), and BYE out. Owner's my_sea.html is left structurally intact — the spectator gets a dedicated, simpler my_sea_visit.html; the read-only draw reuses the existing `latest_draw_slots` payload (no picker surgery). - B1: my_sea_visit(owner_id) spectator view — 403 unless an ACCEPTED SeaInvite(owner, request.user); owner bounced to their own my_sea. Context forces owner-only controls off (sea_btn_active=False, read_only=True); renders the table hex (1C owner / 2C visitor) + owner draw read-only. - B2: visitor gate — my_sea_visit_gate reuses my_sea_gate.html w. a spectator branch (titles the OWNER's Sea, INSERT posts to the visitor endpoint, bud-panel suppressed, gear NVM→visit + BYE). Single-step my_sea_visit_insert_token selects+debits the visitor's token (same priority chain) and records token_deposited_at + a 24h voice_until on the SeaInvite → seat 2C present. Center btn flips GATE VIEW → VIEW DRAW. - B3: spectator gear BYE — my_sea_visit_leave sets status=LEFT, left_at, clears voice_until (frees 2C, ends voice), redirects /gameboard/. _my_sea_gear.html gains a `leave_url`-gated BYE below NVM (owner pages pass no leave_url, so unchanged). - B-seat: one-shot "seated" glow per user-spec 2026-05-27 — new shared apps/gameboard/my-sea-seats.js: on first view (localStorage-gated by a per-occupancy data-seat-token) an occupied seat flares --terUser + --ninUser glow ~1.5s then settles to full-opacity --secUser (.fa-ban already swapped to .fa-circle-check). _room.scss adds .seated / .seat-just-seated + the my-sea-seat-flare keyframes (mirrors the room's .active→.role-confirmed handoff). Wired on BOTH the spectator page (load) and the owner page (load + on the FREE DRAW seat-1 transition). MySeaSeatsSpec.js Jasmine spec covers the gating + timed class removal. - B5: MySeaSpectatorFlowTest FT — accept → visit → GATE VIEW → deposit → VIEW DRAW + seat 2C seated. URLs: my-sea/visit/<uuid:owner_id>/ (+ /gate/, /insert, /leave). 470 IT/UT green; spectator FT + full Jasmine suite green. Phase C (WebRTC mesh voice + coturn droplet) next — the 24h voice_until window set here drives it. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:35:00 -04:00
jasmine.clock().tick(1600);
my-sea voice: voice-btn glow/pulse state machine (sea-precedence, pulse-while-alone, 2x on 2nd party) — TDD Phase 3 of the my-sea voice batch (user-spec 2026-05-29). A --quaUser/--ninUser glow + pulse machine for the burger btn + its voice sub-btn, driven by the voice sub-btn availability (voice_active) + the live mesh state. - voice-mesh.js: VoiceRoom gains a state-change hook — setOnStateChange(cb) + peerCount() + _notify({inCall, peerCount, muted}), fired on join, every peer add/drop, mute toggle, and teardown. No behaviour change without a subscriber (VoiceMeshSpec stays green). - voice-glow.js (new): the glow machine. PRE-JOIN nudge — burger glows when the fan is closed (sea draw-nudge keeps burger precedence; voice reclaims it once the sea glow clears), voice sub-btn glows when the fan opens. LIVE — the glow PULSES on whichever surface shows (voice sub-btn fan-open, burger fan-closed): base 2s cadence while alone, doubled (.voice-pulse--fast) once a 2nd party connects (equalizer stand-in; a true volume-reactive equalizer is a live-only enhancement). Class writes are reconciled (idempotent) so the burger-class MutationObserver doesn't feed back on itself. - _burger.scss: .voice-glow + @keyframes voice-pulse + .voice-pulse(--fast). - loaded on my_sea.html + my_sea_visit.html (after burger-btn.js). - VoiceGlowSpec.js (18 specs) + registered in SpecRunner; MySeaSeatsSpec flare window updated 1.5s → 2s (Phase 2 bump). 428 Jasmine specs green. Live-verify on staging: the actual glow colours/cadence + the equalizer upgrade (item 5) and disconnect states (item 7) land in later phases. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 21:06:02 -04:00
expect(seat.classList.contains('seat-just-seated')).toBe(true);
jasmine.clock().tick(500);
my-sea spectator Phase B: seat-2C occupancy + visitor token gate + one-shot seated glow + gear BYE — TDD Phase B of the my-sea invite → spectator → voice blueprint. An ACCEPTED invitee can watch the owner's my-sea read-only, deposit a token to occupy seat 2C (opening a 24h voice window for Phase C), and BYE out. Owner's my_sea.html is left structurally intact — the spectator gets a dedicated, simpler my_sea_visit.html; the read-only draw reuses the existing `latest_draw_slots` payload (no picker surgery). - B1: my_sea_visit(owner_id) spectator view — 403 unless an ACCEPTED SeaInvite(owner, request.user); owner bounced to their own my_sea. Context forces owner-only controls off (sea_btn_active=False, read_only=True); renders the table hex (1C owner / 2C visitor) + owner draw read-only. - B2: visitor gate — my_sea_visit_gate reuses my_sea_gate.html w. a spectator branch (titles the OWNER's Sea, INSERT posts to the visitor endpoint, bud-panel suppressed, gear NVM→visit + BYE). Single-step my_sea_visit_insert_token selects+debits the visitor's token (same priority chain) and records token_deposited_at + a 24h voice_until on the SeaInvite → seat 2C present. Center btn flips GATE VIEW → VIEW DRAW. - B3: spectator gear BYE — my_sea_visit_leave sets status=LEFT, left_at, clears voice_until (frees 2C, ends voice), redirects /gameboard/. _my_sea_gear.html gains a `leave_url`-gated BYE below NVM (owner pages pass no leave_url, so unchanged). - B-seat: one-shot "seated" glow per user-spec 2026-05-27 — new shared apps/gameboard/my-sea-seats.js: on first view (localStorage-gated by a per-occupancy data-seat-token) an occupied seat flares --terUser + --ninUser glow ~1.5s then settles to full-opacity --secUser (.fa-ban already swapped to .fa-circle-check). _room.scss adds .seated / .seat-just-seated + the my-sea-seat-flare keyframes (mirrors the room's .active→.role-confirmed handoff). Wired on BOTH the spectator page (load) and the owner page (load + on the FREE DRAW seat-1 transition). MySeaSeatsSpec.js Jasmine spec covers the gating + timed class removal. - B5: MySeaSpectatorFlowTest FT — accept → visit → GATE VIEW → deposit → VIEW DRAW + seat 2C seated. URLs: my-sea/visit/<uuid:owner_id>/ (+ /gate/, /insert, /leave). 470 IT/UT green; spectator FT + full Jasmine suite green. Phase C (WebRTC mesh voice + coturn droplet) next — the 24h voice_until window set here drives it. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:35:00 -04:00
expect(seat.classList.contains('seat-just-seated')).toBe(false);
jasmine.clock().uninstall();
});
});
my-sea spectate: live spread-sync + owner seat-ring push + visit caption fix — TDD Three fixes to the my-sea spectator (bud-sea), all flowing over the existing `mysea_<owner>` spectate consumer: VISIT CAPTIONS (.sea-pos-label) — two bugs left every CROWN/COVER/… caption blank on my_sea_visit: - empty-hand: `label_by_position` was built from `latest_draw_slots`, which returns [] when the owner's hand is empty (only a significator placed) — so an owner mid-setup showed no captions, while her OWN my_sea (whose JS seeds labels from the POSITION_LABELS constant) showed them. Now the view pulls captions straight from POSITION_LABELS[spread], drawn-cards-independent. - `--seciUser` typo (used once, never defined) → invalid colour dropped → the labels inherited the body colour, contrasting on some palettes but blending into the felt on others (read as "missing"). → `--secUser`. SPREAD-SYNC — the owner's live draw pushed only the hand, not the spread, so a post-DEL spread switch landed the new cards into the OLD spread's cells (the asymmetry the user hit: owner on desire-obstacle-solution, visitor still laid out as escape-velocity). The spread now rides each `sea_draw` broadcast; `_applySpread` re-sets `data-spread` (CSS keys cell visibility off it), re-captions from a server-sourced POSITION_LABELS json_script, + clears stale fills before `_applyHand` repopulates against the right layout. OWNER-SIDE LIVE SEAT PUSH — the owner's my_sea now subscribes to her own spectate WS for `sea_seats`, so visitors arriving (deposit → 2C-6C) / leaving (BYE) appear without a refresh, same broadcast the spectators get. The visit page's inline `_renderSeats` is hoisted into my-sea-seats.js as the shared `mySeaRenderSeats(seats, myToken)` (+ `mySeaConnectSeatRing`); each page passes its own self-token (owner page passes '' — her 1C isn't --self server-side). Coverage: - ITs: MySeaVisitEmptyHandLabelsTest (captions present + rendered for an empty hand); MySeaLockHandViewTest broadcast test asserts the spread arg; spectate consumer test asserts the hand+spread relay (channels). - Jasmine: 6 new MySeaSeatsSpec cases for mySeaRenderSeats (per-seat rebuild, --self by token, owner-page no-self, no-duplicate re-render, one-shot flare). - Live-verified in Firefox: captions paint khaki on the brown palette; a desire-obstacle-solution sync flips data-spread + relabels Solution/Obstacle/ Desire + hides leave/cover/lay. [[feedback-jsonfield-exclude-sqlite-null]] not implicated; spread map is a plain dict lookup. 304 gameboard ITs + Jasmine green. Code architected by Disco DeDisco <discodedisco@outlook.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 00:35:18 -04:00
// mySeaRenderSeats — the shared seat-ring re-render driven by a `sea_seats`
// broadcast. Used by BOTH the owner's my_sea AND the spectator's my_sea_visit
// (DRY, 2026-05-30); each passes its own `myToken` to mark its own chair.
describe('my-sea-seats mySeaRenderSeats shared ring re-render', function () {
var scene;
beforeEach(function () {
window.localStorage.clear();
scene = document.createElement('div');
scene.className = 'room-table-scene';
document.body.appendChild(scene);
});
afterEach(function () {
if (scene && scene.parentNode) scene.parentNode.removeChild(scene);
window.localStorage.clear();
});
var SEATS = [
{ n: 1, label: '1C', present: true, token: 'owner-9-3' },
{ n: 2, label: '2C', present: true, token: 'visit-42' },
{ n: 3, label: '3C', present: false, token: '' },
];
it('exposes mySeaRenderSeats globally', function () {
expect(typeof window.mySeaRenderSeats).toBe('function');
});
it('rebuilds one .table-seat per seat with the present/seated state', function () {
window.mySeaRenderSeats(SEATS, '');
var seats = scene.querySelectorAll('.table-seat');
expect(seats.length).toBe(3);
expect(seats[0].classList.contains('seated')).toBe(true);
expect(seats[2].classList.contains('seated')).toBe(false);
// Present seats get the check icon; absent seats the ban icon.
expect(seats[0].querySelector('.fa-circle-check')).not.toBeNull();
expect(seats[2].querySelector('.fa-ban')).not.toBeNull();
expect(seats[1].querySelector('.seat-position-label').textContent).toBe('2C');
});
it('marks only the seat matching myToken as --self', function () {
window.mySeaRenderSeats(SEATS, 'visit-42');
var self = scene.querySelectorAll('.table-seat--self');
expect(self.length).toBe(1);
expect(self[0].getAttribute('data-slot')).toBe('2');
});
it('marks no seat --self when myToken is empty (owner page)', function () {
window.mySeaRenderSeats(SEATS, '');
expect(scene.querySelectorAll('.table-seat--self').length).toBe(0);
});
it('clears prior seats before re-rendering (no duplicates)', function () {
window.mySeaRenderSeats(SEATS, '');
window.mySeaRenderSeats(SEATS, '');
expect(scene.querySelectorAll('.table-seat').length).toBe(3);
});
it('flares freshly-seated tokened seats once (localStorage-gated)', function () {
window.mySeaRenderSeats(SEATS, '');
var owner = scene.querySelector('.table-seat[data-slot="1"]');
expect(owner.classList.contains('seat-just-seated')).toBe(true);
// Second render for the same token does not replay the flare.
window.mySeaRenderSeats(SEATS, '');
owner = scene.querySelector('.table-seat[data-slot="1"]');
expect(owner.classList.contains('seat-just-seated')).toBe(false);
});
});