feat(layout): finalize p01 and p02 changes
Complete UI foundation and app layout implementation, stabilize container health checks, and archive both OpenSpec changes after verification.
This commit is contained in:
55
frontend/tests/unit/layout.store.test.ts
Normal file
55
frontend/tests/unit/layout.store.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
||||
|
||||
function getStoreValue<T>(store: { subscribe: (run: (value: T) => void) => () => void }): T {
|
||||
let value!: T;
|
||||
const unsubscribe = store.subscribe((current) => {
|
||||
value = current;
|
||||
});
|
||||
unsubscribe();
|
||||
return value;
|
||||
}
|
||||
|
||||
describe('layout store', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
(localStorage.getItem as Mock).mockReturnValue(null);
|
||||
});
|
||||
|
||||
it('initializes with default values', async () => {
|
||||
const store = await import('../../src/lib/stores/layout');
|
||||
|
||||
expect(getStoreValue(store.sidebarState)).toBe('expanded');
|
||||
expect(getStoreValue(store.theme)).toBe('light');
|
||||
});
|
||||
|
||||
it('toggleSidebar cycles through states', async () => {
|
||||
const store = await import('../../src/lib/stores/layout');
|
||||
|
||||
store.setSidebarState('expanded');
|
||||
store.toggleSidebar();
|
||||
expect(getStoreValue(store.sidebarState)).toBe('collapsed');
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('headroom_sidebar_state', 'collapsed');
|
||||
|
||||
store.toggleSidebar();
|
||||
expect(getStoreValue(store.sidebarState)).toBe('hidden');
|
||||
|
||||
store.toggleSidebar();
|
||||
expect(getStoreValue(store.sidebarState)).toBe('expanded');
|
||||
});
|
||||
|
||||
it('theme toggle works and applies to document', async () => {
|
||||
const store = await import('../../src/lib/stores/layout');
|
||||
|
||||
store.setTheme('light');
|
||||
store.toggleTheme();
|
||||
expect(getStoreValue(store.theme)).toBe('dark');
|
||||
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('headroom_theme', 'dark');
|
||||
|
||||
store.toggleTheme();
|
||||
expect(getStoreValue(store.theme)).toBe('light');
|
||||
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('headroom_theme', 'light');
|
||||
});
|
||||
});
|
||||
17
frontend/tests/unit/navigation.config.test.ts
Normal file
17
frontend/tests/unit/navigation.config.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { navigationSections } from '../../src/lib/config/navigation';
|
||||
|
||||
describe('navigation config', () => {
|
||||
it('has expected section structure', () => {
|
||||
expect(navigationSections).toHaveLength(3);
|
||||
|
||||
expect(navigationSections[0].title).toBe('PLANNING');
|
||||
expect(navigationSections[0].items).toHaveLength(5);
|
||||
|
||||
expect(navigationSections[1].title).toBe('REPORTS');
|
||||
expect(navigationSections[1].items).toHaveLength(5);
|
||||
|
||||
expect(navigationSections[2].title).toBe('ADMIN');
|
||||
expect(navigationSections[2].roles).toEqual(['superuser']);
|
||||
});
|
||||
});
|
||||
44
frontend/tests/unit/period.store.test.ts
Normal file
44
frontend/tests/unit/period.store.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
||||
|
||||
function getStoreValue<T>(store: { subscribe: (run: (value: T) => void) => () => void }): T {
|
||||
let value!: T;
|
||||
const unsubscribe = store.subscribe((current) => {
|
||||
value = current;
|
||||
});
|
||||
unsubscribe();
|
||||
return value;
|
||||
}
|
||||
|
||||
describe('period store', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
(localStorage.getItem as Mock).mockReturnValue(null);
|
||||
});
|
||||
|
||||
it('initializes with current month', async () => {
|
||||
const store = await import('../../src/lib/stores/period');
|
||||
|
||||
const now = new Date();
|
||||
const expected = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
expect(getStoreValue(store.selectedPeriod)).toBe(expected);
|
||||
});
|
||||
|
||||
it('previousMonth decrements correctly', async () => {
|
||||
const store = await import('../../src/lib/stores/period');
|
||||
|
||||
store.setPeriod('2025-01');
|
||||
store.previousMonth();
|
||||
|
||||
expect(getStoreValue(store.selectedPeriod)).toBe('2024-12');
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('headroom_selected_period', '2024-12');
|
||||
});
|
||||
|
||||
it('nextMonth increments correctly', async () => {
|
||||
const store = await import('../../src/lib/stores/period');
|
||||
|
||||
store.setPeriod('2025-12');
|
||||
store.nextMonth();
|
||||
|
||||
expect(getStoreValue(store.selectedPeriod)).toBe('2026-01');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user