import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest'; // musehub.ts registers module-scope event listeners (click, DOMContentLoaded, // htmx:*) that touch `document` at import time — this module must be // dynamically imported after a minimal DOM stub is in place, not statically // imported like a side-effect-free module. const makeEl = (dataset: Record = {}) => ({ dataset }); let body: { className: string }; let elements: Record | undefined>; let syncBodyClassFromContent: () => void; beforeAll(async () => { body = { className: '' }; elements = {}; vi.stubGlobal('document', { body, getElementById: (id: string) => elements[id] ?? null, addEventListener: () => {}, documentElement: { classList: { add: () => {} } }, }); vi.stubGlobal('window', {}); ({ syncBodyClassFromContent } = await import('./musehub')); }); beforeEach(() => { body.className = ''; elements = {}; }); describe('syncBodyClassFromContent', () => { it('applies #content data-body-class to body.className', () => { body.className = 'app-shell'; // stale class from the previous (boosted-from) page elements['content'] = makeEl({ bodyClass: 'app-shell' }); syncBodyClassFromContent(); expect(body.className).toBe('app-shell'); }); it('clears body.className when the new page has no body_class', () => { // Reproduces issue #168: navigating from an app-shell page (e.g. issue // detail) to a non-app-shell page (e.g. blob view) via an htmx-boosted // link must not leave the stale "app-shell" class — and its // overflow:hidden — stuck on . body.className = 'app-shell'; elements['content'] = makeEl({ bodyClass: '' }); syncBodyClassFromContent(); expect(body.className).toBe(''); }); it('defaults to empty string when #content is missing', () => { body.className = 'app-shell'; syncBodyClassFromContent(); expect(body.className).toBe(''); }); });