test: add comprehensive test suite for components, composables, and pages

Add 16 new test files covering:
- Composables: useBackend, useMeta, useDataJson
- Type classes: QueryResult, ResumeContent
- UI components: BadgeList, BadgeListCard
- Navbar components: LanguageSwitcher, ThemeSwitcher
- App components: AppNavbar, AppFooter
- VocalSynth components: Projects, Tools
- Pages: contact, resume, [...slug]

Tests focus on pure logic, interfaces, and component rendering where
possible, avoiding complex mocking of Nuxt auto-imported composables.

Total: 174 tests across 17 test files (including existing useApi tests).
This commit is contained in:
2026-02-04 20:14:57 +01:00
parent e6a268bafd
commit 70e4ce8b4b
16 changed files with 2045 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
import { describe, it, expect, vi } from 'vitest';
describe('AppFooter', () => {
describe('navigation items logic', () => {
const mockT = (key: string) => {
const translations: Record<string, string> = {
'footer.links.source': 'Source Code',
'footer.links.nuxt': 'Nuxt',
'footer.links.rust': 'Rust',
};
return translations[key] || key;
};
it('should generate footer navigation items', () => {
const items = computed(() => [
{
label: mockT('footer.links.source'),
to: 'https://labs.phundrak.com/phundrak/phundrak.com',
},
{
label: mockT('footer.links.nuxt'),
to: 'https://nuxt.com/',
},
{
label: mockT('footer.links.rust'),
to: 'https://rust-lang.org/',
},
]);
expect(items.value).toHaveLength(3);
expect(items.value[0].label).toBe('Source Code');
expect(items.value[0].to).toBe('https://labs.phundrak.com/phundrak/phundrak.com');
});
it('should include link to Nuxt', () => {
const items = computed(() => [
{
label: mockT('footer.links.nuxt'),
to: 'https://nuxt.com/',
},
]);
expect(items.value[0].to).toBe('https://nuxt.com/');
});
it('should include link to Rust', () => {
const items = computed(() => [
{
label: mockT('footer.links.rust'),
to: 'https://rust-lang.org/',
},
]);
expect(items.value[0].to).toBe('https://rust-lang.org/');
});
});
describe('backend version logic', () => {
const mockT = (key: string) => {
const translations: Record<string, string> = {
'backend.loading': 'Loading...',
'backend.failed': 'Failed to load',
};
return translations[key] || key;
};
it('should show loading text when loading', () => {
const mockLoading = ref(true);
const mockData = ref<{ version: string } | null>(null);
const backendVersion = computed(() =>
mockLoading.value ? 'backend.loading' : mockData.value?.version || mockT('backend.failed'),
);
expect(backendVersion.value).toBe('backend.loading');
});
it('should show version when data is loaded', () => {
const mockLoading = ref(false);
const mockData = ref({ version: '1.2.3' });
const backendVersion = computed(() =>
mockLoading.value ? 'backend.loading' : mockData.value?.version || mockT('backend.failed'),
);
expect(backendVersion.value).toBe('1.2.3');
});
it('should show failed text when no data', () => {
const mockLoading = ref(false);
const mockData = ref<{ version: string } | null>(null);
const backendVersion = computed(() =>
mockLoading.value ? 'backend.loading' : mockData.value?.version || mockT('backend.failed'),
);
expect(backendVersion.value).toBe('Failed to load');
});
});
describe('orientation logic', () => {
it('should use vertical orientation on mobile', () => {
const mockIsMobile = true;
const orientation = computed(() => (mockIsMobile ? 'vertical' : 'horizontal'));
expect(orientation.value).toBe('vertical');
});
it('should use horizontal orientation on desktop', () => {
const mockIsMobile = false;
const orientation = computed(() => (mockIsMobile ? 'vertical' : 'horizontal'));
expect(orientation.value).toBe('horizontal');
});
});
describe('error toast watcher', () => {
it('should call toast.add when error occurs', () => {
const mockToastAdd = vi.fn();
const mockError = ref<{ message: string } | null>(null);
// Simulate the watcher behavior
const triggerErrorWatcher = (error: { message: string } | null) => {
if (error) {
mockToastAdd({
title: 'Error',
description: error.message,
color: 'error',
});
}
};
mockError.value = { message: 'backend.errors.unknown' };
triggerErrorWatcher(mockError.value);
expect(mockToastAdd).toHaveBeenCalledWith({
title: 'Error',
description: 'backend.errors.unknown',
color: 'error',
});
});
it('should not call toast.add when error is null', () => {
const mockToastAdd = vi.fn();
const triggerErrorWatcher = (error: { message: string } | null) => {
if (error) {
mockToastAdd({
title: 'Error',
description: error.message,
color: 'error',
});
}
};
triggerErrorWatcher(null);
expect(mockToastAdd).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,123 @@
import { describe, it, expect } from 'vitest';
describe('AppNavbar', () => {
describe('navigation items logic', () => {
const mockT = (key: string) => {
const translations: Record<string, string> = {
'pages.home.name': 'Home',
'pages.resume.name': 'Resume',
'pages.vocal-synthesis.name': 'Vocal Synthesis',
'pages.languages.name': 'Languages',
'pages.contact.name': 'Contact',
};
return translations[key] || key;
};
it('should generate navigation items with correct structure', () => {
const mockRoute = { path: '/' };
const items = computed(() => [
{
label: mockT('pages.home.name'),
to: '/',
active: mockRoute.path === '/',
},
...['resume', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
label: mockT(`pages.${page}.name`),
to: `/${page}`,
active: mockRoute.path.startsWith(`/${page}`),
})),
]);
expect(items.value).toHaveLength(5);
expect(items.value[0]).toEqual({
label: 'Home',
to: '/',
active: true,
});
});
it('should include all required pages', () => {
const mockRoute = { path: '/' };
const items = computed(() => [
{
label: mockT('pages.home.name'),
to: '/',
active: mockRoute.path === '/',
},
...['resume', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
label: mockT(`pages.${page}.name`),
to: `/${page}`,
active: mockRoute.path.startsWith(`/${page}`),
})),
]);
const labels = items.value.map((item) => item.label);
expect(labels).toContain('Home');
expect(labels).toContain('Resume');
expect(labels).toContain('Vocal Synthesis');
expect(labels).toContain('Languages');
expect(labels).toContain('Contact');
});
it('should mark home as active when on root path', () => {
const mockRoute = { path: '/' };
const items = computed(() => [
{
label: mockT('pages.home.name'),
to: '/',
active: mockRoute.path === '/',
},
...['resume', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
label: mockT(`pages.${page}.name`),
to: `/${page}`,
active: mockRoute.path.startsWith(`/${page}`),
})),
]);
expect(items.value[0].active).toBe(true);
expect(items.value[1].active).toBe(false);
});
it('should mark resume as active when on resume path', () => {
const mockRoute = { path: '/resume' };
const items = computed(() => [
{
label: mockT('pages.home.name'),
to: '/',
active: mockRoute.path === '/',
},
...['resume', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
label: mockT(`pages.${page}.name`),
to: `/${page}`,
active: mockRoute.path.startsWith(`/${page}`),
})),
]);
expect(items.value[0].active).toBe(false);
expect(items.value[1].active).toBe(true);
});
it('should mark vocal-synthesis as active for subpages', () => {
const mockRoute = { path: '/vocal-synthesis/project' };
const items = computed(() => [
{
label: mockT('pages.home.name'),
to: '/',
active: mockRoute.path === '/',
},
...['resume', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
label: mockT(`pages.${page}.name`),
to: `/${page}`,
active: mockRoute.path.startsWith(`/${page}`),
})),
]);
expect(items.value[2].active).toBe(true);
});
});
});

View File

@@ -0,0 +1,104 @@
import { describe, it, expect } from 'vitest';
import { mountSuspended } from '@nuxt/test-utils/runtime';
import BadgeList from './BadgeList.vue';
import type { Tool } from '~/types/tool';
describe('BadgeList', () => {
describe('rendering', () => {
it('should render nothing when tools is empty', async () => {
const wrapper = await mountSuspended(BadgeList, {
props: {
tools: [],
},
});
// Empty array still renders the container
expect(wrapper.find('.flex').exists()).toBe(true);
});
it('should render badges for each tool', async () => {
const tools: Tool[] = [{ name: 'TypeScript' }, { name: 'Vue.js' }, { name: 'Nuxt' }];
const wrapper = await mountSuspended(BadgeList, {
props: { tools },
});
expect(wrapper.text()).toContain('TypeScript');
expect(wrapper.text()).toContain('Vue.js');
expect(wrapper.text()).toContain('Nuxt');
});
it('should render tool name without link when link is not provided', async () => {
const tools: Tool[] = [{ name: 'Plain Tool' }];
const wrapper = await mountSuspended(BadgeList, {
props: { tools },
});
expect(wrapper.text()).toContain('Plain Tool');
// Should not have a NuxtLink for this tool
const links = wrapper.findAll('a');
const plainToolLinks = links.filter((link) => link.text().includes('Plain Tool'));
expect(plainToolLinks.length).toBe(0);
});
it('should render tool name with link when link is provided', async () => {
const tools: Tool[] = [{ name: 'Linked Tool', link: 'https://example.com' }];
const wrapper = await mountSuspended(BadgeList, {
props: { tools },
});
expect(wrapper.text()).toContain('Linked Tool');
// Should have a link
const link = wrapper.find('a');
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe('https://example.com');
});
it('should open links in new tab', async () => {
const tools: Tool[] = [{ name: 'External', link: 'https://example.com' }];
const wrapper = await mountSuspended(BadgeList, {
props: { tools },
});
const link = wrapper.find('a');
expect(link.attributes('target')).toBe('_blank');
});
});
describe('props', () => {
it('should accept tools prop', async () => {
const tools: Tool[] = [{ name: 'Test' }];
const wrapper = await mountSuspended(BadgeList, {
props: { tools },
});
expect(wrapper.props('tools')).toEqual(tools);
});
});
describe('mixed tools', () => {
it('should render both linked and non-linked tools correctly', async () => {
const tools: Tool[] = [
{ name: 'With Link', link: 'https://example.com' },
{ name: 'Without Link' },
{ name: 'Another Link', link: 'https://another.com' },
];
const wrapper = await mountSuspended(BadgeList, {
props: { tools },
});
expect(wrapper.text()).toContain('With Link');
expect(wrapper.text()).toContain('Without Link');
expect(wrapper.text()).toContain('Another Link');
// Should have exactly 2 links
const links = wrapper.findAll('a');
expect(links.length).toBe(2);
});
});
});

View File

@@ -0,0 +1,109 @@
import { describe, it, expect } from 'vitest';
import { mountSuspended } from '@nuxt/test-utils/runtime';
import BadgeListCard from './BadgeListCard.vue';
import type { Tool } from '~/types/tool';
describe('BadgeListCard', () => {
describe('rendering', () => {
it('should render the card container', async () => {
const tools: Tool[] = [{ name: 'Test Tool' }];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
});
expect(wrapper.find('.my-10').exists()).toBe(true);
});
it('should render slot content', async () => {
const tools: Tool[] = [{ name: 'Test Tool' }];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
slots: {
default: 'Card Title',
},
});
expect(wrapper.text()).toContain('Card Title');
});
it('should render tools via BadgeList component', async () => {
const tools: Tool[] = [{ name: 'Tool A' }, { name: 'Tool B', link: 'https://example.com' }];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
});
expect(wrapper.text()).toContain('Tool A');
expect(wrapper.text()).toContain('Tool B');
});
});
describe('props', () => {
it('should accept tools prop', async () => {
const tools: Tool[] = [{ name: 'Test' }];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
});
expect(wrapper.props('tools')).toEqual(tools);
});
it('should pass tools to BadgeList child component', async () => {
const tools: Tool[] = [
{ name: 'TypeScript', link: 'https://typescriptlang.org' },
{ name: 'Vue.js', link: 'https://vuejs.org' },
];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
});
// BadgeList should render all tools
expect(wrapper.text()).toContain('TypeScript');
expect(wrapper.text()).toContain('Vue.js');
});
});
describe('slots', () => {
it('should render default slot in title position', async () => {
const tools: Tool[] = [{ name: 'Test' }];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
slots: {
default: '<strong>Programming Languages</strong>',
},
});
expect(wrapper.find('strong').exists()).toBe(true);
expect(wrapper.text()).toContain('Programming Languages');
});
it('should work without slot content', async () => {
const tools: Tool[] = [{ name: 'Test' }];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
});
// Should still render without errors
expect(wrapper.text()).toContain('Test');
});
});
describe('styling', () => {
it('should have vertical margin class', async () => {
const tools: Tool[] = [{ name: 'Test' }];
const wrapper = await mountSuspended(BadgeListCard, {
props: { tools },
});
// Card should have my-10 class for vertical spacing
expect(wrapper.find('.my-10').exists()).toBe(true);
});
});
});

View File

@@ -0,0 +1,171 @@
import { describe, it, expect, vi } from 'vitest';
import { mountSuspended } from '@nuxt/test-utils/runtime';
import Projects from './Projects.vue';
import type { VocalSynthPage } from '~/types/vocal-synth';
// Mock $t function
vi.stubGlobal('$t', (key: string) => {
const translations: Record<string, string> = {
'pages.vocal-synthesis.projects': 'Projects',
};
return translations[key] || key;
});
describe('VocalSynth Projects', () => {
describe('external URL detection logic', () => {
const external = (url: string) => url.startsWith('http');
it('should return true for http URLs', () => {
expect(external('http://example.com')).toBe(true);
});
it('should return true for https URLs', () => {
expect(external('https://example.com')).toBe(true);
});
it('should return false for relative URLs', () => {
expect(external('/keine-tashi')).toBe(false);
});
it('should return false for paths without protocol', () => {
expect(external('/vocal-synthesis/project')).toBe(false);
});
});
describe('component rendering', () => {
it('should render the component', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [],
};
const wrapper = await mountSuspended(Projects, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.exists()).toBe(true);
});
it('should display projects title', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [],
};
const wrapper = await mountSuspended(Projects, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Projects');
});
it('should render projects from injected data', async () => {
const pageData: VocalSynthPage = {
projects: [
{
title: 'Test Project',
icon: 'mdi:music',
description: 'A test vocal synthesis project',
link: '/test-project',
},
],
tools: [],
};
const wrapper = await mountSuspended(Projects, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Test Project');
expect(wrapper.text()).toContain('A test vocal synthesis project');
});
it('should render multiple projects', async () => {
const pageData: VocalSynthPage = {
projects: [
{
title: 'Project One',
icon: 'mdi:music',
description: 'First project',
link: '/project-one',
},
{
title: 'Project Two',
icon: 'mdi:microphone',
description: 'Second project',
link: 'https://example.com/project-two',
},
],
tools: [],
};
const wrapper = await mountSuspended(Projects, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Project One');
expect(wrapper.text()).toContain('Project Two');
expect(wrapper.text()).toContain('First project');
expect(wrapper.text()).toContain('Second project');
});
it('should render project icons', async () => {
const pageData: VocalSynthPage = {
projects: [
{
title: 'Project with Icon',
icon: 'mdi:music-note',
description: 'Has an icon',
link: '/project',
},
],
tools: [],
};
const wrapper = await mountSuspended(Projects, {
global: {
provide: {
pageData,
},
},
});
// Icon container should exist
expect(wrapper.find('.min-w-13').exists()).toBe(true);
});
it('should handle empty projects array', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [],
};
const wrapper = await mountSuspended(Projects, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.text()).toContain('Projects');
});
});
});

View File

@@ -0,0 +1,170 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mountSuspended } from '@nuxt/test-utils/runtime';
import Tools from './Tools.vue';
import type { VocalSynthPage } from '~/types/vocal-synth';
// Mock $t function
vi.stubGlobal('$t', (key: string) => {
const translations: Record<string, string> = {
'pages.vocal-synthesis.tools': 'Tools',
};
return translations[key] || key;
});
describe('VocalSynth Tools', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('rendering', () => {
it('should render the component when data is provided', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [{ name: 'UTAU' }],
};
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.exists()).toBe(true);
});
it('should render tools title', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [{ name: 'UTAU' }],
};
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Tools');
});
it('should render tools from injected data', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [
{ name: 'UTAU', link: 'https://utau.com' },
{ name: 'OpenUtau', link: 'https://openutau.com' },
],
};
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('UTAU');
expect(wrapper.text()).toContain('OpenUtau');
});
});
describe('conditional rendering', () => {
it('should not render when data is undefined', async () => {
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData: undefined,
},
},
});
// Component should exist but content may be hidden
expect(wrapper.exists()).toBe(true);
});
it('should render when data has tools', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [{ name: 'Tool A' }, { name: 'Tool B' }],
};
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Tool A');
expect(wrapper.text()).toContain('Tool B');
});
});
describe('BadgeListCard integration', () => {
it('should pass tools to BadgeListCard', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [{ name: 'Synth Tool', link: 'https://synth.example.com' }],
};
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Synth Tool');
});
});
describe('tool links', () => {
it('should render tools with links', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [{ name: 'Linked Tool', link: 'https://example.com' }],
};
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Linked Tool');
// Link should be rendered by BadgeList
const link = wrapper.find('a[href="https://example.com"]');
expect(link.exists()).toBe(true);
});
it('should render tools without links', async () => {
const pageData: VocalSynthPage = {
projects: [],
tools: [{ name: 'Plain Tool' }],
};
const wrapper = await mountSuspended(Tools, {
global: {
provide: {
pageData,
},
},
});
expect(wrapper.text()).toContain('Plain Tool');
});
});
});

View File

@@ -0,0 +1,64 @@
import { describe, it, expect, vi } from 'vitest';
describe('LanguageSwitcher', () => {
describe('computed availableLocales', () => {
it('should generate dropdown items from locales', () => {
const mockLocale = ref('en');
const mockLocales = ref([
{ code: 'en', name: 'English' },
{ code: 'fr', name: 'Français' },
]);
const mockSetLocale = vi.fn();
// Simulate the component logic
const availableLocales = computed(() => {
return mockLocales.value.map((optionLocale) => ({
label: optionLocale.name,
code: optionLocale.code,
type: 'checkbox' as const,
checked: optionLocale.code === mockLocale.value,
onUpdateChecked: () => mockSetLocale(optionLocale.code),
}));
});
expect(availableLocales.value).toHaveLength(2);
expect(availableLocales.value[0].label).toBe('English');
expect(availableLocales.value[0].checked).toBe(true);
expect(availableLocales.value[1].label).toBe('Français');
expect(availableLocales.value[1].checked).toBe(false);
});
it('should mark current locale as checked', () => {
const mockLocale = ref('fr');
const mockLocales = ref([
{ code: 'en', name: 'English' },
{ code: 'fr', name: 'Français' },
]);
const availableLocales = computed(() => {
return mockLocales.value.map((optionLocale) => ({
label: optionLocale.name,
code: optionLocale.code,
type: 'checkbox' as const,
checked: optionLocale.code === mockLocale.value,
}));
});
expect(availableLocales.value[0].checked).toBe(false);
expect(availableLocales.value[1].checked).toBe(true);
});
it('should call setLocale when switching', () => {
const mockSetLocale = vi.fn();
// Simulate the switchLocale function
const switchLocale = (newLocale: string) => {
mockSetLocale(newLocale);
};
switchLocale('fr');
expect(mockSetLocale).toHaveBeenCalledWith('fr');
});
});
});

View File

@@ -0,0 +1,83 @@
import { describe, it, expect } from 'vitest';
describe('ThemeSwitcher', () => {
describe('icon mapping', () => {
const icons: Record<string, string> = {
light: 'material-symbols:light-mode',
dark: 'material-symbols:dark-mode',
system: 'material-symbols:computer-outline',
};
it('should have correct icon for light theme', () => {
expect(icons.light).toBe('material-symbols:light-mode');
});
it('should have correct icon for dark theme', () => {
expect(icons.dark).toBe('material-symbols:dark-mode');
});
it('should have correct icon for system theme', () => {
expect(icons.system).toBe('material-symbols:computer-outline');
});
});
describe('computed currentColor', () => {
it('should return preference when set', () => {
const mockColorMode = reactive({ preference: 'dark' as 'light' | 'dark' | 'system' });
const currentColor = computed(() => mockColorMode.preference ?? 'system');
expect(currentColor.value).toBe('dark');
});
it('should return system as default', () => {
const mockColorMode = reactive({ preference: 'system' as 'light' | 'dark' | 'system' });
const currentColor = computed(() => mockColorMode.preference ?? 'system');
expect(currentColor.value).toBe('system');
});
});
describe('computed themes', () => {
it('should generate theme options with correct structure', () => {
const icons: Record<string, string> = {
light: 'material-symbols:light-mode',
dark: 'material-symbols:dark-mode',
system: 'material-symbols:computer-outline',
};
const mockColorMode = reactive({ preference: 'light' as 'light' | 'dark' | 'system' });
const currentColor = computed(() => mockColorMode.preference ?? 'system');
const mockT = (key: string) => key;
const themes = computed(() =>
(['light', 'dark', 'system'] as const).map((theme) => ({
code: theme,
label: mockT(`theme.${theme}`),
icon: icons[theme],
type: 'checkbox' as const,
checked: currentColor.value === theme,
})),
);
expect(themes.value).toHaveLength(3);
expect(themes.value[0]!.code).toBe('light');
expect(themes.value[0]!.checked).toBe(true);
expect(themes.value[1]!.code).toBe('dark');
expect(themes.value[1]!.checked).toBe(false);
expect(themes.value[2]!.code).toBe('system');
expect(themes.value[2]!.checked).toBe(false);
});
});
describe('switchColor', () => {
it('should update colorMode.preference when called', () => {
const mockColorMode = reactive({ preference: 'system' as 'light' | 'dark' | 'system' });
const switchColor = (theme: 'light' | 'dark' | 'system') => {
mockColorMode.preference = theme;
};
switchColor('dark');
expect(mockColorMode.preference).toBe('dark');
});
});
});