// ── NoteSpec.js ─────────────────────────────────────────────────────────────── // // Unit specs for note.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: // Note.showBanner(note) // note = { slug, title, description, earned_at } → inject .note-banner // note = null → no-op // // Note.handleSaveResponse(data) // data = { saved: true, note: {...} } → delegates to showBanner // data = { saved: true, note: null } → no-op // // ───────────────────────────────────────────────────────────────────────────── const SAMPLE_NOTE = { slug: 'stargazer', title: 'Stargazer', description: 'You saved your first personal sky chart.', earned_at: '2026-04-22T02:00:00+00:00', }; describe('Note.showBanner', () => { let fixture; beforeEach(() => { fixture = document.createElement('div'); fixture.id = 'note-fixture'; fixture.innerHTML = '

Dash

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