- 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>
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { SEARCH_ENTRIES, isPaletteShortcut, searchEntries, shortcutLabel, type SearchEntry } from '../../src/marketing/search.ts';
|
|
|
|
const entry = (id: string, title: string, description = '', group: SearchEntry['group'] = 'Page'): SearchEntry => ({
|
|
id, title, description, group, href: `/${id}`,
|
|
});
|
|
|
|
describe('searchEntries', () => {
|
|
const entries = [
|
|
entry('a', 'Pricing', 'Plans for every size'),
|
|
entry('b', 'Review and approve', 'Compare before and after, then publish', 'Docs topic'),
|
|
entry('c', 'Trust', 'We never publish without approval'),
|
|
];
|
|
|
|
it('shows the pages when nothing has been typed', () => {
|
|
expect(searchEntries('', entries).map((e) => e.id)).toEqual(['a', 'c']);
|
|
});
|
|
|
|
it('ignores case and surrounding spaces', () => {
|
|
expect(searchEntries(' PRICING ', entries).map((e) => e.id)).toEqual(['a']);
|
|
});
|
|
|
|
it('finds an entry by its description', () => {
|
|
expect(searchEntries('compare', entries).map((e) => e.id)).toEqual(['b']);
|
|
});
|
|
|
|
it('needs every word to match', () => {
|
|
expect(searchEntries('pricing banana', entries)).toEqual([]);
|
|
});
|
|
|
|
it('puts title matches before description-only matches', () => {
|
|
expect(searchEntries('approv', entries).map((e) => e.id)).toEqual(['b', 'c']);
|
|
});
|
|
|
|
it('puts titles that start with the query first', () => {
|
|
const list = [entry('x', 'Undo a change'), entry('y', 'How to undo')];
|
|
expect(searchEntries('undo', list).map((e) => e.id)).toEqual(['x', 'y']);
|
|
});
|
|
});
|
|
|
|
describe('SEARCH_ENTRIES', () => {
|
|
it('only holds results that go somewhere', () => {
|
|
expect(SEARCH_ENTRIES.length).toBeGreaterThan(0);
|
|
for (const e of SEARCH_ENTRIES) expect(e.href).toMatch(/^\//);
|
|
});
|
|
|
|
it('has unique ids', () => {
|
|
const ids = SEARCH_ENTRIES.map((e) => e.id);
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
});
|
|
|
|
it('lists every navigation page', () => {
|
|
const titles = searchEntries('').map((e) => e.title);
|
|
expect(titles).toEqual(expect.arrayContaining(['Product', 'How it works', 'Trust and safety', 'Pricing', 'Docs']));
|
|
});
|
|
|
|
it('finds a docs topic by what it covers', () => {
|
|
expect(searchEntries('invite').map((e) => e.title)).toContain('Team and billing');
|
|
});
|
|
});
|
|
|
|
describe('isPaletteShortcut', () => {
|
|
const key = (k: string, mods: { metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean; altKey?: boolean } = {}) =>
|
|
isPaletteShortcut({ key: k, metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, ...mods });
|
|
|
|
it('accepts Cmd+K and Ctrl+K', () => {
|
|
expect(key('k', { metaKey: true })).toBe(true);
|
|
expect(key('k', { ctrlKey: true })).toBe(true);
|
|
});
|
|
|
|
it('accepts a capital K (Caps Lock)', () => {
|
|
expect(key('K', { metaKey: true })).toBe(true);
|
|
});
|
|
|
|
it('rejects K alone, other letters, and extra modifiers', () => {
|
|
expect(key('k')).toBe(false);
|
|
expect(key('j', { metaKey: true })).toBe(false);
|
|
expect(key('k', { metaKey: true, shiftKey: true })).toBe(false);
|
|
expect(key('k', { ctrlKey: true, altKey: true })).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('shortcutLabel', () => {
|
|
it('shows the Command symbol on Apple devices and Ctrl elsewhere', () => {
|
|
expect(shortcutLabel(true)).toBe('⌘K');
|
|
expect(shortcutLabel(false)).toBe('Ctrl K');
|
|
});
|
|
});
|