feat(web): marketing theme toggle, site search palette, and phone dev access
- Theme toggle in the nav: follows the device until chosen, remembers the choice, sets data-theme before first paint (no flash). - Site search: Cmd/Ctrl+K command palette on every marketing page, opened by the shortcut, a nav Search button, or the Docs search field. Searches pages, docs topics and articles that have a page. - Docs topics move to content.ts so the page and the search index share one source. - next.config: allowedDevOrigins for private network ranges so a phone on the same Wi-Fi hydrates the dev site. Dev only. - Sun and moon icons added by hand (not Figma exports); noted in the icons README. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
0cff3902b2
commit
f69ce33dd0
@@ -0,0 +1,59 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { THEME_KEY, applyTheme, currentTheme, readStoredTheme, themeInitScript } from '../../src/marketing/theme.ts';
|
||||
|
||||
describe('theme', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
});
|
||||
|
||||
it('has no stored theme until the visitor chooses one', () => {
|
||||
expect(readStoredTheme()).toBeNull();
|
||||
});
|
||||
|
||||
it('ignores a stored value that is not light or dark', () => {
|
||||
localStorage.setItem(THEME_KEY, 'purple');
|
||||
expect(readStoredTheme()).toBeNull();
|
||||
});
|
||||
|
||||
it('applies a theme to the page and remembers it', () => {
|
||||
applyTheme('dark');
|
||||
expect(document.documentElement).toHaveAttribute('data-theme', 'dark');
|
||||
expect(readStoredTheme()).toBe('dark');
|
||||
});
|
||||
|
||||
it('still applies the theme when storage is blocked', () => {
|
||||
const original = Storage.prototype.setItem;
|
||||
Storage.prototype.setItem = () => {
|
||||
throw new Error('blocked');
|
||||
};
|
||||
try {
|
||||
expect(() => applyTheme('light')).not.toThrow();
|
||||
expect(document.documentElement).toHaveAttribute('data-theme', 'light');
|
||||
} finally {
|
||||
Storage.prototype.setItem = original;
|
||||
}
|
||||
});
|
||||
|
||||
it('gives the head script that sets the saved theme before first paint', () => {
|
||||
localStorage.setItem(THEME_KEY, 'dark');
|
||||
new Function(themeInitScript)();
|
||||
expect(document.documentElement).toHaveAttribute('data-theme', 'dark');
|
||||
});
|
||||
|
||||
it('leaves the page on the device theme when nothing is saved', () => {
|
||||
new Function(themeInitScript)();
|
||||
expect(document.documentElement).not.toHaveAttribute('data-theme');
|
||||
});
|
||||
|
||||
it('treats a browser without matchMedia as light instead of crashing', () => {
|
||||
const original = window.matchMedia;
|
||||
// @ts-expect-error simulating an environment that lacks matchMedia
|
||||
window.matchMedia = undefined;
|
||||
try {
|
||||
expect(currentTheme()).toBe('light');
|
||||
} finally {
|
||||
window.matchMedia = original;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user