// 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); }); it('removes the flare class after the 2s glow window', function () { jasmine.clock().install(); window.playSeatGlow(seat); expect(seat.classList.contains('seat-just-seated')).toBe(true); // Still flaring mid-window (bumped 1.5s → 2s, user-spec 2026-05-29). jasmine.clock().tick(1600); expect(seat.classList.contains('seat-just-seated')).toBe(true); jasmine.clock().tick(500); expect(seat.classList.contains('seat-just-seated')).toBe(false); jasmine.clock().uninstall(); }); }); // 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); }); });