Skip to content

Commit d76e856

Browse files
test: add coverage for CSP theme-init scripts and navbar handlers
1 parent 67ef87e commit d76e856

3 files changed

Lines changed: 119 additions & 1 deletion

File tree

static/js/app.test.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@ const showProjects = vi.fn();
55
const showWorkspace = vi.fn();
66
const loadSession = vi.fn();
77
const showSearchPage = vi.fn();
8+
const toggleTheme = vi.fn();
9+
const toggleSidebar = vi.fn();
810

911
vi.mock('./projects.js', () => ({ showProjects }));
1012
vi.mock('./sessions.js', () => ({ showWorkspace, loadSession, selectSession: vi.fn(), copyAll: vi.fn() }));
1113
vi.mock('./search.js', () => ({ showSearchPage, doSearch: vi.fn() }));
1214
vi.mock('./export.js', () => ({ bulkExport: vi.fn(), downloadSession: vi.fn() }));
15+
vi.mock('./shared/utils.js', async (importOriginal) => {
16+
const actual = await importOriginal();
17+
return { ...actual, toggleSidebar, closeSidebar: vi.fn() };
18+
});
1319
vi.mock('./shared/theme.js', () => ({
1420
HLJS_THEME_SHEETS: {},
1521
applyHljsTheme: vi.fn(),
1622
applyTheme: vi.fn(),
17-
toggleTheme: vi.fn(),
23+
toggleTheme,
1824
setWorkspaceMode: vi.fn(),
1925
}));
2026

@@ -109,3 +115,55 @@ describe('router (app.js)', () => {
109115
expect(showWorkspace).toHaveBeenCalledWith('other');
110116
});
111117
});
118+
119+
describe('navbar handlers (app.js DOMContentLoaded)', () => {
120+
const origScrollTo = window.scrollTo;
121+
122+
beforeAll(async () => {
123+
vi.resetModules();
124+
window.scrollTo = vi.fn();
125+
document.body.innerHTML = `
126+
<button id="hamburger-btn"></button>
127+
<a id="navbar-brand" href="#"></a>
128+
<a id="nav-search-link" href="#search"></a>
129+
<button id="theme-toggle"></button>
130+
<div id="content"></div>
131+
<span id="footer-year"></span>
132+
`;
133+
await import('./app.js');
134+
document.dispatchEvent(new Event('DOMContentLoaded'));
135+
});
136+
137+
afterAll(() => {
138+
window.scrollTo = origScrollTo;
139+
});
140+
141+
beforeEach(() => {
142+
toggleTheme.mockClear();
143+
toggleSidebar.mockClear();
144+
showProjects.mockClear();
145+
showSearchPage.mockClear();
146+
});
147+
148+
it('wires hamburger click to toggleSidebar', () => {
149+
document.getElementById('hamburger-btn').click();
150+
expect(toggleSidebar).toHaveBeenCalledTimes(1);
151+
});
152+
153+
it('wires navbar brand click to showProjects', () => {
154+
const brand = document.getElementById('navbar-brand');
155+
brand.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
156+
expect(showProjects).toHaveBeenCalled();
157+
});
158+
159+
it('wires nav search link click to showSearchPage', () => {
160+
const link = document.getElementById('nav-search-link');
161+
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
162+
expect(showSearchPage).toHaveBeenCalled();
163+
});
164+
165+
it('wires theme toggle click to toggleTheme', () => {
166+
document.getElementById('theme-toggle').click();
167+
expect(toggleTheme).toHaveBeenCalledTimes(1);
168+
});
169+
});

static/js/hljs-theme-init.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
describe('hljs-theme-init.js', () => {
4+
beforeEach(() => {
5+
vi.resetModules();
6+
document.documentElement.setAttribute('data-theme', 'dark');
7+
document.body.innerHTML = '';
8+
});
9+
10+
it('does nothing when data-theme is dark', async () => {
11+
document.body.innerHTML = '<link id="hljs-theme" href="https://example.com/dark.css" />';
12+
await import('./hljs-theme-init.js');
13+
const link = document.getElementById('hljs-theme');
14+
expect(link.href).toContain('dark.css');
15+
expect(link.getAttribute('integrity')).toBeNull();
16+
});
17+
18+
it('sets light-theme CDN href and SRI when data-theme is light', async () => {
19+
document.documentElement.setAttribute('data-theme', 'light');
20+
document.body.innerHTML = '<link id="hljs-theme" href="about:blank" />';
21+
await import('./hljs-theme-init.js');
22+
const link = document.getElementById('hljs-theme');
23+
expect(link.href).toContain('github.min.css');
24+
expect(link.integrity).toBe(
25+
'sha512-0aPQyyeZrWj9sCA46UlmWgKOP0mUipLQ6OZXu8l4IcAmD2u31EPEy9VcIMvl7SoAaKe8bLXZhYoMaE/in+gcgA==',
26+
);
27+
});
28+
29+
it('no-ops when hljs-theme link is missing', async () => {
30+
document.documentElement.setAttribute('data-theme', 'light');
31+
await import('./hljs-theme-init.js');
32+
expect(document.getElementById('hljs-theme')).toBeNull();
33+
});
34+
});

static/js/theme-init.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
describe('theme-init.js', () => {
4+
beforeEach(() => {
5+
vi.resetModules();
6+
localStorage.clear();
7+
document.documentElement.removeAttribute('data-theme');
8+
});
9+
10+
it('defaults to dark when localStorage has no theme', async () => {
11+
await import('./theme-init.js');
12+
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
13+
});
14+
15+
it('applies saved light theme before paint', async () => {
16+
localStorage.setItem('theme', 'light');
17+
await import('./theme-init.js');
18+
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
19+
});
20+
21+
it('ignores invalid stored theme values', async () => {
22+
localStorage.setItem('theme', 'sepia');
23+
await import('./theme-init.js');
24+
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
25+
});
26+
});

0 commit comments

Comments
 (0)