Component architecture - Reorganise src/lib/components into pages/, sections/, ui/ subdirectories and update all imports across routes and tests. Owner admin dashboard (+2k lines) - Scheduled welcome-pack emails: durable queue with cancel/reschedule and a background sender loop (SCHEDULED_CHECK_INTERVAL_SECONDS, default 60s). - Custom welcome-pack subject line, preview recipients, and client BCC. - Add-client, edit client profile, and reset-onboarding flows. - "View as client" onboarding preview (owner impersonation, dry-run submit). - Tabbed owner welcome route (/owner/welcome/[[tab]]). MYOB integration - New mail_api/myob.py: create new clients as MYOB AccountRight customer contacts. Disabled until all MYOB_* env vars are set (no-op otherwise). Onboarding - New "Does your dog resource guard?" Yes/No question in the Behaviour step. - Persist vetAddress, flea/tick, and pet-insurance fields server-side. Misc - vite config, new hooks.ts, responsive CSS tweaks, deploy/docker config, mail-api README and start-dev.ps1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
2.5 KiB
TypeScript
69 lines
2.5 KiB
TypeScript
import { fireEvent, render } from '@testing-library/svelte';
|
|
import { describe, expect, it } from 'vitest';
|
|
import Header from './Header.svelte';
|
|
import { homepageContent } from '$lib/content/homepage';
|
|
import { setMockPage } from '../../../test/mocks/app-stores';
|
|
|
|
describe('Header', () => {
|
|
it('marks the services link active for service detail pages', () => {
|
|
setMockPage('https://www.goodwalk.co.nz/pack-walks');
|
|
|
|
const { container } = render(Header, {
|
|
navigation: homepageContent.navigation
|
|
});
|
|
|
|
expect(container.querySelector('a.nav-link-active[href="#services"]')).toBeTruthy();
|
|
});
|
|
|
|
it('opens and closes the mobile menu', async () => {
|
|
Object.defineProperty(window, 'innerWidth', {
|
|
configurable: true,
|
|
writable: true,
|
|
value: 390
|
|
});
|
|
|
|
const { container } = render(Header, {
|
|
navigation: homepageContent.navigation
|
|
});
|
|
|
|
const menuToggle = container.querySelector('.hamburger') as HTMLButtonElement;
|
|
const mobileMenu = container.querySelector('.mobile-menu') as HTMLDivElement;
|
|
const mobileMenuShell = container.querySelector('.mobile-menu-shell') as HTMLDivElement;
|
|
const firstMobileLink = mobileMenu.querySelector('a') as HTMLAnchorElement;
|
|
|
|
expect(menuToggle).toHaveAttribute('aria-expanded', 'false');
|
|
|
|
await fireEvent.click(menuToggle);
|
|
expect(menuToggle).toHaveAttribute('aria-expanded', 'true');
|
|
expect(mobileMenuShell.classList.contains('open')).toBe(true);
|
|
|
|
await fireEvent.click(firstMobileLink);
|
|
expect(menuToggle).toHaveAttribute('aria-expanded', 'false');
|
|
expect(mobileMenuShell.classList.contains('open')).toBe(false);
|
|
});
|
|
|
|
it('closes the mobile menu when tapping outside the menu panel', async () => {
|
|
Object.defineProperty(window, 'innerWidth', {
|
|
configurable: true,
|
|
writable: true,
|
|
value: 390
|
|
});
|
|
|
|
const { container } = render(Header, {
|
|
navigation: homepageContent.navigation
|
|
});
|
|
|
|
const menuToggle = container.querySelector('.hamburger') as HTMLButtonElement;
|
|
const mobileMenuShell = container.querySelector('.mobile-menu-shell') as HTMLDivElement;
|
|
const mobileMenuBackdrop = container.querySelector('.mobile-menu-backdrop') as HTMLButtonElement;
|
|
|
|
await fireEvent.click(menuToggle);
|
|
expect(menuToggle).toHaveAttribute('aria-expanded', 'true');
|
|
expect(mobileMenuShell.classList.contains('open')).toBe(true);
|
|
|
|
await fireEvent.click(mobileMenuBackdrop);
|
|
expect(menuToggle).toHaveAttribute('aria-expanded', 'false');
|
|
expect(mobileMenuShell.classList.contains('open')).toBe(false);
|
|
});
|
|
});
|