@glossarist/concept-browser 0.7.51 → 0.7.52
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 +32 -0
- package/env.d.ts +15 -0
- package/package.json +12 -2
- package/scripts/__tests__/doctor.test.mjs +147 -0
- package/scripts/doctor.mjs +327 -0
- package/scripts/generate-data.mjs +136 -0
- package/scripts/generate-ontology-data.mjs +3 -3
- package/scripts/generate-ontology-schema.mjs +3 -3
- package/scripts/lib/agents-turtle.mjs +64 -0
- package/scripts/lib/bibliography-turtle.mjs +54 -0
- package/scripts/lib/build-activity-turtle.mjs +92 -0
- package/scripts/lib/build-cache.mjs +70 -0
- package/scripts/lib/dataset-turtle.mjs +79 -0
- package/scripts/lib/turtle-escape.mjs +0 -0
- package/scripts/lib/version-turtle.mjs +56 -0
- package/scripts/lib/vocab-turtle.mjs +64 -0
- package/scripts/normalize-yaml.mjs +99 -0
- package/scripts/sync-concept-model.mjs +86 -0
- package/scripts/validate-shacl.mjs +194 -0
- package/src/__fixtures__/concept-shape.ttl +20 -0
- package/src/__fixtures__/shacl/bad/concept.ttl +7 -0
- package/src/__fixtures__/shacl/empty/.gitkeep +0 -0
- package/src/__fixtures__/shacl/good/concept.ttl +8 -0
- package/src/__tests__/__fixtures__/concepts.ts +221 -0
- package/src/__tests__/adapters/concept-identity.test.ts +76 -0
- package/src/__tests__/components/error-boundary.test.ts +109 -0
- package/src/__tests__/composables/use-dataset-series.test.ts +262 -0
- package/src/__tests__/concept-rdf/agents-emitter.test.ts +110 -0
- package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +159 -0
- package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +119 -0
- package/src/__tests__/concept-rdf/coerce-date.test.ts +97 -0
- package/src/__tests__/concept-rdf/concept-emitter.test.ts +258 -0
- package/src/__tests__/concept-rdf/dataset-emitter.test.ts +224 -0
- package/src/__tests__/concept-rdf/differential.test.ts +96 -0
- package/src/__tests__/concept-rdf/group-emitter.test.ts +109 -0
- package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +135 -0
- package/src/__tests__/concept-rdf/jsonld-writer.test.ts +109 -0
- package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +177 -0
- package/src/__tests__/concept-rdf/property-based.test.ts +179 -0
- package/src/__tests__/concept-rdf/provenance.test.ts +110 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +43 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.ts +47 -0
- package/src/__tests__/concept-rdf/rdf-components.test.ts +145 -0
- package/src/__tests__/concept-rdf/rdf-graph.test.ts +115 -0
- package/src/__tests__/concept-rdf/round-trip.test.ts +243 -0
- package/src/__tests__/concept-rdf/scoped-examples.test.ts +126 -0
- package/src/__tests__/concept-rdf/sections-builder.test.ts +94 -0
- package/src/__tests__/concept-rdf/shacl-conformance.test.ts +110 -0
- package/src/__tests__/concept-rdf/shape-consistency.test.ts +138 -0
- package/src/__tests__/concept-rdf/snapshot-generation.test.ts +75 -0
- package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +142 -0
- package/src/__tests__/concept-rdf/turtle-writer.test.ts +114 -0
- package/src/__tests__/concept-rdf/use-rdf-document.test.ts +246 -0
- package/src/__tests__/concept-rdf/version-emitter.test.ts +120 -0
- package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +100 -0
- package/src/__tests__/concept-rdf-view.test.ts +136 -0
- package/src/__tests__/dataset-style.test.ts +12 -7
- package/src/__tests__/errors/errors.test.ts +142 -0
- package/src/__tests__/format-downloads.test.ts +47 -65
- package/src/__tests__/markdown-lite.test.ts +19 -0
- package/src/__tests__/perf/bundle-layout.test.ts +50 -0
- package/src/__tests__/perf/serialization-perf.test.ts +121 -0
- package/src/__tests__/scripts/agents-turtle.test.ts +61 -0
- package/src/__tests__/scripts/bibliography-turtle.test.ts +59 -0
- package/src/__tests__/scripts/build-activity-turtle.test.ts +75 -0
- package/src/__tests__/scripts/build-cache.test.ts +78 -0
- package/src/__tests__/scripts/build-integration.test.ts +134 -0
- package/src/__tests__/scripts/dataset-turtle.test.ts +94 -0
- package/src/__tests__/scripts/normalize-yaml.test.ts +98 -0
- package/src/__tests__/scripts/stryker-config.test.ts +33 -0
- package/src/__tests__/scripts/turtle-escape.test.ts +63 -0
- package/src/__tests__/scripts/version-turtle.test.ts +72 -0
- package/src/__tests__/scripts/vocab-turtle.test.ts +63 -0
- package/src/__tests__/use-format-registry.test.ts +125 -0
- package/src/__tests__/utils/bcp47.test.ts +166 -0
- package/src/__tests__/utils/color-theme.test.ts +143 -0
- package/src/__tests__/utils/url-safety.test.ts +65 -0
- package/src/__tests__/validate-shacl.test.ts +100 -0
- package/src/adapters/DatasetAdapter.ts +11 -5
- package/src/adapters/GraphDataSource.ts +2 -1
- package/src/adapters/UriRouter.ts +2 -1
- package/src/adapters/concept-identity.ts +69 -0
- package/src/adapters/factory.ts +3 -2
- package/src/adapters/model-bridge.ts +2 -1
- package/src/adapters/non-verbal/glossarist-augment.d.ts +7 -0
- package/src/adapters/non-verbal-resolver.ts +2 -1
- package/src/components/AppSidebar.vue +189 -93
- package/src/components/ConceptDetail.vue +8 -0
- package/src/components/ConceptEditionRail.vue +222 -0
- package/src/components/ConceptRdfView.vue +37 -377
- package/src/components/DatasetSeriesCard.vue +270 -0
- package/src/components/ErrorBoundary.vue +95 -0
- package/src/components/FormatDownloads.vue +17 -13
- package/src/components/HomeSeriesSection.vue +277 -0
- package/src/components/RelationSphere.vue +1672 -0
- package/src/components/SidebarSeriesSection.vue +239 -0
- package/src/components/concept-rdf/RdfInstanceHeader.vue +47 -0
- package/src/components/concept-rdf/RdfInstanceSection.vue +54 -0
- package/src/components/concept-rdf/RdfPrefixLegend.vue +27 -0
- package/src/components/concept-rdf/RdfSourcePanel.vue +72 -0
- package/src/components/concept-rdf/agents-emitter.ts +82 -0
- package/src/components/concept-rdf/bibliography-emitter.ts +83 -0
- package/src/components/concept-rdf/build-activity-emitter.ts +89 -0
- package/src/components/concept-rdf/concept-emitter.ts +443 -0
- package/src/components/concept-rdf/dataset-emitter.ts +95 -0
- package/src/components/concept-rdf/group-emitter.ts +69 -0
- package/src/components/concept-rdf/image-variant-emitter.ts +46 -0
- package/src/components/concept-rdf/jsonld-writer.ts +82 -0
- package/src/components/concept-rdf/predicates.ts +261 -0
- package/src/components/concept-rdf/provenance.ts +80 -0
- package/src/components/concept-rdf/rdf-graph.ts +211 -0
- package/src/components/concept-rdf/rdf-prefixes.ts +23 -0
- package/src/components/concept-rdf/sections-builder.ts +62 -0
- package/src/components/concept-rdf/table-formula-emitter.ts +101 -0
- package/src/components/concept-rdf/turtle-writer.ts +116 -0
- package/src/components/concept-rdf/use-rdf-document.ts +72 -0
- package/src/components/concept-rdf/version-emitter.ts +65 -0
- package/src/components/concept-rdf/vocabulary-emitter.ts +62 -0
- package/src/composables/use-color-theme.ts +82 -0
- package/src/composables/use-format-registry.ts +42 -0
- package/src/composables/useDatasetSeries.ts +258 -0
- package/src/composables/useSphereProjection.ts +125 -0
- package/src/config/group-types.ts +92 -0
- package/src/config/types.ts +81 -2
- package/src/config/use-site-config.ts +2 -1
- package/src/errors.ts +136 -0
- package/src/i18n/locales/eng.yml +24 -0
- package/src/i18n/locales/fra.yml +24 -0
- package/src/stores/vocabulary.ts +3 -1
- package/src/style.css +17 -2
- package/src/types/agents-version-turtle.d.ts +27 -0
- package/src/types/bibliography-turtle.d.ts +12 -0
- package/src/types/build-activity-turtle.d.ts +16 -0
- package/src/types/build-cache.d.ts +20 -0
- package/src/types/dataset-turtle.d.ts +32 -0
- package/src/types/normalize-yaml.d.ts +16 -0
- package/src/types/turtle-escape.d.ts +6 -0
- package/src/types/vocab-turtle.d.ts +13 -0
- package/src/utils/asciidoc-lite.ts +11 -6
- package/src/utils/bcp47.ts +141 -0
- package/src/utils/color-theme-integration.ts +11 -0
- package/src/utils/color-theme.ts +129 -0
- package/src/utils/dataset-style.ts +31 -6
- package/src/utils/locale.ts +6 -14
- package/src/utils/markdown-lite.ts +6 -1
- package/src/utils/relation-sphere-styling.ts +63 -0
- package/src/utils/relationship-categories.ts +30 -0
- package/src/utils/url-safety.ts +30 -0
- package/src/views/ConceptView.vue +183 -9
- package/src/views/DatasetView.vue +6 -0
- package/src/views/HomeView.vue +5 -0
- package/vite.config.ts +7 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Parser, Store } from 'n3';
|
|
3
|
+
import { buildActivityTurtle } from '../../../scripts/lib/build-activity-turtle.mjs';
|
|
4
|
+
|
|
5
|
+
function makeInput(overrides: any = {}): any {
|
|
6
|
+
return {
|
|
7
|
+
runId: '2026-06-28T12-00-00Z',
|
|
8
|
+
startedAt: '2026-06-28T12:00:00Z',
|
|
9
|
+
endedAt: '2026-06-28T12:05:00Z',
|
|
10
|
+
gitSha: 'abc1234',
|
|
11
|
+
gitBranch: 'main',
|
|
12
|
+
toolId: 'concept-browser',
|
|
13
|
+
toolVersion: '0.7.51',
|
|
14
|
+
datasetRegisters: ['iso-geodetic'],
|
|
15
|
+
conceptCount: 1234,
|
|
16
|
+
associatedAgentIri: 'https://glossarist.org/agent/ci-bot',
|
|
17
|
+
...overrides,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parse(turtle: string): Store {
|
|
22
|
+
const parser = new Parser({ format: 'Turtle' });
|
|
23
|
+
const store = new Store();
|
|
24
|
+
store.addQuads(parser.parse(turtle));
|
|
25
|
+
return store;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ACTIVITY_IRI = (runId: string) => `https://glossarist.org/activity/build/${runId}`;
|
|
29
|
+
const expand = (v: string): string => {
|
|
30
|
+
if (v.startsWith('prov:')) return `http://www.w3.org/ns/prov#${v.slice(5)}`;
|
|
31
|
+
if (v.startsWith('dcterms:')) return `http://purl.org/dc/terms/${v.slice(8)}`;
|
|
32
|
+
if (v.startsWith('rdf:')) return `http://www.w3.org/1999/02/22-rdf-syntax-ns#${v.slice(4)}`;
|
|
33
|
+
if (v.startsWith('xsd:')) return `http://www.w3.org/2001/XMLSchema#${v.slice(4)}`;
|
|
34
|
+
if (v.startsWith('foaf:')) return `http://xmlns.com/foaf/0.1/${v.slice(5)}`;
|
|
35
|
+
return v;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe('buildActivityTurtle (mjs)', () => {
|
|
39
|
+
it('parses without errors', () => {
|
|
40
|
+
const ttl = buildActivityTurtle(makeInput());
|
|
41
|
+
const store = parse(ttl);
|
|
42
|
+
expect(store.size).toBeGreaterThan(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('types the activity as prov:Activity', () => {
|
|
46
|
+
const input = makeInput();
|
|
47
|
+
const store = parse(buildActivityTurtle(input));
|
|
48
|
+
const types = store.getObjects(ACTIVITY_IRI(input.runId), expand('rdf:type'), null).map(q => q.value);
|
|
49
|
+
expect(types).toContain(expand('prov:Activity'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('records git commit and tool as prov:used', () => {
|
|
53
|
+
const input = makeInput();
|
|
54
|
+
const store = parse(buildActivityTurtle(input));
|
|
55
|
+
const used = store.getObjects(ACTIVITY_IRI(input.runId), expand('prov:used'), null).map(q => q.value);
|
|
56
|
+
expect(used).toContain(`https://glossarist.org/commit/${input.gitSha}`);
|
|
57
|
+
expect(used).toContain(`https://glossarist.org/tool/${input.toolId}/${input.toolVersion}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('references every dataset register via prov:used', () => {
|
|
61
|
+
const input = makeInput({ datasetRegisters: ['a', 'b', 'c'] });
|
|
62
|
+
const store = parse(buildActivityTurtle(input));
|
|
63
|
+
const used = store.getObjects(ACTIVITY_IRI(input.runId), expand('prov:used'), null).map(q => q.value);
|
|
64
|
+
for (const r of input.datasetRegisters) {
|
|
65
|
+
expect(used).toContain(`https://glossarist.org/${r}/`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('associates the CI agent when provided', () => {
|
|
70
|
+
const input = makeInput();
|
|
71
|
+
const store = parse(buildActivityTurtle(input));
|
|
72
|
+
const agents = store.getObjects(ACTIVITY_IRI(input.runId), expand('prov:wasAssociatedWith'), null).map(q => q.value);
|
|
73
|
+
expect(agents).toContain(input.associatedAgentIri);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { rmSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { BuildCache } from '../../../scripts/lib/build-cache.mjs';
|
|
6
|
+
|
|
7
|
+
const SUITE_DIR = join(tmpdir(), `build-cache-${process.pid}-${Date.now()}`);
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mkdirSync(SUITE_DIR, { recursive: true });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
rmSync(SUITE_DIR, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('BuildCache', () => {
|
|
18
|
+
it('returns null for a missing key', async () => {
|
|
19
|
+
const cache = new BuildCache(SUITE_DIR);
|
|
20
|
+
expect(await cache.get('missing')).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('round-trips a value through set/get', async () => {
|
|
24
|
+
const cache = new BuildCache(SUITE_DIR);
|
|
25
|
+
await cache.set('concept-3.1.1', { ttl: '@prefix' }, 'aaa');
|
|
26
|
+
const entry = await cache.get('concept-3.1.1');
|
|
27
|
+
expect(entry?.value).toEqual({ ttl: '@prefix' });
|
|
28
|
+
expect(entry?.hash).toBe('aaa');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('readThrough invokes the producer on a miss', async () => {
|
|
32
|
+
const cache = new BuildCache(SUITE_DIR);
|
|
33
|
+
let calls = 0;
|
|
34
|
+
const { value, hit } = await cache.readThrough('k', 'h1', async () => {
|
|
35
|
+
calls++;
|
|
36
|
+
return { ok: true };
|
|
37
|
+
});
|
|
38
|
+
expect(hit).toBe(false);
|
|
39
|
+
expect(calls).toBe(1);
|
|
40
|
+
expect(value).toEqual({ ok: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('readThrough skips the producer on a hit', async () => {
|
|
44
|
+
const cache = new BuildCache(SUITE_DIR);
|
|
45
|
+
await cache.set('k', { ok: true }, 'h1');
|
|
46
|
+
let calls = 0;
|
|
47
|
+
const { value, hit } = await cache.readThrough('k', 'h1', async () => {
|
|
48
|
+
calls++;
|
|
49
|
+
return { ok: false };
|
|
50
|
+
});
|
|
51
|
+
expect(hit).toBe(true);
|
|
52
|
+
expect(calls).toBe(0);
|
|
53
|
+
expect(value).toEqual({ ok: true });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('readThrough re-invokes the producer when the hash differs', async () => {
|
|
57
|
+
const cache = new BuildCache(SUITE_DIR);
|
|
58
|
+
await cache.set('k', 'old', 'h1');
|
|
59
|
+
const { value, hit } = await cache.readThrough('k', 'h2', async () => 'new');
|
|
60
|
+
expect(hit).toBe(false);
|
|
61
|
+
expect(value).toBe('new');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('hash is stable for the same input', () => {
|
|
65
|
+
expect(BuildCache.hash('hello')).toBe(BuildCache.hash('hello'));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('hash differs for different inputs', () => {
|
|
69
|
+
expect(BuildCache.hash('hello')).not.toBe(BuildCache.hash('world'));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('sanitizes unsafe key characters in the on-disk path', async () => {
|
|
73
|
+
const cache = new BuildCache(SUITE_DIR);
|
|
74
|
+
await cache.set('this/has spaces&symbols', 1, 'h');
|
|
75
|
+
const entry = await cache.get('this/has spaces&symbols');
|
|
76
|
+
expect(entry?.value).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { Parser, Store } from 'n3';
|
|
4
|
+
|
|
5
|
+
function parseTurtle(text: string): Store {
|
|
6
|
+
const parser = new Parser({ format: 'Turtle' });
|
|
7
|
+
const store = new Store();
|
|
8
|
+
store.addQuads(parser.parse(text));
|
|
9
|
+
return store;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function runInline(code: string): string {
|
|
13
|
+
return execFileSync('node', ['--input-type=module', '-e', code], { encoding: 'utf8' }).toString();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('WS F Layer 8 — build pipeline integration', () => {
|
|
17
|
+
it('vocab-turtle.mjs produces parseable Turtle with 7 schemes', () => {
|
|
18
|
+
const out = runInline(`import { buildVocabularyTurtle } from './scripts/lib/vocab-turtle.mjs'; console.log(buildVocabularyTurtle());`);
|
|
19
|
+
const store = parseTurtle(out);
|
|
20
|
+
const schemes = [...store].filter(q =>
|
|
21
|
+
q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' &&
|
|
22
|
+
q.object.value === 'http://www.w3.org/2004/02/skos/core#ConceptScheme',
|
|
23
|
+
);
|
|
24
|
+
expect(schemes.length).toBe(7);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('dataset-turtle.mjs produces parseable dcat:Dataset', () => {
|
|
28
|
+
const out = runInline(`
|
|
29
|
+
import { buildDatasetTurtle } from './scripts/lib/dataset-turtle.mjs';
|
|
30
|
+
console.log(buildDatasetTurtle({
|
|
31
|
+
datasetIri: 'https://glossarist.org/test/',
|
|
32
|
+
registerId: 'test', title: 'Test', modified: '2026-06-28',
|
|
33
|
+
languages: ['eng'], distributions: [], topConceptUris: [], sections: [],
|
|
34
|
+
}));
|
|
35
|
+
`);
|
|
36
|
+
const store = parseTurtle(out);
|
|
37
|
+
const types = [...store].filter(q =>
|
|
38
|
+
q.subject.value === 'https://glossarist.org/test/' &&
|
|
39
|
+
q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
|
|
40
|
+
).map(q => q.object.value);
|
|
41
|
+
expect(types).toContain('http://www.w3.org/ns/dcat#Dataset');
|
|
42
|
+
expect(types).toContain('http://www.w3.org/2004/02/skos/core#ConceptScheme');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('build-activity-turtle.mjs produces parseable prov:Activity', () => {
|
|
46
|
+
const out = runInline(`
|
|
47
|
+
import { buildActivityTurtle } from './scripts/lib/build-activity-turtle.mjs';
|
|
48
|
+
console.log(buildActivityTurtle({
|
|
49
|
+
runId: 'test', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z',
|
|
50
|
+
toolId: 'cb', toolVersion: '0.7.52', datasetRegisters: [], conceptCount: 0,
|
|
51
|
+
}));
|
|
52
|
+
`);
|
|
53
|
+
const store = parseTurtle(out);
|
|
54
|
+
const activities = [...store].filter(q =>
|
|
55
|
+
q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' &&
|
|
56
|
+
q.object.value === 'http://www.w3.org/ns/prov#Activity',
|
|
57
|
+
);
|
|
58
|
+
expect(activities.length).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('agents-turtle.mjs produces parseable foaf:Person', () => {
|
|
62
|
+
const out = runInline(`
|
|
63
|
+
import { buildAgentsTurtle } from './scripts/lib/agents-turtle.mjs';
|
|
64
|
+
console.log(buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }]));
|
|
65
|
+
`);
|
|
66
|
+
const store = parseTurtle(out);
|
|
67
|
+
const persons = [...store].filter(q =>
|
|
68
|
+
q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' &&
|
|
69
|
+
q.object.value === 'http://xmlns.com/foaf/0.1/Person',
|
|
70
|
+
);
|
|
71
|
+
expect(persons.length).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('bibliography-turtle.mjs produces parseable dcterms:BibliographicResource', () => {
|
|
75
|
+
const out = runInline(`
|
|
76
|
+
import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle.mjs';
|
|
77
|
+
console.log(buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }));
|
|
78
|
+
`);
|
|
79
|
+
const store = parseTurtle(out);
|
|
80
|
+
const bibs = [...store].filter(q =>
|
|
81
|
+
q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' &&
|
|
82
|
+
q.object.value === 'http://purl.org/dc/terms/BibliographicResource',
|
|
83
|
+
);
|
|
84
|
+
expect(bibs.length).toBe(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('version-turtle.mjs produces parseable prov:Entity chain', () => {
|
|
88
|
+
const out = runInline(`
|
|
89
|
+
import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle.mjs';
|
|
90
|
+
console.log(buildVersionHistoryTurtle({
|
|
91
|
+
registerId: 'test', datasetIri: 'https://glossarist.org/test/',
|
|
92
|
+
versions: [
|
|
93
|
+
{ version: '1.0', generatedAt: '2024-01-01T00:00:00Z' },
|
|
94
|
+
{ version: '1.1', generatedAt: '2024-06-01T00:00:00Z' },
|
|
95
|
+
],
|
|
96
|
+
}));
|
|
97
|
+
`);
|
|
98
|
+
const store = parseTurtle(out);
|
|
99
|
+
const versions = [...store].filter(q =>
|
|
100
|
+
q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' &&
|
|
101
|
+
q.object.value === 'http://www.w3.org/ns/prov#Entity',
|
|
102
|
+
);
|
|
103
|
+
expect(versions.length).toBe(2);
|
|
104
|
+
|
|
105
|
+
const wasRevision = store.getObjects(
|
|
106
|
+
'https://glossarist.org/test/versions/1.1',
|
|
107
|
+
'http://www.w3.org/ns/prov#wasRevisionOf',
|
|
108
|
+
null,
|
|
109
|
+
).map(q => q.value);
|
|
110
|
+
expect(wasRevision).toContain('https://glossarist.org/test/versions/1.0');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('all six emitters produce parseable output in a single subprocess', () => {
|
|
114
|
+
const out = runInline(`
|
|
115
|
+
import { buildVocabularyTurtle } from './scripts/lib/vocab-turtle.mjs';
|
|
116
|
+
import { buildDatasetTurtle } from './scripts/lib/dataset-turtle.mjs';
|
|
117
|
+
import { buildActivityTurtle } from './scripts/lib/build-activity-turtle.mjs';
|
|
118
|
+
import { buildAgentsTurtle } from './scripts/lib/agents-turtle.mjs';
|
|
119
|
+
import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle.mjs';
|
|
120
|
+
import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle.mjs';
|
|
121
|
+
const parts = [
|
|
122
|
+
buildVocabularyTurtle(),
|
|
123
|
+
buildDatasetTurtle({ datasetIri: 'https://glossarist.org/test/', registerId: 'test', title: 't', modified: '2026-01-01', languages: ['eng'], distributions: [], topConceptUris: [], sections: [] }),
|
|
124
|
+
buildActivityTurtle({ runId: 'r', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z', toolId: 'cb', toolVersion: '0', datasetRegisters: [], conceptCount: 0 }),
|
|
125
|
+
buildAgentsTurtle([{ name: 'Ada' }]),
|
|
126
|
+
buildBibliographyTurtle('test', { x: { reference: 'X' } }),
|
|
127
|
+
buildVersionHistoryTurtle({ registerId: 't', datasetIri: 'https://glossarist.org/t/', versions: [{ version: '1', generatedAt: '2026-01-01' }] }),
|
|
128
|
+
];
|
|
129
|
+
console.log(parts.join('\\n'));
|
|
130
|
+
`);
|
|
131
|
+
const store = parseTurtle(out);
|
|
132
|
+
expect(store.size).toBeGreaterThan(100);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Parser, Store } from 'n3';
|
|
3
|
+
import { buildDatasetTurtle } from '../../../scripts/lib/dataset-turtle.mjs';
|
|
4
|
+
|
|
5
|
+
const BASE = 'https://glossarist.org/dataset';
|
|
6
|
+
|
|
7
|
+
function makeInput(overrides: Record<string, any> = {}): any {
|
|
8
|
+
return {
|
|
9
|
+
datasetIri: `${BASE}/test`,
|
|
10
|
+
registerId: 'test',
|
|
11
|
+
title: 'Test Glossary',
|
|
12
|
+
description: 'A test dataset.',
|
|
13
|
+
modified: '2026-06-28',
|
|
14
|
+
languages: ['eng', 'fra'],
|
|
15
|
+
distributions: [
|
|
16
|
+
{
|
|
17
|
+
id: 'test-ttl',
|
|
18
|
+
title: 'Turtle distribution',
|
|
19
|
+
mediaType: 'text/turtle',
|
|
20
|
+
downloadUrl: 'https://glossarist.org/data/test.ttl',
|
|
21
|
+
byteSize: 12345,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
topConceptUris: [`${BASE}/test/concept/1`, `${BASE}/test/concept/2`],
|
|
25
|
+
sections: [
|
|
26
|
+
{
|
|
27
|
+
collectionIri: `${BASE}/test/section/3-1`,
|
|
28
|
+
title: 'Geodetic concepts',
|
|
29
|
+
memberUris: [`${BASE}/test/concept/1`],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parse(turtle: string): Store {
|
|
37
|
+
const parser = new Parser({ format: 'Turtle' });
|
|
38
|
+
const store = new Store();
|
|
39
|
+
store.addQuads(parser.parse(turtle));
|
|
40
|
+
return store;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const expand = (v: string): string => {
|
|
44
|
+
if (v.startsWith('dcat:')) return `http://www.w3.org/ns/dcat#${v.slice(5)}`;
|
|
45
|
+
if (v.startsWith('skos:')) return `http://www.w3.org/2004/02/skos/core#${v.slice(5)}`;
|
|
46
|
+
if (v.startsWith('dcterms:')) return `http://purl.org/dc/terms/${v.slice(8)}`;
|
|
47
|
+
if (v.startsWith('rdf:')) return `http://www.w3.org/1999/02/22-rdf-syntax-ns#${v.slice(4)}`;
|
|
48
|
+
if (v.startsWith('prov:')) return `http://www.w3.org/ns/prov#${v.slice(5)}`;
|
|
49
|
+
return v;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe('buildDatasetTurtle (mjs)', () => {
|
|
53
|
+
it('parses without errors and produces a non-empty graph', () => {
|
|
54
|
+
const ttl = buildDatasetTurtle(makeInput());
|
|
55
|
+
const store = parse(ttl);
|
|
56
|
+
expect(store.size).toBeGreaterThan(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('types the dataset as dcat:Dataset and skos:ConceptScheme', () => {
|
|
60
|
+
const input = makeInput();
|
|
61
|
+
const store = parse(buildDatasetTurtle(input));
|
|
62
|
+
const types = store.getObjects(input.datasetIri, expand('rdf:type'), null).map(q => q.value);
|
|
63
|
+
expect(types).toContain(expand('dcat:Dataset'));
|
|
64
|
+
expect(types).toContain(expand('skos:ConceptScheme'));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('emits a dcat:Distribution blank per distribution', () => {
|
|
68
|
+
const input = makeInput();
|
|
69
|
+
const store = parse(buildDatasetTurtle(input));
|
|
70
|
+
const dists = store.getObjects(input.datasetIri, expand('dcat:distribution'), null);
|
|
71
|
+
expect(dists.length).toBe(1);
|
|
72
|
+
const mediaTypes = store.getObjects(dists[0], expand('dcat:mediaType'), null).map(q => q.value);
|
|
73
|
+
expect(mediaTypes).toContain('text/turtle');
|
|
74
|
+
const byteSizes = store.getObjects(dists[0], expand('dcat:byteSize'), null).map(q => q.value);
|
|
75
|
+
expect(byteSizes).toContain('12345');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('emits skos:Collection per section with skos:member entries', () => {
|
|
79
|
+
const input = makeInput();
|
|
80
|
+
const store = parse(buildDatasetTurtle(input));
|
|
81
|
+
const section = input.sections[0];
|
|
82
|
+
const types = store.getObjects(section.collectionIri, expand('rdf:type'), null).map(q => q.value);
|
|
83
|
+
expect(types).toContain(expand('skos:Collection'));
|
|
84
|
+
const members = store.getObjects(section.collectionIri, expand('skos:member'), null).map(q => q.value);
|
|
85
|
+
expect(members).toContain(`${BASE}/test/concept/1`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('escapes double-quotes and backslashes in titles', () => {
|
|
89
|
+
const input = makeInput({ title: 'has "quotes" and back\\slash' });
|
|
90
|
+
const store = parse(buildDatasetTurtle(input));
|
|
91
|
+
const titles = store.getObjects(input.datasetIri, expand('dcterms:title'), null).map(q => q.value);
|
|
92
|
+
expect(titles).toContain('has "quotes" and back\\slash');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { normalizeYaml } from '../../../scripts/normalize-yaml.mjs';
|
|
6
|
+
|
|
7
|
+
const NFC_STRING = 'café';
|
|
8
|
+
const NON_NFC_STRING = 'café';
|
|
9
|
+
|
|
10
|
+
function makeTempDataset() {
|
|
11
|
+
const dir = mkdtempSync(join(tmpdir(), 'glossarist-nfc-'));
|
|
12
|
+
return dir;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('normalizeYaml', () => {
|
|
16
|
+
let dir: string;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
dir = makeTempDataset();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
rmSync(dir, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('detects non-NFC YAML files in check mode without modifying them', () => {
|
|
27
|
+
const file = join(dir, 'concept.yaml');
|
|
28
|
+
writeFileSync(file, `term: "${NON_NFC_STRING}"\n`, 'utf8');
|
|
29
|
+
|
|
30
|
+
const { checked, nonNfc, fixed, check } = normalizeYaml({ root: dir, check: true, paths: ['.'] });
|
|
31
|
+
|
|
32
|
+
expect(checked).toBe(1);
|
|
33
|
+
expect(nonNfc).toBe(1);
|
|
34
|
+
expect(fixed).toHaveLength(1);
|
|
35
|
+
expect(check).toBe(true);
|
|
36
|
+
|
|
37
|
+
const after = readFileSync(file, 'utf8');
|
|
38
|
+
expect(after).toContain(NON_NFC_STRING);
|
|
39
|
+
expect(after).not.toContain(NFC_STRING);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('normalizes non-NFC YAML files in fix mode', () => {
|
|
43
|
+
const file = join(dir, 'concept.yaml');
|
|
44
|
+
writeFileSync(file, `term: "${NON_NFC_STRING}"\n`, 'utf8');
|
|
45
|
+
|
|
46
|
+
const { checked, nonNfc, fixed } = normalizeYaml({ root: dir, check: false, paths: ['.'] });
|
|
47
|
+
|
|
48
|
+
expect(checked).toBe(1);
|
|
49
|
+
expect(nonNfc).toBe(1);
|
|
50
|
+
expect(fixed).toHaveLength(1);
|
|
51
|
+
|
|
52
|
+
const after = readFileSync(file, 'utf8');
|
|
53
|
+
expect(after).toContain(NFC_STRING);
|
|
54
|
+
expect(after).not.toContain(NON_NFC_STRING);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('passes through already-NFC files without changes', () => {
|
|
58
|
+
writeFileSync(join(dir, 'a.yaml'), `term: "${NFC_STRING}"\n`, 'utf8');
|
|
59
|
+
writeFileSync(join(dir, 'b.yaml'), `term: "hello"\n`, 'utf8');
|
|
60
|
+
|
|
61
|
+
const { checked, nonNfc, fixed } = normalizeYaml({ root: dir, check: true, paths: ['.'] });
|
|
62
|
+
|
|
63
|
+
expect(checked).toBe(2);
|
|
64
|
+
expect(nonNfc).toBe(0);
|
|
65
|
+
expect(fixed).toHaveLength(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('walks subdirectories recursively', () => {
|
|
69
|
+
const subDir = join(dir, 'concepts', 'sub');
|
|
70
|
+
mkdirSync(subDir, { recursive: true });
|
|
71
|
+
writeFileSync(join(subDir, 'deep.yaml'), `text: "${NON_NFC_STRING}"\n`, 'utf8');
|
|
72
|
+
writeFileSync(join(dir, 'top.yaml'), `text: "ok"\n`, 'utf8');
|
|
73
|
+
|
|
74
|
+
const { checked, nonNfc } = normalizeYaml({ root: dir, check: true, paths: ['.'] });
|
|
75
|
+
|
|
76
|
+
expect(checked).toBe(2);
|
|
77
|
+
expect(nonNfc).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('skips node_modules and dist directories', () => {
|
|
81
|
+
mkdirSync(join(dir, 'node_modules'), { recursive: true });
|
|
82
|
+
mkdirSync(join(dir, 'dist'), { recursive: true });
|
|
83
|
+
writeFileSync(join(dir, 'node_modules', 'dep.yaml'), `x: "${NON_NFC_STRING}"\n`, 'utf8');
|
|
84
|
+
writeFileSync(join(dir, 'dist', 'out.yaml'), `x: "${NON_NFC_STRING}"\n`, 'utf8');
|
|
85
|
+
writeFileSync(join(dir, 'good.yaml'), `x: "${NFC_STRING}"\n`, 'utf8');
|
|
86
|
+
|
|
87
|
+
const { checked, nonNfc } = normalizeYaml({ root: dir, check: true, paths: ['.'] });
|
|
88
|
+
|
|
89
|
+
expect(checked).toBe(1);
|
|
90
|
+
expect(nonNfc).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('handles empty or nonexistent search directories gracefully', () => {
|
|
94
|
+
const result = normalizeYaml({ root: '/nonexistent/path', check: true });
|
|
95
|
+
expect(result.checked).toBe(0);
|
|
96
|
+
expect(result.nonNfc).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const STRYKER_CONFIG = join(process.cwd(), 'stryker.config.mjs');
|
|
6
|
+
|
|
7
|
+
describe('WS P2 — stryker mutation testing setup', () => {
|
|
8
|
+
it('ships a stryker config file', () => {
|
|
9
|
+
expect(existsSync(STRYKER_CONFIG)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('config scopes mutation to the three highest-value emitter files', () => {
|
|
13
|
+
const content = readFileSync(STRYKER_CONFIG, 'utf8');
|
|
14
|
+
expect(content).toContain('src/components/concept-rdf/concept-emitter.ts');
|
|
15
|
+
expect(content).toContain('src/components/concept-rdf/dataset-emitter.ts');
|
|
16
|
+
expect(content).toContain('src/components/concept-rdf/bibliography-emitter.ts');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('config declares a break threshold (build fails if mutation score drops)', () => {
|
|
20
|
+
const content = readFileSync(STRYKER_CONFIG, 'utf8');
|
|
21
|
+
expect(content).toMatch(/break:\s*\d+/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('config uses vitest as the test runner', () => {
|
|
25
|
+
const content = readFileSync(STRYKER_CONFIG, 'utf8');
|
|
26
|
+
expect(content).toMatch(/testRunner:\s*['"]vitest['"]/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('package.json exposes npm run mutation:test', () => {
|
|
30
|
+
const pkg = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf8'));
|
|
31
|
+
expect(pkg.scripts['mutation:test']).toMatch(/^stryker run/);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ttlLit, ttlPrefixed, ttlIri, assertValidIri } from '../../../scripts/lib/turtle-escape.mjs';
|
|
3
|
+
|
|
4
|
+
describe('D1/D2 — Turtle escaping hardening', () => {
|
|
5
|
+
describe('ttlLit', () => {
|
|
6
|
+
it('escapes backslashes and quotes', () => {
|
|
7
|
+
expect(ttlLit('has "quotes" and back\\slash')).toBe('"has \\"quotes\\" and back\\\\slash"');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('escapes newlines, tabs, carriage returns', () => {
|
|
11
|
+
expect(ttlLit('line1\nline2\ttab\rreturn')).toBe('"line1\\nline2\\ttab\\rreturn"');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns empty literal for null/undefined', () => {
|
|
15
|
+
expect(ttlLit(null)).toBe('""');
|
|
16
|
+
expect(ttlLit(undefined)).toBe('""');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('coerces non-strings to string', () => {
|
|
20
|
+
expect(ttlLit(42)).toBe('"42"');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('ttlPrefixed', () => {
|
|
25
|
+
it('escapes forward slashes in local names per Turtle PN_LOCAL rules', () => {
|
|
26
|
+
expect(ttlPrefixed('gloss:status/valid')).toBe('gloss:status\\/valid');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('passes through names without slashes unchanged', () => {
|
|
30
|
+
expect(ttlPrefixed('gloss:Concept')).toBe('gloss:Concept');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('assertValidIri / ttlIri', () => {
|
|
35
|
+
it('accepts a clean IRI', () => {
|
|
36
|
+
expect(assertValidIri('https://example.org/path')).toBe('https://example.org/path');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('rejects IRIs with angle brackets', () => {
|
|
40
|
+
expect(() => assertValidIri('https://x.test/<bad>')).toThrow(/forbidden characters/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('rejects IRIs with double quotes', () => {
|
|
44
|
+
expect(() => assertValidIri('https://x.test/"bad"')).toThrow(/forbidden characters/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('rejects IRIs with spaces', () => {
|
|
48
|
+
expect(() => assertValidIri('https://x.test/bad path')).toThrow(/forbidden characters/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('rejects non-string IRIs', () => {
|
|
52
|
+
expect(() => assertValidIri(42)).toThrow(/expected string/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('ttlIri wraps a clean IRI in angle brackets', () => {
|
|
56
|
+
expect(ttlIri('https://example.org/x')).toBe('<https://example.org/x>');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('ttlIri throws on invalid IRIs', () => {
|
|
60
|
+
expect(() => ttlIri('https://x.test/<bad>')).toThrow();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Parser, Store } from 'n3';
|
|
3
|
+
import { buildVersionTurtle, buildVersionHistoryTurtle } from '../../../scripts/lib/version-turtle.mjs';
|
|
4
|
+
|
|
5
|
+
function parse(turtle: string): Store {
|
|
6
|
+
const parser = new Parser({ format: 'Turtle' });
|
|
7
|
+
const store = new Store();
|
|
8
|
+
store.addQuads(parser.parse(turtle));
|
|
9
|
+
return store;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PROV = 'http://www.w3.org/ns/prov#';
|
|
13
|
+
const DCTERMS = 'http://purl.org/dc/terms/';
|
|
14
|
+
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
15
|
+
|
|
16
|
+
describe('buildVersionTurtle (mjs)', () => {
|
|
17
|
+
it('parses without errors', () => {
|
|
18
|
+
const ttl = buildVersionTurtle({
|
|
19
|
+
registerId: 'test',
|
|
20
|
+
version: '1.0',
|
|
21
|
+
versionIri: 'https://glossarist.org/test/versions/1.0',
|
|
22
|
+
datasetIri: 'https://glossarist.org/test/',
|
|
23
|
+
generatedAt: '2026-06-28T12:00:00Z',
|
|
24
|
+
});
|
|
25
|
+
const store = parse(ttl);
|
|
26
|
+
expect(store.size).toBeGreaterThan(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('types the version as prov:Entity', () => {
|
|
30
|
+
const ttl = buildVersionTurtle({
|
|
31
|
+
registerId: 'test',
|
|
32
|
+
version: '1.0',
|
|
33
|
+
versionIri: 'https://glossarist.org/test/versions/1.0',
|
|
34
|
+
datasetIri: 'https://glossarist.org/test/',
|
|
35
|
+
generatedAt: '2026-06-28T12:00:00Z',
|
|
36
|
+
});
|
|
37
|
+
const store = parse(ttl);
|
|
38
|
+
const types = store.getObjects('https://glossarist.org/test/versions/1.0', RDF_TYPE, null).map(q => q.value);
|
|
39
|
+
expect(types).toContain(`${PROV}Entity`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('emits prov:wasRevisionOf when a previous version is provided', () => {
|
|
43
|
+
const ttl = buildVersionTurtle({
|
|
44
|
+
registerId: 'test',
|
|
45
|
+
version: '1.0',
|
|
46
|
+
versionIri: 'https://glossarist.org/test/versions/1.0',
|
|
47
|
+
datasetIri: 'https://glossarist.org/test/',
|
|
48
|
+
generatedAt: '2026-06-28T12:00:00Z',
|
|
49
|
+
previousVersionIri: 'https://glossarist.org/test/versions/0.9',
|
|
50
|
+
});
|
|
51
|
+
const store = parse(ttl);
|
|
52
|
+
const prev = store.getObjects('https://glossarist.org/test/versions/1.0', `${PROV}wasRevisionOf`, null).map(q => q.value);
|
|
53
|
+
expect(prev).toContain('https://glossarist.org/test/versions/0.9');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('buildVersionHistoryTurtle (mjs)', () => {
|
|
58
|
+
it('emits a chain with revision links', () => {
|
|
59
|
+
const ttl = buildVersionHistoryTurtle({
|
|
60
|
+
registerId: 'test',
|
|
61
|
+
datasetIri: 'https://glossarist.org/test/',
|
|
62
|
+
versions: [
|
|
63
|
+
{ version: '1.0', generatedAt: '2024-01-01T00:00:00Z' },
|
|
64
|
+
{ version: '1.1', generatedAt: '2024-06-01T00:00:00Z' },
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
const store = parse(ttl);
|
|
68
|
+
const iri11 = 'https://glossarist.org/test/versions/1.1';
|
|
69
|
+
const prev = store.getObjects(iri11, `${PROV}wasRevisionOf`, null).map(q => q.value);
|
|
70
|
+
expect(prev).toContain('https://glossarist.org/test/versions/1.0');
|
|
71
|
+
});
|
|
72
|
+
});
|