144 lines
7.0 KiB
JavaScript
144 lines
7.0 KiB
JavaScript
|
|
// ── RecognitionSpec.js ────────────────────────────────────────────────────────
|
||
|
|
//
|
||
|
|
// Unit specs for recognition.js — banner injection from sky/save response.
|
||
|
|
//
|
||
|
|
// DOM contract assumed by showBanner():
|
||
|
|
// Any <h2> in the document — banner is inserted immediately after it.
|
||
|
|
// If absent, banner is prepended to <body>.
|
||
|
|
//
|
||
|
|
// 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 = '<h2>Dash</h2>';
|
||
|
|
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();
|
||
|
|
});
|
||
|
|
|
||
|
|
});
|