import { describe, it, expect, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createRouter, createMemoryHistory } from 'vue-router';
import AppSidebar from '../components/AppSidebar.vue';
import { useVocabularyStore } from '../stores/vocabulary';
function makeManifest(id = 'test') {
return { id, datasetUri: `https://glossarist.org/${id}/concept`, title: `Test ${id}`, description: 'A test dataset',
owner: 'ISO', baseUrl: `/data/${id}`, languages: ['eng'], conceptCount: 50,
conceptUrlTemplate: `/data/${id}/concepts/{id}.json`, indexUrl: `/data/${id}/index.json`,
contextUrl: `/data/${id}/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' } as any;
}
function createTestRouter(initialPath = '/') {
return createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', name: 'home', component: { template: '
' } },
{ path: '/dataset/:registerId', name: 'dataset', component: { template: '' } },
{ path: '/dataset/:registerId/concept/:conceptId', name: 'concept', component: { template: '' } },
{ path: '/dataset/:registerId/stats', name: 'stats', component: { template: '' } },
{ path: '/dataset/:registerId/about', name: 'about', component: { template: '' } },
{ path: '/search', name: 'search', component: { template: '' } },
{ path: '/about', name: 'about-global', component: { template: '' } },
],
});
}
function seedStore(datasets: string[] = ['test']) {
const store = useVocabularyStore();
for (const id of datasets) {
store.manifests.set(id, makeManifest(id));
store.datasets.set(id, { index: [], getConceptCount: () => 0, getConcepts: () => [] } as any);
}
}
/**
* Returns all elements with the custom 'active' class.
* This is the class added by the isActive() function in AppSidebar,
* distinct from Vue Router's built-in router-link-active.
*/
function getActiveHrefs(wrapper: ReturnType): string[] {
return wrapper.findAll('a.active').map(l => l.attributes('href') || '');
}
describe('AppSidebar — nav highlighting (isActive)', () => {
let pinia: ReturnType;
beforeEach(() => {
pinia = createPinia();
setActivePinia(pinia);
});
async function mountSidebar(initialPath = '/') {
const router = createTestRouter(initialPath);
router.push(initialPath);
await router.isReady();
return mount(AppSidebar, {
global: { plugins: [pinia, router], stubs: { NavIcon: true } },
});
}
// ── The bug: dataset About should NOT activate global About ─────────
it('on /dataset/test/about: ONLY dataset about link is active', async () => {
seedStore();
const wrapper = await mountSidebar('/dataset/test/about');
const activeHrefs = getActiveHrefs(wrapper);
// Dataset about link IS active
expect(activeHrefs).toContain('/dataset/test/about');
// Global /about link is NOT active (this was the bug)
expect(activeHrefs).not.toContain('/about');
// Only one active link total
expect(activeHrefs).toEqual(['/dataset/test/about']);
});
it('on /dataset/test/about: no link to /about (global) has active class', async () => {
seedStore();
const wrapper = await mountSidebar('/dataset/test/about');
const allLinks = wrapper.findAll('a');
const globalAboutLinks = allLinks.filter(l => l.attributes('href') === '/about');
// If global about link exists, it must NOT have custom active class
for (const link of globalAboutLinks) {
expect(link.classes()).not.toContain('active');
}
});
// ── Global About page still works ───────────────────────────────────
it('on /about: dataset about links are NOT active', async () => {
seedStore();
const wrapper = await mountSidebar('/about');
const allLinks = wrapper.findAll('a');
const datasetAboutActive = allLinks.filter(
l => (l.attributes('href') || '').includes('/dataset/') &&
(l.attributes('href') || '').includes('/about') &&
l.classes().includes('active')
);
expect(datasetAboutActive).toEqual([]);
});
// ── Dataset root: dataset Concepts active, global Home not ──────────
it('on /dataset/test: dataset sub-nav is active', async () => {
seedStore();
const wrapper = await mountSidebar('/dataset/test');
// Something should be active (the dataset concepts sub-page)
const activeHrefs = getActiveHrefs(wrapper);
expect(activeHrefs.length).toBeGreaterThanOrEqual(1);
});
it('on /dataset/test: global nav links are NOT active', async () => {
seedStore();
const wrapper = await mountSidebar('/dataset/test');
const allLinks = wrapper.findAll('a');
// Global Search link must NOT be active
const searchActive = allLinks.filter(
l => l.attributes('href') === '/search' && l.classes().includes('active')
);
expect(searchActive).toEqual([]);
});
// ── Concept page: dataset Concepts still active ─────────────────────
it('on /dataset/test/concept/1.2: dataset sub-nav is active', async () => {
seedStore();
const wrapper = await mountSidebar('/dataset/test/concept/1.2');
const activeHrefs = getActiveHrefs(wrapper);
expect(activeHrefs.length).toBeGreaterThanOrEqual(1);
});
// ── Cross-dataset isolation ─────────────────────────────────────────
it('on /dataset/ds-a/about: ds-b about is NOT active', async () => {
seedStore(['ds-a', 'ds-b']);
const wrapper = await mountSidebar('/dataset/ds-a/about');
const activeHrefs = getActiveHrefs(wrapper);
expect(activeHrefs).toContain('/dataset/ds-a/about');
expect(activeHrefs).not.toContain('/dataset/ds-b/about');
});
// ── Root route ──────────────────────────────────────────────────────
it('on /: Home link is active', async () => {
seedStore();
const wrapper = await mountSidebar('/');
const activeHrefs = getActiveHrefs(wrapper);
expect(activeHrefs).toContain('/');
});
it('on /: search and about are NOT active', async () => {
seedStore();
const wrapper = await mountSidebar('/');
const activeHrefs = getActiveHrefs(wrapper);
expect(activeHrefs).not.toContain('/search');
expect(activeHrefs).not.toContain('/about');
});
// ── Dataset stats page ──────────────────────────────────────────────
it('on /dataset/test/stats: stats sub-page is active', async () => {
seedStore();
const wrapper = await mountSidebar('/dataset/test/stats');
const activeHrefs = getActiveHrefs(wrapper);
expect(activeHrefs).toContain('/dataset/test/stats');
});
it('on /dataset/test/stats: about sub-page is NOT active', async () => {
seedStore();
const wrapper = await mountSidebar('/dataset/test/stats');
const activeHrefs = getActiveHrefs(wrapper);
expect(activeHrefs).not.toContain('/dataset/test/about');
});
});