@noeldemartin/solid-utils 0.6.0-next.8a6a0b585adc7a1c329fb0fed5c420bb72cdccda → 0.6.0-next.956cbec11a55691bab3170059fa908865d683e20

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.
@@ -9,11 +9,9 @@ export interface SubjectParts {
9
9
  }
10
10
 
11
11
  function getContainerPath(parts: UrlParts): string | null {
12
- if (!parts.path || !parts.path.startsWith('/'))
13
- return null;
12
+ if (!parts.path || !parts.path.startsWith('/')) return null;
14
13
 
15
- if (parts.path.match(/^\/[^/]*$/))
16
- return '/';
14
+ if (parts.path.match(/^\/[^/]*$/)) return '/';
17
15
 
18
16
  return `/${arr(parts.path.split('/')).filter().slice(0, -1).join('/')}/`.replace('//', '/');
19
17
  }
@@ -27,30 +25,29 @@ function getContainerUrl(parts: UrlParts): string | null {
27
25
  }
28
26
 
29
27
  function __mintJsonLDIdentifiers(jsonld: JsonLD): void {
30
- if (!('@type' in jsonld) || '@value' in jsonld)
31
- return;
28
+ if (!('@type' in jsonld) || '@value' in jsonld) return;
32
29
 
33
30
  jsonld['@id'] = jsonld['@id'] ?? uuid();
34
31
 
35
32
  for (const propertyValue of Object.values(jsonld)) {
36
- if (isObject(propertyValue))
37
- __mintJsonLDIdentifiers(propertyValue);
33
+ if (isObject(propertyValue)) __mintJsonLDIdentifiers(propertyValue);
38
34
 
39
- if (isArray(propertyValue))
40
- propertyValue.forEach(value => isObject(value) && __mintJsonLDIdentifiers(value));
35
+ if (isArray(propertyValue)) propertyValue.forEach((value) => isObject(value) && __mintJsonLDIdentifiers(value));
41
36
  }
42
37
  }
43
38
 
44
39
  export function mintJsonLDIdentifiers(jsonld: JsonLD): JsonLDResource {
45
- return tap(objectDeepClone(jsonld) as JsonLDResource, clone => __mintJsonLDIdentifiers(clone));
40
+ return tap(objectDeepClone(jsonld) as JsonLDResource, (clone) => __mintJsonLDIdentifiers(clone));
46
41
  }
47
42
 
48
43
  export function parseResourceSubject(subject: string): SubjectParts {
49
44
  const parts = urlParse(subject);
50
45
 
51
- return !parts ? {} : objectWithoutEmpty({
52
- containerUrl: getContainerUrl(parts),
53
- documentName: parts.path ? parts.path.split('/').pop() : null,
54
- resourceHash: parts.fragment,
55
- });
46
+ return !parts
47
+ ? {}
48
+ : objectWithoutEmpty({
49
+ containerUrl: getContainerUrl(parts),
50
+ documentName: parts.path ? parts.path.split('/').pop() : null,
51
+ resourceHash: parts.fragment,
52
+ });
56
53
  }
package/src/helpers/io.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as jsonld from 'jsonld';
1
+ import jsonld from 'jsonld';
2
2
  import md5 from 'md5';
3
3
  import { arr, arrayFilter, arrayReplace, objectWithoutEmpty, stringMatchAll, tap } from '@noeldemartin/utils';
4
4
  import { BlankNode as N3BlankNode, Quad as N3Quad, Parser, Writer } from 'n3';
@@ -1,4 +1,4 @@
1
- import * as jsonld from 'jsonld';
1
+ import jsonld from 'jsonld';
2
2
  import type { JsonLdDocument } from 'jsonld';
3
3
 
4
4
  export type JsonLD = Partial<{
@@ -23,22 +23,19 @@ export function defineIRIPrefix(name: string, value: string): void {
23
23
  }
24
24
 
25
25
  export function expandIRI(iri: string, options: Partial<ExpandIRIOptions> = {}): string {
26
- if (iri.startsWith('http'))
27
- return iri;
26
+ if (iri.startsWith('http')) return iri;
28
27
 
29
28
  const [prefix, name] = iri.split(':');
30
29
 
31
30
  if (prefix && name) {
32
31
  const expandedPrefix = knownPrefixes[prefix] ?? options.extraContext?.[prefix] ?? null;
33
32
 
34
- if (!expandedPrefix)
35
- throw new Error(`Can't expand IRI with unknown prefix: '${iri}'`);
33
+ if (!expandedPrefix) throw new Error(`Can't expand IRI with unknown prefix: '${iri}'`);
36
34
 
37
35
  return expandedPrefix + name;
38
36
  }
39
37
 
40
- if (!options.defaultPrefix)
41
- throw new Error(`Can't expand IRI without a default prefix: '${iri}'`);
38
+ if (!options.defaultPrefix) throw new Error(`Can't expand IRI without a default prefix: '${iri}'`);
42
39
 
43
40
  return options.defaultPrefix + prefix;
44
41
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './errors';
2
2
  export * from './helpers';
3
3
  export * from './models';
4
+ export * from './testing/types';
@@ -0,0 +1,35 @@
1
+ import { sparqlEquals, turtleEquals } from '@noeldemartin/solid-utils/testing/helpers';
2
+ import type { EqualityResult } from '@noeldemartin/solid-utils/testing/helpers';
3
+
4
+ export function defineChaiAssertions<T extends Record<string, (this: Chai.AssertionStatic, ...args: any[]) => void>>(
5
+ assertions: T): T {
6
+ return assertions;
7
+ }
8
+
9
+ export default defineChaiAssertions({
10
+ turtle(graph: string): void {
11
+ const self = this as unknown as Chai.AssertionStatic;
12
+ const actual = self._obj as string;
13
+ const assert = self.assert.bind(this);
14
+ const expected = graph;
15
+ const result = turtleEquals(expected, actual);
16
+
17
+ assert(result.success, result.message, '', result.expected, result.actual);
18
+ },
19
+ sparql(query: string): void {
20
+ const self = this as unknown as Chai.AssertionStatic;
21
+ const actual = self._obj as string;
22
+ const assert = self.assert.bind(this);
23
+ const expected = query;
24
+ const result = sparqlEquals(expected, actual);
25
+
26
+ assert(result.success, result.message, '', result.expected, result.actual);
27
+ },
28
+ equalityResult(): void {
29
+ const self = this as unknown as Chai.AssertionStatic;
30
+ const result = self._obj as EqualityResult;
31
+ const assert = self.assert.bind(this);
32
+
33
+ assert(result.success, result.message, '', result.expected, result.actual);
34
+ },
35
+ });
@@ -0,0 +1,19 @@
1
+ import assertions from './assertions';
2
+
3
+ export type ChaiSolidAssertions = {
4
+ [assertion in keyof typeof assertions]: (typeof assertions)[assertion];
5
+ };
6
+
7
+ export function installChaiSolidAssertions(): void {
8
+ (globalThis as { chai?: Chai.ChaiStatic }).chai?.use((_chai) => {
9
+ return Object.entries(assertions).forEach(([name, method]) => _chai.Assertion.addMethod(name, method));
10
+ });
11
+ }
12
+
13
+ declare global {
14
+ // eslint-disable-next-line @typescript-eslint/no-namespace
15
+ namespace Chai {
16
+ interface Assertion extends ChaiSolidAssertions {}
17
+ interface Include extends ChaiSolidAssertions {}
18
+ }
19
+ }
@@ -0,0 +1,187 @@
1
+ import { JSError, arrayRemove, pull, stringMatchAll } from '@noeldemartin/utils';
2
+ import type { Quad, Quad_Object } from '@rdfjs/types';
3
+
4
+ import {
5
+ jsonldToQuads,
6
+ quadToTurtle,
7
+ quadsToTurtle,
8
+ sparqlToQuadsSync,
9
+ turtleToQuadsSync,
10
+ } from '@noeldemartin/solid-utils/helpers/io';
11
+ import type { JsonLD } from '@noeldemartin/solid-utils/helpers/jsonld';
12
+
13
+ let patternsRegExpsIndex: Record<string, RegExp> = {};
14
+ const builtInPatterns: Record<string, string> = {
15
+ '%uuid%': '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
16
+ };
17
+
18
+ class ExpectedQuadAssertionError extends JSError {
19
+
20
+ constructor(public readonly expectedQuad: Quad) {
21
+ super(`Couldn't find the following triple: ${quadToTurtle(expectedQuad)}`);
22
+ }
23
+
24
+ }
25
+
26
+ function assertExpectedQuadsExist(expectedQuads: Quad[], actualQuads: Quad[]): void {
27
+ for (const expectedQuad of expectedQuads) {
28
+ const matchingQuad = actualQuads.find((actualQuad) => quadEquals(expectedQuad, actualQuad));
29
+
30
+ if (!matchingQuad) throw new ExpectedQuadAssertionError(expectedQuad);
31
+
32
+ arrayRemove(actualQuads, matchingQuad);
33
+ }
34
+ }
35
+
36
+ function containsPatterns(value: string): boolean {
37
+ return /\[\[(.*\]\[)?([^\]]+)\]\]/.test(value);
38
+ }
39
+
40
+ function createPatternRegexp(expected: string): RegExp {
41
+ const patternAliases = [];
42
+ const patternMatches = stringMatchAll<4, 1 | 2>(expected, /\[\[((.*?)\]\[)?([^\]]+)\]\]/g);
43
+ const patterns: string[] = [];
44
+ let expectedRegExp = expected;
45
+
46
+ for (const patternMatch of patternMatches) {
47
+ patternMatch[2] && patternAliases.push(patternMatch[2]);
48
+
49
+ patterns.push(patternMatch[3]);
50
+
51
+ expectedRegExp = expectedRegExp.replace(patternMatch[0], `%PATTERN${patterns.length - 1}%`);
52
+ }
53
+
54
+ expectedRegExp = expectedRegExp.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
55
+
56
+ for (const [patternIndex, pattern] of Object.entries(patterns)) {
57
+ expectedRegExp = expectedRegExp.replace(`%PATTERN${patternIndex}%`, builtInPatterns[pattern] ?? pattern);
58
+ }
59
+
60
+ return new RegExp(expectedRegExp);
61
+ }
62
+
63
+ function quadValueEquals(expected: string, actual: string): boolean {
64
+ return containsPatterns(expected)
65
+ ? (patternsRegExpsIndex[expected] ??= createPatternRegexp(expected)).test(actual)
66
+ : expected === actual;
67
+ }
68
+
69
+ function quadObjectEquals(expected: Quad_Object, actual: Quad_Object): boolean {
70
+ if (expected.termType !== actual.termType) return false;
71
+
72
+ if (expected.termType === 'Literal' && actual.termType === 'Literal') {
73
+ if (expected.datatype.value !== actual.datatype.value) return false;
74
+
75
+ if (!containsPatterns(expected.value))
76
+ return expected.datatype.value === 'http://www.w3.org/2001/XMLSchema#dateTime'
77
+ ? new Date(expected.value).getTime() === new Date(actual.value).getTime()
78
+ : expected.value === actual.value;
79
+ }
80
+
81
+ return quadValueEquals(expected.value, actual.value);
82
+ }
83
+
84
+ function quadEquals(expected: Quad, actual: Quad): boolean {
85
+ return (
86
+ quadObjectEquals(expected.object, actual.object) &&
87
+ quadValueEquals(expected.subject.value, actual.subject.value) &&
88
+ quadValueEquals(expected.predicate.value, actual.predicate.value)
89
+ );
90
+ }
91
+
92
+ function resetPatterns(): void {
93
+ patternsRegExpsIndex = {};
94
+ }
95
+
96
+ export interface EqualityResult {
97
+ success: boolean;
98
+ message: string;
99
+ expected: string;
100
+ actual: string;
101
+ }
102
+
103
+ export async function jsonldEquals(expected: JsonLD, actual: JsonLD): Promise<EqualityResult> {
104
+ // TODO catch parsing errors and improve message.
105
+ resetPatterns();
106
+
107
+ const expectedQuads = await jsonldToQuads(expected);
108
+ const actualQuads = await jsonldToQuads(actual);
109
+ const expectedTurtle = quadsToTurtle(expectedQuads);
110
+ const actualTurtle = quadsToTurtle(actualQuads);
111
+ const result = (success: boolean, message: string) => ({
112
+ success,
113
+ message,
114
+ expected: expectedTurtle,
115
+ actual: actualTurtle,
116
+ });
117
+
118
+ if (expectedQuads.length !== actualQuads.length)
119
+ return result(false, `Expected ${expectedQuads.length} triples, found ${actualQuads.length}.`);
120
+
121
+ try {
122
+ assertExpectedQuadsExist(expectedQuads, actualQuads);
123
+ } catch (error) {
124
+ if (!(error instanceof ExpectedQuadAssertionError)) throw error;
125
+
126
+ return result(false, error.message);
127
+ }
128
+
129
+ return result(true, 'jsonld matches');
130
+ }
131
+
132
+ export function sparqlEquals(expected: string, actual: string): EqualityResult {
133
+ // TODO catch parsing errors and improve message.
134
+ resetPatterns();
135
+
136
+ const expectedOperations = sparqlToQuadsSync(expected, { normalizeBlankNodes: true });
137
+ const actualOperations = sparqlToQuadsSync(actual, { normalizeBlankNodes: true });
138
+ const result = (success: boolean, message: string) => ({ success, message, expected, actual });
139
+
140
+ for (const operation of Object.keys(expectedOperations)) {
141
+ if (!(operation in actualOperations)) return result(false, `Couldn't find expected ${operation} operation.`);
142
+
143
+ const expectedQuads = pull(expectedOperations, operation);
144
+ const actualQuads = pull(actualOperations, operation);
145
+
146
+ if (expectedQuads.length !== actualQuads.length)
147
+ return result(false, `Expected ${expectedQuads.length} ${operation} triples, found ${actualQuads.length}.`);
148
+
149
+ try {
150
+ assertExpectedQuadsExist(expectedQuads, actualQuads);
151
+ } catch (error) {
152
+ if (!(error instanceof ExpectedQuadAssertionError)) throw error;
153
+
154
+ return result(
155
+ false,
156
+ `Couldn't find the following ${operation} triple: ${quadToTurtle(error.expectedQuad)}`,
157
+ );
158
+ }
159
+ }
160
+
161
+ const unexpectedOperation = Object.keys(actualOperations)[0] ?? null;
162
+ if (unexpectedOperation) return result(false, `Did not expect to find ${unexpectedOperation} triples.`);
163
+
164
+ return result(true, 'sparql matches');
165
+ }
166
+
167
+ export function turtleEquals(expected: string, actual: string): EqualityResult {
168
+ // TODO catch parsing errors and improve message.
169
+ resetPatterns();
170
+
171
+ const expectedQuads = turtleToQuadsSync(expected, { normalizeBlankNodes: true });
172
+ const actualQuads = turtleToQuadsSync(actual, { normalizeBlankNodes: true });
173
+ const result = (success: boolean, message: string) => ({ success, message, expected, actual });
174
+
175
+ if (expectedQuads.length !== actualQuads.length)
176
+ return result(false, `Expected ${expectedQuads.length} triples, found ${actualQuads.length}.`);
177
+
178
+ try {
179
+ assertExpectedQuadsExist(expectedQuads, actualQuads);
180
+ } catch (error) {
181
+ if (!(error instanceof ExpectedQuadAssertionError)) throw error;
182
+
183
+ return result(false, error.message);
184
+ }
185
+
186
+ return result(true, 'turtle matches');
187
+ }
@@ -0,0 +1,329 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { sparqlEquals, turtleEquals } from './helpers';
4
+
5
+ describe('Testing helpers', () => {
6
+
7
+ it('Compares sparql', () => {
8
+ // Arrange
9
+ const expected = 'INSERT DATA { <#me> a <http://xmlns.com/foaf/0.1/Person> . }';
10
+ const actual = 'INSERT DATA { <#me> a <http://xmlns.com/foaf/0.1/Person> . }';
11
+
12
+ // Act
13
+ const result = sparqlEquals(expected, actual);
14
+
15
+ // Assert
16
+ expect(result.success).toBe(true);
17
+ });
18
+
19
+ it('Compares sparql operations', () => {
20
+ // Arrange
21
+ const expected = `
22
+ INSERT DATA { <#me> <http://xmlns.com/foaf/0.1/name> "Amy Doe" . } ;
23
+ DELETE DATA { <#me> <http://xmlns.com/foaf/0.1/name> "John Doe" . }
24
+ `;
25
+ const actual = 'INSERT DATA { <#me> <http://xmlns.com/foaf/0.1/name> "Amy Doe" . }';
26
+
27
+ // Act
28
+ const result = sparqlEquals(expected, actual);
29
+
30
+ // Assert
31
+ expect(result.success).toBe(false);
32
+ });
33
+
34
+ it('Compares sparql triples', () => {
35
+ // Arrange
36
+ const expected = 'INSERT DATA { <#me> a <http://xmlns.com/foaf/0.1/Person> . }';
37
+ const actual = 'INSERT DATA { <#me> <http://xmlns.com/foaf/0.1/name> "Amy Doe" . }';
38
+
39
+ // Act
40
+ const result = sparqlEquals(expected, actual);
41
+
42
+ // Assert
43
+ expect(result.success).toBe(false);
44
+ });
45
+
46
+ it('Compares expanded ordered lists in Turtle', () => {
47
+ // Arrange
48
+ const expected = `
49
+ @prefix schema: <https://schema.org/> .
50
+
51
+ <#ramen>
52
+ a schema:Recipe ;
53
+ schema:name "Ramen" ;
54
+ schema:recipeIngredient ( "Broth" "Noodles" ) .
55
+ `;
56
+ const actual = `
57
+ @prefix schema: <https://schema.org/> .
58
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
59
+
60
+ <#ramen>
61
+ a schema:Recipe ;
62
+ schema:name "Ramen" ;
63
+ schema:recipeIngredient _:b0 .
64
+
65
+ _:b0
66
+ rdf:first "Broth" ;
67
+ rdf:rest _:b1 .
68
+
69
+ _:b1
70
+ rdf:first "Noodles" ;
71
+ rdf:rest rdf:nil .
72
+ `;
73
+
74
+ // Act
75
+ const result = turtleEquals(expected, actual);
76
+
77
+ // Assert
78
+ expect(result.success).toBe(true);
79
+ });
80
+
81
+ it('Compares different ordered lists in Turtle', () => {
82
+ // Arrange
83
+ const expected = `
84
+ @prefix schema: <https://schema.org/> .
85
+
86
+ <#ramen>
87
+ a schema:Recipe ;
88
+ schema:name "Ramen" ;
89
+ schema:recipeIngredient ( "Broth" "Noodles" ) .
90
+ `;
91
+ const actual = `
92
+ @prefix schema: <https://schema.org/> .
93
+
94
+ <#ramen>
95
+ a schema:Recipe ;
96
+ schema:name "Ramen" ;
97
+ schema:recipeIngredient ( "Noodles" "Broth" ) .
98
+ `;
99
+
100
+ // Act
101
+ const result = turtleEquals(expected, actual);
102
+
103
+ // Assert
104
+ expect(result.success).toBe(false);
105
+ });
106
+
107
+ it('Compares different unordered lists in Turtle', () => {
108
+ // Arrange
109
+ const expected = `
110
+ @prefix schema: <https://schema.org/> .
111
+
112
+ <#ramen>
113
+ a schema:Recipe ;
114
+ schema:name "Ramen" ;
115
+ schema:recipeIngredient "Broth", "Noodles" .
116
+ `;
117
+ const actual = `
118
+ @prefix schema: <https://schema.org/> .
119
+
120
+ <#ramen>
121
+ a schema:Recipe ;
122
+ schema:name "Ramen" ;
123
+ schema:recipeIngredient "Noodles", "Broth" .
124
+ `;
125
+
126
+ // Act
127
+ const result = turtleEquals(expected, actual);
128
+
129
+ // Assert
130
+ expect(result.success).toBe(true);
131
+ });
132
+
133
+ it('Compares sparql using regex patterns', () => {
134
+ // Arrange
135
+ const expected = `
136
+ INSERT DATA {
137
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
138
+ @prefix foaf: <http://xmlns.com/foaf/> .
139
+ @prefix purl: <http://purl.org/dc/terms/> .
140
+
141
+ <#me>
142
+ foaf:name "[[.*]] Doe" ;
143
+ foaf:age 42 .
144
+
145
+ <#something-[[.*]]>
146
+ purl:created "[[.*]]"^^xsd:dateTime ;
147
+ purl:modified "2021-01-16T[[.*]]"^^xsd:dateTime ;
148
+ purl:available "2021-01-16T12:34:56Z"^^xsd:dateTime .
149
+ }
150
+ `;
151
+ const actual = `
152
+ INSERT DATA {
153
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
154
+ @prefix foaf: <http://xmlns.com/foaf/> .
155
+ @prefix purl: <http://purl.org/dc/terms/> .
156
+
157
+ <#me>
158
+ foaf:name "John Doe" ;
159
+ foaf:age 42 .
160
+
161
+ <#something-123456>
162
+ purl:created "2021-01-16T12:12:50.123Z"^^xsd:dateTime ;
163
+ purl:modified "2021-01-16T12:12:50.123Z"^^xsd:dateTime ;
164
+ purl:available "2021-01-16T12:34:56.000Z"^^xsd:dateTime .
165
+ }
166
+ `;
167
+
168
+ // Act
169
+ const result = sparqlEquals(expected, actual);
170
+
171
+ // Assert
172
+ expect(result.success).toBe(true);
173
+ });
174
+
175
+ it('supports aliases in regex patterns', () => {
176
+ // Arrange
177
+ const expected = `
178
+ @prefix schema: <https://schema.org/> .
179
+
180
+ <#ramen>
181
+ a schema:Recipe ;
182
+ schema:name "Ramen" ;
183
+ schema:recipeInstructions <#[[step-1][.*]]>, <#[[step-2][.*]]> .
184
+
185
+ <#[[step-1][.*]]>
186
+ a schema:HowToStep ;
187
+ schema:text "Boil the noodles" .
188
+
189
+ <#[[step-2][.*]]>
190
+ a schema:HowToStep ;
191
+ schema:text "Dip them into the broth" .
192
+ `;
193
+ const actual = `
194
+ @prefix schema: <https://schema.org/> .
195
+
196
+ <#ramen>
197
+ a schema:Recipe ;
198
+ schema:name "Ramen" ;
199
+ schema:recipeInstructions <#ramen-step-1>, <#ramen-step-2> .
200
+
201
+ <#ramen-step-1>
202
+ a schema:HowToStep ;
203
+ schema:text "Boil the noodles" .
204
+
205
+ <#ramen-step-2>
206
+ a schema:HowToStep ;
207
+ schema:text "Dip them into the broth" .
208
+ `;
209
+
210
+ // Act
211
+ const result = turtleEquals(expected, actual);
212
+
213
+ // Assert
214
+ expect(result.success).toBe(true);
215
+ });
216
+
217
+ it('counts matching triples only once', () => {
218
+ // Arrange
219
+ const expected = `
220
+ @prefix schema: <https://schema.org/> .
221
+
222
+ <#ramen>
223
+ a schema:Recipe ;
224
+ schema:name "Ramen", "Ramen" .
225
+ `;
226
+ const actual = `
227
+ @prefix schema: <https://schema.org/> .
228
+
229
+ <#ramen>
230
+ a schema:Recipe ;
231
+ schema:name "Ramen" ;
232
+ schema:description "is life" .
233
+ `;
234
+
235
+ // Act
236
+ const result = turtleEquals(expected, actual);
237
+
238
+ // Assert
239
+ expect(result.success).toBe(false);
240
+ });
241
+
242
+ it('allows regex patterns to be mixed up', () => {
243
+ // Arrange
244
+ const expected = `
245
+ @prefix schema: <https://schema.org/> .
246
+
247
+ <#[[instruction-1][.*]]-operation-[[operation-1][.*]]> schema:object <#[[instruction-1][.*]]> .
248
+ <#[[instruction-1][.*]]-metadata> schema:object <#[[instruction-1][.*]]> .
249
+ `;
250
+ const actual = `
251
+ @prefix schema: <https://schema.org/> .
252
+
253
+ <#ramen-step-1-metadata> schema:object <#ramen> .
254
+ <#ramen-step-1-operation-1> schema:object <#ramen> .
255
+ `;
256
+
257
+ // Act
258
+ const result = turtleEquals(expected, actual);
259
+
260
+ // Assert
261
+ expect(result.success).toBe(true);
262
+ });
263
+
264
+ it('matches built-in patterns', () => {
265
+ // Arrange
266
+ const expected = `
267
+ @prefix schema: <https://schema.org/> .
268
+
269
+ <#[[foobar][%uuid%]]> schema:description "Lorem ipsum" .
270
+ <#[[%uuid%]]> schema:description "Dolor sit amet" .
271
+ `;
272
+ const actual = `
273
+ @prefix schema: <https://schema.org/> .
274
+
275
+ <#20421db7-0c7d-419c-b27e-2c9b3cc026b3> schema:description "Lorem ipsum" .
276
+ <#d4b41533-dd5d-4a66-9d3f-316f80f135b2> schema:description "Dolor sit amet" .
277
+ `;
278
+
279
+ // Act
280
+ const result = turtleEquals(expected, actual);
281
+
282
+ // Assert
283
+ expect(result.success).toBe(true);
284
+ });
285
+
286
+ // TODO
287
+ it.skip('aliases match regex patterns', () => {
288
+ // Arrange
289
+ const expected = `
290
+ @prefix schema: <https://schema.org/> .
291
+
292
+ <#ramen>
293
+ a schema:Recipe ;
294
+ schema:name "Ramen" ;
295
+ schema:recipeInstructions <#[[step-1][.*]]>, <#[[step-2][.*]]> .
296
+
297
+ <#[[step-1][.*]]>
298
+ a schema:HowToStep ;
299
+ schema:text "Boil the noodles" .
300
+
301
+ <#[[step-2][.*]]>
302
+ a schema:HowToStep ;
303
+ schema:text "Dip them into the broth" .
304
+ `;
305
+ const actual = `
306
+ @prefix schema: <https://schema.org/> .
307
+
308
+ <#ramen>
309
+ a schema:Recipe ;
310
+ schema:name "Ramen" ;
311
+ schema:recipeInstructions <#ramen-step-1>, <#ramen-step-2> .
312
+
313
+ <#ramen-step-1>
314
+ a schema:HowToStep ;
315
+ schema:text "Boil the noodles" .
316
+
317
+ <#ramen-step-3>
318
+ a schema:HowToStep ;
319
+ schema:text "Dip them into the broth" .
320
+ `;
321
+
322
+ // Act
323
+ const result = turtleEquals(expected, actual);
324
+
325
+ // Assert
326
+ expect(result.success).toBe(false);
327
+ });
328
+
329
+ });
@@ -0,0 +1,3 @@
1
+ export * from './vitest';
2
+ export * from './helpers';
3
+ export * from './chai';
@@ -0,0 +1,2 @@
1
+ export type { ChaiSolidAssertions } from '@noeldemartin/solid-utils/testing/chai';
2
+ export type { VitestSolidMatchers } from '@noeldemartin/solid-utils/testing/vitest';