import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { bootConfig, bootSkeletonScript, type BootLayoutConfig } from '../boot-script';
import { THEME_CACHE_KEY } from '../keys';
/**
* Runs the exact string the root layout inlines into
, in a scope with
* none of this module's imports: a function that reached for one would throw
* here (and in the browser), and the test would see default attributes.
*/
function boot(path: string, config: Partial = {}) {
window.history.replaceState(null, '', path);
new Function(bootSkeletonScript({ ...bootConfig(), ...config }))();
}
const html = () => document.documentElement;
const attr = (name: string) => html().getAttribute(`data-boot-${name}`);
const cssVar = (name: string) => html().style.getPropertyValue(name);
const settings = (state: Record) =>
localStorage.setItem('settings-storage', JSON.stringify({ state, version: 12 }));
const signedIn = () =>
localStorage.setItem('auth-storage', JSON.stringify({ state: { isAuthenticated: true }, version: 0 }));
function desktop(matches: boolean) {
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
matches: matches && query.includes('64rem'),
media: query,
addEventListener: () => {},
removeEventListener: () => {},
})) as unknown as typeof window.matchMedia;
}
beforeEach(() => {
localStorage.clear();
sessionStorage.clear();
for (const a of [...html().attributes]) {
if (a.name.startsWith('data-boot-') || a.name === 'data-interface-layout' || a.name === 'data-theme-skin' || a.name === 'style') {
html().removeAttribute(a.name);
}
}
document.head.querySelectorAll('#active-theme, #active-theme-skin').forEach((el) => el.remove());
desktop(true);
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('boot script: defaults', () => {
it('draws the split inbox with the regular density and default widths when nothing is stored', () => {
boot('/en/mail/folder/inbox');
expect(attr('surface')).toBe('mail');
expect(attr('mail-layout')).toBe('split');
expect(html().getAttribute('data-interface-layout')).toBe('default');
expect(cssVar('--list-item-height')).toBe('48px');
expect(cssVar('--density-item-py')).toBe('12px');
expect(cssVar('--boot-mail-sidebar-w')).toBe('256px');
expect(cssVar('--boot-mail-list-w')).toBe('384px');
expect(cssVar('--boot-mail-list-h')).toBe('320px');
expect(attr('pane')).toBeNull();
expect(attr('pro')).toBeNull();
});
it('treats the bare root as mail', () => {
boot('/');
expect(attr('surface')).toBe('mail');
});
it('survives corrupt JSON in every key it reads', () => {
for (const key of ['settings-storage', 'column-widths', 'calendar-storage', 'auth-storage', 'account-registry', 'bulwark-boot-hints', THEME_CACHE_KEY]) {
localStorage.setItem(key, '{not json');
}
boot('/en/calendar');
expect(attr('surface')).toBe('calendar');
expect(attr('calendar-view')).toBe('month');
expect(cssVar('--list-item-height')).toBe('48px');
expect(cssVar('--boot-mail-sidebar-w')).toBe('256px');
expect(attr('rail')).toBeNull();
expect(document.getElementById('active-theme')).toBeNull();
});
it('survives storage that throws on access', () => {
const throwing = { get: () => { throw new Error('SecurityError'); } };
const local = Object.getOwnPropertyDescriptor(window, 'localStorage');
const session = Object.getOwnPropertyDescriptor(window, 'sessionStorage');
Object.defineProperty(window, 'localStorage', { configurable: true, ...throwing });
Object.defineProperty(window, 'sessionStorage', { configurable: true, ...throwing });
try {
boot('/en/settings/filters');
expect(attr('surface')).toBe('settings');
expect(attr('settings-tab')).toBe('filters');
expect(cssVar('--boot-settings-nav-w')).toBe('256px');
} finally {
Object.defineProperty(window, 'localStorage', local!);
Object.defineProperty(window, 'sessionStorage', session!);
}
});
it('ignores state that is not an object', () => {
localStorage.setItem('settings-storage', JSON.stringify({ state: 'oops' }));
localStorage.setItem('column-widths', JSON.stringify([1, 2]));
boot('/en/mail');
expect(attr('mail-layout')).toBe('split');
expect(cssVar('--boot-mail-list-w')).toBe('384px');
});
});
describe('boot script: layout settings', () => {
it('applies Mountain View, its density rhythm and forces the focus layout', () => {
settings({ interfaceLayout: 'mountain-view', density: 'compact', mailLayout: 'split' });
boot('/en/mail');
expect(html().getAttribute('data-interface-layout')).toBe('mountain-view');
expect(cssVar('--list-item-height')).toBe('32px');
expect(cssVar('--density-item-py')).toBe('6px');
expect(cssVar('--density-item-gap')).toBe('8px');
expect(attr('mail-layout')).toBe('focus');
});
it.each([
['extra-compact', 'auto', '2px', '0px'],
['compact', 'auto', '4px', '1px'],
['regular', '48px', '12px', '4px'],
['comfortable', '64px', '16px', '6px'],
])('writes the %s density variables', (density, height, py, sidebar) => {
settings({ density });
boot('/en/mail');
expect(cssVar('--list-item-height')).toBe(height);
expect(cssVar('--density-item-py')).toBe(py);
expect(cssVar('--density-sidebar-py')).toBe(sidebar);
expect(attr('density')).toBe(density);
});
it('names the density and whether previews are off, for the mail rows', () => {
boot('/en/mail');
expect(attr('density')).toBe('regular');
expect(attr('preview')).toBeNull();
settings({ density: 'extra-compact', showPreview: false });
boot('/en/mail');
expect(attr('density')).toBe('extra-compact');
expect(attr('preview')).toBe('off');
settings({ density: 'cosy', showPreview: true });
boot('/en/mail');
expect(attr('density')).toBe('regular');
expect(attr('preview')).toBeNull();
});
it('falls back to regular for an unknown density', () => {
settings({ density: 'cosy' });
boot('/en/mail');
expect(cssVar('--list-item-height')).toBe('48px');
expect(attr('density')).toBe('regular');
});
it('keeps the horizontal and focus layouts, and simple mode forces focus', () => {
settings({ mailLayout: 'horizontal' });
boot('/en/mail');
expect(attr('mail-layout')).toBe('horizontal');
settings({ mailLayout: 'focus' });
boot('/en/mail');
expect(attr('mail-layout')).toBe('focus');
settings({ mailLayout: 'horizontal', simpleMode: true });
boot('/en/mail');
expect(attr('mail-layout')).toBe('focus');
});
it('clamps stored column widths the way the mail store does', () => {
localStorage.setItem('column-widths', JSON.stringify({ sidebarWidth: 1000, emailListWidth: 100, emailListHeight: 500 }));
boot('/en/mail');
expect(cssVar('--boot-mail-sidebar-w')).toBe('400px');
expect(cssVar('--boot-mail-list-w')).toBe('240px');
expect(cssVar('--boot-mail-list-h')).toBe('500px');
});
it('reads the other surfaces\' widths, defaulting bad values', () => {
localStorage.setItem('calendar-sidebar-width', '300');
localStorage.setItem('contacts-sidebar-width', 'abc');
localStorage.setItem('contacts-list-width', '450');
localStorage.setItem('files-sidebar-width', '999');
localStorage.setItem('settings-sidebar-width', '210');
boot('/en/contacts');
expect(cssVar('--boot-cal-sidebar-w')).toBe('300px');
expect(cssVar('--boot-contacts-sidebar-w')).toBe('256px');
expect(cssVar('--boot-contacts-list-w')).toBe('450px');
expect(cssVar('--boot-files-sidebar-w')).toBe('400px');
expect(cssVar('--boot-settings-nav-w')).toBe('210px');
});
it('marks the files view and details panel', () => {
settings({ filesSettings: { defaultViewMode: 'grid' } });
localStorage.setItem('files-show-details', '1');
boot('/en/files/Documents');
expect(attr('surface')).toBe('files');
expect(attr('files-view')).toBe('grid');
expect(attr('files-details')).toBe('on');
});
});
describe('boot script: mail and contacts panes', () => {
it.each([
['/en/mail/message/M123', 'message'],
['/en/mail/thread/T9', 'thread'],
['/en/mail?email=M1', 'message'],
['/en/mail/folder/inbox', null],
['/en/mail/message', null],
])('%s opens pane %s', (path, pane) => {
boot(path);
expect(attr('pane')).toBe(pane);
});
it.each([
['/en/contacts', null],
['/en/contacts/new', 'create'],
['/en/contacts?addEmail=a%40example.com', 'create'],
['/en/contacts/abc/edit', 'detail'],
['/en/contacts?contactId=abc', 'detail'],
])('%s opens pane %s', (path, pane) => {
boot(path);
expect(attr('surface')).toBe('contacts');
expect(attr('pane')).toBe(pane);
});
});
describe('boot script: calendar', () => {
it.each(['month', 'week', 'day', 'agenda', 'tasks'])('takes the %s view from the URL', (view) => {
localStorage.setItem('calendar-storage', JSON.stringify({ state: { viewMode: 'agenda' } }));
boot(`/en/calendar/${view}/2026-11-04`);
expect(attr('calendar-view')).toBe(view);
});
it('opens a bare date in month view, like parseCalendarPath', () => {
localStorage.setItem('calendar-storage', JSON.stringify({ state: { viewMode: 'week' } }));
boot('/en/calendar/2026-11-04');
expect(attr('calendar-view')).toBe('month');
});
it('uses the stored view for /calendar and event links, month without one', () => {
localStorage.setItem('calendar-storage', JSON.stringify({ state: { viewMode: 'day' } }));
boot('/en/calendar');
expect(attr('calendar-view')).toBe('day');
boot('/en/calendar/event/E1');
expect(attr('calendar-view')).toBe('day');
localStorage.setItem('calendar-storage', JSON.stringify({ state: { viewMode: 'year' } }));
boot('/en/calendar');
expect(attr('calendar-view')).toBe('month');
});
it('counts week columns and display hours from the settings', () => {
boot('/en/calendar/week');
expect(cssVar('--boot-cal-cols')).toBe('7');
expect(cssVar('--boot-cal-hours')).toBe('12');
settings({ calendarHideNonWorkingDays: true, calendarWorkingDays: [1, 2, 3, 4, 5], calendarDayStartHour: 7, calendarDayEndHour: 19 });
boot('/en/calendar/week');
expect(cssVar('--boot-cal-cols')).toBe('5');
expect(cssVar('--boot-cal-hours')).toBe('12');
settings({ calendarLimitHours: false, calendarHideNonWorkingDays: true, calendarWorkingDays: [] });
boot('/en/calendar/week');
expect(cssVar('--boot-cal-cols')).toBe('7');
expect(cssVar('--boot-cal-hours')).toBe('24');
settings({ calendarDayStartHour: 20, calendarDayEndHour: 8 });
boot('/en/calendar/week');
expect(cssVar('--boot-cal-hours')).toBe('24');
});
it('marks a collapsed calendar sidebar', () => {
localStorage.setItem('calendar-sidebar-collapsed', '1');
boot('/en/calendar');
expect(attr('collapsed')).toBe('calendar');
});
it('gives the first shown hour, 0 for the whole day', () => {
boot('/en/calendar/week');
expect(cssVar('--boot-cal-start')).toBe('8');
settings({ calendarDayStartHour: 6, calendarDayEndHour: 22 });
boot('/en/calendar/week');
expect(cssVar('--boot-cal-start')).toBe('6');
settings({ calendarLimitHours: false, calendarDayStartHour: 6, calendarDayEndHour: 22 });
boot('/en/calendar/week');
expect(cssVar('--boot-cal-start')).toBe('0');
});
describe('with a fixed clock', () => {
afterEach(() => vi.useRealTimers());
it('opens the hour grid an hour before now, at the top when now is outside the shown hours', () => {
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(new Date(2026, 10, 4, 15, 30));
boot('/en/calendar/week');
expect(cssVar('--boot-cal-scroll')).toBe('6');
settings({ calendarLimitHours: false });
boot('/en/calendar/day');
expect(cssVar('--boot-cal-scroll')).toBe('14');
vi.setSystemTime(new Date(2026, 10, 4, 21, 0));
settings({});
boot('/en/calendar/week');
expect(cssVar('--boot-cal-scroll')).toBe('-1');
});
it('reads the clock in the time zone the settings name', () => {
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(new Date(Date.UTC(2026, 10, 4, 14, 0)));
settings({ calendarLimitHours: false, timeZone: 'Asia/Tokyo' });
boot('/en/calendar/week');
// 23:00 in Tokyo.
expect(cssVar('--boot-cal-scroll')).toBe('22');
settings({ calendarLimitHours: false, timeZone: 'Not/AZone' });
boot('/en/calendar/week');
expect(cssVar('--boot-cal-scroll')).toBe(String(new Date().getHours() - 1));
});
it('counts the mini month rows of the linked month, else this one', () => {
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(new Date(2026, 1, 10, 12, 0));
// February 2026 starts on a Sunday and has 28 days.
boot('/en/calendar');
expect(cssVar('--boot-cal-mini-rows')).toBe('5');
settings({ firstDayOfWeek: 0 });
boot('/en/calendar');
expect(cssVar('--boot-cal-mini-rows')).toBe('4');
// November 2026 starts on a Sunday: six rows from Monday, five from Sunday.
settings({});
boot('/en/calendar/week/2026-11-04');
expect(cssVar('--boot-cal-mini-rows')).toBe('6');
boot('/en/calendar/2026-11-04');
expect(cssVar('--boot-cal-mini-rows')).toBe('6');
settings({ firstDayOfWeek: 0 });
boot('/en/calendar/week/2026-11-04');
expect(cssVar('--boot-cal-mini-rows')).toBe('5');
});
});
it('marks week numbers in the mini month', () => {
boot('/en/calendar');
expect(attr('cal-weeknums')).toBeNull();
settings({ showWeekNumbers: true });
boot('/en/calendar');
expect(attr('cal-weeknums')).toBe('on');
boot('/en/mail');
expect(attr('cal-weeknums')).toBeNull();
});
it('marks calendar tasks as on', () => {
boot('/en/calendar');
expect(attr('cal-tasks')).toBeNull();
settings({ enableCalendarTasks: true });
boot('/en/calendar');
expect(attr('cal-tasks')).toBe('on');
boot('/en/contacts');
expect(attr('cal-tasks')).toBeNull();
});
});
describe('boot script: settings', () => {
it('opens the page a URL names, migrating legacy ids', () => {
boot('/en/settings/themes');
expect(attr('settings-tab')).toBe('appearance');
expect(attr('pane')).toBe('detail');
boot('/en/settings/filters/rules');
expect(attr('settings-tab')).toBe('filters');
});
it('falls back to the stored tab for an unknown or missing URL tab', () => {
localStorage.setItem('settings-active-tab', 'keywords');
boot('/en/settings/no-such-tab');
expect(attr('settings-tab')).toBe('organize');
expect(attr('pane')).toBeNull();
boot('/en/settings');
expect(attr('settings-tab')).toBe('organize');
});
it('defaults to appearance, and honours the one-shot sidebar gear link', () => {
boot('/en/settings');
expect(attr('settings-tab')).toBe('appearance');
sessionStorage.setItem('settings-deep-link-tab', 'folders');
boot('/en/settings');
expect(attr('settings-tab')).toBe('organize');
expect(attr('pane')).toBe('detail');
});
});
describe('boot script: pages that lead elsewhere', () => {
it.each([
['/connector/app', 'mail', null],
['/connector/mail_folder?ref=inbox', 'mail', null],
['/connector/contacts', 'contacts', null],
['/connector/files?path=/Docs', 'files', null],
['/connector/setup', 'setup', null],
['/connector/admin?tab=logs', 'admin', 'detail'],
['/connector/admin', 'admin', null],
['/connector/admin_plugin?id=x', 'admin', 'detail'],
])('%s draws %s', (path, surface, pane) => {
boot(path);
expect(attr('surface')).toBe(surface);
expect(attr('via')).toBe('connector');
expect(attr('pane')).toBe(pane);
});
it('draws a connector calendar link in its view and a settings link on its page', () => {
boot('/connector/calendar?view=agenda&date=2026-11-04');
expect(attr('surface')).toBe('calendar');
expect(attr('calendar-view')).toBe('agenda');
boot('/connector/calendar');
expect(attr('calendar-view')).toBe('month');
boot('/connector/settings?tab=templates');
expect(attr('surface')).toBe('settings');
expect(attr('settings-tab')).toBe('composing');
});
it('leaves an unknown connector target without a surface', () => {
boot('/connector/nope');
expect(attr('surface')).toBeNull();
});
it('draws mail behind mailto and the calendar behind webcal', () => {
boot('/protocol/mailto?url=mailto%3Aa%40b.c');
expect(attr('surface')).toBe('mail');
expect(attr('via')).toBe('mailto');
boot('/protocol/webcal?url=webcal%3A%2F%2Fx');
expect(attr('surface')).toBe('calendar');
expect(attr('via')).toBe('webcal');
});
it.each([
['/admin', 'admin', null],
['/admin/branding', 'admin', 'detail'],
['/admin/login', 'admin-login', null],
['/setup', 'setup', null],
['/en/login', 'login', null],
['/en/pro', 'pro', null],
['/en/apps/plugin-x/page-y', 'apps', null],
])('%s is %s', (path, surface, pane) => {
boot(path);
expect(attr('surface')).toBe(surface);
expect(attr('pane')).toBe(pane);
});
});
describe('boot script: Pro interface', () => {
it('predicts the hop to /pro on a desktop with the Pro interface on', () => {
settings({ proInterface: true });
boot('/en/mail/folder/inbox');
expect(attr('pro')).toBe('on');
boot('/en/settings/appearance');
expect(attr('pro')).toBe('on');
boot('/en/pro');
expect(attr('pro')).toBe('on');
});
it('not on a narrow screen, not without the setting, not for other pages', () => {
settings({ proInterface: true });
desktop(false);
boot('/en/mail');
expect(attr('pro')).toBeNull();
desktop(true);
boot('/admin');
expect(attr('pro')).toBeNull();
settings({ proInterface: false });
boot('/en/mail');
expect(attr('pro')).toBeNull();
});
});
describe('boot script: Pro tabs', () => {
const tab = (id: string, kind: string, paneId = 'main') => ({ id, kind, labelKey: kind, closeable: id !== 'home-mail', paneId });
const proTabs = (state: Record) =>
localStorage.setItem('pro-tabs', JSON.stringify({ state, version: 4 }));
const pro = () => ({
split: attr('pro-split'),
focus: attr('pro-focus'),
kind: attr('pro-kind'),
main: [attr('pro-main-tabs'), attr('pro-main-active'), attr('pro-main-kind')],
side: [attr('pro-split-tabs'), attr('pro-split-active'), attr('pro-split-kind')],
});
beforeEach(() => settings({ proInterface: true }));
it('draws the single home mail tab when nothing is stored', () => {
boot('/en/pro');
expect(pro()).toEqual({ split: null, focus: 'main', kind: 'mail', main: ['1', '0', 'mail'], side: [null, null, null] });
});
it('falls back to the home mail tab for corrupt or empty tabs', () => {
localStorage.setItem('pro-tabs', '{not json');
boot('/en/pro');
expect(pro().main).toEqual(['1', '0', 'mail']);
proTabs({ tabs: 'nope', activeTabId: 3 });
boot('/en/pro');
expect(pro().main).toEqual(['1', '0', 'mail']);
proTabs({ tabs: [null, { id: 'x' }, 7], activeTabId: 'x' });
boot('/en/pro');
expect(pro().main).toEqual(['1', '0', 'mail']);
});
it('reads the tab count, the active tab and the split', () => {
proTabs({
tabs: [tab('home-mail', 'mail'), tab('c', 'calendar'), tab('f', 'folder'), tab('s', 'settings', 'split'), tab('e', 'email', 'split')],
activeTabId: 'c',
activeSplitTabId: 'e',
focusedPaneId: 'split',
splitSide: 'left',
});
boot('/en/pro');
expect(pro()).toEqual({
split: 'left', focus: 'split', kind: 'email',
main: ['3', '1', 'calendar'],
side: ['2', '1', 'other'],
});
});
it('gives the calendar and settings variants to a pane that shows them', () => {
proTabs({ tabs: [tab('home-mail', 'mail'), tab('c', 'calendar'), tab('s', 'settings', 'split')], activeTabId: 'c', activeSplitTabId: 's' });
localStorage.setItem('calendar-storage', JSON.stringify({ state: { viewMode: 'week' }, version: 0 }));
localStorage.setItem('settings-active-tab', 'filters');
boot('/en/pro');
expect(attr('calendar-view')).toBe('week');
expect(cssVar('--boot-cal-cols')).toBe('7');
expect(attr('settings-tab')).toBe('filters');
expect(attr('pane')).toBeNull();
});
it('merges a split pane into an empty main pane', () => {
proTabs({ tabs: [tab('c', 'calendar', 'split'), tab('f', 'files', 'split')], activeTabId: 'gone', activeSplitTabId: 'f', focusedPaneId: 'split' });
boot('/en/pro');
expect(pro()).toEqual({ split: null, focus: 'main', kind: 'files', main: ['2', '1', 'files'], side: [null, null, null] });
});
it('focuses the tab the redirect opens for a standard page', () => {
proTabs({
tabs: [tab('home-mail', 'mail'), tab('c', 'calendar', 'split')],
activeTabId: 'home-mail',
activeSplitTabId: 'c',
focusedPaneId: 'main',
});
boot('/en/calendar/week/2026-11-04');
expect(pro()).toMatchObject({ split: 'right', focus: 'split', kind: 'calendar', main: ['1', '0', 'mail'], side: ['1', '0', 'calendar'] });
expect(attr('calendar-view')).toBe('week');
// No contacts tab yet: a new one at the end of the focused pane.
boot('/en/contacts');
expect(pro()).toMatchObject({ focus: 'main', kind: 'contacts', main: ['2', '1', 'contacts'], side: ['1', '0', 'calendar'] });
});
it('caps the drawn tabs at eight', () => {
const many = Array.from({ length: 12 }, (_, i) => tab(`t${i}`, i === 11 ? 'files' : 'email'));
proTabs({ tabs: many, activeTabId: 't11' });
boot('/en/pro');
expect(pro().main).toEqual(['8', '7', 'files']);
});
it('sets nothing when Pro does not take the page over', () => {
proTabs({ tabs: [tab('home-mail', 'mail'), tab('c', 'calendar', 'split')], activeSplitTabId: 'c' });
desktop(false);
boot('/en/mail');
expect(pro()).toEqual({ split: null, focus: null, kind: null, main: [null, null, null], side: [null, null, null] });
expect(attr('calendar-view')).toBeNull();
});
});
describe('boot script: signed-out visitors', () => {
it('marks a visitor with nothing stored as signed out on app pages', () => {
boot('/en/mail');
expect(attr('auth')).toBe('out');
boot('/en/calendar/week');
expect(attr('auth')).toBe('out');
});
it('not on the login, admin or setup pages', () => {
for (const path of ['/en/login', '/admin', '/admin/login', '/setup']) {
boot(path);
expect(attr('auth')).toBeNull();
}
});
it('not with a persisted session, a remembered account, a demo account or an impersonation hand-off', () => {
signedIn();
boot('/en/mail');
expect(attr('auth')).toBeNull();
localStorage.clear();
localStorage.setItem('account-registry', JSON.stringify({ state: { accounts: [{ id: 'a', rememberMe: true }] } }));
boot('/en/mail');
expect(attr('auth')).toBeNull();
localStorage.setItem('account-registry', JSON.stringify({ state: { accounts: [{ id: 'd', rememberMe: false, serverUrl: 'https://demo.example.com' }] } }));
boot('/en/mail');
expect(attr('auth')).toBeNull();
localStorage.clear();
boot('/en/mail?impersonated=1');
expect(attr('auth')).toBeNull();
});
it('counts an account that will be dropped (no remember me) as signed out', () => {
localStorage.setItem('account-registry', JSON.stringify({ state: { accounts: [{ id: 'a', rememberMe: false }] } }));
boot('/en/mail');
expect(attr('auth')).toBe('out');
boot('/en/mail', { lite: true });
expect(attr('auth')).toBeNull();
});
});
describe('boot script: rail hints', () => {
it('passes the stored rail items on, dropping anything odd', () => {
localStorage.setItem('bulwark-boot-hints', JSON.stringify({ v: 1, rail: ['mail', 'calendar', 'quota', '', 3] }));
boot('/en/mail');
expect(attr('rail')).toBe('mail calendar quota');
});
it('passes the settings pages the nav listed on, known ids only', () => {
localStorage.setItem('bulwark-boot-hints', JSON.stringify({ v: 1, rail: ['mail'], settings: ['account', 'filters', 'recovery', 'plugin:x', 'nope', 7] }));
boot('/en/settings');
expect(attr('settings-tabs')).toBe('account filters recovery');
});
it('leaves the settings pages out without hints', () => {
boot('/en/settings');
expect(attr('settings-tabs')).toBeNull();
});
});
describe('boot script: Lite', () => {
it('reads a parked deep link instead of the surface root it was replayed on', () => {
sessionStorage.setItem('bulwark-lite:pending-path', '/de/calendar/day/2026-11-04');
boot('/de/calendar/', { lite: true });
expect(attr('surface')).toBe('calendar');
expect(attr('calendar-view')).toBe('day');
});
it('ignores the parked path outside Lite', () => {
sessionStorage.setItem('bulwark-lite:pending-path', '/de/calendar/day/2026-11-04');
localStorage.setItem('calendar-storage', JSON.stringify({ state: { viewMode: 'week' } }));
boot('/de/calendar/');
expect(attr('calendar-view')).toBe('week');
});
it('strips the base path and the Lite mount', () => {
boot('/webmail/en/files/Docs', { basePath: '/webmail' });
expect(attr('surface')).toBe('files');
(window as unknown as Record).__BULWARK_LITE_MOUNT__ = '/bulwark';
try {
boot('/bulwark/en/settings/filters', { lite: true });
expect(attr('surface')).toBe('settings');
expect(attr('settings-tab')).toBe('filters');
} finally {
delete (window as unknown as Record).__BULWARK_LITE_MOUNT__;
}
});
});
describe('boot script: cached theme', () => {
it('puts the cached theme and skin back before the page renders', () => {
localStorage.setItem(THEME_CACHE_KEY, JSON.stringify({ v: 1, css: ':root{--color-primary:red}', skin: 'html[data-theme-skin="t1"] body{font-family:x}', skinId: 't1' }));
boot('/en/mail');
expect(document.getElementById('active-theme')?.textContent).toBe(':root{--color-primary:red}');
expect(document.getElementById('active-theme-skin')?.textContent).toContain('font-family:x');
expect(html().getAttribute('data-theme-skin')).toBe('t1');
});
it('reuses existing elements instead of adding a second copy', () => {
localStorage.setItem(THEME_CACHE_KEY, JSON.stringify({ v: 1, css: 'a{}' }));
boot('/en/mail');
boot('/en/mail');
expect(document.querySelectorAll('#active-theme')).toHaveLength(1);
});
it('ignores a cache from another format version, or a skin without its id', () => {
localStorage.setItem(THEME_CACHE_KEY, JSON.stringify({ v: 2, css: 'a{}' }));
boot('/en/mail');
expect(document.getElementById('active-theme')).toBeNull();
localStorage.setItem(THEME_CACHE_KEY, JSON.stringify({ v: 1, skin: 'b{}' }));
boot('/en/mail');
expect(document.getElementById('active-theme-skin')).toBeNull();
});
});