@noeldemartin/solid-utils 0.0.0-next.f9716490938d6dbabb0920bc834315c53ccd2505 → 0.1.1-next.1f0cf6ccc237588ae655211348e94eba9ba16c8d

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.
@@ -1,25 +1,54 @@
1
- import { pull } from '@noeldemartin/utils';
1
+ import { Error, arrayRemove, pull } from '@noeldemartin/utils';
2
+ import type { JsonLD } from '@/helpers/jsonld';
2
3
  import type { Quad, Quad_Object } from 'rdf-js';
3
4
 
4
- import { quadToTurtle, sparqlToQuadsSync, turtleToQuadsSync } from './io';
5
+ import { jsonldToQuads, quadToTurtle, quadsToTurtle, sparqlToQuadsSync, turtleToQuadsSync } from './io';
5
6
 
6
- const patternRegExps: Record<string, RegExp> = {};
7
+ let patternsRegExpsIndex: Record<string, RegExp> = {};
8
+ const builtInPatterns: Record<string, string> = {
9
+ '%uuid%': '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
10
+ };
11
+
12
+ class ExpectedQuadAssertionError extends Error {
13
+
14
+ constructor(public readonly expectedQuad: Quad) {
15
+ super(`Couldn't find the following triple: ${quadToTurtle(expectedQuad)}`);
16
+ }
17
+
18
+ }
19
+
20
+ function assertExpectedQuadsExist(expectedQuads: Quad[], actualQuads: Quad[]): void {
21
+ for (const expectedQuad of expectedQuads) {
22
+ const matchingQuad = actualQuads.find(actualQuad => quadEquals(expectedQuad, actualQuad));
23
+
24
+ if (!matchingQuad)
25
+ throw new ExpectedQuadAssertionError(expectedQuad);
26
+
27
+ arrayRemove(actualQuads, matchingQuad);
28
+ }
29
+ }
7
30
 
8
31
  function containsPatterns(value: string): boolean {
9
- return /\[\[([^\]]+)\]\]/.test(value);
32
+ return /\[\[(.*\]\[)?([^\]]+)\]\]/.test(value);
10
33
  }
11
34
 
12
35
  function quadValueEquals(expected: string, actual: string): boolean {
13
36
  if (!containsPatterns(expected))
14
37
  return expected === actual;
15
38
 
16
- if (!(expected in patternRegExps)) {
17
- const patternMatches = expected.matchAll(/\[\[([^\]]+)\]\]/g);
39
+ const patternAliases = [];
40
+
41
+ if (!(expected in patternsRegExpsIndex)) {
42
+ const patternMatches = expected.matchAll(/\[\[((.*?)\]\[)?([^\]]+)\]\]/g);
18
43
  const patterns: string[] = [];
19
44
  let expectedRegExp = expected;
20
45
 
21
46
  for (const patternMatch of patternMatches) {
22
- patterns.push(patternMatch[1]);
47
+ if (patternMatch[2]) {
48
+ patternAliases.push(patternMatch[2]);
49
+ }
50
+
51
+ patterns.push(patternMatch[3]);
23
52
 
24
53
  expectedRegExp = expectedRegExp.replace(patternMatch[0], `%PATTERN${patterns.length - 1}%`);
25
54
  }
@@ -27,13 +56,13 @@ function quadValueEquals(expected: string, actual: string): boolean {
27
56
  expectedRegExp = expectedRegExp.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
28
57
 
29
58
  for (const [patternIndex, pattern] of Object.entries(patterns)) {
30
- expectedRegExp = expectedRegExp.replace(`%PATTERN${patternIndex}%`, pattern);
59
+ expectedRegExp = expectedRegExp.replace(`%PATTERN${patternIndex}%`, builtInPatterns[pattern] ?? pattern);
31
60
  }
32
61
 
33
- patternRegExps[expected] = new RegExp(expectedRegExp);
62
+ patternsRegExpsIndex[expected] = new RegExp(expectedRegExp);
34
63
  }
35
64
 
36
- return patternRegExps[expected].test(actual);
65
+ return patternsRegExpsIndex[expected].test(actual);
37
66
  }
38
67
 
39
68
  function quadObjectEquals(expected: Quad_Object, actual: Quad_Object): boolean {
@@ -59,6 +88,10 @@ function quadEquals(expected: Quad, actual: Quad): boolean {
59
88
  && quadValueEquals(expected.predicate.value, actual.predicate.value);
60
89
  }
61
90
 
91
+ function resetPatterns(): void {
92
+ patternsRegExpsIndex = {};
93
+ }
94
+
62
95
  export interface EqualityResult {
63
96
  success: boolean;
64
97
  message: string;
@@ -66,8 +99,39 @@ export interface EqualityResult {
66
99
  actual: string;
67
100
  }
68
101
 
102
+ export async function jsonldEquals(expected: JsonLD, actual: JsonLD): Promise<EqualityResult> {
103
+ // TODO catch parsing errors and improve message.
104
+ resetPatterns();
105
+
106
+ const expectedQuads = await jsonldToQuads(expected);
107
+ const actualQuads = await jsonldToQuads(actual);
108
+ const expectedTurtle = quadsToTurtle(expectedQuads);
109
+ const actualTurtle = quadsToTurtle(actualQuads);
110
+ const result = (success: boolean, message: string) => ({
111
+ success,
112
+ message,
113
+ expected: expectedTurtle,
114
+ actual: actualTurtle,
115
+ });
116
+
117
+ if (expectedQuads.length !== actualQuads.length)
118
+ return result(false, `Expected ${expectedQuads.length} triples, found ${actualQuads.length}.`);
119
+
120
+ try {
121
+ assertExpectedQuadsExist(expectedQuads, actualQuads);
122
+ } catch (error) {
123
+ if (!(error instanceof ExpectedQuadAssertionError))
124
+ throw error;
125
+
126
+ return result(false, error.message);
127
+ }
128
+
129
+ return result(true, 'jsonld matches');
130
+ }
131
+
69
132
  export function sparqlEquals(expected: string, actual: string): EqualityResult {
70
133
  // TODO catch parsing errors and improve message.
134
+ resetPatterns();
71
135
 
72
136
  const expectedOperations = sparqlToQuadsSync(expected, { normalizeBlankNodes: true });
73
137
  const actualOperations = sparqlToQuadsSync(actual, { normalizeBlankNodes: true });
@@ -83,9 +147,16 @@ export function sparqlEquals(expected: string, actual: string): EqualityResult {
83
147
  if (expectedQuads.length !== actualQuads.length)
84
148
  return result(false, `Expected ${expectedQuads.length} ${operation} triples, found ${actualQuads.length}.`);
85
149
 
86
- for (const expectedQuad of expectedQuads) {
87
- if (!actualQuads.some(actualQuad => quadEquals(expectedQuad, actualQuad)))
88
- return result(false, `Couldn't find the following ${operation} triple: ${quadToTurtle(expectedQuad)}`);
150
+ try {
151
+ assertExpectedQuadsExist(expectedQuads, actualQuads);
152
+ } catch (error) {
153
+ if (!(error instanceof ExpectedQuadAssertionError))
154
+ throw error;
155
+
156
+ return result(
157
+ false,
158
+ `Couldn't find the following ${operation} triple: ${quadToTurtle(error.expectedQuad)}`,
159
+ );
89
160
  }
90
161
  }
91
162
 
@@ -98,6 +169,7 @@ export function sparqlEquals(expected: string, actual: string): EqualityResult {
98
169
 
99
170
  export function turtleEquals(expected: string, actual: string): EqualityResult {
100
171
  // TODO catch parsing errors and improve message.
172
+ resetPatterns();
101
173
 
102
174
  const expectedQuads = turtleToQuadsSync(expected, { normalizeBlankNodes: true });
103
175
  const actualQuads = turtleToQuadsSync(actual, { normalizeBlankNodes: true });
@@ -106,9 +178,13 @@ export function turtleEquals(expected: string, actual: string): EqualityResult {
106
178
  if (expectedQuads.length !== actualQuads.length)
107
179
  return result(false, `Expected ${expectedQuads.length} triples, found ${actualQuads.length}.`);
108
180
 
109
- for (const expectedQuad of expectedQuads) {
110
- if (!actualQuads.some(actualQuad => quadEquals(expectedQuad, actualQuad)))
111
- return result(false, `Couldn't find the following triple: ${quadToTurtle(expectedQuad)}`);
181
+ try {
182
+ assertExpectedQuadsExist(expectedQuads, actualQuads);
183
+ } catch (error) {
184
+ if (!(error instanceof ExpectedQuadAssertionError))
185
+ throw error;
186
+
187
+ return result(false, error.message);
112
188
  }
113
189
 
114
190
  return result(true, 'turtle matches');
@@ -1,4 +1,4 @@
1
- import { sparqlEquals } from '@/helpers/testing';
1
+ import { sparqlEquals, turtleEquals } from '@/helpers/testing';
2
2
 
3
3
  type CustomAssertions = {
4
4
  [assertion in keyof typeof assertions]: typeof assertions[assertion];
@@ -17,6 +17,15 @@ declare global {
17
17
  }
18
18
 
19
19
  const assertions: Record<string, (this: Chai.AssertionStatic, ...args: any[]) => void> = {
20
+ turtle(graph: string): void {
21
+ const self = this as unknown as Chai.AssertionStatic;
22
+ const actual = self._obj;
23
+ const assert = self.assert.bind(this);
24
+ const expected = graph;
25
+ const result = turtleEquals(expected, actual);
26
+
27
+ assert(result.success, result.message, '', result.expected, result.actual);
28
+ },
20
29
  sparql(query: string): void {
21
30
  const self = this as unknown as Chai.AssertionStatic;
22
31
  const actual = self._obj;
@@ -24,7 +33,7 @@ const assertions: Record<string, (this: Chai.AssertionStatic, ...args: any[]) =>
24
33
  const expected = query;
25
34
  const result = sparqlEquals(expected, actual);
26
35
 
27
- assert(result.success, result.message, expected, actual);
36
+ assert(result.success, result.message, '', result.expected, result.actual);
28
37
  },
29
38
  };
30
39
 
@@ -7,6 +7,7 @@ declare global {
7
7
  // TODO generate automatically
8
8
  interface Chainer<Subject> {
9
9
  (chainer: 'be.sparql', update: string): Cypress.Chainable<Subject>;
10
+ (chainer: 'be.turtle', graph: string): Cypress.Chainable<Subject>;
10
11
  }
11
12
 
12
13
  }
@@ -1,41 +1,56 @@
1
- import diff from 'jest-diff';
2
-
3
1
  import { normalizeSparql } from '@/helpers/io';
4
- import { sparqlEquals } from '@/helpers/testing';
2
+ import { jsonldEquals, sparqlEquals } from '@/helpers/testing';
3
+ import type { EqualityResult } from '@/helpers/testing';
4
+
5
+ interface FormatResultOptions {
6
+ context: jest.MatcherContext;
7
+ hint: string;
8
+ expected: unknown;
9
+ received: unknown;
10
+ }
11
+
12
+ function formatResult(result: EqualityResult, options: FormatResultOptions) {
13
+ const pass = result.success;
14
+ const utils = options.context.utils;
15
+ const message = pass
16
+ ? () => [
17
+ result.message,
18
+ utils.matcherHint(options.hint),
19
+ [
20
+ `Expected: not ${utils.printExpected(options.expected)}`,
21
+ `Received: ${utils.printReceived(options.received)}`,
22
+ ].join('\n'),
23
+ ].join('\n\n')
24
+ : () => {
25
+ return [
26
+ result.message,
27
+ utils.matcherHint(options.hint),
28
+ ].join('\n\n');
29
+ };
30
+
31
+ return { pass, message };
32
+ }
5
33
 
6
34
  const matchers: jest.ExpectExtendMap = {
35
+ async toEqualJsonLD(received, expected) {
36
+ const result = await jsonldEquals(expected, received);
37
+
38
+ return formatResult(result, {
39
+ context: this,
40
+ hint: 'toEqualJsonLD',
41
+ expected,
42
+ received,
43
+ });
44
+ },
7
45
  toEqualSparql(received, expected) {
8
46
  const result = sparqlEquals(expected, received);
9
- const pass = result.success;
10
- const normalizedReceived = normalizeSparql(received);
11
- const normalizedExpected = normalizeSparql(expected);
12
- const message = pass
13
- ? () => [
14
- result.message,
15
- this.utils.matcherHint('toEqualSparql'),
16
- [
17
- `Expected: not ${this.utils.printExpected(normalizedExpected)}`,
18
- `Received: ${this.utils.printReceived(normalizedReceived)}`,
19
- ].join('\n'),
20
- ].join('\n\n')
21
- : () => {
22
- const diffString = diff(normalizedExpected, normalizedReceived, {
23
- expand: this.expand,
24
- });
25
-
26
- return [
27
- result.message,
28
- this.utils.matcherHint('toEqualJsonLD'),
29
- diffString && diffString.includes('- Expect')
30
- ? `Difference:\n\n${diffString}`
31
- : [
32
- `Expected: ${this.utils.printExpected(normalizedExpected)}`,
33
- `Received: ${this.utils.printReceived(normalizedReceived)}`,
34
- ].join('\n'),
35
- ].join('\n\n');
36
- };
37
47
 
38
- return { pass, message };
48
+ return formatResult(result, {
49
+ context: this,
50
+ hint: 'toEqualSparql',
51
+ expected: normalizeSparql(expected),
52
+ received: normalizeSparql(received),
53
+ });
39
54
  },
40
55
  };
41
56
 
@@ -5,6 +5,7 @@ declare global {
5
5
  // TODO generate automatically
6
6
  interface Matchers<R> {
7
7
  toEqualSparql(sparql: string): R;
8
+ toEqualJsonLD(jsonld: JsonLD): Promise<R>;
8
9
  }
9
10
 
10
11
  }