@glossarist/concept-browser 0.7.85 → 0.7.86
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 +2 -2
- package/scripts/build-edges.js +42 -26
- package/scripts/generate-data.mjs +45 -5
- package/scripts/migrate-v1-to-v3.d.mts +6 -0
- package/scripts/migrate-v1-to-v3.mjs +176 -0
- package/src/__tests__/components/PartitiveRelationDiagram.test.ts +92 -136
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +115 -87
- package/src/__tests__/partitive-relation-styling.test.ts +12 -54
- package/src/__tests__/partitive-relation.test.ts +42 -90
- package/src/__tests__/scripts/migrate-v1-to-v3.d.mts +8 -0
- package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +115 -0
- package/src/__tests__/utils/partitive-multiplicity.test.ts +113 -0
- package/src/adapters/model-bridge.ts +15 -19
- package/src/adapters/non-verbal/glossarist-augment.d.ts +34 -18
- package/src/adapters/types.ts +2 -8
- package/src/components/PartitiveRelationDiagram.vue +88 -76
- package/src/components/PartitiveRelationList.vue +26 -34
- package/src/components/RelationSphere.vue +57 -72
- package/src/composables/use-concept-edges.ts +21 -21
- package/src/utils/partitive-multiplicity.ts +106 -0
- package/src/utils/partitive-relation-styling.ts +29 -38
- package/src/views/ConceptView.vue +7 -13
- package/src/__tests__/model-bridge-partitive.test.ts +0 -146
|
@@ -3,26 +3,26 @@ import { mount } from '@vue/test-utils';
|
|
|
3
3
|
import PartitiveRelationDiagram, {
|
|
4
4
|
type PartitiveMemberLabeled,
|
|
5
5
|
} from '../../components/PartitiveRelationDiagram.vue';
|
|
6
|
+
import { rakeStrokeStyle, type PartitiveMultiplicity } from '../../utils/partitive-multiplicity';
|
|
6
7
|
|
|
7
|
-
const members = (
|
|
8
|
+
const members = (
|
|
9
|
+
n: number,
|
|
10
|
+
opts: { multiplicity?: PartitiveMultiplicity; isDelimiting?: boolean } = {},
|
|
11
|
+
): PartitiveMemberLabeled[] =>
|
|
8
12
|
Array.from({ length: n }, (_, i) => ({
|
|
9
13
|
uri: `https://example.org/test/concept/1.${i + 1}`,
|
|
10
14
|
label: `1.${i + 1}`,
|
|
11
|
-
|
|
15
|
+
multiplicity: opts.multiplicity ?? 'compulsory',
|
|
16
|
+
isDelimiting: opts.isDelimiting ?? false,
|
|
12
17
|
}));
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
return wrapper.findAll('line');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe('PartitiveRelationDiagram — ISO 704 rake rendering', () => {
|
|
19
|
+
describe('PartitiveRelationDiagram — ISO 704:2022 rake rendering', () => {
|
|
19
20
|
it('renders comprehensive + N partitive nodes as text', () => {
|
|
20
21
|
const w = mount(PartitiveRelationDiagram, {
|
|
21
22
|
props: {
|
|
22
23
|
comprehensiveLabel: '1.3',
|
|
23
24
|
partitives: members(3),
|
|
24
25
|
completeness: 'complete',
|
|
25
|
-
plurality: null,
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
28
|
const texts = w.findAll('text').map(t => t.text());
|
|
@@ -38,133 +38,108 @@ describe('PartitiveRelationDiagram — ISO 704 rake rendering', () => {
|
|
|
38
38
|
comprehensiveLabel: '1.3',
|
|
39
39
|
partitives: members(2),
|
|
40
40
|
completeness: 'complete',
|
|
41
|
-
plurality: null,
|
|
42
41
|
criterion: 'physical structure',
|
|
43
42
|
},
|
|
44
43
|
});
|
|
45
44
|
expect(w.findAll('text').map(t => t.text())).toContain('physical structure');
|
|
46
45
|
});
|
|
47
46
|
|
|
48
|
-
describe('
|
|
49
|
-
it('
|
|
47
|
+
describe('per-member multiplicity line rendering', () => {
|
|
48
|
+
it('compulsory: stem + spine + 1 solid line per drop', () => {
|
|
50
49
|
const w = mount(PartitiveRelationDiagram, {
|
|
51
50
|
props: {
|
|
52
|
-
comprehensiveLabel: '
|
|
51
|
+
comprehensiveLabel: '0',
|
|
53
52
|
partitives: members(2),
|
|
54
53
|
completeness: 'complete',
|
|
55
|
-
plurality: null,
|
|
56
54
|
},
|
|
57
55
|
});
|
|
58
|
-
// stem (1) + spine (1) +
|
|
59
|
-
expect(
|
|
60
|
-
// none should be dashed
|
|
61
|
-
const dashed = svgLinesOf(w).filter(l => l.attributes('stroke-dasharray'));
|
|
62
|
-
expect(dashed).toHaveLength(0);
|
|
56
|
+
// stem (1) + spine (1) + 1 drop line per member × 2 members = 4
|
|
57
|
+
expect(w.findAll('line')).toHaveLength(4);
|
|
63
58
|
});
|
|
64
59
|
|
|
65
|
-
it('
|
|
60
|
+
it('compulsory_multiple: each drop has 2 lines', () => {
|
|
66
61
|
const w = mount(PartitiveRelationDiagram, {
|
|
67
62
|
props: {
|
|
68
|
-
comprehensiveLabel: '
|
|
69
|
-
partitives: members(2),
|
|
63
|
+
comprehensiveLabel: '0',
|
|
64
|
+
partitives: members(2, { multiplicity: 'compulsory_multiple' }),
|
|
70
65
|
completeness: 'complete',
|
|
71
|
-
plurality: { isShared: true, isUncertain: false },
|
|
72
66
|
},
|
|
73
67
|
});
|
|
74
|
-
// stem
|
|
75
|
-
expect(
|
|
76
|
-
const dashed = svgLinesOf(w).filter(l => l.attributes('stroke-dasharray'));
|
|
77
|
-
expect(dashed).toHaveLength(0);
|
|
68
|
+
// stem + spine + 2 drop lines per member × 2 = 6
|
|
69
|
+
expect(w.findAll('line')).toHaveLength(6);
|
|
78
70
|
});
|
|
79
71
|
|
|
80
|
-
it('
|
|
72
|
+
it('optional: drop is dashed', () => {
|
|
81
73
|
const w = mount(PartitiveRelationDiagram, {
|
|
82
74
|
props: {
|
|
83
|
-
comprehensiveLabel: '
|
|
84
|
-
partitives: members(2),
|
|
75
|
+
comprehensiveLabel: '0',
|
|
76
|
+
partitives: members(2, { multiplicity: 'optional' }),
|
|
85
77
|
completeness: 'complete',
|
|
86
|
-
plurality: { isShared: true, isUncertain: true },
|
|
87
78
|
},
|
|
88
79
|
});
|
|
89
|
-
// stem
|
|
90
|
-
const lines =
|
|
91
|
-
expect(lines).toHaveLength(6);
|
|
80
|
+
// stem + spine (frame, not dashed) + 2 drops (dashed)
|
|
81
|
+
const lines = w.findAll('line');
|
|
92
82
|
const dashed = lines.filter(l => l.attributes('stroke-dasharray'));
|
|
93
|
-
// exactly 2 dashed lines (the second stem + the second spine)
|
|
94
83
|
expect(dashed).toHaveLength(2);
|
|
95
84
|
});
|
|
96
|
-
});
|
|
97
85
|
|
|
98
|
-
|
|
99
|
-
it('extends spine past last tooth when completeness=partial', () => {
|
|
86
|
+
it('optional_multiple: each drop has 2 dashed lines', () => {
|
|
100
87
|
const w = mount(PartitiveRelationDiagram, {
|
|
101
88
|
props: {
|
|
102
|
-
comprehensiveLabel: '
|
|
103
|
-
partitives: members(2),
|
|
104
|
-
completeness: '
|
|
105
|
-
plurality: null,
|
|
89
|
+
comprehensiveLabel: '0',
|
|
90
|
+
partitives: members(2, { multiplicity: 'optional_multiple' }),
|
|
91
|
+
completeness: 'complete',
|
|
106
92
|
},
|
|
107
93
|
});
|
|
108
|
-
const lines =
|
|
109
|
-
|
|
110
|
-
expect(
|
|
111
|
-
// The spine line's x2 should be > the last partitive's x
|
|
112
|
-
const lastMember = w.props('partitives')[1];
|
|
113
|
-
// visual assertion: the spine line's x2 is greater than the second
|
|
114
|
-
// partitive's slot x. We can't compute slot exactly without internals,
|
|
115
|
-
// but we know PARTIAL_TAIL (32) is added.
|
|
116
|
-
const spineLine = lines.find(l => {
|
|
117
|
-
const y1 = l.attributes('y1');
|
|
118
|
-
const y2 = l.attributes('y2');
|
|
119
|
-
return y1 && y2 && y1 === y2; // horizontal
|
|
120
|
-
});
|
|
121
|
-
expect(spineLine).toBeDefined();
|
|
122
|
-
const x2 = Number(spineLine!.attributes('x2'));
|
|
123
|
-
// last member x ≈ PADDING_X + NODE_W * 1.5 + GAP_X = 24 + 180 + 16 = 220
|
|
124
|
-
// spine end with PARTIAL_TAIL = 220 + 32 = 252
|
|
125
|
-
expect(x2).toBeGreaterThan(220);
|
|
126
|
-
void lastMember;
|
|
94
|
+
const lines = w.findAll('line');
|
|
95
|
+
const dashed = lines.filter(l => l.attributes('stroke-dasharray'));
|
|
96
|
+
expect(dashed).toHaveLength(4); // 2 members × 2 dashed lines each
|
|
127
97
|
});
|
|
128
|
-
});
|
|
129
98
|
|
|
130
|
-
|
|
131
|
-
it('renders possible members with dashed border', () => {
|
|
132
|
-
const possibleMembers: PartitiveMemberLabeled[] = [
|
|
133
|
-
{ uri: 'u1', label: '1', certainty: 'confirmed' },
|
|
134
|
-
{ uri: 'u2', label: '2', certainty: 'possible' },
|
|
135
|
-
];
|
|
99
|
+
it('compulsory_at_least_one: each drop has 1 solid + 1 dashed line', () => {
|
|
136
100
|
const w = mount(PartitiveRelationDiagram, {
|
|
137
101
|
props: {
|
|
138
102
|
comprehensiveLabel: '0',
|
|
139
|
-
partitives:
|
|
103
|
+
partitives: members(2, { multiplicity: 'compulsory_at_least_one' }),
|
|
140
104
|
completeness: 'complete',
|
|
141
|
-
plurality: null,
|
|
142
105
|
},
|
|
143
106
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
const possibleRect = rects[2];
|
|
150
|
-
expect(possibleRect.attributes('stroke-dasharray')).toBeTruthy();
|
|
107
|
+
const lines = w.findAll('line');
|
|
108
|
+
// stem + spine (solid) + 1 solid + 1 dashed per member × 2 = 6
|
|
109
|
+
expect(lines).toHaveLength(6);
|
|
110
|
+
const dashed = lines.filter(l => l.attributes('stroke-dasharray'));
|
|
111
|
+
expect(dashed).toHaveLength(2); // 1 dashed per member
|
|
151
112
|
});
|
|
152
113
|
|
|
153
|
-
it('
|
|
114
|
+
it('delimiting: drops use 3× stroke width', () => {
|
|
154
115
|
const w = mount(PartitiveRelationDiagram, {
|
|
155
116
|
props: {
|
|
156
117
|
comprehensiveLabel: '0',
|
|
157
|
-
partitives: members(2),
|
|
118
|
+
partitives: members(2, { isDelimiting: true }),
|
|
158
119
|
completeness: 'complete',
|
|
159
|
-
plurality: null,
|
|
160
120
|
},
|
|
161
121
|
});
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
122
|
+
// The drop lines should have stroke-width = 4.5 (vs frame's 1.4)
|
|
123
|
+
const lines = w.findAll('line');
|
|
124
|
+
const dropWidths = lines
|
|
125
|
+
.map(l => Number(l.attributes('stroke-width') || '0'))
|
|
126
|
+
.filter(w => w > 2); // delimiting width is 4.5
|
|
127
|
+
expect(dropWidths).toHaveLength(2); // 1 drop per member × 2 members
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('emits navigate when a partitive is clicked', async () => {
|
|
132
|
+
const w = mount(PartitiveRelationDiagram, {
|
|
133
|
+
props: {
|
|
134
|
+
comprehensiveLabel: '0',
|
|
135
|
+
partitives: members(2),
|
|
136
|
+
completeness: 'complete',
|
|
137
|
+
},
|
|
167
138
|
});
|
|
139
|
+
const groups = w.findAll('g.cursor-pointer');
|
|
140
|
+
expect(groups.length).toBeGreaterThan(0);
|
|
141
|
+
await groups[0].trigger('click');
|
|
142
|
+
expect(w.emitted('navigate')).toBeTruthy();
|
|
168
143
|
});
|
|
169
144
|
|
|
170
145
|
describe('designation-aware sizing', () => {
|
|
@@ -174,75 +149,56 @@ describe('PartitiveRelationDiagram — ISO 704 rake rendering', () => {
|
|
|
174
149
|
comprehensiveLabel: '1.3',
|
|
175
150
|
partitives: members(2),
|
|
176
151
|
completeness: 'complete',
|
|
177
|
-
plurality: null,
|
|
178
152
|
},
|
|
179
153
|
});
|
|
180
154
|
const svg = w.find('svg');
|
|
181
|
-
|
|
182
|
-
const viewBox = svg.attributes('viewBox') || '';
|
|
183
|
-
const width = Number(viewBox.split(' ')[2]);
|
|
155
|
+
const width = Number((svg.attributes('viewBox') || '').split(' ')[2]);
|
|
184
156
|
expect(width).toBeLessThan(300);
|
|
185
157
|
});
|
|
186
158
|
|
|
187
159
|
it('grows nodes for long designations', () => {
|
|
188
|
-
const longMembers: PartitiveMemberLabeled[] = [
|
|
189
|
-
{ uri: 'u1', label: 'system of quantities', certainty: 'confirmed' },
|
|
190
|
-
{ uri: 'u2', label: 'International System of Units', certainty: 'confirmed' },
|
|
191
|
-
{ uri: 'u3', label: 'base quantity dimension', certainty: 'confirmed' },
|
|
192
|
-
];
|
|
193
160
|
const w = mount(PartitiveRelationDiagram, {
|
|
194
161
|
props: {
|
|
195
162
|
comprehensiveLabel: 'coordinate system of quantities',
|
|
196
|
-
partitives: longMembers,
|
|
197
|
-
completeness: 'complete',
|
|
198
|
-
plurality: null,
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
const svg = w.find('svg');
|
|
202
|
-
const viewBox = svg.attributes('viewBox') || '';
|
|
203
|
-
const width = Number(viewBox.split(' ')[2]);
|
|
204
|
-
// Each node wider than MIN_NODE_W → total > 3*110 = 330
|
|
205
|
-
expect(width).toBeGreaterThan(330);
|
|
206
|
-
// Capped: node width ≤ MAX_NODE_W=220 → total ≤ 3*220 + 2*16 + 40 = 732
|
|
207
|
-
expect(width).toBeLessThanOrEqual(732);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('wraps long labels to 2 lines via tspan when needed', () => {
|
|
211
|
-
const longLabel = 'International System of Quantities and Units';
|
|
212
|
-
const w = mount(PartitiveRelationDiagram, {
|
|
213
|
-
props: {
|
|
214
|
-
comprehensiveLabel: longLabel,
|
|
215
163
|
partitives: [
|
|
216
|
-
{ uri: 'u1', label: '
|
|
217
|
-
{ uri: 'u2', label: '
|
|
164
|
+
{ uri: 'u1', label: 'system of quantities', multiplicity: 'compulsory', isDelimiting: false },
|
|
165
|
+
{ uri: 'u2', label: 'International System of Units', multiplicity: 'compulsory', isDelimiting: false },
|
|
166
|
+
{ uri: 'u3', label: 'base quantity dimension', multiplicity: 'compulsory', isDelimiting: false },
|
|
218
167
|
],
|
|
219
168
|
completeness: 'complete',
|
|
220
|
-
plurality: null,
|
|
221
169
|
},
|
|
222
170
|
});
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const compText = texts[0]; // first text is the comprehensive
|
|
226
|
-
const tspans = compText.findAll('tspan');
|
|
227
|
-
expect(tspans.length).toBeGreaterThanOrEqual(2);
|
|
171
|
+
const width = Number((w.find('svg').attributes('viewBox') || '').split(' ')[2]);
|
|
172
|
+
expect(width).toBeGreaterThan(330);
|
|
228
173
|
});
|
|
174
|
+
});
|
|
229
175
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
176
|
+
describe('consistency with rakeStrokeStyle helper', () => {
|
|
177
|
+
// Ensure the component's line rendering matches the pure helper
|
|
178
|
+
// (single source of truth — both must agree).
|
|
179
|
+
it('renders exactly the line count + dash pattern the helper predicts', () => {
|
|
180
|
+
const testCases: Array<{ multiplicity: PartitiveMultiplicity; isDelimiting: boolean }> = [
|
|
181
|
+
{ multiplicity: 'compulsory', isDelimiting: false },
|
|
182
|
+
{ multiplicity: 'compulsory', isDelimiting: true },
|
|
183
|
+
{ multiplicity: 'optional', isDelimiting: false },
|
|
184
|
+
{ multiplicity: 'compulsory_multiple', isDelimiting: false },
|
|
185
|
+
{ multiplicity: 'optional_multiple', isDelimiting: false },
|
|
186
|
+
{ multiplicity: 'compulsory_at_least_one', isDelimiting: false },
|
|
187
|
+
];
|
|
188
|
+
for (const tc of testCases) {
|
|
189
|
+
const expected = rakeStrokeStyle(tc.multiplicity, tc.isDelimiting);
|
|
190
|
+
const w = mount(PartitiveRelationDiagram, {
|
|
191
|
+
props: {
|
|
192
|
+
comprehensiveLabel: '0',
|
|
193
|
+
partitives: members(1, tc),
|
|
194
|
+
completeness: 'complete',
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const lines = w.findAll('line');
|
|
198
|
+
// stem (1) + spine (1) + drop lineCount per member
|
|
199
|
+
const expectedLines = 2 + expected.lineCount;
|
|
200
|
+
expect(lines.length).toBe(expectedLines);
|
|
201
|
+
}
|
|
246
202
|
});
|
|
247
203
|
});
|
|
248
204
|
});
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
2
|
+
// glossarist 0.4.24's top-level d.ts doesn't re-export the v3
|
|
3
|
+
// PartitiveRelation/PartitiveMember classes; import from glossarist/models
|
|
4
|
+
// (which our local augmentation extends) so the types are correct.
|
|
5
|
+
import type { PartitiveRelation, PartitiveMember, ConceptRef } from 'glossarist/models';
|
|
3
6
|
import {
|
|
4
7
|
PartitiveRelation as PartitiveRelationModel,
|
|
5
8
|
PartitiveMember as PartitiveMemberModel,
|
|
6
|
-
TypeSharedPlurality as TypeSharedPluralityModel,
|
|
7
9
|
ConceptRef as ConceptRefModel,
|
|
8
10
|
} from 'glossarist/models';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
* Verifies glossarist-js's native
|
|
13
|
+
* Verifies glossarist-js 0.4.24's native v3 PartitiveRelation model
|
|
14
|
+
* (ISO 704:2022 multiplicity + is_delimiting).
|
|
12
15
|
*
|
|
13
|
-
* The concept-browser now consumes this directly (no
|
|
14
|
-
* bridge). These specs lock in the contract so an upstream
|
|
16
|
+
* The concept-browser now consumes this directly (no migration adapter,
|
|
17
|
+
* no `as any` bridge). These specs lock in the contract so an upstream
|
|
15
18
|
* regression is caught here.
|
|
16
19
|
*/
|
|
17
20
|
|
|
@@ -19,114 +22,139 @@ function makeRef(source: string, id: string): ConceptRef {
|
|
|
19
22
|
return ConceptRefModel.fromJSON({ source, id }) as ConceptRef;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
function makePartitive(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
function makePartitive(
|
|
26
|
+
source: string,
|
|
27
|
+
id: string,
|
|
28
|
+
multiplicity?: 'compulsory' | 'optional' | 'compulsory_multiple' | 'optional_multiple' | 'compulsory_at_least_one',
|
|
29
|
+
isDelimiting?: boolean,
|
|
30
|
+
): PartitiveMember {
|
|
31
|
+
const data: {
|
|
32
|
+
ref: ConceptRef;
|
|
33
|
+
multiplicity?: typeof multiplicity;
|
|
34
|
+
is_delimiting?: boolean;
|
|
35
|
+
} = { ref: makeRef(source, id) };
|
|
36
|
+
if (multiplicity) data.multiplicity = multiplicity;
|
|
37
|
+
if (isDelimiting) data.is_delimiting = isDelimiting;
|
|
25
38
|
return PartitiveMemberModel.fromJSON(data as unknown as Record<string, unknown>) as PartitiveMember;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
|
-
function makePlurality(isShared: boolean, isUncertain = false): TypeSharedPlurality {
|
|
29
|
-
return TypeSharedPluralityModel.fromJSON({ isShared, isUncertain }) as TypeSharedPlurality;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
41
|
function makeRelation(opts: {
|
|
33
42
|
comprehensive: ConceptRef;
|
|
34
43
|
partitives: PartitiveMember[];
|
|
35
44
|
completeness?: 'complete' | 'partial';
|
|
36
|
-
plurality?: TypeSharedPlurality | null;
|
|
37
45
|
criterion?: Record<string, string>;
|
|
38
46
|
}): PartitiveRelation {
|
|
39
47
|
return PartitiveRelationModel.fromJSON(opts as unknown as Record<string, unknown>) as PartitiveRelation;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
describe('native
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
describe('useConceptEdges — native v3 PartitiveRelation (glossarist 0.4.24)', () => {
|
|
51
|
+
describe('PartitiveMember (ISO 704:2022 multiplicity + delimiting)', () => {
|
|
52
|
+
it('multiplicity defaults to compulsory when omitted', () => {
|
|
53
|
+
const m = makePartitive('test', '1.1');
|
|
54
|
+
expect(m.multiplicity).toBe('compulsory');
|
|
55
|
+
expect(m.isCompulsory).toBe(true);
|
|
56
|
+
});
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
it('multiplicity is preserved when set to optional', () => {
|
|
59
|
+
const m = makePartitive('test', '1.1', 'optional');
|
|
60
|
+
expect(m.multiplicity).toBe('optional');
|
|
61
|
+
expect(m.isOptional).toBe(true);
|
|
62
|
+
});
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
it('is_delimiting defaults to false', () => {
|
|
65
|
+
const m = makePartitive('test', '1.1');
|
|
66
|
+
expect(m.is_delimiting).toBe(false);
|
|
67
|
+
expect(m.isDelimiting).toBe(false);
|
|
68
|
+
});
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
70
|
+
it('is_delimiting is preserved when set to true', () => {
|
|
71
|
+
const m = makePartitive('test', '1.1', 'compulsory', true);
|
|
72
|
+
expect(m.is_delimiting).toBe(true);
|
|
64
73
|
});
|
|
65
|
-
expect(r.completeness).toBe('complete');
|
|
66
|
-
expect(r.isComplete).toBe(true);
|
|
67
|
-
});
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
completeness: 'partial',
|
|
75
|
+
it('multiplicity + is_delimiting are orthogonal axes', () => {
|
|
76
|
+
const m = makePartitive('test', '1.1', 'compulsory_multiple', true);
|
|
77
|
+
expect(m.multiplicity).toBe('compulsory_multiple');
|
|
78
|
+
expect(m.is_delimiting).toBe(true);
|
|
74
79
|
});
|
|
75
|
-
expect(r.completeness).toBe('partial');
|
|
76
|
-
expect(r.isPartial).toBe(true);
|
|
77
80
|
});
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
describe('PartitiveRelation', () => {
|
|
83
|
+
it('accepts 2 compulsory members with completeness=complete', () => {
|
|
84
|
+
const r = makeRelation({
|
|
85
|
+
comprehensive: makeRef('test', '1.3'),
|
|
86
|
+
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
87
|
+
});
|
|
88
|
+
expect(r.partitives).toHaveLength(2);
|
|
89
|
+
expect(r.completeness).toBe('complete');
|
|
90
|
+
expect(r.isComplete).toBe(true);
|
|
91
|
+
expect(r.isPartial).toBe(false);
|
|
84
92
|
});
|
|
85
|
-
expect(r.hasPlurality()).toBe(true);
|
|
86
|
-
expect(r.plurality?.isShared).toBe(true);
|
|
87
|
-
expect(r.plurality?.isUncertain).toBe(true);
|
|
88
|
-
});
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
it('completeness=partial is preserved', () => {
|
|
95
|
+
const r = makeRelation({
|
|
96
|
+
comprehensive: makeRef('test', '1.3'),
|
|
97
|
+
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
98
|
+
completeness: 'partial',
|
|
99
|
+
});
|
|
100
|
+
expect(r.completeness).toBe('partial');
|
|
101
|
+
expect(r.isPartial).toBe(true);
|
|
95
102
|
});
|
|
96
|
-
expect(r.hasCriterion()).toBe(true);
|
|
97
|
-
expect(r.criterion?.eng).toBe('physical structure');
|
|
98
|
-
});
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
})
|
|
105
|
-
});
|
|
104
|
+
it('member with compulsory_multiple has isCompulsory false', () => {
|
|
105
|
+
const m = makePartitive('test', '1.4', 'compulsory_multiple');
|
|
106
|
+
expect(m.isCompulsory).toBe(false);
|
|
107
|
+
expect(m.multiplicity).not.toBe('compulsory');
|
|
108
|
+
});
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
]
|
|
114
|
-
})
|
|
115
|
-
|
|
110
|
+
it('all compulsory-single members have isCompulsory true', () => {
|
|
111
|
+
const r = makeRelation({
|
|
112
|
+
comprehensive: makeRef('test', '1.3'),
|
|
113
|
+
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
114
|
+
});
|
|
115
|
+
expect(r.partitives[0].isCompulsory).toBe(true);
|
|
116
|
+
expect(r.partitives[1].isCompulsory).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('isCoordinate true when ≥2 partitives', () => {
|
|
120
|
+
const r = makeRelation({
|
|
121
|
+
comprehensive: makeRef('test', '1.3'),
|
|
122
|
+
partitives: [makePartitive('test', '1.4'), makePartitive('test', '1.5')],
|
|
123
|
+
});
|
|
124
|
+
expect(r.isCoordinate).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('rejects < 2 partitives (ISO 704 requirement)', () => {
|
|
128
|
+
expect(() => makeRelation({
|
|
129
|
+
comprehensive: makeRef('test', '1.3'),
|
|
130
|
+
partitives: [makePartitive('test', '1.4')],
|
|
131
|
+
})).toThrow(/≥2 partitives/);
|
|
132
|
+
});
|
|
116
133
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
it('round-trips through toJSON with v3 fields', () => {
|
|
135
|
+
const r = makeRelation({
|
|
136
|
+
comprehensive: makeRef('test', '1.3'),
|
|
137
|
+
partitives: [
|
|
138
|
+
makePartitive('test', '1.4', 'compulsory', false),
|
|
139
|
+
makePartitive('test', '1.5', 'compulsory_multiple', true),
|
|
140
|
+
],
|
|
141
|
+
completeness: 'partial',
|
|
142
|
+
criterion: { eng: 'physical structure' },
|
|
143
|
+
});
|
|
144
|
+
const json = r.toJSON() as {
|
|
145
|
+
comprehensive: unknown;
|
|
146
|
+
partitives: Array<{ multiplicity?: string; is_delimiting?: boolean; ref: unknown }>;
|
|
147
|
+
completeness: string;
|
|
148
|
+
criterion?: Record<string, string>;
|
|
149
|
+
};
|
|
150
|
+
expect(json.comprehensive).toBeDefined();
|
|
151
|
+
expect(json.partitives).toHaveLength(2);
|
|
152
|
+
// compulsory is the default multiplicity — not emitted in toJSON
|
|
153
|
+
expect(json.partitives[0].multiplicity).toBeUndefined();
|
|
154
|
+
expect(json.partitives[1].multiplicity).toBe('compulsory_multiple');
|
|
155
|
+
expect(json.partitives[1].is_delimiting).toBe(true);
|
|
156
|
+
expect(json.completeness).toBe('partial');
|
|
157
|
+
expect(json.criterion).toEqual({ eng: 'physical structure' });
|
|
124
158
|
});
|
|
125
|
-
const json = r.toJSON();
|
|
126
|
-
expect(json.completeness).toBe('partial');
|
|
127
|
-
expect(json.partitives).toHaveLength(2);
|
|
128
|
-
expect(json.partitives[1].certainty).toBe('possible');
|
|
129
|
-
expect(json.plurality?.is_shared).toBe(true);
|
|
130
|
-
expect(json.criterion?.eng).toBe('criterion text');
|
|
131
159
|
});
|
|
132
160
|
});
|