@noeldemartin/solid-utils 0.6.0-next.8a6a0b585adc7a1c329fb0fed5c420bb72cdccda → 0.6.0-next.95fe731be0689c25d9040cc1411e27c49f69901d
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/dist/io-CMHtz5bu.js +401 -0
- package/dist/io-CMHtz5bu.js.map +1 -0
- package/dist/noeldemartin-solid-utils.js +165 -531
- package/dist/noeldemartin-solid-utils.js.map +1 -1
- package/dist/testing.d.ts +47 -0
- package/dist/testing.js +176 -0
- package/dist/testing.js.map +1 -0
- package/package.json +13 -9
- package/src/errors/UnauthorizedError.ts +1 -3
- package/src/errors/UnsuccessfulNetworkRequestError.ts +2 -2
- package/src/helpers/identifiers.ts +13 -16
- package/src/helpers/io.ts +1 -1
- package/src/helpers/jsonld.ts +1 -1
- package/src/helpers/vocabs.ts +3 -6
- package/src/testing/chai/assertions.ts +44 -0
- package/src/testing/chai/index.ts +7 -0
- package/src/testing/helpers.ts +187 -0
- package/src/testing/hepers.test.ts +329 -0
- package/src/testing/index.ts +3 -0
- package/src/testing/vitest/index.ts +15 -0
- package/src/testing/vitest/matchers.ts +68 -0
- package/src/types/n3.d.ts +0 -2
package/src/helpers/vocabs.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { sparqlEquals, turtleEquals } from '@noeldemartin/solid-utils/testing/helpers';
|
|
2
|
+
import type { EqualityResult } from '@noeldemartin/solid-utils/testing/helpers';
|
|
3
|
+
|
|
4
|
+
type CustomAssertions = {
|
|
5
|
+
[assertion in keyof typeof assertions]: (typeof assertions)[assertion];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
|
+
namespace Chai {
|
|
11
|
+
interface Assertion extends CustomAssertions {}
|
|
12
|
+
interface Include extends CustomAssertions {}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const assertions: Record<string, (this: Chai.AssertionStatic, ...args: any[]) => void> = {
|
|
17
|
+
turtle(graph: string): void {
|
|
18
|
+
const self = this as unknown as Chai.AssertionStatic;
|
|
19
|
+
const actual = self._obj as string;
|
|
20
|
+
const assert = self.assert.bind(this);
|
|
21
|
+
const expected = graph;
|
|
22
|
+
const result = turtleEquals(expected, actual);
|
|
23
|
+
|
|
24
|
+
assert(result.success, result.message, '', result.expected, result.actual);
|
|
25
|
+
},
|
|
26
|
+
sparql(query: string): void {
|
|
27
|
+
const self = this as unknown as Chai.AssertionStatic;
|
|
28
|
+
const actual = self._obj as string;
|
|
29
|
+
const assert = self.assert.bind(this);
|
|
30
|
+
const expected = query;
|
|
31
|
+
const result = sparqlEquals(expected, actual);
|
|
32
|
+
|
|
33
|
+
assert(result.success, result.message, '', result.expected, result.actual);
|
|
34
|
+
},
|
|
35
|
+
equalityResult(): void {
|
|
36
|
+
const self = this as unknown as Chai.AssertionStatic;
|
|
37
|
+
const result = self._obj as EqualityResult;
|
|
38
|
+
const assert = self.assert.bind(this);
|
|
39
|
+
|
|
40
|
+
assert(result.success, result.message, '', result.expected, result.actual);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default assertions;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import assertions from './assertions';
|
|
2
|
+
|
|
3
|
+
export function installChaiPlugin(): void {
|
|
4
|
+
(globalThis as { chai?: Chai.ChaiStatic }).chai?.use((_chai) => {
|
|
5
|
+
return Object.entries(assertions).forEach(([name, method]) => _chai.Assertion.addMethod(name, method));
|
|
6
|
+
});
|
|
7
|
+
}
|
|
@@ -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,15 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import matchers from './matchers';
|
|
4
|
+
|
|
5
|
+
export type VitestSolidMatchers<R = unknown> = {
|
|
6
|
+
[K in keyof typeof matchers]: (
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
...args: Parameters<(typeof matchers)[K]> extends [any, ...infer Rest] ? Rest : never
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
) => ReturnType<(typeof matchers)[K]> extends Promise<any> ? Promise<R> : R;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function installVitestSolidMatchers(): void {
|
|
14
|
+
expect.extend(matchers);
|
|
15
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { MatcherState, MatchersObject } from '@vitest/expect';
|
|
2
|
+
|
|
3
|
+
import { normalizeSparql, normalizeTurtle } from '@noeldemartin/solid-utils/helpers/io';
|
|
4
|
+
import { jsonldEquals, sparqlEquals, turtleEquals } from '@noeldemartin/solid-utils/testing/helpers';
|
|
5
|
+
import type { EqualityResult } from '@noeldemartin/solid-utils/testing/helpers';
|
|
6
|
+
import type { JsonLD } from '@noeldemartin/solid-utils/helpers';
|
|
7
|
+
|
|
8
|
+
interface FormatResultOptions {
|
|
9
|
+
state: MatcherState;
|
|
10
|
+
hint: string;
|
|
11
|
+
expected: unknown;
|
|
12
|
+
received: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatResult(result: EqualityResult, options: FormatResultOptions) {
|
|
16
|
+
const pass = result.success;
|
|
17
|
+
const utils = options.state.utils;
|
|
18
|
+
const message = pass
|
|
19
|
+
? () => [result.message, utils.matcherHint(options.hint)].join('\n\n')
|
|
20
|
+
: () =>
|
|
21
|
+
[
|
|
22
|
+
result.message,
|
|
23
|
+
utils.matcherHint(options.hint),
|
|
24
|
+
[
|
|
25
|
+
`Expected: not ${utils.printExpected(options.expected)}`,
|
|
26
|
+
`Received: ${utils.printReceived(options.received)}`,
|
|
27
|
+
].join('\n'),
|
|
28
|
+
].join('\n\n');
|
|
29
|
+
|
|
30
|
+
return { pass, message };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function defineMatchers<T extends MatchersObject>(matchers: T): T {
|
|
34
|
+
return matchers;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default defineMatchers({
|
|
38
|
+
async toEqualJsonLD(received, expected: JsonLD) {
|
|
39
|
+
const result = await jsonldEquals(expected, received);
|
|
40
|
+
|
|
41
|
+
return formatResult(result, {
|
|
42
|
+
state: this,
|
|
43
|
+
hint: 'toEqualJsonLD',
|
|
44
|
+
expected,
|
|
45
|
+
received,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
toEqualSparql(received, expected: string) {
|
|
49
|
+
const result = sparqlEquals(expected, received);
|
|
50
|
+
|
|
51
|
+
return formatResult(result, {
|
|
52
|
+
state: this,
|
|
53
|
+
hint: 'toEqualSparql',
|
|
54
|
+
expected: normalizeSparql(expected),
|
|
55
|
+
received: normalizeSparql(received),
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
toEqualTurtle(received, expected: string) {
|
|
59
|
+
const result = turtleEquals(expected, received);
|
|
60
|
+
|
|
61
|
+
return formatResult(result, {
|
|
62
|
+
state: this,
|
|
63
|
+
hint: 'toEqualTurtle',
|
|
64
|
+
expected: normalizeTurtle(expected),
|
|
65
|
+
received: normalizeTurtle(received),
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
});
|