Skip to content

Commit 3ca1b72

Browse files
committed
Add specific test case for coloring
1 parent 9b9aacb commit 3ca1b72

4 files changed

Lines changed: 252 additions & 100 deletions

File tree

packages/api-graph/src/private/Graph.spec.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,27 @@ scenario('Graph class', bdd => {
88
.given('a graph', () => ({ graph: new Graph() }))
99
.and('a node', ({ graph }) => ({
1010
graph,
11-
node: { '@id': '_:b1', value: ['abc'] } satisfies SlantNode
11+
node: {
12+
'@id': '_:b1',
13+
'@type': ['Person'],
14+
value: ['abc']
15+
} satisfies SlantNode
1216
}))
1317
.and('an observer', condition => ({ ...condition, iterator: condition.graph.observe() }))
1418
.when('upserted', ({ graph, node }) => {
1519
graph.upsert(node);
1620
})
1721
.then('the graph should have the node', ({ graph }) => {
18-
expect(Array.from(graph.snapshot().entries())).toEqual([['_:b1', { '@id': '_:b1', value: ['abc'] }]]);
22+
expect(Array.from(graph.snapshot().entries())).toEqual([
23+
[
24+
'_:b1',
25+
{
26+
'@id': '_:b1',
27+
'@type': ['Person'],
28+
value: ['abc']
29+
}
30+
]
31+
]);
1932
})
2033
.and('observer', async ({ iterator }) => {
2134
const result = await iterator.next();
@@ -27,7 +40,10 @@ scenario('Graph class', bdd => {
2740
.given('a graph', () => new Graph())
2841
.when('upserting 2 nodes with same @id', graph => {
2942
try {
30-
graph.upsert({ '@id': '_:b1' } satisfies SlantNode, { '@id': '_:b1' } satisfies SlantNode);
43+
graph.upsert(
44+
{ '@id': '_:b1', '@type': ['Person'] } satisfies SlantNode,
45+
{ '@id': '_:b1', '@type': ['Person'] } satisfies SlantNode
46+
);
3147
} catch (error) {
3248
return error;
3349
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import { expect } from '@jest/globals';
2+
import { scenario } from '@testduet/given-when-then';
3+
import colorNode, { type SlantNode } from './colorNode';
4+
5+
function executeWhen([node]: [unknown, string | SlantNode]): [unknown] | [undefined, SlantNode] {
6+
let result: SlantNode;
7+
8+
try {
9+
result = colorNode(node as any);
10+
} catch (error) {
11+
return [error];
12+
}
13+
14+
return [undefined, result];
15+
}
16+
17+
function executeThen([_, errorOrResult]: [unknown, string | SlantNode], result: [unknown] | [undefined, SlantNode]) {
18+
if (typeof errorOrResult === 'string') {
19+
expect(() => {
20+
if (result[0]) {
21+
throw result[0];
22+
}
23+
}).toThrow(errorOrResult);
24+
} else {
25+
expect(result[1]).toEqual(errorOrResult);
26+
}
27+
}
28+
29+
scenario('Must have `@id`: every node in the graph must be identifiable', bdd => {
30+
bdd.given
31+
.oneOf<[unknown, string | SlantNode]>([
32+
[
33+
'node with @id',
34+
() => [
35+
{ '@id': '_:b1', '@type': ['Person'] },
36+
{ '@id': '_:b1', '@type': ['Person'] }
37+
]
38+
],
39+
['node with @id of empty string', () => [{ '@id': '' }, '@id is required and must be a string']],
40+
['node without @id', () => [{}, '@id is required and must be a string']]
41+
])
42+
.when('colored', executeWhen)
43+
.then('should match result', executeThen);
44+
});
45+
46+
scenario('Uniform getter/setter: every property value is an array, except `@context` and `@id`', bdd => {
47+
bdd.given
48+
.oneOf<[unknown, string | SlantNode]>([
49+
[
50+
'node with literal value in plain',
51+
() => [
52+
{ '@context': 'https://schema.org', '@id': '_:b1', '@type': ['Person'], name: 'John Doe' },
53+
{ '@context': 'https://schema.org', '@id': '_:b1', '@type': ['Person'], name: ['John Doe'] }
54+
]
55+
],
56+
[
57+
'node with literal value in array',
58+
() => [
59+
{ '@context': 'https://schema.org', '@id': '_:b1', '@type': ['Person'], name: ['John Doe'] },
60+
{ '@context': 'https://schema.org', '@id': '_:b1', '@type': ['Person'], name: ['John Doe'] }
61+
]
62+
]
63+
])
64+
.when('colored', executeWhen)
65+
.then('should match result', executeThen);
66+
});
67+
68+
scenario('Uniform typing: node reference must be `{ "@id": string }` to reduce confusion with a plain string', bdd => {
69+
bdd.given
70+
.oneOf<[unknown, string | SlantNode]>([
71+
[
72+
'node with reference',
73+
() => [
74+
{ '@id': '_:b1', '@type': ['Person'], name: { '@id': '_:b2' } },
75+
{ '@id': '_:b1', '@type': ['Person'], name: [{ '@id': '_:b2' }] }
76+
]
77+
]
78+
])
79+
.when('colored', executeWhen)
80+
.then('should match result', executeThen);
81+
});
82+
83+
scenario('Support multiple types: every `@type` must be an array of string', bdd => {
84+
bdd.given
85+
.oneOf<[unknown, string | SlantNode]>([
86+
[
87+
'node with literal value in plain',
88+
() => [
89+
{ '@id': '_:b1', '@type': 'Person' },
90+
{ '@id': '_:b1', '@type': ['Person'] }
91+
]
92+
],
93+
[
94+
'node with literal value in array',
95+
() => [
96+
{ '@id': '_:b1', '@type': ['Person'] },
97+
{ '@id': '_:b1', '@type': ['Person'] }
98+
]
99+
]
100+
])
101+
.when('colored', executeWhen)
102+
.then('should match result', executeThen);
103+
});
104+
105+
scenario('Reduce confusion: empty array and `null` is removed', bdd => {
106+
bdd.given
107+
.oneOf<[unknown, string | SlantNode]>([
108+
[
109+
'node with empty array',
110+
() => [
111+
{ '@id': '_:b1', '@type': ['Person'], value: [] },
112+
{ '@id': '_:b1', '@type': ['Person'] }
113+
]
114+
],
115+
[
116+
'node with null',
117+
() => [
118+
{ '@id': '_:b1', '@type': ['Person'], value: null },
119+
{ '@id': '_:b1', '@type': ['Person'] }
120+
]
121+
],
122+
[
123+
'node with array of null',
124+
() => [
125+
{ '@id': '_:b1', '@type': ['Person'], value: [null] },
126+
'Invalid type: Expected (Array | Object | (boolean | number | string) | null) but received Array'
127+
]
128+
],
129+
[
130+
'node with hasPart of empty array',
131+
() => [
132+
{ '@id': '_:b1', '@type': ['Person'], hasPart: [] },
133+
{ '@id': '_:b1', '@type': ['Person'] }
134+
]
135+
],
136+
[
137+
'node with hasPart of null',
138+
() => [
139+
{ '@id': '_:b1', '@type': ['Person'], hasPart: null },
140+
{ '@id': '_:b1', '@type': ['Person'] }
141+
]
142+
],
143+
[
144+
'node with isPartOf of empty array',
145+
() => [
146+
{ '@id': '_:b1', '@type': ['Person'], isPartOf: [] },
147+
{ '@id': '_:b1', '@type': ['Person'] }
148+
]
149+
],
150+
[
151+
'node with isPartOf of null',
152+
() => [
153+
{ '@id': '_:b1', '@type': ['Person'], isPartOf: null },
154+
{ '@id': '_:b1', '@type': ['Person'] }
155+
]
156+
]
157+
])
158+
.when('colored', executeWhen)
159+
.then('should match result', executeThen);
160+
});
161+
162+
// scenario('Flattened: property values must be non-null literals or node reference, no nested objects', () => {
163+
// // TODO: Need to move flattenNodeObject into colorNode.
164+
// });
165+
166+
scenario('Do not handle full JSON-LD spec: `@context` is an opaque string and the schema is not honored', bdd => {
167+
bdd.given
168+
.oneOf<[unknown, string | SlantNode]>([
169+
[
170+
'node with @context of string',
171+
() => [
172+
{ '@context': 'https://schema.org', '@id': '_:b1', '@type': ['Person'] },
173+
{ '@context': 'https://schema.org', '@id': '_:b1', '@type': ['Person'] }
174+
]
175+
],
176+
[
177+
'node with @context of object',
178+
() => [
179+
{ '@context': {}, '@id': '_:b1', '@type': ['Person'] },
180+
'Invalid type: Expected (Array | Object | (boolean | number | string) | null) but received Object'
181+
]
182+
]
183+
])
184+
.when('colored', executeWhen)
185+
.then('should match result', executeThen);
186+
});
187+
188+
// scenario('Auto-linking for Schema.org: `hasPart` and `isPartOf` are auto-inversed', bdd => {
189+
// // TODO: This is a feature of Graph.
190+
// });
191+
192+
scenario('Debuggability: must have at least one `@type`', bdd => {
193+
bdd.given
194+
.oneOf<[unknown, string | SlantNode]>([
195+
[
196+
'node with @type',
197+
() => [
198+
{ '@id': '_:b1', '@type': ['Person'] },
199+
{ '@id': '_:b1', '@type': ['Person'] }
200+
]
201+
],
202+
['node without @type', () => [{ '@id': '_:b1' }, 'Invalid key: Expected "@type" but received undefined']]
203+
])
204+
.when('colored', executeWhen)
205+
.then('should match result', executeThen);
206+
});

0 commit comments

Comments
 (0)