// ── RecognitionSpec.js ──────────────────────────────────────────────────────── // // Unit specs for recognition.js — banner injection from sky/save response. // // DOM contract assumed by showBanner(): // Any

in the document — banner is inserted immediately after it. // If absent, banner is prepended to . // // API under test: // Recognition.showBanner(recognition) // recognition = { slug, title, description, earned_at } → inject .recog-banner // recognition = null → no-op // // Recognition.handleSaveResponse(data) // data = { saved: true, recognition: {...} } → delegates to showBanner // data = { saved: true, recognition: null } → no-op // // ───────────────────────────────────────────────────────────────────────────── const SAMPLE_RECOGNITION = { slug: 'stargazer', title: 'Stargazer', description: 'You saved your first personal sky chart.', earned_at: '2026-04-22T02:00:00+00:00', }; describe('Recognition.showBanner', () => { let fixture; beforeEach(() => { fixture = document.createElement('div'); fixture.id = 'recognition-fixture'; fixture.innerHTML = '

Dash

'; document.body.appendChild(fixture); }); afterEach(() => { document.querySelectorAll('.recog-banner').forEach(b => b.remove()); fixture.remove(); }); // ── T1 ── null → no banner ──────────────────────────────────────────────── it('T1: showBanner(null) does not inject a banner', () => { Recognition.showBanner(null); expect(document.querySelector('.recog-banner')).toBeNull(); }); // ── T2 ── recognition present → banner in DOM ───────────────────────────── it('T2: showBanner(recognition) injects .recog-banner into the document', () => { Recognition.showBanner(SAMPLE_RECOGNITION); expect(document.querySelector('.recog-banner')).not.toBeNull(); }); // ── T3 ── title ─────────────────────────────────────────────────────────── it('T3: banner .recog-banner__title contains recognition.title', () => { Recognition.showBanner(SAMPLE_RECOGNITION); const el = document.querySelector('.recog-banner__title'); expect(el).not.toBeNull(); expect(el.textContent).toContain('Stargazer'); }); // ── T4 ── description ───────────────────────────────────────────────────── it('T4: banner .recog-banner__description contains recognition.description', () => { Recognition.showBanner(SAMPLE_RECOGNITION); const el = document.querySelector('.recog-banner__description'); expect(el).not.toBeNull(); expect(el.textContent).toContain('You saved your first personal sky chart.'); }); // ── T5 ── timestamp ─────────────────────────────────────────────────────── it('T5: banner has a .recog-banner__timestamp element', () => { Recognition.showBanner(SAMPLE_RECOGNITION); expect(document.querySelector('.recog-banner__timestamp')).not.toBeNull(); }); // ── T6 ── image area ────────────────────────────────────────────────────── it('T6: banner has a .recog-banner__image element', () => { Recognition.showBanner(SAMPLE_RECOGNITION); expect(document.querySelector('.recog-banner__image')).not.toBeNull(); }); // ── T7 ── NVM button ────────────────────────────────────────────────────── it('T7: banner has a .btn.btn-danger NVM button', () => { Recognition.showBanner(SAMPLE_RECOGNITION); expect(document.querySelector('.recog-banner .btn.btn-danger')).not.toBeNull(); }); // ── T8 ── FYI link ──────────────────────────────────────────────────────── it('T8: banner has a .btn.btn-caution FYI link pointing to /billboard/recognition/', () => { Recognition.showBanner(SAMPLE_RECOGNITION); const fyi = document.querySelector('.recog-banner .btn.btn-caution'); expect(fyi).not.toBeNull(); expect(fyi.getAttribute('href')).toBe('/billboard/recognition/'); }); // ── T9 ── NVM dismissal ─────────────────────────────────────────────────── it('T9: clicking the NVM button removes the banner from the DOM', () => { Recognition.showBanner(SAMPLE_RECOGNITION); document.querySelector('.recog-banner .btn.btn-danger').click(); expect(document.querySelector('.recog-banner')).toBeNull(); }); // ── T10 ── placement after h2 ───────────────────────────────────────────── it('T10: banner is inserted immediately after the first h2 in the document', () => { Recognition.showBanner(SAMPLE_RECOGNITION); const h2 = fixture.querySelector('h2'); expect(h2.nextElementSibling.classList.contains('recog-banner')).toBeTrue(); }); }); describe('Recognition.handleSaveResponse', () => { afterEach(() => { document.querySelectorAll('.recog-banner').forEach(b => b.remove()); }); // ── T11 ── delegates when recognition present ───────────────────────────── it('T11: handleSaveResponse shows banner when data.recognition is present', () => { Recognition.handleSaveResponse({ saved: true, recognition: SAMPLE_RECOGNITION }); expect(document.querySelector('.recog-banner')).not.toBeNull(); }); // ── T12 ── no banner when recognition null ──────────────────────────────── it('T12: handleSaveResponse does not show banner when data.recognition is null', () => { Recognition.handleSaveResponse({ saved: true, recognition: null }); expect(document.querySelector('.recog-banner')).toBeNull(); }); });