import { describe, it, expect, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createRouter, createMemoryHistory } from 'vue-router';
import AppHeader from '../components/AppHeader.vue';
import { useVocabularyStore } from '../stores/vocabulary';
import type { Manifest } from '../adapters/types';
function makeManifest(): Manifest {
return {
id: 'test',
datasetUri: 'https://glossarist.org/test/concept',
title: 'Test Dataset',
description: 'A test dataset',
owner: 'ISO',
baseUrl: '/data/test',
languages: ['eng'],
conceptCount: 10,
conceptUrlTemplate: '/data/test/concepts/{id}.json',
indexUrl: '/data/test/index.json',
contextUrl: '/data/test/context.json',
uriBase: 'https://glossarist.org',
status: 'published',
schemaVersion: '1.0',
tags: [],
lastUpdated: '2025-01-01',
sourceRepo: 'https://example.com/repo',
chunkSize: 1000,
color: '#3366ff',
};
}
async function createTestRouter() {
return createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', name: 'home', component: { template: '
' } },
{ path: '/search', name: 'search', component: { template: '' } },
],
});
}
describe('AppHeader', () => {
let pinia: ReturnType;
let router: Awaited>;
beforeEach(async () => {
pinia = createPinia();
setActivePinia(pinia);
router = await createTestRouter();
router.push('/');
await router.isReady();
const store = useVocabularyStore();
store.manifests.set('test', makeManifest());
store.datasets.set('test', { index: [], getConceptCount: () => 0, getConcepts: () => [] } as any);
});
function mountHeader() {
return mount(AppHeader, {
global: { plugins: [pinia, router] },
});
}
it('renders logo/title text', () => {
const wrapper = mountHeader();
expect(wrapper.text()).toContain('Glossarist');
});
it('renders search input', () => {
const wrapper = mountHeader();
const input = wrapper.find('input[aria-label="Search concepts"]');
expect(input.exists()).toBe(true);
});
it('renders mobile hamburger button', () => {
const wrapper = mountHeader();
const hamburger = wrapper.find('button[aria-label="Open navigation menu"]');
expect(hamburger.exists()).toBe(true);
});
it('navigates home on logo click', async () => {
const wrapper = mountHeader();
const logoBtn = wrapper.findAll('button').find(b => b.text().includes('Glossarist'));
expect(logoBtn).toBeDefined();
await logoBtn!.trigger('click');
await flushPromises();
expect(router.currentRoute.value.name).toBe('home');
});
it('navigates to search on form submit with query', async () => {
const wrapper = mountHeader();
const input = wrapper.find('input[aria-label="Search concepts"]');
await input.setValue('road');
const form = wrapper.find('form');
await form.trigger('submit');
await flushPromises();
expect(router.currentRoute.value.name).toBe('search');
expect(router.currentRoute.value.query.q).toBe('road');
});
it('does not navigate on empty search', async () => {
const wrapper = mountHeader();
const form = wrapper.find('form');
await form.trigger('submit');
await flushPromises();
expect(router.currentRoute.value.name).toBe('home');
});
it('renders theme toggle button', () => {
const wrapper = mountHeader();
const themeBtn = wrapper.findAll('button').find(b =>
b.attributes('aria-label')?.includes('Switch to')
);
expect(themeBtn).toBeDefined();
});
it('toggles theme on button click', async () => {
const wrapper = mountHeader();
const themeBtn = wrapper.findAll('button').find(b =>
b.attributes('aria-label')?.includes('Switch to')
);
await themeBtn!.trigger('click');
expect(wrapper.html()).toContain('M12 3v1m0 16v1m9-9h-1');
});
it('shows dataset count', () => {
const wrapper = mountHeader();
expect(wrapper.text()).toContain('1 datasets');
});
});