@noeldemartin/solid-utils 0.5.0 → 0.6.0-next.70fba2db4562da63a3ae210c80936d148acfc0de

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.
Files changed (55) hide show
  1. package/dist/io-CMHtz5bu.js +401 -0
  2. package/dist/io-CMHtz5bu.js.map +1 -0
  3. package/dist/noeldemartin-solid-utils.d.ts +13 -65
  4. package/dist/noeldemartin-solid-utils.js +224 -0
  5. package/dist/noeldemartin-solid-utils.js.map +1 -0
  6. package/dist/testing.d.ts +45 -0
  7. package/dist/testing.js +157 -0
  8. package/dist/testing.js.map +1 -0
  9. package/package.json +58 -63
  10. package/src/errors/UnauthorizedError.ts +1 -3
  11. package/src/errors/UnsuccessfulNetworkRequestError.ts +2 -2
  12. package/src/helpers/auth.test.ts +221 -0
  13. package/src/helpers/auth.ts +28 -27
  14. package/src/helpers/identifiers.test.ts +76 -0
  15. package/src/helpers/identifiers.ts +14 -17
  16. package/src/helpers/index.ts +0 -1
  17. package/src/helpers/interop.ts +23 -16
  18. package/src/helpers/io.test.ts +228 -0
  19. package/src/helpers/io.ts +57 -77
  20. package/src/helpers/jsonld.ts +6 -6
  21. package/src/helpers/vocabs.ts +3 -6
  22. package/src/helpers/wac.test.ts +64 -0
  23. package/src/helpers/wac.ts +10 -6
  24. package/src/index.ts +3 -0
  25. package/src/models/SolidDocument.test.ts +77 -0
  26. package/src/models/SolidDocument.ts +14 -18
  27. package/src/models/SolidStore.ts +22 -12
  28. package/src/models/SolidThing.ts +5 -7
  29. package/src/models/index.ts +2 -0
  30. package/src/{helpers/testing.ts → testing/helpers.ts} +24 -27
  31. package/src/testing/hepers.test.ts +329 -0
  32. package/src/testing/index.ts +2 -2
  33. package/src/testing/vitest/index.ts +15 -0
  34. package/src/testing/vitest/matchers.ts +68 -0
  35. package/src/types/n3.d.ts +0 -2
  36. package/.github/workflows/ci.yml +0 -16
  37. package/.nvmrc +0 -1
  38. package/CHANGELOG.md +0 -70
  39. package/dist/noeldemartin-solid-utils.cjs.js +0 -2
  40. package/dist/noeldemartin-solid-utils.cjs.js.map +0 -1
  41. package/dist/noeldemartin-solid-utils.esm.js +0 -2
  42. package/dist/noeldemartin-solid-utils.esm.js.map +0 -1
  43. package/dist/noeldemartin-solid-utils.umd.js +0 -90
  44. package/dist/noeldemartin-solid-utils.umd.js.map +0 -1
  45. package/noeldemartin.config.js +0 -9
  46. package/src/main.ts +0 -5
  47. package/src/plugins/chai/assertions.ts +0 -40
  48. package/src/plugins/chai/index.ts +0 -5
  49. package/src/plugins/cypress/types.d.ts +0 -15
  50. package/src/plugins/index.ts +0 -2
  51. package/src/plugins/jest/index.ts +0 -5
  52. package/src/plugins/jest/matchers.ts +0 -65
  53. package/src/plugins/jest/types.d.ts +0 -14
  54. package/src/testing/ResponseStub.ts +0 -46
  55. package/src/testing/mocking.ts +0 -33
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import SolidDocument from '@noeldemartin/solid-utils/models/SolidDocument';
4
+ import { turtleToQuadsSync } from '@noeldemartin/solid-utils/helpers';
5
+
6
+ describe('SolidDocument', () => {
7
+
8
+ it('Identifies storage documents', () => {
9
+ const hasStorageHeader = (link: string) => {
10
+ const document = new SolidDocument('', [], new Headers({ Link: link }));
11
+
12
+ return document.isStorage();
13
+ };
14
+
15
+ /* eslint-disable max-len */
16
+ expect(hasStorageHeader('')).toBe(false);
17
+ expect(hasStorageHeader('<http://www.w3.org/ns/pim/space#Storage>; rel="type"')).toBe(true);
18
+ expect(hasStorageHeader('<http://www.w3.org/ns/pim/space#Storage>; rel="something-else"; rel="type"')).toBe(
19
+ true,
20
+ );
21
+ expect(
22
+ hasStorageHeader(
23
+ '<http://www.w3.org/ns/pim/space#Storage>; rel="something-else", <http://example.com>; rel="type"',
24
+ ),
25
+ ).toBe(false);
26
+ /* eslint-enable max-len */
27
+ });
28
+
29
+ it('Parses last modified from header', () => {
30
+ const document = new SolidDocument('', [], new Headers({ 'Last-Modified': 'Fri, 03 Sept 2021 16:09:12 GMT' }));
31
+
32
+ expect(document.getLastModified()).toEqual(new Date(1630685352000));
33
+ });
34
+
35
+ it('Parses last modified from document purl:modified', () => {
36
+ const document = new SolidDocument(
37
+ 'https://pod.example.org/my-document',
38
+ turtleToQuadsSync(
39
+ `
40
+ <./fallback>
41
+ <http://purl.org/dc/terms/modified>
42
+ "2021-09-03T16:23:25.000Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
43
+
44
+ <>
45
+ <http://purl.org/dc/terms/modified>
46
+ "2021-09-03T16:09:12.000Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
47
+ `,
48
+ { baseIRI: 'https://pod.example.org/my-document' },
49
+ ),
50
+ new Headers({ 'Last-Modified': 'invalid date' }),
51
+ );
52
+
53
+ expect(document.getLastModified()).toEqual(new Date(1630685352000));
54
+ });
55
+
56
+ it('Parses last modified from any purl date', () => {
57
+ const document = new SolidDocument(
58
+ 'https://pod.example.org/my-document',
59
+ turtleToQuadsSync(
60
+ `
61
+ <./fallback-one>
62
+ <http://purl.org/dc/terms/modified>
63
+ "2021-05-03T16:09:12.000Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
64
+
65
+ <./fallback-two>
66
+ <http://purl.org/dc/terms/created>
67
+ "2021-09-03T16:09:12.000Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
68
+ `,
69
+ { baseIRI: 'https://pod.example.org/my-document' },
70
+ ),
71
+ new Headers({ 'Last-Modified': 'invalid date' }),
72
+ );
73
+
74
+ expect(document.getLastModified()).toEqual(new Date(1630685352000));
75
+ });
76
+
77
+ });
@@ -1,7 +1,7 @@
1
1
  import { arrayFilter, parseDate, stringMatch } from '@noeldemartin/utils';
2
- import type { Quad } from 'rdf-js';
2
+ import type { Quad } from '@rdfjs/types';
3
3
 
4
- import { expandIRI } from '@/helpers/vocabs';
4
+ import { expandIRI } from '@noeldemartin/solid-utils/helpers/vocabs';
5
5
 
6
6
  import SolidStore from './SolidStore';
7
7
 
@@ -25,16 +25,13 @@ export default class SolidDocument extends SolidStore {
25
25
  }
26
26
 
27
27
  public isACPResource(): boolean {
28
- return !!this.headers.get('Link')
28
+ return !!this.headers
29
+ .get('Link')
29
30
  ?.match(/<http:\/\/www\.w3\.org\/ns\/solid\/acp#AccessControlResource>;[^,]+rel="type"/);
30
31
  }
31
32
 
32
33
  public isPersonalProfile(): boolean {
33
- return !!this.statement(
34
- this.url,
35
- expandIRI('rdf:type'),
36
- expandIRI('foaf:PersonalProfileDocument'),
37
- );
34
+ return !!this.statement(this.url, expandIRI('rdf:type'), expandIRI('foaf:PersonalProfileDocument'));
38
35
  }
39
36
 
40
37
  public isStorage(): boolean {
@@ -54,10 +51,12 @@ export default class SolidDocument extends SolidStore {
54
51
  }
55
52
 
56
53
  public getLastModified(): Date | null {
57
- return parseDate(this.headers.get('last-modified'))
58
- ?? parseDate(this.statement(this.url, 'purl:modified')?.object.value)
59
- ?? this.getLatestDocumentDate()
60
- ?? null;
54
+ return (
55
+ parseDate(this.headers.get('last-modified')) ??
56
+ parseDate(this.statement(this.url, 'purl:modified')?.object.value) ??
57
+ this.getLatestDocumentDate() ??
58
+ null
59
+ );
61
60
  }
62
61
 
63
62
  protected expandIRI(iri: string): string {
@@ -65,14 +64,11 @@ export default class SolidDocument extends SolidStore {
65
64
  }
66
65
 
67
66
  private getLatestDocumentDate(): Date | null {
68
- const dates = [
69
- ...this.statements(undefined, 'purl:modified'),
70
- ...this.statements(undefined, 'purl:created'),
71
- ]
72
- .map(statement => parseDate(statement.object.value))
67
+ const dates = [...this.statements(undefined, 'purl:modified'), ...this.statements(undefined, 'purl:created')]
68
+ .map((statement) => parseDate(statement.object.value))
73
69
  .filter((date): date is Date => date !== null);
74
70
 
75
- return dates.length > 0 ? dates.reduce((a, b) => a > b ? a : b) : null;
71
+ return dates.length > 0 ? dates.reduce((a, b) => (a > b ? a : b)) : null;
76
72
  }
77
73
 
78
74
  private getPermissionsFromWAC(type: string): SolidDocumentPermission[] {
@@ -1,9 +1,11 @@
1
- import type { Quad } from 'rdf-js';
1
+ import type { BlankNode, Literal, NamedNode, Quad, Variable } from '@rdfjs/types';
2
2
 
3
- import { expandIRI } from '@/helpers/vocabs';
3
+ import { expandIRI } from '@noeldemartin/solid-utils/helpers/vocabs';
4
4
 
5
5
  import SolidThing from './SolidThing';
6
6
 
7
+ export type Term = NamedNode | Literal | BlankNode | Quad | Variable;
8
+
7
9
  export default class SolidStore {
8
10
 
9
11
  private quads: Quad[];
@@ -24,21 +26,21 @@ export default class SolidStore {
24
26
  this.quads.push(...quads);
25
27
  }
26
28
 
27
- public statements(subject?: string, predicate?: string, object?: string): Quad[] {
29
+ public statements(subject?: Term | string, predicate?: Term | string, object?: Term | string): Quad[] {
28
30
  return this.quads.filter(
29
- statement =>
30
- (!object || statement.object.value === this.expandIRI(object)) &&
31
- (!subject || statement.subject.value === this.expandIRI(subject)) &&
32
- (!predicate || statement.predicate.value === this.expandIRI(predicate)),
31
+ (statement) =>
32
+ (!object || this.termMatches(statement.object, object)) &&
33
+ (!subject || this.termMatches(statement.subject, subject)) &&
34
+ (!predicate || this.termMatches(statement.predicate, predicate)),
33
35
  );
34
36
  }
35
37
 
36
- public statement(subject?: string, predicate?: string, object?: string): Quad | null {
38
+ public statement(subject?: Term | string, predicate?: Term | string, object?: Term | string): Quad | null {
37
39
  const statement = this.quads.find(
38
- statement =>
39
- (!object || statement.object.value === this.expandIRI(object)) &&
40
- (!subject || statement.subject.value === this.expandIRI(subject)) &&
41
- (!predicate || statement.predicate.value === this.expandIRI(predicate)),
40
+ (_statement) =>
41
+ (!object || this.termMatches(_statement.object, object)) &&
42
+ (!subject || this.termMatches(_statement.subject, subject)) &&
43
+ (!predicate || this.termMatches(_statement.predicate, predicate)),
42
44
  );
43
45
 
44
46
  return statement ?? null;
@@ -58,4 +60,12 @@ export default class SolidStore {
58
60
  return expandIRI(iri);
59
61
  }
60
62
 
63
+ protected termMatches(term: Term, value: Term | string): boolean {
64
+ if (typeof value === 'string') {
65
+ return this.expandIRI(value) === term.value;
66
+ }
67
+
68
+ return term.termType === term.termType && term.value === value.value;
69
+ }
70
+
61
71
  }
@@ -1,6 +1,6 @@
1
- import type { Quad } from 'rdf-js';
1
+ import type { Quad } from '@rdfjs/types';
2
2
 
3
- import { expandIRI } from '@/helpers/vocabs';
3
+ import { expandIRI } from '@noeldemartin/solid-utils/helpers/vocabs';
4
4
 
5
5
  export default class SolidThing {
6
6
 
@@ -13,15 +13,13 @@ export default class SolidThing {
13
13
  }
14
14
 
15
15
  public value(property: string): string | undefined {
16
- return this.quads
17
- .find(quad => quad.predicate.value === expandIRI(property))
18
- ?.object.value;
16
+ return this.quads.find((quad) => quad.predicate.value === expandIRI(property))?.object.value;
19
17
  }
20
18
 
21
19
  public values(property: string): string[] {
22
20
  return this.quads
23
- .filter(quad => quad.predicate.value === expandIRI(property))
24
- .map(quad => quad.object.value);
21
+ .filter((quad) => quad.predicate.value === expandIRI(property))
22
+ .map((quad) => quad.object.value);
25
23
  }
26
24
 
27
25
  }
@@ -1,3 +1,5 @@
1
1
  export { default as SolidDocument, SolidDocumentPermission } from './SolidDocument';
2
2
  export { default as SolidStore } from './SolidStore';
3
3
  export { default as SolidThing } from './SolidThing';
4
+
5
+ export * from './SolidStore';
@@ -1,8 +1,14 @@
1
1
  import { JSError, arrayRemove, pull, stringMatchAll } from '@noeldemartin/utils';
2
- import type { JsonLD } from '@/helpers/jsonld';
3
- import type { Quad, Quad_Object } from 'rdf-js';
2
+ import type { Quad, Quad_Object } from '@rdfjs/types';
4
3
 
5
- import { jsonldToQuads, quadToTurtle, quadsToTurtle, sparqlToQuadsSync, turtleToQuadsSync } from './io';
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';
6
12
 
7
13
  let patternsRegExpsIndex: Record<string, RegExp> = {};
8
14
  const builtInPatterns: Record<string, string> = {
@@ -19,10 +25,9 @@ class ExpectedQuadAssertionError extends JSError {
19
25
 
20
26
  function assertExpectedQuadsExist(expectedQuads: Quad[], actualQuads: Quad[]): void {
21
27
  for (const expectedQuad of expectedQuads) {
22
- const matchingQuad = actualQuads.find(actualQuad => quadEquals(expectedQuad, actualQuad));
28
+ const matchingQuad = actualQuads.find((actualQuad) => quadEquals(expectedQuad, actualQuad));
23
29
 
24
- if (!matchingQuad)
25
- throw new ExpectedQuadAssertionError(expectedQuad);
30
+ if (!matchingQuad) throw new ExpectedQuadAssertionError(expectedQuad);
26
31
 
27
32
  arrayRemove(actualQuads, matchingQuad);
28
33
  }
@@ -34,10 +39,7 @@ function containsPatterns(value: string): boolean {
34
39
 
35
40
  function createPatternRegexp(expected: string): RegExp {
36
41
  const patternAliases = [];
37
- const patternMatches = stringMatchAll<4, 1 | 2>(
38
- expected,
39
- /\[\[((.*?)\]\[)?([^\]]+)\]\]/g,
40
- );
42
+ const patternMatches = stringMatchAll<4, 1 | 2>(expected, /\[\[((.*?)\]\[)?([^\]]+)\]\]/g);
41
43
  const patterns: string[] = [];
42
44
  let expectedRegExp = expected;
43
45
 
@@ -65,12 +67,10 @@ function quadValueEquals(expected: string, actual: string): boolean {
65
67
  }
66
68
 
67
69
  function quadObjectEquals(expected: Quad_Object, actual: Quad_Object): boolean {
68
- if (expected.termType !== actual.termType)
69
- return false;
70
+ if (expected.termType !== actual.termType) return false;
70
71
 
71
72
  if (expected.termType === 'Literal' && actual.termType === 'Literal') {
72
- if (expected.datatype.value !== actual.datatype.value)
73
- return false;
73
+ if (expected.datatype.value !== actual.datatype.value) return false;
74
74
 
75
75
  if (!containsPatterns(expected.value))
76
76
  return expected.datatype.value === 'http://www.w3.org/2001/XMLSchema#dateTime'
@@ -82,9 +82,11 @@ function quadObjectEquals(expected: Quad_Object, actual: Quad_Object): boolean {
82
82
  }
83
83
 
84
84
  function quadEquals(expected: Quad, actual: Quad): boolean {
85
- return quadObjectEquals(expected.object, actual.object)
86
- && quadValueEquals(expected.subject.value, actual.subject.value)
87
- && quadValueEquals(expected.predicate.value, actual.predicate.value);
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
+ );
88
90
  }
89
91
 
90
92
  function resetPatterns(): void {
@@ -119,8 +121,7 @@ export async function jsonldEquals(expected: JsonLD, actual: JsonLD): Promise<Eq
119
121
  try {
120
122
  assertExpectedQuadsExist(expectedQuads, actualQuads);
121
123
  } catch (error) {
122
- if (!(error instanceof ExpectedQuadAssertionError))
123
- throw error;
124
+ if (!(error instanceof ExpectedQuadAssertionError)) throw error;
124
125
 
125
126
  return result(false, error.message);
126
127
  }
@@ -137,8 +138,7 @@ export function sparqlEquals(expected: string, actual: string): EqualityResult {
137
138
  const result = (success: boolean, message: string) => ({ success, message, expected, actual });
138
139
 
139
140
  for (const operation of Object.keys(expectedOperations)) {
140
- if (!(operation in actualOperations))
141
- return result(false, `Couldn't find expected ${operation} operation.`);
141
+ if (!(operation in actualOperations)) return result(false, `Couldn't find expected ${operation} operation.`);
142
142
 
143
143
  const expectedQuads = pull(expectedOperations, operation);
144
144
  const actualQuads = pull(actualOperations, operation);
@@ -149,8 +149,7 @@ export function sparqlEquals(expected: string, actual: string): EqualityResult {
149
149
  try {
150
150
  assertExpectedQuadsExist(expectedQuads, actualQuads);
151
151
  } catch (error) {
152
- if (!(error instanceof ExpectedQuadAssertionError))
153
- throw error;
152
+ if (!(error instanceof ExpectedQuadAssertionError)) throw error;
154
153
 
155
154
  return result(
156
155
  false,
@@ -160,8 +159,7 @@ export function sparqlEquals(expected: string, actual: string): EqualityResult {
160
159
  }
161
160
 
162
161
  const unexpectedOperation = Object.keys(actualOperations)[0] ?? null;
163
- if (unexpectedOperation)
164
- return result(false, `Did not expect to find ${unexpectedOperation} triples.`);
162
+ if (unexpectedOperation) return result(false, `Did not expect to find ${unexpectedOperation} triples.`);
165
163
 
166
164
  return result(true, 'sparql matches');
167
165
  }
@@ -180,8 +178,7 @@ export function turtleEquals(expected: string, actual: string): EqualityResult {
180
178
  try {
181
179
  assertExpectedQuadsExist(expectedQuads, actualQuads);
182
180
  } catch (error) {
183
- if (!(error instanceof ExpectedQuadAssertionError))
184
- throw error;
181
+ if (!(error instanceof ExpectedQuadAssertionError)) throw error;
185
182
 
186
183
  return result(false, error.message);
187
184
  }
@@ -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
+ });