@glossarist/concept-browser 0.7.96 → 0.7.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.97",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import type { Router } from 'vue-router';
|
|
4
|
+
import { useConceptEdges } from '../../composables/use-concept-edges';
|
|
5
|
+
import { useVocabularyStore } from '../../stores/vocabulary';
|
|
6
|
+
import { conceptFromJson } from '../../adapters/model-bridge';
|
|
7
|
+
import type { Manifest, GraphEdge } from '../../adapters/types';
|
|
8
|
+
import { makeManifest, setupPinia } from '../test-helpers';
|
|
9
|
+
import { getFactory, resetFactory } from '../../adapters/factory';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* End-to-end pipeline test using the actual live JSON-LD shape from
|
|
13
|
+
* the deployed oimlsmart/vocab site (vim-2012 concept 1.9).
|
|
14
|
+
*
|
|
15
|
+
* Reproduces the live bug: 1.9 has gl:genericRelations but the sphere
|
|
16
|
+
* renders nothing because conceptGenericRelations is empty.
|
|
17
|
+
*/
|
|
18
|
+
describe('1.9 live pipeline — generic projection through model-bridge', () => {
|
|
19
|
+
const URN = 'urn:oiml:pub:v:2:2012';
|
|
20
|
+
const manifest: Manifest = makeManifest({
|
|
21
|
+
id: 'vim-2012',
|
|
22
|
+
datasetUri: URN,
|
|
23
|
+
uriBase: 'https://oimlsmart.github.io/vocab',
|
|
24
|
+
});
|
|
25
|
+
const noEdges: GraphEdge[] = [];
|
|
26
|
+
const noopRouter = { push() {} } as unknown as Router;
|
|
27
|
+
|
|
28
|
+
let store: ReturnType<typeof useVocabularyStore>;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
setupPinia();
|
|
32
|
+
resetFactory();
|
|
33
|
+
store = useVocabularyStore();
|
|
34
|
+
store.manifests.set(manifest.id, manifest);
|
|
35
|
+
|
|
36
|
+
const factory = getFactory();
|
|
37
|
+
factory.resolveRelatedRef = vi.fn((ref: { source: string | null; id: string | null } | null) =>
|
|
38
|
+
ref?.source === URN && ref.id
|
|
39
|
+
? { registerId: manifest.id, conceptId: ref.id }
|
|
40
|
+
: null,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('projects gl:genericRelations → conceptGenericRelations through conceptFromJson', () => {
|
|
45
|
+
const liveJson = {
|
|
46
|
+
'@context': 'https://glossarist.org/ns/context.jsonld',
|
|
47
|
+
'@id': 'https://oimlsmart.github.io/vocab/vim-2012/concept/1.9',
|
|
48
|
+
'@type': 'gl:Concept',
|
|
49
|
+
'gl:identifier': '1.9',
|
|
50
|
+
'gl:status': 'valid',
|
|
51
|
+
'gl:localizedConcept': {
|
|
52
|
+
eng: {
|
|
53
|
+
'@type': 'gl:LocalizedConcept',
|
|
54
|
+
'gl:languageCode': 'eng',
|
|
55
|
+
'gl:entryStatus': 'valid',
|
|
56
|
+
'gl:designation': [{ '@type': 'gl:Expression', 'gl:normativeStatus': 'preferred', 'gl:term': 'measurement unit' }],
|
|
57
|
+
'gl:definition': [{ '@type': 'gl:DetailedDefinition', 'gl:content': 'real scalar quantity' }],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
'gl:related': [
|
|
61
|
+
{ '@type': 'gl:RelatedConcept', 'gl:relationshipType': 'broader_generic',
|
|
62
|
+
'gl:ref': { '@type': 'gl:ConceptRef', 'gl:source': URN, 'gl:id': '1.12' } },
|
|
63
|
+
],
|
|
64
|
+
'gl:genericRelations': [
|
|
65
|
+
{
|
|
66
|
+
'@type': 'gl:GenericRelation',
|
|
67
|
+
'gl:comprehensive': { '@type': 'gl:ConceptRef', 'gl:source': URN, 'gl:id': '1.9' },
|
|
68
|
+
'gl:criterion': { eng: 'by magnitude relationship to a reference measurement unit' },
|
|
69
|
+
'gl:hasGeneric': [
|
|
70
|
+
{ '@type': 'gl:GenericMember',
|
|
71
|
+
'gl:ref': { '@type': 'gl:ConceptRef', 'gl:source': URN, 'gl:id': '1.17' },
|
|
72
|
+
'gl:presence': 'required', 'gl:count': 'at_least_one',
|
|
73
|
+
'gl:delimitingCharacteristic': { eng: 'multiple of a unit' } },
|
|
74
|
+
{ '@type': 'gl:GenericMember',
|
|
75
|
+
'gl:ref': { '@type': 'gl:ConceptRef', 'gl:source': URN, 'gl:id': '1.18' },
|
|
76
|
+
'gl:presence': 'required', 'gl:count': 'at_least_one',
|
|
77
|
+
'gl:delimitingCharacteristic': { eng: 'submultiple of a unit' } },
|
|
78
|
+
{ '@type': 'gl:GenericMember',
|
|
79
|
+
'gl:ref': { '@type': 'gl:ConceptRef', 'gl:source': URN, 'gl:id': '1.12' },
|
|
80
|
+
'gl:presence': 'required', 'gl:count': 'at_least_one',
|
|
81
|
+
'gl:delimitingCharacteristic': { eng: 'coherent derived unit' } },
|
|
82
|
+
],
|
|
83
|
+
'gl:completeness': 'partial',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const concept = conceptFromJson(liveJson as any);
|
|
89
|
+
|
|
90
|
+
/* Sanity: model-bridge produced a Concept with relations. */
|
|
91
|
+
expect(concept).toBeTruthy();
|
|
92
|
+
|
|
93
|
+
/* Project through useConceptEdges, exactly as ConceptView does. */
|
|
94
|
+
const { conceptGenericRelations } = useConceptEdges(
|
|
95
|
+
computed(() => concept),
|
|
96
|
+
computed(() => 'vim-2012'),
|
|
97
|
+
computed(() => manifest),
|
|
98
|
+
computed(() => noEdges),
|
|
99
|
+
noopRouter,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const rels = conceptGenericRelations.value;
|
|
103
|
+
/* This is the live-bug assertion: must NOT be empty. */
|
|
104
|
+
expect(rels).toHaveLength(1);
|
|
105
|
+
expect(rels[0].members).toHaveLength(3);
|
|
106
|
+
expect(rels[0].members.map(m => m.uri)).toEqual([
|
|
107
|
+
'https://oimlsmart.github.io/vocab/vim-2012/concept/1.17',
|
|
108
|
+
'https://oimlsmart.github.io/vocab/vim-2012/concept/1.18',
|
|
109
|
+
'https://oimlsmart.github.io/vocab/vim-2012/concept/1.12',
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -141,6 +141,8 @@ export interface JsonLdPartitiveMember {
|
|
|
141
141
|
'gl:count'?: string;
|
|
142
142
|
/** ISO 704:2022: delimiting part (3× stroke width in diagrams) */
|
|
143
143
|
'gl:isDelimiting'?: boolean;
|
|
144
|
+
/** ISO 704:2022 §5.5.4.2.1: GenericMember delimiting characteristic (required on generic members) */
|
|
145
|
+
'gl:delimitingCharacteristic'?: Record<string, string> | string;
|
|
144
146
|
/** Legacy v3 field — one-string multiplicity, split into presence+count */
|
|
145
147
|
'gl:multiplicity'?: string;
|
|
146
148
|
/** Legacy v2 field — migrated to presence+count */
|
|
@@ -516,6 +516,59 @@ function mapPartitiveRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<stri
|
|
|
516
516
|
return out;
|
|
517
517
|
}
|
|
518
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Map a GenericRelation JSON-LD entry to the dict shape that
|
|
521
|
+
* glossarist-js's GenericHyperedge constructor expects.
|
|
522
|
+
*
|
|
523
|
+
* Mirrors mapPartitiveRelationFromJsonLd but reads `gl:hasGeneric` and
|
|
524
|
+
* carries each member's `gl:delimitingCharacteristic` (ISO 704:2022
|
|
525
|
+
* §5.5.4.2.1 — required on every GenericMember).
|
|
526
|
+
*/
|
|
527
|
+
function mapGenericRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<string, unknown> | null {
|
|
528
|
+
const comprehensive = r[GL.COMPREHENSIVE] ? mapRefFromJsonLd(r[GL.COMPREHENSIVE]) : null;
|
|
529
|
+
if (!comprehensive) return null;
|
|
530
|
+
|
|
531
|
+
const members = (r[GL.HAS_GENERIC] ?? [])
|
|
532
|
+
.map((m): Record<string, unknown> | null => {
|
|
533
|
+
const ref = m[GL.REF] ? mapRefFromJsonLd(m[GL.REF]) : null;
|
|
534
|
+
if (!ref) return null;
|
|
535
|
+
const out: Record<string, unknown> = { ref };
|
|
536
|
+
const presence = m[GL.PRESENCE];
|
|
537
|
+
const count = m[GL.COUNT];
|
|
538
|
+
if (isPartitivePresence(presence) && isPartitiveCount(count)) {
|
|
539
|
+
out.presence = presence;
|
|
540
|
+
out.count = count;
|
|
541
|
+
} else {
|
|
542
|
+
const raw = m[GL.MULTIPLICITY] ?? splitLegacyCertainty(m[GL.CERTAINTY]);
|
|
543
|
+
if (raw) {
|
|
544
|
+
const parts = splitMultiplicity(raw);
|
|
545
|
+
out.presence = parts.presence;
|
|
546
|
+
out.count = parts.count;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const dc = m[GL.DELIMITING_CHARACTERISTIC];
|
|
550
|
+
if (dc) {
|
|
551
|
+
out.delimitingCharacteristic = typeof dc === 'string' ? { default: dc } : dc;
|
|
552
|
+
}
|
|
553
|
+
return out;
|
|
554
|
+
})
|
|
555
|
+
.filter((m): m is Record<string, unknown> => m !== null);
|
|
556
|
+
|
|
557
|
+
if (members.length < 2) return null;
|
|
558
|
+
|
|
559
|
+
const out: Record<string, unknown> = { comprehensive, members };
|
|
560
|
+
|
|
561
|
+
if (r[GL.COMPLETENESS]) out.completeness = r[GL.COMPLETENESS];
|
|
562
|
+
|
|
563
|
+
if (r[GL.CRITERION]) {
|
|
564
|
+
out.criterion = typeof r[GL.CRITERION] === 'string'
|
|
565
|
+
? { default: r[GL.CRITERION] }
|
|
566
|
+
: r[GL.CRITERION];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return out;
|
|
570
|
+
}
|
|
571
|
+
|
|
519
572
|
/** Migrate legacy v2 certainty → old v3 multiplicity string. */
|
|
520
573
|
function splitLegacyCertainty(certainty: string | undefined): string | null {
|
|
521
574
|
if (!certainty) return null;
|
|
@@ -549,6 +602,9 @@ function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
|
549
602
|
const partitiveRelations = (doc[GL.PARTITIVE_RELATIONS] ?? [])
|
|
550
603
|
.map(mapPartitiveRelationFromJsonLd)
|
|
551
604
|
.filter((r): r is Record<string, unknown> => r !== null);
|
|
605
|
+
const genericRelations = (doc[GL.GENERIC_RELATIONS] ?? [])
|
|
606
|
+
.map(mapGenericRelationFromJsonLd)
|
|
607
|
+
.filter((r): r is Record<string, unknown> => r !== null);
|
|
552
608
|
const tagsArr = doc[GL.TAGS];
|
|
553
609
|
const tags = Array.isArray(tagsArr) ? [...tagsArr] : [];
|
|
554
610
|
|
|
@@ -559,6 +615,7 @@ function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
|
559
615
|
localizations,
|
|
560
616
|
related,
|
|
561
617
|
partitive_relations: partitiveRelations,
|
|
618
|
+
generic_relations: genericRelations,
|
|
562
619
|
tags,
|
|
563
620
|
figures: normalizeEntityRefs(doc[GL.FIGURE_REF]),
|
|
564
621
|
tables: normalizeEntityRefs(doc[GL.TABLE_REF]),
|
|
@@ -1078,7 +1078,7 @@ const legendItems = computed(() => {
|
|
|
1078
1078
|
if (partitiveCount > 0) {
|
|
1079
1079
|
typeMap.set('__partitive__', {
|
|
1080
1080
|
type: '__partitive__',
|
|
1081
|
-
label: '
|
|
1081
|
+
label: 'Partitive Relation',
|
|
1082
1082
|
color: uiStore.isDark ? '#2dd4bf' : '#0d9488',
|
|
1083
1083
|
count: partitiveCount,
|
|
1084
1084
|
});
|
|
@@ -1088,7 +1088,7 @@ const legendItems = computed(() => {
|
|
|
1088
1088
|
if (genericCount > 0) {
|
|
1089
1089
|
typeMap.set('__generic__', {
|
|
1090
1090
|
type: '__generic__',
|
|
1091
|
-
label: '
|
|
1091
|
+
label: 'General Relation',
|
|
1092
1092
|
color: uiStore.isDark ? '#fbbf24' : '#b45309',
|
|
1093
1093
|
count: genericCount,
|
|
1094
1094
|
});
|