@glossarist/concept-browser 0.7.100 → 0.7.102
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/cli/index.mjs +14 -251
- package/cli/index.ts +289 -0
- package/package.json +3 -2
- package/src/__tests__/__fixtures__/concepts.ts +3 -3
- package/src/__tests__/components/learn/learn-card-dimension-card.test.ts +112 -0
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +1 -0
- package/src/__tests__/designation-relationship.test.ts +2 -2
- package/src/__tests__/perf/serialization-perf.test.ts +1 -0
- package/src/__tests__/use-rdf-document.test.ts +1 -0
- package/src/__tests__/views/learn/learn-designations-view.test.ts +84 -0
- package/src/__tests__/views/learn/learn-index-view.test.ts +45 -0
- package/src/__tests__/views/learn/learn-relationships-view.test.ts +83 -0
- package/src/__tests__/views/learn/learn-statuses-view.test.ts +60 -0
- package/src/adapters/bibliography-adapter.ts +1 -1
- package/src/adapters/model-bridge.ts +15 -15
- package/src/adapters/non-verbal/figure-bridge.ts +2 -2
- package/src/adapters/non-verbal/formula-bridge.ts +2 -2
- package/src/adapters/non-verbal/table-bridge.ts +2 -2
- package/src/components/CitationDisplay.vue +1 -1
- package/src/components/ConceptDetail.vue +2 -2
- package/src/components/ConceptDiffModal.vue +2 -2
- package/src/components/ConceptDiffView.vue +1 -1
- package/src/components/DesignationList.vue +4 -4
- package/src/components/NavIcon.vue +1 -0
- package/src/components/RelationSphere.vue +1 -1
- package/src/components/figure/FigureDisplay.vue +5 -5
- package/src/components/formula/FormulaDisplay.vue +4 -4
- package/src/components/learn/DimensionCard.vue +71 -0
- package/src/components/learn/LearnCard.vue +59 -0
- package/src/components/table/TableDisplay.vue +3 -3
- package/src/components/use-rdf-document.ts +16 -7
- package/src/composables/use-concept-content.ts +3 -3
- package/src/composables/use-concept-diff.ts +2 -2
- package/src/composables/use-concept-edges.ts +3 -3
- package/src/config/page-types.ts +1 -1
- package/src/i18n/locales/eng.yml +114 -9
- package/src/i18n/locales/fra.yml +114 -9
- package/src/layouts/Default.astro +32 -2
- package/src/router/index.ts +21 -2
- package/src/utils/content-renderer.ts +1 -1
- package/src/utils/partitive-multiplicity.ts +3 -2
- package/src/views/ConceptView.vue +3 -3
- package/src/views/learn/LearnDesignationsView.vue +190 -0
- package/src/views/learn/LearnIndexView.vue +75 -0
- package/src/views/learn/LearnRelationshipsView.vue +317 -0
- package/src/views/learn/LearnStatusesView.vue +114 -0
- package/tailwind.config.js +3 -3
- package/tsconfig.json +1 -1
- package/src/__tests__/views/relation-types-view.test.ts +0 -85
- package/src/adapters/model-bridge/bridges.ts +0 -159
- package/src/adapters/model-bridge/concept.ts +0 -97
- package/src/adapters/model-bridge/entity-refs.ts +0 -49
- package/src/adapters/model-bridge/hyperedge.ts +0 -103
- package/src/adapters/model-bridge/index.ts +0 -46
- package/src/adapters/model-bridge/mappers.ts +0 -277
- package/src/adapters/model-bridge/partitive.ts +0 -79
- package/src/adapters/non-verbal/glossarist-augment.d.ts +0 -339
- /package/src/adapters/{model-bridge/jsonld-types.ts → jsonld-types.ts} +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import LearnCard from '../../../components/learn/LearnCard.vue';
|
|
5
|
+
import DimensionCard from '../../../components/learn/DimensionCard.vue';
|
|
6
|
+
|
|
7
|
+
describe('LearnCard', () => {
|
|
8
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
9
|
+
|
|
10
|
+
it('renders the title and ISO reference', () => {
|
|
11
|
+
const w = mount(LearnCard, {
|
|
12
|
+
props: { title: 'partitive relation', isoReference: 'ISO 704:2022 §5.5.4.3' },
|
|
13
|
+
});
|
|
14
|
+
expect(w.find('h3').text()).toBe('partitive relation');
|
|
15
|
+
expect(w.find('.font-mono').text()).toBe('ISO 704:2022 §5.5.4.3');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders the body slot', () => {
|
|
19
|
+
const w = mount(LearnCard, {
|
|
20
|
+
props: { title: 'x' },
|
|
21
|
+
slots: { default: '<p>Body content</p>' },
|
|
22
|
+
});
|
|
23
|
+
expect(w.find('.learn-card-body').html()).toContain('Body content');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders the example slot in its own section', () => {
|
|
27
|
+
const w = mount(LearnCard, {
|
|
28
|
+
props: { title: 'x' },
|
|
29
|
+
slots: { example: 'VIM 1.9 measurement unit' },
|
|
30
|
+
});
|
|
31
|
+
expect(w.find('.learn-card-example').text()).toContain('VIM 1.9');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('omits the "learn more" footer when learnMoreUrl is absent', () => {
|
|
35
|
+
const w = mount(LearnCard, { props: { title: 'x' } });
|
|
36
|
+
expect(w.find('.learn-card-footer').exists()).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders the learn-more footer when learnMoreUrl is present', () => {
|
|
40
|
+
const w = mount(LearnCard, {
|
|
41
|
+
props: { title: 'x', learnMoreUrl: 'https://www.glossarist.org/model/x' },
|
|
42
|
+
});
|
|
43
|
+
const link = w.find('.learn-card-footer a');
|
|
44
|
+
expect(link.attributes('href')).toBe('https://www.glossarist.org/model/x');
|
|
45
|
+
expect(link.attributes('target')).toBe('_blank');
|
|
46
|
+
expect(link.attributes('rel')).toBe('noopener');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('binds the id prop for deep-link anchors', () => {
|
|
50
|
+
const w = mount(LearnCard, {
|
|
51
|
+
props: { id: '__partitive__', title: 'x' },
|
|
52
|
+
});
|
|
53
|
+
expect(w.find('article').attributes('id')).toBe('__partitive__');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('DimensionCard', () => {
|
|
58
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
59
|
+
|
|
60
|
+
it('renders one row per value', () => {
|
|
61
|
+
const w = mount(DimensionCard, {
|
|
62
|
+
props: {
|
|
63
|
+
name: 'Presence',
|
|
64
|
+
summary: 'Compulsory or optional',
|
|
65
|
+
values: [
|
|
66
|
+
{ value: 'required', label: 'required', meaning: 'must be present' },
|
|
67
|
+
{ value: 'optional', label: 'optional', meaning: 'may be absent' },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
expect(w.findAll('.dimension-value-row').length).toBe(2);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('renders value labels and meanings', () => {
|
|
75
|
+
const w = mount(DimensionCard, {
|
|
76
|
+
props: {
|
|
77
|
+
name: 'x',
|
|
78
|
+
summary: 's',
|
|
79
|
+
values: [{ value: 'required', label: 'required', meaning: 'must be present' }],
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const row = w.find('.dimension-value-row');
|
|
83
|
+
expect(row.text()).toContain('required');
|
|
84
|
+
expect(row.text()).toContain('must be present');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('exposes a visual scoped slot per value', () => {
|
|
88
|
+
const w = mount(DimensionCard, {
|
|
89
|
+
props: {
|
|
90
|
+
name: 'x',
|
|
91
|
+
summary: 's',
|
|
92
|
+
values: [{ value: 'required', label: 'required', meaning: 'm' }],
|
|
93
|
+
},
|
|
94
|
+
slots: {
|
|
95
|
+
visual: '<svg data-test="custom-visual" />',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
expect(w.find('svg[data-test="custom-visual"]').exists()).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('binds the id prop for deep-link anchors', () => {
|
|
102
|
+
const w = mount(DimensionCard, {
|
|
103
|
+
props: {
|
|
104
|
+
id: 'partitive-presence',
|
|
105
|
+
name: 'x',
|
|
106
|
+
summary: 's',
|
|
107
|
+
values: [{ value: 'required', label: 'required', meaning: 'm' }],
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
expect(w.find('article').attributes('id')).toBe('partitive-presence');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -197,8 +197,8 @@ describe('designation target matching robustness', () => {
|
|
|
197
197
|
// Terms are in model order, not raw order
|
|
198
198
|
const pdf = lc.terms.find(t => t.designation === 'PDF')!;
|
|
199
199
|
const iso = lc.terms.find(t => t.designation === 'ISO')!;
|
|
200
|
-
expect(getDesignationTarget(pdf.related[0])).toBe('Portable Document Format');
|
|
201
|
-
expect(getDesignationTarget(iso.related[0])).toBe('International Organization for Standardization');
|
|
200
|
+
expect(getDesignationTarget(pdf.related[0] as any)).toBe('Portable Document Format');
|
|
201
|
+
expect(getDesignationTarget(iso.related[0] as any)).toBe('International Organization for Standardization');
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
204
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import LearnDesignationsView from '../../../views/learn/LearnDesignationsView.vue';
|
|
6
|
+
|
|
7
|
+
async function makeRouter() {
|
|
8
|
+
const router = createRouter({
|
|
9
|
+
history: createMemoryHistory(),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
12
|
+
{ path: '/learn/designations', name: 'learn-designations', component: LearnDesignationsView },
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
await router.push('/learn/designations');
|
|
16
|
+
await router.isReady();
|
|
17
|
+
return router;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('LearnDesignationsView', () => {
|
|
21
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
22
|
+
|
|
23
|
+
it('renders the page header', async () => {
|
|
24
|
+
const router = await makeRouter();
|
|
25
|
+
const w = mount(LearnDesignationsView, { global: { plugins: [router] } });
|
|
26
|
+
expect(w.find('h1').text()).toContain('Designations');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders three top-level sections (normative, kinds, types)', async () => {
|
|
30
|
+
const router = await makeRouter();
|
|
31
|
+
const w = mount(LearnDesignationsView, { global: { plugins: [router] } });
|
|
32
|
+
const sections = w.findAll('section[aria-label]');
|
|
33
|
+
expect(sections.length).toBe(3);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('normative section has all three ratings (preferred/admitted/deprecated)', async () => {
|
|
37
|
+
const router = await makeRouter();
|
|
38
|
+
const w = mount(LearnDesignationsView, { global: { plugins: [router] } });
|
|
39
|
+
const normative = w.find('section[aria-label="Normative status"]');
|
|
40
|
+
const cardTitles = normative.findAll('h3').map(h => h.text());
|
|
41
|
+
expect(cardTitles).toEqual(expect.arrayContaining([
|
|
42
|
+
'preferred term', 'admitted term', 'deprecated term',
|
|
43
|
+
]));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('each normative card notes "there can be more than one"', async () => {
|
|
47
|
+
/* The user's specific concern: explain that there can be multiple. */
|
|
48
|
+
const router = await makeRouter();
|
|
49
|
+
const w = mount(LearnDesignationsView, { global: { plugins: [router] } });
|
|
50
|
+
const normative = w.find('section[aria-label="Normative status"]');
|
|
51
|
+
const notes = normative.findAll('.italic').map(i => i.text().toLowerCase());
|
|
52
|
+
expect(notes.filter(t => t.includes('more than one')).length).toBe(3);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('kinds section renders all four kinds (full form, abbreviated, borrowed, variant)', async () => {
|
|
56
|
+
const router = await makeRouter();
|
|
57
|
+
const w = mount(LearnDesignationsView, { global: { plugins: [router] } });
|
|
58
|
+
const kinds = w.find('section[aria-label="Kinds and forms of term"]');
|
|
59
|
+
const titles = kinds.findAll('h3').map(h => h.text());
|
|
60
|
+
expect(titles).toEqual(expect.arrayContaining([
|
|
61
|
+
'full form', 'abbreviated form', 'borrowed term', 'variant',
|
|
62
|
+
]));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('types section renders term, acronym, initialism, symbol', async () => {
|
|
66
|
+
const router = await makeRouter();
|
|
67
|
+
const w = mount(LearnDesignationsView, { global: { plugins: [router] } });
|
|
68
|
+
const types = w.find('section[aria-label="Designation types"]');
|
|
69
|
+
const titles = types.findAll('h3').map(h => h.text());
|
|
70
|
+
expect(titles).toEqual(expect.arrayContaining([
|
|
71
|
+
'term (expression)', 'acronym', 'initialism', 'symbol',
|
|
72
|
+
]));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('every example slot mentions a concrete ISO example', async () => {
|
|
76
|
+
const router = await makeRouter();
|
|
77
|
+
const w = mount(LearnDesignationsView, { global: { plugins: [router] } });
|
|
78
|
+
const examples = w.findAll('.learn-card-example').map(e => e.text());
|
|
79
|
+
expect(examples.some(t => t.includes('compact disc'))).toBe(true);
|
|
80
|
+
expect(examples.some(t => t.includes('WWW'))).toBe(true);
|
|
81
|
+
expect(examples.some(t => t.includes('laser'))).toBe(true);
|
|
82
|
+
expect(examples.some(t => t.includes('OIML'))).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import LearnIndexView from '../../../views/learn/LearnIndexView.vue';
|
|
6
|
+
|
|
7
|
+
async function makeRouter() {
|
|
8
|
+
const router = createRouter({
|
|
9
|
+
history: createMemoryHistory(),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
12
|
+
{ path: '/learn', name: 'learn', component: LearnIndexView },
|
|
13
|
+
{ path: '/learn/relationships', name: 'learn-relationships', component: { template: '<div/>' } },
|
|
14
|
+
{ path: '/learn/designations', name: 'learn-designations', component: { template: '<div/>' } },
|
|
15
|
+
{ path: '/learn/statuses', name: 'learn-statuses', component: { template: '<div/>' } },
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
await router.push('/learn');
|
|
19
|
+
await router.isReady();
|
|
20
|
+
return router;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('LearnIndexView', () => {
|
|
24
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
25
|
+
|
|
26
|
+
it('renders the page header with translated title', async () => {
|
|
27
|
+
const router = await makeRouter();
|
|
28
|
+
const w = mount(LearnIndexView, { global: { plugins: [router] } });
|
|
29
|
+
expect(w.find('h1').text()).toContain('Learn');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders exactly 3 topic cards for Phase 1', async () => {
|
|
33
|
+
const router = await makeRouter();
|
|
34
|
+
const w = mount(LearnIndexView, { global: { plugins: [router] } });
|
|
35
|
+
/* Each RouterLink wraps a LearnCard. Phase 1 = 3 topics. */
|
|
36
|
+
expect(w.findAllComponents({ name: 'LearnCard' }).length).toBe(3);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('each topic card links to one of the Phase 1 routes', async () => {
|
|
40
|
+
const router = await makeRouter();
|
|
41
|
+
const w = mount(LearnIndexView, { global: { plugins: [router] } });
|
|
42
|
+
const links = w.findAll('a').map(a => a.attributes('href'));
|
|
43
|
+
expect(links).toEqual(expect.arrayContaining(['/learn/relationships', '/learn/designations', '/learn/statuses']));
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import LearnRelationshipsView from '../../../views/learn/LearnRelationshipsView.vue';
|
|
6
|
+
|
|
7
|
+
async function makeRouter() {
|
|
8
|
+
const router = createRouter({
|
|
9
|
+
history: createMemoryHistory(),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
12
|
+
{ path: '/learn/relationships', name: 'learn-relationships', component: LearnRelationshipsView },
|
|
13
|
+
],
|
|
14
|
+
});
|
|
15
|
+
await router.push('/learn/relationships');
|
|
16
|
+
await router.isReady();
|
|
17
|
+
return router;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('LearnRelationshipsView', () => {
|
|
21
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
22
|
+
|
|
23
|
+
it('renders the page header with translated title', async () => {
|
|
24
|
+
const router = await makeRouter();
|
|
25
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
26
|
+
expect(w.find('h1').text()).toContain('Relationships');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders three top-level sections (bilateral, partitive, general)', async () => {
|
|
30
|
+
const router = await makeRouter();
|
|
31
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
32
|
+
const sections = w.findAll('section[aria-label]');
|
|
33
|
+
expect(sections.length).toBe(3);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renders the hyperedge cards with the expected anchors', async () => {
|
|
37
|
+
const router = await makeRouter();
|
|
38
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
39
|
+
const ids = w.findAll('.learn-card[id]').map(c => c.attributes('id'));
|
|
40
|
+
expect(ids).toContain('__partitive__');
|
|
41
|
+
expect(ids).toContain('__generic__');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders three DimensionCards for the partitive dimensions', async () => {
|
|
45
|
+
const router = await makeRouter();
|
|
46
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
47
|
+
const dimIds = w.findAll('.dimension-card[id]').map(c => c.attributes('id'));
|
|
48
|
+
expect(dimIds).toEqual(expect.arrayContaining(['partitive-presence', 'partitive-count', 'partitive-delimiting']));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders the bilateral section grouped by category', async () => {
|
|
52
|
+
const router = await makeRouter();
|
|
53
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
54
|
+
const bilateralArticles = w.find('section[aria-label="Bilateral relations (one-to-one)"]').findAll('article');
|
|
55
|
+
expect(bilateralArticles.length).toBeGreaterThanOrEqual(5);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('every bilateral type row has an id anchor matching the type key', async () => {
|
|
59
|
+
const router = await makeRouter();
|
|
60
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
61
|
+
const rows = w.findAll('.bilateral-row');
|
|
62
|
+
expect(rows.length).toBeGreaterThan(10);
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
const id = row.attributes('id');
|
|
65
|
+
expect(id).toBeTruthy();
|
|
66
|
+
expect(id).toMatch(/^[a-z_]+$/);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders a live diagram for each hyperedge card', async () => {
|
|
71
|
+
const router = await makeRouter();
|
|
72
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
73
|
+
expect(w.findAll('svg.hyperedge-diagram').length).toBe(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('renders ISO references on each hyperedge card', async () => {
|
|
77
|
+
const router = await makeRouter();
|
|
78
|
+
const w = mount(LearnRelationshipsView, { global: { plugins: [router] } });
|
|
79
|
+
const isoRefs = w.findAll('.learn-card span.font-mono').map(s => s.text());
|
|
80
|
+
expect(isoRefs.some(t => t.includes('ISO 704:2022 §5.5.4.3'))).toBe(true);
|
|
81
|
+
expect(isoRefs.some(t => t.includes('ISO 704:2022 §5.5.4.2'))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
5
|
+
import LearnStatusesView from '../../../views/learn/LearnStatusesView.vue';
|
|
6
|
+
|
|
7
|
+
async function makeRouter() {
|
|
8
|
+
const router = createRouter({
|
|
9
|
+
history: createMemoryHistory(),
|
|
10
|
+
routes: [
|
|
11
|
+
{ path: '/', name: 'home', component: { template: '<div/>' } },
|
|
12
|
+
{ path: '/learn/statuses', name: 'learn-statuses', component: LearnStatusesView },
|
|
13
|
+
{ path: '/learn/designations', name: 'learn-designations', component: { template: '<div/>' } },
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
await router.push('/learn/statuses');
|
|
17
|
+
await router.isReady();
|
|
18
|
+
return router;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('LearnStatusesView', () => {
|
|
22
|
+
beforeEach(() => setActivePinia(createPinia()));
|
|
23
|
+
|
|
24
|
+
it('renders the page header', async () => {
|
|
25
|
+
const router = await makeRouter();
|
|
26
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
27
|
+
expect(w.find('h1').text()).toContain('Statuses');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders three top-level sections (entry, normative, source)', async () => {
|
|
31
|
+
const router = await makeRouter();
|
|
32
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
33
|
+
const sections = w.findAll('section[aria-label]');
|
|
34
|
+
expect(sections.length).toBe(3);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('entry section renders all four entry statuses with anchors', async () => {
|
|
38
|
+
const router = await makeRouter();
|
|
39
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
40
|
+
const entrySection = w.find('section[aria-label="Entry status"]');
|
|
41
|
+
const ids = entrySection.findAll('.learn-card[id]').map(c => c.attributes('id'));
|
|
42
|
+
expect(ids).toEqual(expect.arrayContaining(['entry-valid', 'entry-withdrawn', 'entry-draft', 'entry-superseded']));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('normative section cross-links to /learn/designations', async () => {
|
|
46
|
+
const router = await makeRouter();
|
|
47
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
48
|
+
const normative = w.find('section[aria-label="Normative status"]');
|
|
49
|
+
const link = normative.find('a');
|
|
50
|
+
expect(link.attributes('href')).toBe('/learn/designations');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('source section renders the four source statuses with anchors', async () => {
|
|
54
|
+
const router = await makeRouter();
|
|
55
|
+
const w = mount(LearnStatusesView, { global: { plugins: [router] } });
|
|
56
|
+
const sourceSection = w.find('section[aria-label="Source status"]');
|
|
57
|
+
const ids = sourceSection.findAll('.learn-card[id]').map(c => c.attributes('id'));
|
|
58
|
+
expect(ids).toEqual(expect.arrayContaining(['source-identical', 'source-modified', 'source-restyled', 'source-lineage']));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -61,7 +61,7 @@ import type {
|
|
|
61
61
|
JsonLdPartitiveMember,
|
|
62
62
|
JsonLdPartitiveRelation,
|
|
63
63
|
JsonLdConcept,
|
|
64
|
-
} from './
|
|
64
|
+
} from './jsonld-types';
|
|
65
65
|
|
|
66
66
|
// ── Bridges for fields not yet in glossarist-js ────────────────────────────
|
|
67
67
|
// Remove each bridge when glossarist-js publishes native support.
|
|
@@ -173,7 +173,7 @@ function attachBridges(concept: Concept, localizations: Record<string, unknown>)
|
|
|
173
173
|
* instances, since RelatedConcept.fromJSON only reads type/content/ref.
|
|
174
174
|
*/
|
|
175
175
|
function attachRelatedBridges(
|
|
176
|
-
modelRelated:
|
|
176
|
+
modelRelated: ReadonlyArray<{ type?: string | null; content?: Record<string, string> | string | null; ref?: any; related?: ReadonlyArray<any> }>,
|
|
177
177
|
rawRelated: unknown[],
|
|
178
178
|
): void {
|
|
179
179
|
for (const rawRel of rawRelated) {
|
|
@@ -241,7 +241,7 @@ function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown>
|
|
|
241
241
|
|
|
242
242
|
const pronunciation = d[GL.PRONUNCIATION];
|
|
243
243
|
if (pronunciation?.length) {
|
|
244
|
-
result.pronunciation = pronunciation.map(p => ({
|
|
244
|
+
result.pronunciation = pronunciation.map((p: any) => ({
|
|
245
245
|
content: p[GL.CONTENT] ?? null,
|
|
246
246
|
language: p[GL.LANGUAGE] ?? null,
|
|
247
247
|
script: p[GL.SCRIPT] ?? null,
|
|
@@ -257,7 +257,7 @@ function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown>
|
|
|
257
257
|
|
|
258
258
|
const related = d[GL.RELATED];
|
|
259
259
|
if (related?.length) {
|
|
260
|
-
result.related = related.map(r => {
|
|
260
|
+
result.related = related.map((r: any) => {
|
|
261
261
|
const relType = r[GL.RELATIONSHIP_TYPE] ?? 'references';
|
|
262
262
|
if (DESIGNATION_REL_TYPES.has(relType) && r[GL.TARGET]) {
|
|
263
263
|
return { type: relType, target: r[GL.TARGET] };
|
|
@@ -272,7 +272,7 @@ function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown>
|
|
|
272
272
|
}
|
|
273
273
|
const grammarInfo = d[GL.GRAMMAR_INFO];
|
|
274
274
|
if (grammarInfo?.length) {
|
|
275
|
-
result.grammar_info = grammarInfo.map(gi => ({
|
|
275
|
+
result.grammar_info = grammarInfo.map((gi: any) => ({
|
|
276
276
|
gender: gi[GL.GENDER] ?? null,
|
|
277
277
|
number: gi[GL.NUMBER] ?? null,
|
|
278
278
|
part_of_speech: gi[GL.PART_OF_SPEECH] ?? null,
|
|
@@ -354,7 +354,7 @@ function mapSourceFromJsonLd(s: JsonLdSource): Record<string, unknown> {
|
|
|
354
354
|
|
|
355
355
|
const sf = s[GL.SOURCED_FROM] ?? s[GL.SOURCED_FROM_ALT];
|
|
356
356
|
if (sf?.length) {
|
|
357
|
-
result.sourced_from = sf.map(item => mapOriginFromJsonLd(item));
|
|
357
|
+
result.sourced_from = sf.map((item: any) => mapOriginFromJsonLd(item));
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
return result;
|
|
@@ -445,7 +445,7 @@ function mapLocalizedFromJsonLd(lc: JsonLdLocalizedConcept): Record<string, unkn
|
|
|
445
445
|
|
|
446
446
|
const dates = lc[GL.DATES];
|
|
447
447
|
if (dates?.length) {
|
|
448
|
-
data.dates = dates.map(d => ({
|
|
448
|
+
data.dates = dates.map((d: any) => ({
|
|
449
449
|
date: d[GL.DATE] ?? null,
|
|
450
450
|
type: d[GL.DATE_TYPE] ?? null,
|
|
451
451
|
}));
|
|
@@ -471,7 +471,7 @@ function mapPartitiveRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<stri
|
|
|
471
471
|
if (!comprehensive) return null;
|
|
472
472
|
|
|
473
473
|
const partitives = (r[GL.HAS_PARTITIVE] ?? [])
|
|
474
|
-
.map((m): Record<string, unknown> | null => {
|
|
474
|
+
.map((m: any): Record<string, unknown> | null => {
|
|
475
475
|
const ref = m[GL.REF] ? mapRefFromJsonLd(m[GL.REF]) : null;
|
|
476
476
|
if (!ref) return null;
|
|
477
477
|
const out: Record<string, unknown> = { ref };
|
|
@@ -494,7 +494,7 @@ function mapPartitiveRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<stri
|
|
|
494
494
|
if (m[GL.IS_DELIMITING] === true) out.is_delimiting = true;
|
|
495
495
|
return out;
|
|
496
496
|
})
|
|
497
|
-
.filter((m): m is Record<string, unknown> => m !== null);
|
|
497
|
+
.filter((m: any): m is Record<string, unknown> => m !== null);
|
|
498
498
|
|
|
499
499
|
// ISO 704 requires ≥2 partitives. Skip malformed relations rather
|
|
500
500
|
// than letting the constructor throw and break the whole concept.
|
|
@@ -529,7 +529,7 @@ function mapGenericRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<string
|
|
|
529
529
|
if (!comprehensive) return null;
|
|
530
530
|
|
|
531
531
|
const members = (r[GL.HAS_GENERIC] ?? [])
|
|
532
|
-
.map((m): Record<string, unknown> | null => {
|
|
532
|
+
.map((m: any): Record<string, unknown> | null => {
|
|
533
533
|
const ref = m[GL.REF] ? mapRefFromJsonLd(m[GL.REF]) : null;
|
|
534
534
|
if (!ref) return null;
|
|
535
535
|
const out: Record<string, unknown> = { ref };
|
|
@@ -552,7 +552,7 @@ function mapGenericRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<string
|
|
|
552
552
|
}
|
|
553
553
|
return out;
|
|
554
554
|
})
|
|
555
|
-
.filter((m): m is Record<string, unknown> => m !== null);
|
|
555
|
+
.filter((m: any): m is Record<string, unknown> => m !== null);
|
|
556
556
|
|
|
557
557
|
if (members.length < 2) return null;
|
|
558
558
|
|
|
@@ -589,7 +589,7 @@ function splitMultiplicity(m: string): { presence: 'required' | 'optional'; coun
|
|
|
589
589
|
|
|
590
590
|
function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
591
591
|
const id = String(doc[GL.IDENTIFIER] ?? doc['@id']?.split('/').pop() ?? '');
|
|
592
|
-
const localizations: Record<string, unknown
|
|
592
|
+
const localizations: Record<string, Record<string, unknown>> = {};
|
|
593
593
|
|
|
594
594
|
const rawLc = doc[GL.LOCALIZED_CONCEPT] ?? {};
|
|
595
595
|
for (const [lang, lc] of Object.entries(rawLc)) {
|
|
@@ -601,10 +601,10 @@ function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
|
601
601
|
const related = (doc[GL.RELATED] ?? []).map(mapRelatedFromJsonLd);
|
|
602
602
|
const partitiveRelations = (doc[GL.PARTITIVE_RELATIONS] ?? [])
|
|
603
603
|
.map(mapPartitiveRelationFromJsonLd)
|
|
604
|
-
.filter((r): r is Record<string, unknown> => r !== null);
|
|
604
|
+
.filter((r: any): r is Record<string, unknown> => r !== null);
|
|
605
605
|
const genericRelations = (doc[GL.GENERIC_RELATIONS] ?? [])
|
|
606
606
|
.map(mapGenericRelationFromJsonLd)
|
|
607
|
-
.filter((r): r is Record<string, unknown> => r !== null);
|
|
607
|
+
.filter((r: any): r is Record<string, unknown> => r !== null);
|
|
608
608
|
const tagsArr = doc[GL.TAGS];
|
|
609
609
|
const tags = Array.isArray(tagsArr) ? [...tagsArr] : [];
|
|
610
610
|
|
|
@@ -612,7 +612,7 @@ function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
|
612
612
|
id,
|
|
613
613
|
term: doc[GL.TERM] ?? null,
|
|
614
614
|
uri: doc['@id'] ?? null,
|
|
615
|
-
localizations,
|
|
615
|
+
localizations: localizations as Record<string, any>,
|
|
616
616
|
related,
|
|
617
617
|
partitive_relations: partitiveRelations,
|
|
618
618
|
generic_relations: genericRelations,
|
|
@@ -48,7 +48,7 @@ function imageFromJsonLd(raw: Record<string, unknown>): FigureImage | null {
|
|
|
48
48
|
...(typeof width === 'number' && { width }),
|
|
49
49
|
...(typeof height === 'number' && { height }),
|
|
50
50
|
...(typeof scale === 'number' && { scale }),
|
|
51
|
-
});
|
|
51
|
+
} as any);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function imagesFromJsonLd(raw: unknown): FigureImage[] {
|
|
@@ -96,5 +96,5 @@ export function figureFromJsonLd(doc: Record<string, unknown>): Figure | null {
|
|
|
96
96
|
...(description && { description }),
|
|
97
97
|
...(subfigures && { subfigures }),
|
|
98
98
|
...(sources.length && { sources }),
|
|
99
|
-
});
|
|
99
|
+
} as any);
|
|
100
100
|
}
|
|
@@ -40,11 +40,11 @@ export function formulaFromJsonLd(doc: Record<string, unknown>): Formula | null
|
|
|
40
40
|
|
|
41
41
|
return new Formula({
|
|
42
42
|
id,
|
|
43
|
-
expression,
|
|
43
|
+
expression: expression as any,
|
|
44
44
|
notation,
|
|
45
45
|
...(identifier && { identifier }),
|
|
46
46
|
...(caption && { caption }),
|
|
47
47
|
...(description && { description }),
|
|
48
48
|
...(sources.length && { sources }),
|
|
49
|
-
});
|
|
49
|
+
} as any);
|
|
50
50
|
}
|
|
@@ -90,11 +90,11 @@ export function tableFromJsonLd(doc: Record<string, unknown>): Table | null {
|
|
|
90
90
|
|
|
91
91
|
return new Table({
|
|
92
92
|
id,
|
|
93
|
-
content: content as
|
|
93
|
+
content: content as any,
|
|
94
94
|
...(identifier && { identifier }),
|
|
95
95
|
...(caption && { caption }),
|
|
96
96
|
...(description && { description }),
|
|
97
97
|
...(format && { format }),
|
|
98
98
|
...(sources.length && { sources }),
|
|
99
|
-
});
|
|
99
|
+
} as any);
|
|
100
100
|
}
|
|
@@ -107,7 +107,7 @@ async function loadPreview() {
|
|
|
107
107
|
const def = lc?.definitions?.[0]?.content || concept.definition('eng');
|
|
108
108
|
const term = lc?.terms?.[0]?.designation || concept.primaryDesignation('eng');
|
|
109
109
|
preview.value = {
|
|
110
|
-
designation: term || entry?.designations?.eng || conceptId,
|
|
110
|
+
designation: String(term || entry?.designations?.eng || conceptId),
|
|
111
111
|
definition: typeof def === 'string' ? def.slice(0, 200) : '',
|
|
112
112
|
};
|
|
113
113
|
} catch {
|
|
@@ -200,7 +200,7 @@ function getTermForLang(lang: string): string {
|
|
|
200
200
|
|
|
201
201
|
function getDesignationsForLang(lang: string): Designation[] {
|
|
202
202
|
const lc = props.concept.localization(lang);
|
|
203
|
-
return lc?.terms ?? [];
|
|
203
|
+
return [...(lc?.terms ?? [])];
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
function hasDefinition(lang: string): boolean {
|
|
@@ -444,7 +444,7 @@ const nonVerbalReps = computed(() => {
|
|
|
444
444
|
</div>
|
|
445
445
|
|
|
446
446
|
<!-- Non-verbal representations -->
|
|
447
|
-
<NonVerbalRepDisplay v-if="lc.lc.nonVerbalRep?.length" :reps="lc.lc.nonVerbalRep" :locale="lc.lang" :register-id="registerId" :dataset-locales="languages" />
|
|
447
|
+
<NonVerbalRepDisplay v-if="lc.lc.nonVerbalRep?.length" :reps="[...lc.lc.nonVerbalRep]" :locale="lc.lang" :register-id="registerId" :dataset-locales="languages" />
|
|
448
448
|
|
|
449
449
|
<!-- Sources -->
|
|
450
450
|
<div v-if="lc.sources.length" class="space-y-2">
|
|
@@ -33,7 +33,7 @@ const { t } = useI18n();
|
|
|
33
33
|
const targetConcept = ref<Concept | null>(null);
|
|
34
34
|
const loadError = ref<string | null>(null);
|
|
35
35
|
|
|
36
|
-
function conceptToData(c:
|
|
36
|
+
function conceptToData(c: any, registerId: string): ConceptLikeData | null {
|
|
37
37
|
if (!c) return null;
|
|
38
38
|
const langs = c.languages ?? [];
|
|
39
39
|
const localizations: Record<string, unknown> = {};
|
|
@@ -51,7 +51,7 @@ function conceptToData(c: Concept | null, registerId: string): ConceptLikeData |
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const oldData = computed(() => conceptToData(targetConcept.value, props.targetRegisterId));
|
|
54
|
-
const newData = computed(() => conceptToData(props.currentConcept, props.currentRegisterId));
|
|
54
|
+
const newData = computed(() => conceptToData(props.currentConcept as any, props.currentRegisterId));
|
|
55
55
|
|
|
56
56
|
async function loadTarget() {
|
|
57
57
|
if (!props.open || !props.targetRegisterId || !props.targetConceptId) return;
|
|
@@ -51,7 +51,7 @@ watchEffect(() => {
|
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
const effectiveDiff = computed(() => props.diff ?? internal.diffResult.value);
|
|
54
|
+
const effectiveDiff = computed(() => (props.diff ?? internal.diffResult.value) as any);
|
|
55
55
|
const sections = computed(() => deriveDiffSections(effectiveDiff.value, props.lang));
|
|
56
56
|
const similarity = computed(() => computeSimilarity(effectiveDiff.value));
|
|
57
57
|
const hasChanges = computed(() => effectiveDiff.value?.hasChanges === true);
|