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:
25
frontend/tests/component/app-layout.test.ts
Normal file
25
frontend/tests/component/app-layout.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import AppLayout, { getContentOffsetClass } from '../../src/lib/components/layout/AppLayout.svelte';
|
||||
|
||||
describe('AppLayout component', () => {
|
||||
it('exports a component module', () => {
|
||||
expect(AppLayout).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders children via slot', () => {
|
||||
const source = readFileSync(
|
||||
resolve(process.cwd(), 'src/lib/components/layout/AppLayout.svelte'),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
expect(source).toContain('<slot />');
|
||||
});
|
||||
|
||||
it('maps sidebar states to layout offsets', () => {
|
||||
expect(getContentOffsetClass('expanded')).toBe('md:ml-60');
|
||||
expect(getContentOffsetClass('collapsed')).toBe('md:ml-16');
|
||||
expect(getContentOffsetClass('hidden')).toBe('md:ml-0');
|
||||
});
|
||||
});
|
||||
14
frontend/tests/component/breadcrumbs.test.ts
Normal file
14
frontend/tests/component/breadcrumbs.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import Breadcrumbs, { generateBreadcrumbs } from '../../src/lib/components/layout/Breadcrumbs.svelte';
|
||||
|
||||
describe('Breadcrumbs component', () => {
|
||||
it('exports a component module', () => {
|
||||
expect(Breadcrumbs).toBeDefined();
|
||||
});
|
||||
|
||||
it('generates correct crumbs', () => {
|
||||
const crumbs = generateBreadcrumbs('/reports/allocation-matrix');
|
||||
expect(crumbs.map((crumb) => crumb.label)).toEqual(['Home', 'Reports', 'Allocation Matrix']);
|
||||
expect(crumbs[2].href).toBe('/reports/allocation-matrix');
|
||||
});
|
||||
});
|
||||
9
frontend/tests/component/lucide-icon.test.ts
Normal file
9
frontend/tests/component/lucide-icon.test.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { Menu } from 'lucide-svelte';
|
||||
|
||||
describe('lucide icon', () => {
|
||||
it('exports menu icon component', () => {
|
||||
expect(Menu).toBeDefined();
|
||||
expect(typeof Menu).toBe('function');
|
||||
});
|
||||
});
|
||||
37
frontend/tests/component/month-selector.test.ts
Normal file
37
frontend/tests/component/month-selector.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import MonthSelector, {
|
||||
formatMonth,
|
||||
generateMonthOptions
|
||||
} from '../../src/lib/components/layout/MonthSelector.svelte';
|
||||
import { selectedPeriod, setPeriod } from '../../src/lib/stores/period';
|
||||
|
||||
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('MonthSelector component', () => {
|
||||
it('exports a component module', () => {
|
||||
expect(MonthSelector).toBeDefined();
|
||||
});
|
||||
|
||||
it('formats month labels', () => {
|
||||
expect(formatMonth('2026-02')).toBe('Feb 2026');
|
||||
});
|
||||
|
||||
it('builds +/- 6 month options', () => {
|
||||
const options = generateMonthOptions(new Date(2026, 1, 1));
|
||||
expect(options).toHaveLength(13);
|
||||
expect(options[0]).toBe('2025-08');
|
||||
expect(options[12]).toBe('2026-08');
|
||||
});
|
||||
|
||||
it('selection updates period store', () => {
|
||||
setPeriod('2026-02');
|
||||
expect(getStoreValue(selectedPeriod)).toBe('2026-02');
|
||||
});
|
||||
});
|
||||
9
frontend/tests/component/sidebar-item.test.ts
Normal file
9
frontend/tests/component/sidebar-item.test.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import SidebarItem from '../../src/lib/components/layout/SidebarItem.svelte';
|
||||
|
||||
describe('SidebarItem component', () => {
|
||||
it('exports a component module', () => {
|
||||
expect(SidebarItem).toBeDefined();
|
||||
expect(typeof SidebarItem).toBe('function');
|
||||
});
|
||||
});
|
||||
9
frontend/tests/component/sidebar-section.test.ts
Normal file
9
frontend/tests/component/sidebar-section.test.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import SidebarSection from '../../src/lib/components/layout/SidebarSection.svelte';
|
||||
|
||||
describe('SidebarSection component', () => {
|
||||
it('exports a component module', () => {
|
||||
expect(SidebarSection).toBeDefined();
|
||||
expect(typeof SidebarSection).toBe('function');
|
||||
});
|
||||
});
|
||||
14
frontend/tests/component/sidebar.test.ts
Normal file
14
frontend/tests/component/sidebar.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import Sidebar, { isSectionVisible } from '../../src/lib/components/layout/Sidebar.svelte';
|
||||
|
||||
describe('Sidebar component', () => {
|
||||
it('exports a component module', () => {
|
||||
expect(Sidebar).toBeDefined();
|
||||
});
|
||||
|
||||
it('supports role-based visibility checks', () => {
|
||||
expect(isSectionVisible({ title: 'ADMIN', roles: ['superuser'], items: [] }, 'superuser')).toBe(true);
|
||||
expect(isSectionVisible({ title: 'ADMIN', roles: ['superuser'], items: [] }, 'manager')).toBe(false);
|
||||
expect(isSectionVisible({ title: 'PLANNING', items: [] }, null)).toBe(true);
|
||||
});
|
||||
});
|
||||
21
frontend/tests/component/topbar.test.ts
Normal file
21
frontend/tests/component/topbar.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import TopBar from '../../src/lib/components/layout/TopBar.svelte';
|
||||
|
||||
describe('TopBar component', () => {
|
||||
it('exports a component module', () => {
|
||||
expect(TopBar).toBeDefined();
|
||||
});
|
||||
|
||||
it('includes breadcrumbs, month selector, and user menu', () => {
|
||||
const source = readFileSync(
|
||||
resolve(process.cwd(), 'src/lib/components/layout/TopBar.svelte'),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
expect(source).toContain('<Breadcrumbs />');
|
||||
expect(source).toContain('<MonthSelector />');
|
||||
expect(source).toContain('<UserMenu />');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user