@glossarist/concept-browser 0.7.66 → 0.7.68

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.
Files changed (70) hide show
  1. package/cli/index.mjs +38 -33
  2. package/package.json +2 -2
  3. package/scripts/__tests__/cli-script-contract.test.mjs +39 -0
  4. package/scripts/__tests__/first-non-empty.test.mjs +51 -0
  5. package/scripts/__tests__/process-about-pages.test.mjs +106 -0
  6. package/scripts/build-edges.js +10 -2
  7. package/scripts/fetch-datasets.mjs +61 -54
  8. package/scripts/generate-data.mjs +40 -26
  9. package/scripts/lib/first-non-empty.mjs +19 -0
  10. package/scripts/process-about-pages.mjs +63 -15
  11. package/scripts/validate-shacl.mjs +1 -1
  12. package/src/__tests__/concept-rdf-view.test.ts +21 -13
  13. package/src/__tests__/perf/serialization-perf.test.ts +13 -30
  14. package/src/__tests__/tc204-deployment.test.ts +139 -0
  15. package/src/__tests__/use-rdf-document.test.ts +125 -0
  16. package/src/adapters/types.ts +7 -0
  17. package/src/components/ConceptRdfView.vue +4 -4
  18. package/src/components/use-rdf-document.ts +137 -0
  19. package/src/composables/useDatasetSeries.ts +1 -1
  20. package/src/__tests__/concept-rdf/agents-emitter.test.ts +0 -110
  21. package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +0 -159
  22. package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +0 -119
  23. package/src/__tests__/concept-rdf/coerce-date.test.ts +0 -97
  24. package/src/__tests__/concept-rdf/concept-emitter.test.ts +0 -258
  25. package/src/__tests__/concept-rdf/dataset-emitter.test.ts +0 -224
  26. package/src/__tests__/concept-rdf/differential.test.ts +0 -96
  27. package/src/__tests__/concept-rdf/group-emitter.test.ts +0 -109
  28. package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +0 -135
  29. package/src/__tests__/concept-rdf/jsonld-writer.test.ts +0 -109
  30. package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +0 -177
  31. package/src/__tests__/concept-rdf/property-based.test.ts +0 -179
  32. package/src/__tests__/concept-rdf/provenance.test.ts +0 -110
  33. package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +0 -43
  34. package/src/__tests__/concept-rdf/quad-isomorphism.ts +0 -47
  35. package/src/__tests__/concept-rdf/rdf-components.test.ts +0 -145
  36. package/src/__tests__/concept-rdf/rdf-graph.test.ts +0 -115
  37. package/src/__tests__/concept-rdf/round-trip.test.ts +0 -243
  38. package/src/__tests__/concept-rdf/scoped-examples.test.ts +0 -126
  39. package/src/__tests__/concept-rdf/sections-builder.test.ts +0 -94
  40. package/src/__tests__/concept-rdf/shacl-conformance.test.ts +0 -110
  41. package/src/__tests__/concept-rdf/shape-consistency.test.ts +0 -138
  42. package/src/__tests__/concept-rdf/snapshot-generation.test.ts +0 -75
  43. package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +0 -142
  44. package/src/__tests__/concept-rdf/turtle-writer.test.ts +0 -114
  45. package/src/__tests__/concept-rdf/use-rdf-document.test.ts +0 -246
  46. package/src/__tests__/concept-rdf/version-emitter.test.ts +0 -120
  47. package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +0 -100
  48. package/src/__tests__/perf/bundle-layout.test.ts +0 -50
  49. package/src/components/concept-rdf/agents-emitter.ts +0 -82
  50. package/src/components/concept-rdf/bibliography-emitter.ts +0 -83
  51. package/src/components/concept-rdf/build-activity-emitter.ts +0 -89
  52. package/src/components/concept-rdf/concept-emitter.ts +0 -443
  53. package/src/components/concept-rdf/dataset-emitter.ts +0 -95
  54. package/src/components/concept-rdf/group-emitter.ts +0 -69
  55. package/src/components/concept-rdf/image-variant-emitter.ts +0 -46
  56. package/src/components/concept-rdf/jsonld-writer.ts +0 -82
  57. package/src/components/concept-rdf/predicates.ts +0 -261
  58. package/src/components/concept-rdf/provenance.ts +0 -80
  59. package/src/components/concept-rdf/rdf-graph.ts +0 -211
  60. package/src/components/concept-rdf/sections-builder.ts +0 -62
  61. package/src/components/concept-rdf/table-formula-emitter.ts +0 -101
  62. package/src/components/concept-rdf/turtle-writer.ts +0 -116
  63. package/src/components/concept-rdf/use-rdf-document.ts +0 -72
  64. package/src/components/concept-rdf/version-emitter.ts +0 -65
  65. package/src/components/concept-rdf/vocabulary-emitter.ts +0 -62
  66. /package/src/components/{concept-rdf/RdfInstanceHeader.vue → RdfInstanceHeader.vue} +0 -0
  67. /package/src/components/{concept-rdf/RdfInstanceSection.vue → RdfInstanceSection.vue} +0 -0
  68. /package/src/components/{concept-rdf/RdfPrefixLegend.vue → RdfPrefixLegend.vue} +0 -0
  69. /package/src/components/{concept-rdf/RdfSourcePanel.vue → RdfSourcePanel.vue} +0 -0
  70. /package/src/components/{concept-rdf/rdf-prefixes.ts → rdf-prefixes.ts} +0 -0
@@ -0,0 +1,137 @@
1
+ import type { ComputedRef } from 'vue';
2
+ import { computed } from 'vue';
3
+ import type { Concept } from 'glossarist';
4
+ import {
5
+ conceptToQuads,
6
+ provenanceToQuads,
7
+ quadSectionsToClassInstances,
8
+ writeTurtleSync,
9
+ collectQuads,
10
+ PREFIXES,
11
+ type ClassInstance,
12
+ type PropValue,
13
+ type Quad,
14
+ } from 'glossarist/rdf';
15
+
16
+ export type { ClassInstance, PropValue };
17
+
18
+ export interface RdfDocument {
19
+ sections: ComputedRef<ClassInstance[]>;
20
+ turtle: ComputedRef<string>;
21
+ jsonld: ComputedRef<string>;
22
+ typeChain: ComputedRef<string[]>;
23
+ }
24
+
25
+ export interface UseRdfDocumentOptions {
26
+ readonly registerId?: string;
27
+ readonly uriBase?: string;
28
+ readonly lazy?: boolean;
29
+ }
30
+
31
+ const SERIALIZER_VERSION =
32
+ typeof __CONCEPT_BROWSER_VERSION__ !== 'undefined'
33
+ ? __CONCEPT_BROWSER_VERSION__
34
+ : 'dev';
35
+
36
+ function formatJsonLd(quads: readonly Quad[]): string {
37
+ // Minimal sync JSON-LD serializer: @context + @graph array of nodes.
38
+ // Predicates and types are emitted as compact IRIs; values are emitted
39
+ // as their term-type-appropriate JSON form.
40
+ const context: Record<string, string> = {};
41
+ for (const prefix of ['gloss', 'skos', 'skosxl', 'dcterms', 'prov', 'rdf', 'rdfs', 'xsd', 'owl']) {
42
+ // Skip — handled by RDF_PREFIXES for the Vue UI; JSON-LD context is
43
+ // minimal here because glossarist/rdf's writeJsonld is async and
44
+ // the composable must stay sync.
45
+ }
46
+
47
+ // Group quads by subject
48
+ const bySubject = new Map<string, { subject: string; quads: Quad[] }>();
49
+ const order: string[] = [];
50
+ for (const q of quads) {
51
+ const s = q.subject.value;
52
+ if (!bySubject.has(s)) {
53
+ bySubject.set(s, { subject: s, quads: [] });
54
+ order.push(s);
55
+ }
56
+ bySubject.get(s)!.quads.push(q);
57
+ }
58
+
59
+ const nodes = order.map(s => {
60
+ const entry = bySubject.get(s)!;
61
+ const node: Record<string, unknown> = { '@id': entry.subject };
62
+ const types: string[] = [];
63
+ const props: Record<string, unknown[]> = {};
64
+
65
+ for (const q of entry.quads) {
66
+ if (q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') {
67
+ types.push(q.object.value);
68
+ continue;
69
+ }
70
+ const pred = q.predicate.value;
71
+ const val = termToJsonLd(q.object);
72
+ if (!props[pred]) props[pred] = [];
73
+ props[pred].push(val);
74
+ }
75
+
76
+ if (types.length === 1) node['@type'] = types[0];
77
+ else if (types.length > 1) node['@type'] = types;
78
+
79
+ for (const [pred, vals] of Object.entries(props)) {
80
+ node[pred] = vals.length === 1 ? vals[0] : vals;
81
+ }
82
+ return node;
83
+ });
84
+
85
+ return JSON.stringify({ '@context': context, '@graph': nodes }, null, 2);
86
+ }
87
+
88
+ function termToJsonLd(term: Quad['object']): unknown {
89
+ switch (term.termType) {
90
+ case 'NamedNode':
91
+ return { '@id': term.value };
92
+ case 'Literal':
93
+ if (term.language) return { '@value': term.value, '@language': term.language };
94
+ if (term.datatype && term.datatype.value !== 'http://www.w3.org/2001/XMLSchema#string') {
95
+ return { '@value': term.value, '@type': term.datatype.value };
96
+ }
97
+ return term.value;
98
+ case 'BlankNode':
99
+ return { '@id': `_:${term.value}` };
100
+ default:
101
+ return term.value;
102
+ }
103
+ }
104
+
105
+ export function useRdfDocument(
106
+ getConcept: () => Concept,
107
+ getConceptUri: () => string,
108
+ options: UseRdfDocumentOptions = {},
109
+ ): RdfDocument {
110
+ const emission = computed(() => {
111
+ const concept = getConcept();
112
+ const uri = getConceptUri();
113
+ const conceptQuads = collectQuads(
114
+ conceptToQuads(concept, {
115
+ registerId: options.registerId ?? '',
116
+ uriBase: options.uriBase ?? '',
117
+ }),
118
+ );
119
+ const provenanceQuads = collectQuads(
120
+ provenanceToQuads({
121
+ subjectUri: uri,
122
+ serializer: 'concept-browser',
123
+ serializerVersion: SERIALIZER_VERSION,
124
+ generatedAt: new Date().toISOString(),
125
+ }),
126
+ );
127
+ return [...conceptQuads, ...provenanceQuads];
128
+ });
129
+
130
+ const sections = computed(() => quadSectionsToClassInstances(emission.value));
131
+ const turtle = computed(() => writeTurtleSync(emission.value, { prefixes: PREFIXES }));
132
+ const jsonld = computed(() => formatJsonLd(emission.value));
133
+
134
+ const typeChain = computed(() => ['owl:Thing', 'skos:Concept', 'gloss:Concept']);
135
+
136
+ return { sections, turtle, jsonld, typeChain };
137
+ }
@@ -92,7 +92,7 @@ function deriveSeriesTitle(m: Manifest | undefined): string {
92
92
  }
93
93
 
94
94
  function manifestToMember(m: Manifest, activeDatasetId?: string): DatasetSeriesMember {
95
- const year = extractYear(m.id) ?? extractYear(m.ref ?? '') ?? extractYear(m.title);
95
+ const year = m.year ?? extractYear(m.id) ?? extractYear(m.ref ?? '') ?? extractYear(m.title);
96
96
  return {
97
97
  id: m.id,
98
98
  ref: m.ref ?? m.title,
@@ -1,110 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Parser, Store } from 'n3';
3
- import { writeTurtle } from '../../components/concept-rdf/turtle-writer';
4
- import {
5
- agentsFromContributors,
6
- emitAgentsGraph,
7
- } from '../../components/concept-rdf/agents-emitter';
8
-
9
- const FOAF = 'http://xmlns.com/foaf/0.1/';
10
- const PROV = 'http://www.w3.org/ns/prov#';
11
- const DCTERMS = 'http://purl.org/dc/terms/';
12
- const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
13
-
14
- function parse(turtle: string): Store {
15
- const parser = new Parser({ format: 'Turtle' });
16
- const store = new Store();
17
- store.addQuads(parser.parse(turtle));
18
- return store;
19
- }
20
-
21
- describe('agentsFromContributors', () => {
22
- it('turns each contributor into an AgentInput with a slugified IRI', () => {
23
- const agents = agentsFromContributors([
24
- { name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society', email: 'ada@example.org' },
25
- { name: 'Grace Hopper', organization: 'USN' },
26
- ]);
27
- expect(agents.length).toBe(2);
28
- expect(agents[0].agentIri).toBe('https://glossarist.org/agent/ada-lovelace');
29
- expect(agents[1].agentIri).toBe('https://glossarist.org/agent/grace-hopper');
30
- expect(agents[0].role).toBe('Editor');
31
- });
32
-
33
- it('deduplicates organizations by slug', () => {
34
- const result = emitAgentsGraph(agentsFromContributors([
35
- { name: 'Alice', organization: 'ISO' },
36
- { name: 'Bob', organization: 'ISO' },
37
- { name: 'Carol', organization: 'IEC' },
38
- ]));
39
- expect(result.organizationIris.length).toBe(2);
40
- });
41
- });
42
-
43
- describe('emitAgentsGraph — J4 agent records', () => {
44
- it('types each person as foaf:Person, prov:Person, prov:Agent', () => {
45
- const graph = emitAgentsGraph(agentsFromContributors([
46
- { name: 'Ada Lovelace', email: 'ada@example.org' },
47
- ]));
48
- const store = parse(writeTurtle(graph.graph));
49
- const types = store.getObjects('https://glossarist.org/agent/ada-lovelace', RDF_TYPE, null).map(q => q.value);
50
- expect(types).toContain(`${FOAF}Person`);
51
- expect(types).toContain(`${PROV}Person`);
52
- expect(types).toContain(`${PROV}Agent`);
53
- });
54
-
55
- it('records name, email (as mailto IRI), role, and url', () => {
56
- const graph = emitAgentsGraph(agentsFromContributors([
57
- { name: 'Ada Lovelace', role: 'Editor', email: 'ada@example.org', url: 'https://example.org/ada' },
58
- ]));
59
- const store = parse(writeTurtle(graph.graph));
60
- const names = store.getObjects('https://glossarist.org/agent/ada-lovelace', `${FOAF}name`, null).map(q => q.value);
61
- expect(names).toContain('Ada Lovelace');
62
-
63
- const mboxes = store.getObjects('https://glossarist.org/agent/ada-lovelace', `${FOAF}mbox`, null).map(q => q.value);
64
- expect(mboxes).toContain('mailto:ada@example.org');
65
-
66
- const roles = store.getObjects('https://glossarist.org/agent/ada-lovelace', `${DCTERMS}description`, null).map(q => q.value);
67
- expect(roles).toContain('Editor');
68
-
69
- const seeAlso = store.getObjects('https://glossarist.org/agent/ada-lovelace', 'http://www.w3.org/2000/01/rdf-schema#seeAlso', null).map(q => q.value);
70
- expect(seeAlso).toContain('https://example.org/ada');
71
- });
72
-
73
- it('creates a single prov:Organization per unique organization', () => {
74
- const result = emitAgentsGraph(agentsFromContributors([
75
- { name: 'Alice', organization: 'ISO' },
76
- { name: 'Bob', organization: 'ISO' },
77
- ]));
78
- expect(result.organizationIris).toEqual(['https://glossarist.org/org/iso']);
79
-
80
- const store = parse(writeTurtle(result.graph));
81
- const types = store.getObjects('https://glossarist.org/org/iso', RDF_TYPE, null).map(q => q.value);
82
- expect(types).toContain(`${FOAF}Organization`);
83
- expect(types).toContain(`${PROV}Organization`);
84
- expect(types).toContain(`${PROV}Agent`);
85
-
86
- const names = store.getObjects('https://glossarist.org/org/iso', `${FOAF}name`, null).map(q => q.value);
87
- expect(names).toContain('ISO');
88
- });
89
-
90
- it('links every person to their organization via prov:actedOnBehalfOf', () => {
91
- const result = emitAgentsGraph(agentsFromContributors([
92
- { name: 'Alice', organization: 'ISO' },
93
- { name: 'Bob', organization: 'IEC' },
94
- ]));
95
- const store = parse(writeTurtle(result.graph));
96
- const aliceOrg = store.getObjects('https://glossarist.org/agent/alice', `${PROV}actedOnBehalfOf`, null).map(q => q.value);
97
- expect(aliceOrg).toContain('https://glossarist.org/org/iso');
98
- const bobOrg = store.getObjects('https://glossarist.org/agent/bob', `${PROV}actedOnBehalfOf`, null).map(q => q.value);
99
- expect(bobOrg).toContain('https://glossarist.org/org/iec');
100
- });
101
-
102
- it('omits mbox, seeAlso, and actedOnBehalfOf when not provided', () => {
103
- const graph = emitAgentsGraph(agentsFromContributors([{ name: 'Solo' }]));
104
- const store = parse(writeTurtle(graph.graph));
105
- const mbox = store.getObjects('https://glossarist.org/agent/solo', `${FOAF}mbox`, null);
106
- expect(mbox).toHaveLength(0);
107
- const acted = store.getObjects('https://glossarist.org/agent/solo', `${PROV}actedOnBehalfOf`, null);
108
- expect(acted).toHaveLength(0);
109
- });
110
- });
@@ -1,159 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Parser, Store } from 'n3';
3
- import { writeTurtle } from '../../components/concept-rdf/turtle-writer';
4
- import {
5
- emitBibliographyGraph,
6
- bibliographyEntryIri,
7
- normalizeBibliographyData,
8
- } from '../../components/concept-rdf/bibliography-emitter';
9
-
10
- const FOAF = 'http://xmlns.com/foaf/0.1/';
11
- const DCTERMS = 'http://purl.org/dc/terms/';
12
- const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
13
-
14
- function parse(turtle: string): Store {
15
- const parser = new Parser({ format: 'Turtle' });
16
- const store = new Store();
17
- store.addQuads(parser.parse(turtle));
18
- return store;
19
- }
20
-
21
- describe('emitBibliographyGraph — K5 bibliography graph', () => {
22
- it('types each entry as dcterms:BibliographicResource', () => {
23
- const graph = emitBibliographyGraph({
24
- registerId: 'iso-geodetic',
25
- entries: [{ id: 'iso-704', reference: 'ISO 704' }],
26
- });
27
- const store = parse(writeTurtle(graph));
28
- const iri = bibliographyEntryIri('iso-geodetic', 'iso-704');
29
- const types = store.getObjects(iri, RDF_TYPE, null).map(q => q.value);
30
- expect(types).toContain(`${DCTERMS}BibliographicResource`);
31
- });
32
-
33
- it('emits identifier, bibliographicCitation, and title as literals', () => {
34
- const graph = emitBibliographyGraph({
35
- registerId: 'iso-geodetic',
36
- entries: [
37
- { id: 'iso-704', reference: 'ISO 704', title: 'Terminology work — Principles and methods' },
38
- ],
39
- });
40
- const store = parse(writeTurtle(graph));
41
- const iri = bibliographyEntryIri('iso-geodetic', 'iso-704');
42
- const ids = store.getObjects(iri, `${DCTERMS}identifier`, null).map(q => q.value);
43
- expect(ids).toContain('iso-704');
44
- const cites = store.getObjects(iri, `${DCTERMS}bibliographicCitation`, null).map(q => q.value);
45
- expect(cites).toContain('ISO 704');
46
- const titles = store.getObjects(iri, `${DCTERMS}title`, null).map(q => q.value);
47
- expect(titles).toContain('Terminology work — Principles and methods');
48
- });
49
-
50
- it('emits foaf:page when link is provided', () => {
51
- const graph = emitBibliographyGraph({
52
- registerId: 'iso-geodetic',
53
- entries: [{ id: 'ref', reference: 'X', link: 'https://example.org/x' }],
54
- });
55
- const store = parse(writeTurtle(graph));
56
- const iri = bibliographyEntryIri('iso-geodetic', 'ref');
57
- const pages = store.getObjects(iri, `${FOAF}page`, null).map(q => q.value);
58
- expect(pages).toContain('https://example.org/x');
59
- });
60
-
61
- it('omits foaf:page when link is absent', () => {
62
- const graph = emitBibliographyGraph({
63
- registerId: 'iso-geodetic',
64
- entries: [{ id: 'no-link', reference: 'Y' }],
65
- });
66
- const store = parse(writeTurtle(graph));
67
- const iri = bibliographyEntryIri('iso-geodetic', 'no-link');
68
- const pages = store.getObjects(iri, `${FOAF}page`, null);
69
- expect(pages).toHaveLength(0);
70
- });
71
-
72
- it('emits one resource per entry', () => {
73
- const graph = emitBibliographyGraph({
74
- registerId: 'iso-geodetic',
75
- entries: [
76
- { id: 'a', reference: 'A' },
77
- { id: 'b', reference: 'B' },
78
- { id: 'c', reference: 'C' },
79
- ],
80
- });
81
- const iriA = bibliographyEntryIri('iso-geodetic', 'a');
82
- const iriB = bibliographyEntryIri('iso-geodetic', 'b');
83
- const iriC = bibliographyEntryIri('iso-geodetic', 'c');
84
- const store = parse(writeTurtle(graph));
85
- expect(store.getObjects(iriA, `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('A');
86
- expect(store.getObjects(iriB, `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('B');
87
- expect(store.getObjects(iriC, `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('C');
88
- });
89
-
90
- it('links each entry to the dataset via dcterms:isPartOf', () => {
91
- const graph = emitBibliographyGraph({
92
- registerId: 'iso-geodetic',
93
- entries: [{ id: 'a', reference: 'A' }],
94
- });
95
- const store = parse(writeTurtle(graph));
96
- const iri = bibliographyEntryIri('iso-geodetic', 'a');
97
- const parts = store.getObjects(iri, `${DCTERMS}isPartOf`, null).map(q => q.value);
98
- expect(parts).toContain('https://glossarist.org/iso-geodetic/');
99
- });
100
- });
101
-
102
- describe('B3/C — V3 bibliography shape (wrapper key)', () => {
103
- it('normalizeBibliographyData unwraps { bibliography: [...] } V3 shape', () => {
104
- const v3 = {
105
- bibliography: [
106
- { id: 'iso704', reference: 'ISO 704', title: 'Terminology work', type: 'normative' },
107
- { id: 'iso10241', reference: 'ISO 10241-1' },
108
- ],
109
- };
110
- const entries = normalizeBibliographyData(v3);
111
- expect(entries.length).toBe(2);
112
- expect(entries[0].id).toBe('iso704');
113
- expect(entries[0].type).toBe('normative');
114
- expect(entries[1].id).toBe('iso10241');
115
- });
116
-
117
- it('normalizeBibliographyData accepts the legacy flat-map shape', () => {
118
- const legacy = {
119
- iso704: { reference: 'ISO 704', title: 'Terminology work' },
120
- iso10241: { reference: 'ISO 10241-1' },
121
- };
122
- const entries = normalizeBibliographyData(legacy);
123
- expect(entries.length).toBe(2);
124
- expect(entries.find(e => e.id === 'iso704')?.title).toBe('Terminology work');
125
- });
126
-
127
- it('emitBibliographyGraph handles both V3 and legacy shapes identically', () => {
128
- const v3 = { bibliography: [{ id: 'iso704', reference: 'ISO 704', title: 'T' }] };
129
- const legacy = { iso704: { reference: 'ISO 704', title: 'T' } };
130
-
131
- const graphV3 = emitBibliographyGraph({
132
- registerId: 'r',
133
- entries: normalizeBibliographyData(v3),
134
- });
135
- const graphLegacy = emitBibliographyGraph({
136
- registerId: 'r',
137
- entries: normalizeBibliographyData(legacy),
138
- });
139
-
140
- const storeV3 = parse(writeTurtle(graphV3));
141
- const storeLegacy = parse(writeTurtle(graphLegacy));
142
-
143
- const iri = bibliographyEntryIri('r', 'iso704');
144
- const v3Titles = storeV3.getObjects(iri, `${DCTERMS}title`, null).map(q => q.value);
145
- const legacyTitles = storeLegacy.getObjects(iri, `${DCTERMS}title`, null).map(q => q.value);
146
- expect(v3Titles).toEqual(legacyTitles);
147
- });
148
-
149
- it('emits dcterms:type when entry.type is provided', () => {
150
- const graph = emitBibliographyGraph({
151
- registerId: 'r',
152
- entries: [{ id: 'a', reference: 'X', type: 'normative' }],
153
- });
154
- const store = parse(writeTurtle(graph));
155
- const iri = bibliographyEntryIri('r', 'a');
156
- const types = store.getObjects(iri, `${DCTERMS}type`, null).map(q => q.value);
157
- expect(types.some(t => t.includes('bibtype/normative'))).toBe(true);
158
- });
159
- });
@@ -1,119 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Parser, Store } from 'n3';
3
- import { writeTurtle } from '../../components/concept-rdf/turtle-writer';
4
- import { emitBuildActivityGraph, activityIri } from '../../components/concept-rdf/build-activity-emitter';
5
- import type { BuildActivityInput } from '../../components/concept-rdf/build-activity-emitter';
6
-
7
- function makeInput(overrides: Partial<BuildActivityInput> = {}): BuildActivityInput {
8
- return {
9
- runId: '2026-06-28T12-00-00Z',
10
- startedAt: '2026-06-28T12:00:00Z',
11
- endedAt: '2026-06-28T12:05:00Z',
12
- gitSha: 'abc1234',
13
- gitBranch: 'main',
14
- toolId: 'concept-browser',
15
- toolVersion: '0.7.51',
16
- datasetRegisters: ['iso-geodetic', 'iso-10303-2-terms'],
17
- conceptCount: 1234,
18
- associatedAgentIri: 'https://glossarist.org/agent/ci-bot',
19
- ...overrides,
20
- };
21
- }
22
-
23
- function parse(turtle: string): Store {
24
- const parser = new Parser({ format: 'Turtle' });
25
- const store = new Store();
26
- store.addQuads(parser.parse(turtle));
27
- return store;
28
- }
29
-
30
- const expand = (v: string): string => {
31
- if (v.startsWith('prov:')) return `http://www.w3.org/ns/prov#${v.slice(5)}`;
32
- if (v.startsWith('dcterms:')) return `http://purl.org/dc/terms/${v.slice(8)}`;
33
- if (v.startsWith('rdf:')) return `http://www.w3.org/1999/02/22-rdf-syntax-ns#${v.slice(4)}`;
34
- if (v.startsWith('xsd:')) return `http://www.w3.org/2001/XMLSchema#${v.slice(4)}`;
35
- if (v.startsWith('foaf:')) return `http://xmlns.com/foaf/0.1/${v.slice(5)}`;
36
- return v;
37
- };
38
-
39
- describe('emitBuildActivityGraph — J7 build activity records', () => {
40
- it('types the activity as prov:Activity', () => {
41
- const input = makeInput();
42
- const graph = emitBuildActivityGraph(input);
43
- const store = parse(writeTurtle(graph));
44
- const iri = activityIri(input);
45
- const types = store.getObjects(iri, expand('rdf:type'), null).map(q => q.value);
46
- expect(types).toContain(expand('prov:Activity'));
47
- });
48
-
49
- it('records the start and end times', () => {
50
- const input = makeInput();
51
- const graph = emitBuildActivityGraph(input);
52
- const store = parse(writeTurtle(graph));
53
- const iri = activityIri(input);
54
- const generated = store.getObjects(iri, expand('prov:generatedAtTime'), null).map(q => q.value);
55
- expect(generated).toContain('2026-06-28T12:05:00Z');
56
- });
57
-
58
- it('types the tool as prov:SoftwareAgent with version', () => {
59
- const input = makeInput();
60
- const graph = emitBuildActivityGraph(input);
61
- const store = parse(writeTurtle(graph));
62
- const toolIri = `https://glossarist.org/tool/${input.toolId}/${input.toolVersion}`;
63
- const types = store.getObjects(toolIri, expand('rdf:type'), null).map(q => q.value);
64
- expect(types).toContain(expand('prov:SoftwareAgent'));
65
- const version = store.getObjects(toolIri, expand('prov:version'), null).map(q => q.value);
66
- expect(version).toContain(input.toolVersion);
67
- });
68
-
69
- it('records the git commit and the tool as prov:used entities', () => {
70
- const input = makeInput();
71
- const graph = emitBuildActivityGraph(input);
72
- const store = parse(writeTurtle(graph));
73
- const iri = activityIri(input);
74
- const used = store.getObjects(iri, expand('prov:used'), null).map(q => q.value);
75
- expect(used).toContain(`https://glossarist.org/commit/${input.gitSha}`);
76
- expect(used).toContain(`https://glossarist.org/tool/${input.toolId}/${input.toolVersion}`);
77
- });
78
-
79
- it('references every dataset register via prov:used', () => {
80
- const input = makeInput();
81
- const graph = emitBuildActivityGraph(input);
82
- const store = parse(writeTurtle(graph));
83
- const iri = activityIri(input);
84
- const used = store.getObjects(iri, expand('prov:used'), null).map(q => q.value);
85
- for (const register of input.datasetRegisters) {
86
- expect(used).toContain(`https://glossarist.org/${register}/`);
87
- }
88
- });
89
-
90
- it('records the concept count as an xsd:integer literal', () => {
91
- const input = makeInput();
92
- const graph = emitBuildActivityGraph(input);
93
- const store = parse(writeTurtle(graph));
94
- const iri = activityIri(input);
95
- const counts = store.getObjects(iri, 'https://www.glossarist.org/ontologies/conceptCount', null).map(q => q.value);
96
- expect(counts).toContain('1234');
97
- });
98
-
99
- it('associates the CI agent when provided', () => {
100
- const input = makeInput();
101
- const graph = emitBuildActivityGraph(input);
102
- const store = parse(writeTurtle(graph));
103
- const iri = activityIri(input);
104
- const agents = store.getObjects(iri, expand('prov:wasAssociatedWith'), null).map(q => q.value);
105
- expect(agents).toContain(input.associatedAgentIri);
106
-
107
- const agentTypes = store.getObjects(input.associatedAgentIri!, expand('rdf:type'), null).map(q => q.value);
108
- expect(agentTypes).toContain(expand('prov:Agent'));
109
- });
110
-
111
- it('omits the commit entity when gitSha is not provided', () => {
112
- const input = makeInput({ gitSha: undefined, gitBranch: undefined });
113
- const graph = emitBuildActivityGraph(input);
114
- const store = parse(writeTurtle(graph));
115
- const iri = activityIri(input);
116
- const used = store.getObjects(iri, expand('prov:used'), null).map(q => q.value);
117
- expect(used.some(u => u.includes('/commit/'))).toBe(false);
118
- });
119
- });
@@ -1,97 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Parser, Store } from 'n3';
3
- import { Concept } from 'glossarist';
4
- import { writeTurtle } from '../../components/concept-rdf/turtle-writer';
5
- import { emitConceptGraph } from '../../components/concept-rdf/concept-emitter';
6
-
7
- const G = 'https://www.glossarist.org/ontologies/';
8
- const XSD = 'http://www.w3.org/2001/XMLSchema#';
9
-
10
- function parse(turtle: string): Store {
11
- const parser = new Parser({ format: 'Turtle' });
12
- const store = new Store();
13
- store.addQuads(parser.parse(turtle));
14
- return store;
15
- }
16
-
17
- const BASE = 'https://glossarist.org/fixtures/dates';
18
-
19
- describe('B4/C — coerceToDateTime handles year-only and month-only dates', () => {
20
- it('coerces yyyy-mm-dd to xsd:dateTime unchanged (with T00:00:00Z)', () => {
21
- const concept = Concept.fromJSON({
22
- id: 'd.1', uri: `${BASE}/d.1`, status: 'valid',
23
- dates: [{ type: 'accepted', date: '2020-06-15' }],
24
- localizations: {
25
- eng: {
26
- language_code: 'eng', entry_status: 'valid',
27
- terms: [{ type: 'expression', designation: 'd1', normative_status: 'preferred' }],
28
- definition: [{ content: 'def' }],
29
- },
30
- },
31
- });
32
- const { graph } = emitConceptGraph(concept, concept.uri ?? '');
33
- const store = parse(writeTurtle(graph));
34
- const dateBlanks = store.getObjects(concept.uri ?? '', `${G}hasDate`, null);
35
- const values = store.getObjects(dateBlanks[0], `${G}dateValue`, null);
36
- expect((values[0] as any).value).toBe('2020-06-15T00:00:00Z');
37
- expect((values[0] as any).datatype.value).toBe(`${XSD}dateTime`);
38
- });
39
-
40
- it('coerces year-only "2020" to "2020-01-01T00:00:00Z"', () => {
41
- const concept = Concept.fromJSON({
42
- id: 'd.2', uri: `${BASE}/d.2`, status: 'valid',
43
- dates: [{ type: 'accepted', date: '2020' }],
44
- localizations: {
45
- eng: {
46
- language_code: 'eng', entry_status: 'valid',
47
- terms: [{ type: 'expression', designation: 'd2', normative_status: 'preferred' }],
48
- definition: [{ content: 'def' }],
49
- },
50
- },
51
- });
52
- const { graph } = emitConceptGraph(concept, concept.uri ?? '');
53
- const store = parse(writeTurtle(graph));
54
- const dateBlanks = store.getObjects(concept.uri ?? '', `${G}hasDate`, null);
55
- const values = store.getObjects(dateBlanks[0], `${G}dateValue`, null);
56
- expect((values[0] as any).value).toBe('2020-01-01T00:00:00Z');
57
- expect((values[0] as any).datatype.value).toBe(`${XSD}dateTime`);
58
- });
59
-
60
- it('coerces month-only "2020-06" to "2020-06-01T00:00:00Z"', () => {
61
- const concept = Concept.fromJSON({
62
- id: 'd.3', uri: `${BASE}/d.3`, status: 'valid',
63
- dates: [{ type: 'accepted', date: '2020-06' }],
64
- localizations: {
65
- eng: {
66
- language_code: 'eng', entry_status: 'valid',
67
- terms: [{ type: 'expression', designation: 'd3', normative_status: 'preferred' }],
68
- definition: [{ content: 'def' }],
69
- },
70
- },
71
- });
72
- const { graph } = emitConceptGraph(concept, concept.uri ?? '');
73
- const store = parse(writeTurtle(graph));
74
- const dateBlanks = store.getObjects(concept.uri ?? '', `${G}hasDate`, null);
75
- const values = store.getObjects(dateBlanks[0], `${G}dateValue`, null);
76
- expect((values[0] as any).value).toBe('2020-06-01T00:00:00Z');
77
- });
78
-
79
- it('passes through ISO 8601 datetimes unchanged', () => {
80
- const concept = Concept.fromJSON({
81
- id: 'd.4', uri: `${BASE}/d.4`, status: 'valid',
82
- dates: [{ type: 'accepted', date: '2020-06-15T13:45:30Z' }],
83
- localizations: {
84
- eng: {
85
- language_code: 'eng', entry_status: 'valid',
86
- terms: [{ type: 'expression', designation: 'd4', normative_status: 'preferred' }],
87
- definition: [{ content: 'def' }],
88
- },
89
- },
90
- });
91
- const { graph } = emitConceptGraph(concept, concept.uri ?? '');
92
- const store = parse(writeTurtle(graph));
93
- const dateBlanks = store.getObjects(concept.uri ?? '', `${G}hasDate`, null);
94
- const values = store.getObjects(dateBlanks[0], `${G}dateValue`, null);
95
- expect((values[0] as any).value).toBe('2020-06-15T13:45:30Z');
96
- });
97
- });