@glossarist/concept-browser 0.3.4 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/__tests__/about-view.test.ts +98 -0
- package/src/__tests__/app-footer.test.ts +38 -0
- package/src/__tests__/app-header.test.ts +130 -0
- package/src/__tests__/app-sidebar.test.ts +159 -0
- package/src/__tests__/asciidoc-lite.test.ts +1 -1
- package/src/__tests__/concept-card.test.ts +115 -0
- package/src/__tests__/concept-detail-interaction.test.ts +251 -0
- package/src/__tests__/concept-timeline.test.ts +175 -0
- package/src/__tests__/concept-view.test.ts +75 -0
- package/src/__tests__/contributors-view.test.ts +103 -0
- package/src/__tests__/dataset-view.test.ts +231 -0
- package/src/__tests__/format-downloads.test.ts +98 -0
- package/src/__tests__/graph-view.test.ts +69 -0
- package/src/__tests__/home-interaction.test.ts +157 -0
- package/src/__tests__/language-detail.test.ts +146 -0
- package/src/__tests__/nav-icon.test.ts +48 -0
- package/src/__tests__/news-view.test.ts +87 -0
- package/src/__tests__/page-view.test.ts +83 -0
- package/src/__tests__/resolve-view.test.ts +77 -0
- package/src/__tests__/router.test.ts +65 -0
- package/src/__tests__/search-bar.test.ts +219 -0
- package/src/__tests__/search-view.test.ts +41 -0
- package/src/__tests__/stats-view.test.ts +77 -0
- package/src/__tests__/test-helpers.ts +168 -0
- package/src/__tests__/ui-store.test.ts +100 -0
- package/src/__tests__/v-math.test.ts +8 -7
- package/src/adapters/DatasetAdapter.ts +17 -15
- package/src/adapters/types.ts +1 -1
- package/src/components/ConceptDetail.vue +16 -54
- package/src/components/ConceptTimeline.vue +1 -8
- package/src/components/LanguageDetail.vue +2 -25
- package/src/composables/use-render-options.ts +1 -4
- package/src/router/index.ts +1 -1
- package/src/stores/vocabulary.ts +7 -7
- package/src/utils/asciidoc-lite.ts +17 -19
- package/src/utils/concept-helpers.ts +34 -0
- package/src/utils/escape.ts +7 -0
- package/src/utils/markdown-lite.ts +1 -3
- package/src/utils/math.ts +2 -11
- package/src/utils/plurimath.ts +2 -7
- package/src/views/ConceptView.vue +22 -1
- package/src/views/DatasetView.vue +7 -2
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import DatasetView from '../views/DatasetView.vue';
|
|
6
|
+
import { useVocabularyStore } from '../stores/vocabulary';
|
|
7
|
+
import type { Manifest, ConceptSummary } from '../adapters/types';
|
|
8
|
+
|
|
9
|
+
function makeManifest(overrides: Partial<Manifest> = {}): Manifest {
|
|
10
|
+
return {
|
|
11
|
+
id: 'test',
|
|
12
|
+
datasetUri: 'https://glossarist.org/test/concept',
|
|
13
|
+
title: 'Test Dataset',
|
|
14
|
+
description: 'A test dataset',
|
|
15
|
+
owner: 'ISO',
|
|
16
|
+
baseUrl: '/data/test',
|
|
17
|
+
languages: ['eng', 'fra'],
|
|
18
|
+
conceptCount: 100,
|
|
19
|
+
conceptUrlTemplate: '/data/test/concepts/{id}.json',
|
|
20
|
+
indexUrl: '/data/test/index.json',
|
|
21
|
+
contextUrl: '/data/test/context.json',
|
|
22
|
+
uriBase: 'https://glossarist.org',
|
|
23
|
+
status: 'published',
|
|
24
|
+
schemaVersion: '1.0',
|
|
25
|
+
tags: [],
|
|
26
|
+
lastUpdated: '2025-01-01',
|
|
27
|
+
sourceRepo: 'https://example.com/repo',
|
|
28
|
+
chunkSize: 1000,
|
|
29
|
+
color: '#3366ff',
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeConcepts(count: number): ConceptSummary[] {
|
|
35
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
36
|
+
id: String(i + 1),
|
|
37
|
+
eng: `term ${i + 1}`,
|
|
38
|
+
status: i % 10 === 0 ? 'superseded' : 'valid',
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeAdapter(concepts: ConceptSummary[] = []) {
|
|
43
|
+
const dense = concepts.filter(Boolean);
|
|
44
|
+
return {
|
|
45
|
+
registerId: 'test',
|
|
46
|
+
index: dense,
|
|
47
|
+
manifest: null,
|
|
48
|
+
getConceptCount: () => dense.length,
|
|
49
|
+
getConcepts: () => dense,
|
|
50
|
+
isRangeLoaded: () => true,
|
|
51
|
+
ensureChunksForRange: async () => {},
|
|
52
|
+
ensureAllChunksLoaded: async () => {},
|
|
53
|
+
getAdjacentConcepts: () => ({ prev: null, next: null }),
|
|
54
|
+
} as any;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function createTestRouter() {
|
|
58
|
+
const router = createRouter({
|
|
59
|
+
history: createMemoryHistory(),
|
|
60
|
+
routes: [
|
|
61
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
62
|
+
{ path: '/dataset/:registerId', name: 'dataset', component: { template: '<div/>' } },
|
|
63
|
+
{ path: '/dataset/:registerId/concept/:conceptId', name: 'concept', component: { template: '<div/>' } },
|
|
64
|
+
{ path: '/dataset/:registerId/stats', name: 'stats', component: { template: '<div/>' } },
|
|
65
|
+
{ path: '/dataset/:registerId/about', name: 'about', component: { template: '<div/>' } },
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
router.push('/dataset/test');
|
|
69
|
+
await router.isReady();
|
|
70
|
+
return router;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe('DatasetView', () => {
|
|
74
|
+
let pinia: ReturnType<typeof createPinia>;
|
|
75
|
+
let router: Awaited<ReturnType<typeof createTestRouter>>;
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
pinia = createPinia();
|
|
79
|
+
setActivePinia(pinia);
|
|
80
|
+
router = await createTestRouter();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function mountDataset(concepts: ConceptSummary[] = [], manifestOverrides: Partial<Manifest> = {}) {
|
|
84
|
+
const store = useVocabularyStore();
|
|
85
|
+
const manifest = makeManifest(manifestOverrides);
|
|
86
|
+
store.manifests.set('test', manifest);
|
|
87
|
+
store.datasets.set('test', makeAdapter(concepts));
|
|
88
|
+
return mount(DatasetView, {
|
|
89
|
+
global: { plugins: [pinia, router] },
|
|
90
|
+
props: { registerId: 'test' },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
it('renders dataset title and description', async () => {
|
|
95
|
+
const wrapper = mountDataset(makeConcepts(10));
|
|
96
|
+
await flushPromises();
|
|
97
|
+
expect(wrapper.text()).toContain('Test Dataset');
|
|
98
|
+
expect(wrapper.text()).toContain('A test dataset');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('shows concept count badge', async () => {
|
|
102
|
+
const wrapper = mountDataset(makeConcepts(100));
|
|
103
|
+
await flushPromises();
|
|
104
|
+
expect(wrapper.text()).toContain('100 concepts');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('shows language count badge', async () => {
|
|
108
|
+
const wrapper = mountDataset(makeConcepts(10));
|
|
109
|
+
await flushPromises();
|
|
110
|
+
expect(wrapper.text()).toContain('2 languages');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('shows owner badge', async () => {
|
|
114
|
+
const wrapper = mountDataset(makeConcepts(10));
|
|
115
|
+
await flushPromises();
|
|
116
|
+
expect(wrapper.text()).toContain('ISO');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('shows concept cards for loaded concepts', async () => {
|
|
120
|
+
const wrapper = mountDataset(makeConcepts(3));
|
|
121
|
+
await flushPromises();
|
|
122
|
+
expect(wrapper.text()).toContain('term 1');
|
|
123
|
+
expect(wrapper.text()).toContain('term 2');
|
|
124
|
+
expect(wrapper.text()).toContain('term 3');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('paginates at 50 concepts per page', async () => {
|
|
128
|
+
const wrapper = mountDataset(makeConcepts(120));
|
|
129
|
+
await flushPromises();
|
|
130
|
+
// Should show 50 on first page
|
|
131
|
+
expect(wrapper.text()).toContain('term 1');
|
|
132
|
+
expect(wrapper.text()).not.toContain('term 51');
|
|
133
|
+
// Should show pagination
|
|
134
|
+
expect(wrapper.text()).toContain('Prev');
|
|
135
|
+
expect(wrapper.text()).toContain('Next');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('shows correct page count in pagination', async () => {
|
|
139
|
+
const wrapper = mountDataset(makeConcepts(120));
|
|
140
|
+
await flushPromises();
|
|
141
|
+
// 120 / 50 = 3 pages (ceil)
|
|
142
|
+
const pageButtons = wrapper.findAll('button').filter(b => /^\d+$/.test(b.text().trim()));
|
|
143
|
+
const pageNumbers = pageButtons.map(b => parseInt(b.text().trim()));
|
|
144
|
+
expect(pageNumbers).toContain(1);
|
|
145
|
+
expect(pageNumbers).toContain(3);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('filters concepts by term', async () => {
|
|
149
|
+
const concepts: ConceptSummary[] = [
|
|
150
|
+
{ id: '1', eng: 'road network', status: 'valid' },
|
|
151
|
+
{ id: '2', eng: 'bridge design', status: 'valid' },
|
|
152
|
+
{ id: '3', eng: 'road user', status: 'valid' },
|
|
153
|
+
];
|
|
154
|
+
const wrapper = mountDataset(concepts);
|
|
155
|
+
await flushPromises();
|
|
156
|
+
const input = wrapper.find('input[aria-label="Filter concepts"]');
|
|
157
|
+
await input.setValue('road');
|
|
158
|
+
await flushPromises();
|
|
159
|
+
expect(wrapper.text()).toContain('road network');
|
|
160
|
+
expect(wrapper.text()).toContain('road user');
|
|
161
|
+
expect(wrapper.text()).not.toContain('bridge design');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('filters concepts by ID', async () => {
|
|
165
|
+
const concepts: ConceptSummary[] = [
|
|
166
|
+
{ id: '3.1.1.1', eng: 'term one', status: 'valid' },
|
|
167
|
+
{ id: '3.1.1.2', eng: 'term two', status: 'valid' },
|
|
168
|
+
];
|
|
169
|
+
const wrapper = mountDataset(concepts);
|
|
170
|
+
await flushPromises();
|
|
171
|
+
const input = wrapper.find('input[aria-label="Filter concepts"]');
|
|
172
|
+
await input.setValue('3.1.1.1');
|
|
173
|
+
await flushPromises();
|
|
174
|
+
expect(wrapper.text()).toContain('term one');
|
|
175
|
+
expect(wrapper.text()).not.toContain('term two');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('shows empty state when filter matches nothing', async () => {
|
|
179
|
+
const wrapper = mountDataset(makeConcepts(5));
|
|
180
|
+
await flushPromises();
|
|
181
|
+
const input = wrapper.find('input[aria-label="Filter concepts"]');
|
|
182
|
+
await input.setValue('zzzznonexistent');
|
|
183
|
+
await flushPromises();
|
|
184
|
+
expect(wrapper.text()).toContain('No concepts match your filter');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('shows clear filter button on empty state', async () => {
|
|
188
|
+
const wrapper = mountDataset(makeConcepts(5));
|
|
189
|
+
await flushPromises();
|
|
190
|
+
const input = wrapper.find('input[aria-label="Filter concepts"]');
|
|
191
|
+
await input.setValue('zzzznonexistent');
|
|
192
|
+
await flushPromises();
|
|
193
|
+
expect(wrapper.text()).toContain('Clear filter');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('shows links to stats and about pages', async () => {
|
|
197
|
+
const wrapper = mountDataset(makeConcepts(5));
|
|
198
|
+
await flushPromises();
|
|
199
|
+
expect(wrapper.text()).toContain('Statistics');
|
|
200
|
+
expect(wrapper.text()).toContain('About');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('shows bulk downloads when manifest has bulkFormats', async () => {
|
|
204
|
+
const wrapper = mountDataset(makeConcepts(5), {
|
|
205
|
+
bulkFormats: [
|
|
206
|
+
{ file: 'all.ttl', format: 'turtle', size: 1024 },
|
|
207
|
+
{ file: 'all.jsonld', format: 'jsonld', size: 2048 },
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
await flushPromises();
|
|
211
|
+
expect(wrapper.text()).toContain('Download');
|
|
212
|
+
expect(wrapper.text()).toContain('1.0 KB');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('shows 0 of N concepts in filter count', async () => {
|
|
216
|
+
const wrapper = mountDataset(makeConcepts(10));
|
|
217
|
+
await flushPromises();
|
|
218
|
+
const input = wrapper.find('input[aria-label="Filter concepts"]');
|
|
219
|
+
await input.setValue('term 5');
|
|
220
|
+
await flushPromises();
|
|
221
|
+
expect(wrapper.text()).toContain('of 10 concepts');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('disables Prev on first page', async () => {
|
|
225
|
+
const wrapper = mountDataset(makeConcepts(120));
|
|
226
|
+
await flushPromises();
|
|
227
|
+
const prevBtn = wrapper.findAll('button').find(b => b.text().includes('Prev'));
|
|
228
|
+
expect(prevBtn).toBeDefined();
|
|
229
|
+
expect(prevBtn!.attributes('disabled')).toBeDefined();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import FormatDownloads from '../components/FormatDownloads.vue';
|
|
6
|
+
|
|
7
|
+
async function createTestRouter() {
|
|
8
|
+
return createRouter({
|
|
9
|
+
history: createMemoryHistory(),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
12
|
+
],
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('FormatDownloads', () => {
|
|
17
|
+
it('renders download links for known formats', async () => {
|
|
18
|
+
const pinia = createPinia();
|
|
19
|
+
setActivePinia(pinia);
|
|
20
|
+
const router = await createTestRouter();
|
|
21
|
+
const wrapper = mount(FormatDownloads, {
|
|
22
|
+
global: { plugins: [pinia, router] },
|
|
23
|
+
props: { registerId: 'test', conceptId: '3.1.1.1', formats: ['ttl', 'jsonld'] },
|
|
24
|
+
});
|
|
25
|
+
expect(wrapper.text()).toContain('Downloads');
|
|
26
|
+
expect(wrapper.text()).toContain('Turtle RDF');
|
|
27
|
+
expect(wrapper.text()).toContain('JSON-LD');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('generates correct URLs', async () => {
|
|
31
|
+
const pinia = createPinia();
|
|
32
|
+
setActivePinia(pinia);
|
|
33
|
+
const router = await createTestRouter();
|
|
34
|
+
const wrapper = mount(FormatDownloads, {
|
|
35
|
+
global: { plugins: [pinia, router] },
|
|
36
|
+
props: { registerId: 'test', conceptId: '3.1.1.1', formats: ['ttl'] },
|
|
37
|
+
});
|
|
38
|
+
const link = wrapper.find('a');
|
|
39
|
+
expect(link.attributes('href')).toBe('/data/test/concepts/3.1.1.1.ttl');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('filters unknown formats', async () => {
|
|
43
|
+
const pinia = createPinia();
|
|
44
|
+
setActivePinia(pinia);
|
|
45
|
+
const router = await createTestRouter();
|
|
46
|
+
const wrapper = mount(FormatDownloads, {
|
|
47
|
+
global: { plugins: [pinia, router] },
|
|
48
|
+
props: { registerId: 'test', conceptId: '1', formats: ['ttl', 'unknown'] },
|
|
49
|
+
});
|
|
50
|
+
const links = wrapper.findAll('a');
|
|
51
|
+
expect(links.length).toBe(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders nothing when all formats are unknown', async () => {
|
|
55
|
+
const pinia = createPinia();
|
|
56
|
+
setActivePinia(pinia);
|
|
57
|
+
const router = await createTestRouter();
|
|
58
|
+
const wrapper = mount(FormatDownloads, {
|
|
59
|
+
global: { plugins: [pinia, router] },
|
|
60
|
+
props: { registerId: 'test', conceptId: '1', formats: ['unknown'] },
|
|
61
|
+
});
|
|
62
|
+
expect(wrapper.find('a').exists()).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('renders nothing when formats array is empty', async () => {
|
|
66
|
+
const pinia = createPinia();
|
|
67
|
+
setActivePinia(pinia);
|
|
68
|
+
const router = await createTestRouter();
|
|
69
|
+
const wrapper = mount(FormatDownloads, {
|
|
70
|
+
global: { plugins: [pinia, router] },
|
|
71
|
+
props: { registerId: 'test', conceptId: '1', formats: [] },
|
|
72
|
+
});
|
|
73
|
+
expect(wrapper.find('a').exists()).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('sets download attribute on links', async () => {
|
|
77
|
+
const pinia = createPinia();
|
|
78
|
+
setActivePinia(pinia);
|
|
79
|
+
const router = await createTestRouter();
|
|
80
|
+
const wrapper = mount(FormatDownloads, {
|
|
81
|
+
global: { plugins: [pinia, router] },
|
|
82
|
+
props: { registerId: 'test', conceptId: '3.1.1.1', formats: ['jsonld'] },
|
|
83
|
+
});
|
|
84
|
+
const link = wrapper.find('a');
|
|
85
|
+
expect(link.attributes('download')).toBe('3.1.1.1.jsonld');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('shows TBX-XML format', async () => {
|
|
89
|
+
const pinia = createPinia();
|
|
90
|
+
setActivePinia(pinia);
|
|
91
|
+
const router = await createTestRouter();
|
|
92
|
+
const wrapper = mount(FormatDownloads, {
|
|
93
|
+
global: { plugins: [pinia, router] },
|
|
94
|
+
props: { registerId: 'test', conceptId: '1', formats: ['tbx'] },
|
|
95
|
+
});
|
|
96
|
+
expect(wrapper.text()).toContain('TBX-XML');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import GraphView from '../views/GraphView.vue';
|
|
6
|
+
import { useVocabularyStore } from '../stores/vocabulary';
|
|
7
|
+
|
|
8
|
+
async function createTestRouter() {
|
|
9
|
+
const router = createRouter({
|
|
10
|
+
history: createMemoryHistory(),
|
|
11
|
+
routes: [
|
|
12
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
13
|
+
{ path: '/graph', name: 'graph', component: { template: '<div/>' } },
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
router.push('/graph');
|
|
17
|
+
await router.isReady();
|
|
18
|
+
return router;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('GraphView', () => {
|
|
22
|
+
let pinia: ReturnType<typeof createPinia>;
|
|
23
|
+
let router: Awaited<ReturnType<typeof createTestRouter>>;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
pinia = createPinia();
|
|
27
|
+
setActivePinia(pinia);
|
|
28
|
+
router = await createTestRouter();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function mountGraph() {
|
|
32
|
+
return mount(GraphView, {
|
|
33
|
+
global: {
|
|
34
|
+
plugins: [pinia, router],
|
|
35
|
+
stubs: { GraphPanel: true },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
it('renders breadcrumb navigation', () => {
|
|
41
|
+
const wrapper = mountGraph();
|
|
42
|
+
expect(wrapper.text()).toContain('Home');
|
|
43
|
+
expect(wrapper.text()).toContain('Graph View');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('shows loading spinner initially', () => {
|
|
47
|
+
const wrapper = mountGraph();
|
|
48
|
+
expect(wrapper.find('.animate-spin').exists()).toBe(true);
|
|
49
|
+
expect(wrapper.text()).toContain('Loading graph data');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders GraphPanel after loading', async () => {
|
|
53
|
+
const wrapper = mountGraph();
|
|
54
|
+
await flushPromises();
|
|
55
|
+
expect(wrapper.findComponent({ name: 'GraphPanel' }).exists()).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('shows edge count after loading', async () => {
|
|
59
|
+
const wrapper = mountGraph();
|
|
60
|
+
await flushPromises();
|
|
61
|
+
expect(wrapper.text()).toContain('edges');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('has full-height container', () => {
|
|
65
|
+
const wrapper = mountGraph();
|
|
66
|
+
const container = wrapper.find('.flex.flex-col');
|
|
67
|
+
expect(container.exists()).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import HomeView from '../views/HomeView.vue';
|
|
6
|
+
import { useVocabularyStore } from '../stores/vocabulary';
|
|
7
|
+
import type { Manifest } from '../adapters/types';
|
|
8
|
+
|
|
9
|
+
function makeManifest(overrides: Partial<Manifest> = {}): Manifest {
|
|
10
|
+
return {
|
|
11
|
+
id: 'test',
|
|
12
|
+
datasetUri: 'https://glossarist.org/test/concept',
|
|
13
|
+
title: 'Test Dataset',
|
|
14
|
+
description: 'A test dataset',
|
|
15
|
+
owner: 'ISO',
|
|
16
|
+
baseUrl: '/data/test',
|
|
17
|
+
languages: ['eng', 'fra'],
|
|
18
|
+
conceptCount: 100,
|
|
19
|
+
conceptUrlTemplate: '/data/test/concepts/{id}.json',
|
|
20
|
+
indexUrl: '/data/test/index.json',
|
|
21
|
+
contextUrl: '/data/test/context.json',
|
|
22
|
+
uriBase: 'https://glossarist.org',
|
|
23
|
+
status: 'published',
|
|
24
|
+
schemaVersion: '1.0',
|
|
25
|
+
tags: [],
|
|
26
|
+
lastUpdated: '2025-01-01',
|
|
27
|
+
sourceRepo: 'https://example.com/repo',
|
|
28
|
+
chunkSize: 1000,
|
|
29
|
+
color: '#3366ff',
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function createTestRouter() {
|
|
35
|
+
const router = createRouter({
|
|
36
|
+
history: createMemoryHistory(),
|
|
37
|
+
routes: [
|
|
38
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
39
|
+
{ path: '/dataset/:registerId', name: 'dataset', component: { template: '<div/>' } },
|
|
40
|
+
{ path: '/search', name: 'search', component: { template: '<div/>' } },
|
|
41
|
+
{ path: '/graph', name: 'graph', component: { template: '<div/>' } },
|
|
42
|
+
{ path: '/dataset/:registerId/concept/:conceptId', name: 'concept', component: { template: '<div/>' } },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
router.push('/');
|
|
46
|
+
await router.isReady();
|
|
47
|
+
return router;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('HomeView interactions', () => {
|
|
51
|
+
let pinia: ReturnType<typeof createPinia>;
|
|
52
|
+
let router: Awaited<ReturnType<typeof createTestRouter>>;
|
|
53
|
+
|
|
54
|
+
beforeEach(async () => {
|
|
55
|
+
pinia = createPinia();
|
|
56
|
+
setActivePinia(pinia);
|
|
57
|
+
router = await createTestRouter();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function mountHome() {
|
|
61
|
+
return mount(HomeView, {
|
|
62
|
+
global: { plugins: [pinia, router] },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
it('renders the welcome hero section', () => {
|
|
67
|
+
const wrapper = mountHome();
|
|
68
|
+
expect(wrapper.find('h1').text()).toContain('Glossarist');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('renders Search, Graph View, and Surprise Me buttons', () => {
|
|
72
|
+
const wrapper = mountHome();
|
|
73
|
+
const buttons = wrapper.findAll('button');
|
|
74
|
+
const texts = buttons.map(b => b.text());
|
|
75
|
+
expect(texts.some(t => t.includes('Search'))).toBe(true);
|
|
76
|
+
expect(texts.some(t => t.includes('Graph View'))).toBe(true);
|
|
77
|
+
expect(texts.some(t => t.includes('Surprise Me'))).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('navigates to search on Search button click', async () => {
|
|
81
|
+
const wrapper = mountHome();
|
|
82
|
+
const searchBtn = wrapper.findAll('button').find(b => b.text().includes('Search'));
|
|
83
|
+
await searchBtn!.trigger('click');
|
|
84
|
+
await flushPromises();
|
|
85
|
+
expect(router.currentRoute.value.name).toBe('search');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('navigates to graph on Graph View button click', async () => {
|
|
89
|
+
const wrapper = mountHome();
|
|
90
|
+
const graphBtn = wrapper.findAll('button').find(b => b.text().includes('Graph View'));
|
|
91
|
+
await graphBtn!.trigger('click');
|
|
92
|
+
await flushPromises();
|
|
93
|
+
expect(router.currentRoute.value.name).toBe('graph');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('shows stats counters', () => {
|
|
97
|
+
const store = useVocabularyStore();
|
|
98
|
+
store.manifests.set('test', makeManifest());
|
|
99
|
+
store.datasets.set('test', { index: [], getConceptCount: () => 0 } as any);
|
|
100
|
+
|
|
101
|
+
const wrapper = mountHome();
|
|
102
|
+
expect(wrapper.text()).toContain('Datasets');
|
|
103
|
+
expect(wrapper.text()).toContain('Concepts');
|
|
104
|
+
expect(wrapper.text()).toContain('Languages');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('shows a CTA card for a single dataset', () => {
|
|
108
|
+
const store = useVocabularyStore();
|
|
109
|
+
store.manifests.set('test', makeManifest());
|
|
110
|
+
store.datasets.set('test', { index: [], getConceptCount: () => 0 } as any);
|
|
111
|
+
|
|
112
|
+
const wrapper = mountHome();
|
|
113
|
+
expect(wrapper.text()).toContain('Test Dataset');
|
|
114
|
+
expect(wrapper.text()).toContain('Browse concepts');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('navigates to dataset on CTA click', async () => {
|
|
118
|
+
const store = useVocabularyStore();
|
|
119
|
+
store.manifests.set('test', makeManifest());
|
|
120
|
+
store.datasets.set('test', { index: [], getConceptCount: () => 0 } as any);
|
|
121
|
+
|
|
122
|
+
const wrapper = mountHome();
|
|
123
|
+
const cta = wrapper.findAll('button').find(b => b.text().includes('Browse concepts'));
|
|
124
|
+
await cta!.trigger('click');
|
|
125
|
+
await flushPromises();
|
|
126
|
+
expect(router.currentRoute.value.name).toBe('dataset');
|
|
127
|
+
expect(router.currentRoute.value.params.registerId).toBe('test');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('shows dataset cards for multiple datasets', () => {
|
|
131
|
+
const store = useVocabularyStore();
|
|
132
|
+
store.manifests.set('ds1', makeManifest({ id: 'ds1', title: 'Dataset One' }));
|
|
133
|
+
store.manifests.set('ds2', makeManifest({ id: 'ds2', title: 'Dataset Two' }));
|
|
134
|
+
store.datasets.set('ds1', { index: [], getConceptCount: () => 0 } as any);
|
|
135
|
+
store.datasets.set('ds2', { index: [], getConceptCount: () => 0 } as any);
|
|
136
|
+
|
|
137
|
+
const wrapper = mountHome();
|
|
138
|
+
expect(wrapper.text()).toContain('Dataset One');
|
|
139
|
+
expect(wrapper.text()).toContain('Dataset Two');
|
|
140
|
+
expect(wrapper.text()).toContain('Available Datasets');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('navigates to dataset on card click', async () => {
|
|
144
|
+
const store = useVocabularyStore();
|
|
145
|
+
store.manifests.set('ds1', makeManifest({ id: 'ds1', title: 'Dataset One' }));
|
|
146
|
+
store.manifests.set('ds2', makeManifest({ id: 'ds2', title: 'Dataset Two' }));
|
|
147
|
+
store.datasets.set('ds1', { index: [], getConceptCount: () => 0 } as any);
|
|
148
|
+
store.datasets.set('ds2', { index: [], getConceptCount: () => 0 } as any);
|
|
149
|
+
|
|
150
|
+
const wrapper = mountHome();
|
|
151
|
+
const card = wrapper.findAll('button').find(b => b.text().includes('Dataset One'));
|
|
152
|
+
await card!.trigger('click');
|
|
153
|
+
await flushPromises();
|
|
154
|
+
expect(router.currentRoute.value.name).toBe('dataset');
|
|
155
|
+
expect(router.currentRoute.value.params.registerId).toBe('ds1');
|
|
156
|
+
});
|
|
157
|
+
});
|