@noeldemartin/solid-utils 0.5.0 → 0.6.0-next.508449b33de64b0bcade86b642c9793381434231

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 (66) hide show
  1. package/dist/chai.d.ts +31 -0
  2. package/dist/chai.js +23 -0
  3. package/dist/chai.js.map +1 -0
  4. package/dist/helpers-DGXMj9cx.js +107 -0
  5. package/dist/helpers-DGXMj9cx.js.map +1 -0
  6. package/dist/io-wCcrq4b9.js +401 -0
  7. package/dist/io-wCcrq4b9.js.map +1 -0
  8. package/dist/noeldemartin-solid-utils.d.ts +61 -65
  9. package/dist/noeldemartin-solid-utils.js +224 -0
  10. package/dist/noeldemartin-solid-utils.js.map +1 -0
  11. package/dist/testing.d.ts +40 -0
  12. package/dist/testing.js +7 -0
  13. package/dist/testing.js.map +1 -0
  14. package/dist/vitest.d.ts +50 -0
  15. package/dist/vitest.js +55 -0
  16. package/dist/vitest.js.map +1 -0
  17. package/package.json +67 -63
  18. package/src/chai/assertions.ts +35 -0
  19. package/src/chai/index.ts +19 -0
  20. package/src/errors/UnauthorizedError.ts +1 -3
  21. package/src/errors/UnsuccessfulNetworkRequestError.ts +2 -2
  22. package/src/helpers/auth.test.ts +221 -0
  23. package/src/helpers/auth.ts +28 -27
  24. package/src/helpers/identifiers.test.ts +76 -0
  25. package/src/helpers/identifiers.ts +14 -17
  26. package/src/helpers/index.ts +0 -1
  27. package/src/helpers/interop.ts +23 -16
  28. package/src/helpers/io.test.ts +228 -0
  29. package/src/helpers/io.ts +57 -77
  30. package/src/helpers/jsonld.ts +6 -6
  31. package/src/helpers/vocabs.ts +3 -6
  32. package/src/helpers/wac.test.ts +64 -0
  33. package/src/helpers/wac.ts +10 -6
  34. package/src/index.ts +4 -0
  35. package/src/models/SolidDocument.test.ts +77 -0
  36. package/src/models/SolidDocument.ts +14 -18
  37. package/src/models/SolidStore.ts +22 -12
  38. package/src/models/SolidThing.ts +5 -7
  39. package/src/models/index.ts +2 -0
  40. package/src/{helpers/testing.ts → testing/helpers.ts} +24 -27
  41. package/src/testing/hepers.test.ts +329 -0
  42. package/src/testing/index.ts +1 -2
  43. package/src/types/index.ts +2 -0
  44. package/src/types/n3.d.ts +0 -2
  45. package/src/vitest/index.ts +20 -0
  46. package/src/vitest/matchers.ts +68 -0
  47. package/.github/workflows/ci.yml +0 -16
  48. package/.nvmrc +0 -1
  49. package/CHANGELOG.md +0 -70
  50. package/dist/noeldemartin-solid-utils.cjs.js +0 -2
  51. package/dist/noeldemartin-solid-utils.cjs.js.map +0 -1
  52. package/dist/noeldemartin-solid-utils.esm.js +0 -2
  53. package/dist/noeldemartin-solid-utils.esm.js.map +0 -1
  54. package/dist/noeldemartin-solid-utils.umd.js +0 -90
  55. package/dist/noeldemartin-solid-utils.umd.js.map +0 -1
  56. package/noeldemartin.config.js +0 -9
  57. package/src/main.ts +0 -5
  58. package/src/plugins/chai/assertions.ts +0 -40
  59. package/src/plugins/chai/index.ts +0 -5
  60. package/src/plugins/cypress/types.d.ts +0 -15
  61. package/src/plugins/index.ts +0 -2
  62. package/src/plugins/jest/index.ts +0 -5
  63. package/src/plugins/jest/matchers.ts +0 -65
  64. package/src/plugins/jest/types.d.ts +0 -14
  65. package/src/testing/ResponseStub.ts +0 -46
  66. package/src/testing/mocking.ts +0 -33
@@ -19,7 +19,10 @@ export interface SolidUserProfile {
19
19
  privateTypeIndexUrl?: string;
20
20
  }
21
21
 
22
- async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?: FetchSolidDocumentOptions): Promise<{
22
+ async function fetchExtendedUserProfile(
23
+ webIdDocument: SolidDocument,
24
+ options?: FetchSolidDocumentOptions,
25
+ ): Promise<{
23
26
  store: SolidStore;
24
27
  cloaked: boolean;
25
28
  writableProfileUrl: string | null;
@@ -29,12 +32,12 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?:
29
32
  const addReferencedDocumentUrls = (document: SolidDocument) => {
30
33
  document
31
34
  .statements(undefined, 'foaf:isPrimaryTopicOf')
32
- .map(quad => quad.object.value)
33
- .forEach(profileDocumentUrl => documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null);
35
+ .map((quad) => quad.object.value)
36
+ .forEach((profileDocumentUrl) => (documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null));
34
37
  document
35
38
  .statements(undefined, 'foaf:primaryTopic')
36
- .map(quad => quad.subject.value)
37
- .forEach(profileDocumentUrl => documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null);
39
+ .map((quad) => quad.subject.value)
40
+ .forEach((profileDocumentUrl) => (documents[profileDocumentUrl] = documents[profileDocumentUrl] ?? null));
38
41
  };
39
42
  const loadProfileDocuments = async (): Promise<void> => {
40
43
  for (const [url, document] of Object.entries(documents)) {
@@ -43,12 +46,12 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?:
43
46
  }
44
47
 
45
48
  try {
46
- const document = await fetchSolidDocument(url, options);
49
+ const _document = await fetchSolidDocument(url, options);
47
50
 
48
- documents[url] = document;
49
- store.addQuads(document.getQuads());
51
+ documents[url] = _document;
52
+ store.addQuads(_document.getQuads());
50
53
 
51
- addReferencedDocumentUrls(document);
54
+ addReferencedDocumentUrls(_document);
52
55
  } catch (error) {
53
56
  if (error instanceof UnauthorizedError) {
54
57
  documents[url] = false;
@@ -65,18 +68,16 @@ async function fetchExtendedUserProfile(webIdDocument: SolidDocument, options?:
65
68
 
66
69
  do {
67
70
  await loadProfileDocuments();
68
- } while (Object.values(documents).some(document => document === null));
71
+ } while (Object.values(documents).some((document) => document === null));
69
72
 
70
73
  return {
71
74
  store,
72
- cloaked: Object.values(documents).some(document => document === false),
73
- writableProfileUrl:
74
- webIdDocument.isUserWritable()
75
- ? webIdDocument.url
76
- : Object
77
- .values(documents)
78
- .find((document): document is SolidDocument => !!document && document.isUserWritable())
79
- ?.url ?? null,
75
+ cloaked: Object.values(documents).some((document) => document === false),
76
+ writableProfileUrl: webIdDocument.isUserWritable()
77
+ ? webIdDocument.url
78
+ : (Object.values(documents).find(
79
+ (document): document is SolidDocument => !!document && document.isUserWritable(),
80
+ )?.url ?? null),
80
81
  };
81
82
  }
82
83
 
@@ -97,7 +98,7 @@ async function fetchUserProfile(webId: string, options: FetchUserProfileOptions
97
98
  }
98
99
 
99
100
  const { store, writableProfileUrl, cloaked } = await fetchExtendedUserProfile(document, options);
100
- const storageUrls = store.statements(webId, 'pim:storage').map(storage => storage.object.value);
101
+ const storageUrls = store.statements(webId, 'pim:storage').map((storage) => storage.object.value);
101
102
  const publicTypeIndex = store.statement(webId, 'solid:publicTypeIndex');
102
103
  const privateTypeIndex = store.statement(webId, 'solid:privateTypeIndex');
103
104
 
@@ -118,7 +119,7 @@ async function fetchUserProfile(webId: string, options: FetchUserProfileOptions
118
119
  throw new Error(`Could not find any storage for ${webId}.`);
119
120
  }
120
121
 
121
- await options.onLoaded?.(new SolidStore(store.statements(webId)));
122
+ await options.onLoaded?.(store);
122
123
 
123
124
  return {
124
125
  webId,
@@ -126,9 +127,7 @@ async function fetchUserProfile(webId: string, options: FetchUserProfileOptions
126
127
  writableProfileUrl,
127
128
  storageUrls: arrayUnique(storageUrls) as [string, ...string[]],
128
129
  ...objectWithoutEmpty({
129
- name:
130
- store.statement(webId, 'vcard:fn')?.object.value ??
131
- store.statement(webId, 'foaf:name')?.object.value,
130
+ name: store.statement(webId, 'vcard:fn')?.object.value ?? store.statement(webId, 'foaf:name')?.object.value,
132
131
  avatarUrl:
133
132
  store.statement(webId, 'vcard:hasPhoto')?.object.value ??
134
133
  store.statement(webId, 'foaf:img')?.object.value,
@@ -156,9 +155,11 @@ export async function fetchLoginUserProfile(
156
155
  return fetchUserProfile(loginUrl, options);
157
156
  }
158
157
 
159
- const fetchProfile = silenced(url => fetchUserProfile(url, options));
158
+ const fetchProfile = silenced((url) => fetchUserProfile(url, options));
160
159
 
161
- return await fetchProfile(loginUrl)
162
- ?? await fetchProfile(loginUrl.replace(/\/$/, '').concat('/profile/card#me'))
163
- ?? await fetchProfile(urlRoot(loginUrl).concat('/profile/card#me'));
160
+ return (
161
+ (await fetchProfile(loginUrl)) ??
162
+ (await fetchProfile(loginUrl.replace(/\/$/, '').concat('/profile/card#me'))) ??
163
+ (await fetchProfile(urlRoot(loginUrl).concat('/profile/card#me')))
164
+ );
164
165
  }
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { mintJsonLDIdentifiers } from '@noeldemartin/solid-utils/helpers';
4
+ import { parseResourceSubject } from '@noeldemartin/solid-utils/helpers/identifiers';
5
+ import type { JsonLD } from '@noeldemartin/solid-utils/helpers';
6
+
7
+ describe('Identifiers helpers', () => {
8
+
9
+ it('mints JsonLD identifiers', () => {
10
+ // Arrange
11
+ const jsonld = {
12
+ '@context': { '@vocab': 'https://schema.org/' },
13
+ '@type': 'Recipe',
14
+ 'name': 'Ramen',
15
+ 'ingredients': ['Broth', 'Noodles'],
16
+ 'instructions': [
17
+ {
18
+ '@type': 'HowToStep',
19
+ 'text': 'Boil Noodles',
20
+ },
21
+ {
22
+ '@type': 'HowToStep',
23
+ 'text': 'Dip them into the broth',
24
+ },
25
+ ],
26
+ 'http://purl.org/dc/terms/created': {
27
+ '@type': 'http://www.w3.org/2001/XMLSchema#dateTime',
28
+ '@value': '1997-07-21T23:42:00.000Z',
29
+ },
30
+ };
31
+
32
+ // Act
33
+ const jsonldWithIds = mintJsonLDIdentifiers(jsonld);
34
+
35
+ // Assert
36
+ expect(jsonldWithIds['@id']).not.toBeUndefined();
37
+
38
+ const createdAt = jsonldWithIds['http://purl.org/dc/terms/created'] as Record<string, unknown>;
39
+ expect(createdAt['@id']).toBeUndefined();
40
+
41
+ const instructions = jsonldWithIds['instructions'] as [JsonLD, JsonLD];
42
+ expect(instructions[0]['@id']).not.toBeUndefined();
43
+ expect(instructions[1]['@id']).not.toBeUndefined();
44
+ });
45
+
46
+ it('parses subjects', () => {
47
+ expect(parseResourceSubject('https://my-pod.com/profile/card#me')).toEqual({
48
+ containerUrl: 'https://my-pod.com/profile/',
49
+ documentName: 'card',
50
+ resourceHash: 'me',
51
+ });
52
+ expect(parseResourceSubject('https://my-pod.com/about')).toEqual({
53
+ containerUrl: 'https://my-pod.com/',
54
+ documentName: 'about',
55
+ });
56
+ expect(parseResourceSubject('/profile/card#me')).toEqual({
57
+ containerUrl: '/profile/',
58
+ documentName: 'card',
59
+ resourceHash: 'me',
60
+ });
61
+ expect(parseResourceSubject('/about#sections')).toEqual({
62
+ containerUrl: '/',
63
+ documentName: 'about',
64
+ resourceHash: 'sections',
65
+ });
66
+ expect(parseResourceSubject('about#sections')).toEqual({
67
+ documentName: 'about',
68
+ resourceHash: 'sections',
69
+ });
70
+ expect(parseResourceSubject('about')).toEqual({
71
+ documentName: 'about',
72
+ });
73
+ expect(parseResourceSubject('')).toEqual({});
74
+ });
75
+
76
+ });
@@ -1,6 +1,6 @@
1
1
  import { arr, isArray, isObject, objectDeepClone, objectWithoutEmpty, tap, urlParse, uuid } from '@noeldemartin/utils';
2
2
  import type { UrlParts } from '@noeldemartin/utils';
3
- import type { JsonLD, JsonLDResource } from '@/helpers';
3
+ import type { JsonLD, JsonLDResource } from '@noeldemartin/solid-utils/helpers';
4
4
 
5
5
  export interface SubjectParts {
6
6
  containerUrl?: string;
@@ -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
  }
@@ -3,6 +3,5 @@ export * from './identifiers';
3
3
  export * from './interop';
4
4
  export * from './io';
5
5
  export * from './jsonld';
6
- export * from './testing';
7
6
  export * from './vocabs';
8
7
  export * from './wac';
@@ -1,8 +1,13 @@
1
1
  import { uuid } from '@noeldemartin/utils';
2
2
 
3
- import { createSolidDocument, fetchSolidDocument, solidDocumentExists, updateSolidDocument } from '@/helpers/io';
4
- import type { Fetch } from '@/helpers/io';
5
- import type { SolidUserProfile } from '@/helpers/auth';
3
+ import {
4
+ createSolidDocument,
5
+ fetchSolidDocument,
6
+ solidDocumentExists,
7
+ updateSolidDocument,
8
+ } from '@noeldemartin/solid-utils/helpers/io';
9
+ import type { Fetch } from '@noeldemartin/solid-utils/helpers/io';
10
+ import type { SolidUserProfile } from '@noeldemartin/solid-utils/helpers/auth';
6
11
 
7
12
  type TypeIndexType = 'public' | 'private';
8
13
 
@@ -12,7 +17,7 @@ async function mintTypeIndexUrl(user: SolidUserProfile, type: TypeIndexType, fet
12
17
  const storageUrl = user.storageUrls[0];
13
18
  const typeIndexUrl = `${storageUrl}settings/${type}TypeIndex`;
14
19
 
15
- return await solidDocumentExists(typeIndexUrl, { fetch })
20
+ return (await solidDocumentExists(typeIndexUrl, { fetch }))
16
21
  ? `${storageUrl}settings/${type}TypeIndex-${uuid()}`
17
22
  : typeIndexUrl;
18
23
  }
@@ -25,9 +30,10 @@ async function createTypeIndex(user: SolidUserProfile, type: TypeIndexType, fetc
25
30
  fetch = fetch ?? window.fetch.bind(fetch);
26
31
 
27
32
  const typeIndexUrl = await mintTypeIndexUrl(user, type, fetch);
28
- const typeIndexBody = type === 'public'
29
- ? '<> a <http://www.w3.org/ns/solid/terms#TypeIndex> .'
30
- : `
33
+ const typeIndexBody =
34
+ type === 'public'
35
+ ? '<> a <http://www.w3.org/ns/solid/terms#TypeIndex> .'
36
+ : `
31
37
  <> a
32
38
  <http://www.w3.org/ns/solid/terms#TypeIndex>,
33
39
  <http://www.w3.org/ns/solid/terms#UnlistedDocument> .
@@ -60,15 +66,16 @@ async function findRegistrations(
60
66
  const typeIndex = await fetchSolidDocument(typeIndexUrl, { fetch });
61
67
  const types = Array.isArray(type) ? type : [type];
62
68
 
63
- return types.map(
64
- type => typeIndex
65
- .statements(undefined, 'rdf:type', 'solid:TypeRegistration')
66
- .filter(statement => typeIndex.contains(statement.subject.value, 'solid:forClass', type))
67
- .map(statement => typeIndex.statements(statement.subject.value, predicate))
68
- .flat()
69
- .map(statement => statement.object.value)
70
- .filter(url => !!url),
71
- ).flat();
69
+ return types
70
+ .map((_type) =>
71
+ typeIndex
72
+ .statements(undefined, 'rdf:type', 'solid:TypeRegistration')
73
+ .filter((statement) => typeIndex.contains(statement.subject.value, 'solid:forClass', _type))
74
+ .map((statement) => typeIndex.statements(statement.subject.value, predicate))
75
+ .flat()
76
+ .map((statement) => statement.object.value)
77
+ .filter((url) => !!url))
78
+ .flat();
72
79
  }
73
80
 
74
81
  /**
@@ -0,0 +1,228 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { jsonldToQuads, normalizeSparql, quadsToJsonLD, sparqlToQuadsSync, turtleToQuadsSync } from './io';
4
+ import type { Quad } from '@rdfjs/types';
5
+
6
+ describe('IO', () => {
7
+
8
+ it('parses jsonld', async () => {
9
+ // Arrange
10
+ const jsonld = {
11
+ '@context': { '@vocab': 'https://schema.org/' },
12
+ '@type': 'Movie',
13
+ 'name': 'Spirited Away',
14
+ };
15
+
16
+ // Act
17
+ const quads = (await jsonldToQuads(jsonld)) as [Quad, Quad];
18
+
19
+ // Assert
20
+ expect(quads).toHaveLength(2);
21
+
22
+ expect(quads[0].subject.termType).toEqual('BlankNode');
23
+ expect(quads[0].predicate.termType).toEqual('NamedNode');
24
+ expect(quads[0].predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
25
+ expect(quads[0].object.termType).toEqual('NamedNode');
26
+ expect(quads[0].object.value).toEqual('https://schema.org/Movie');
27
+
28
+ expect(quads[1].subject.termType).toEqual('BlankNode');
29
+ expect(quads[1].predicate.termType).toEqual('NamedNode');
30
+ expect(quads[1].predicate.value).toEqual('https://schema.org/name');
31
+ expect(quads[1].object.termType).toEqual('Literal');
32
+ expect(quads[1].object.value).toEqual('Spirited Away');
33
+ });
34
+
35
+ it('parses jsonld with anonymous subjects', async () => {
36
+ // Arrange
37
+ const jsonld = {
38
+ '@context': { '@vocab': 'https://schema.org/' },
39
+ '@id': '#it',
40
+ '@type': 'Movie',
41
+ 'name': 'Spirited Away',
42
+ };
43
+
44
+ // Act
45
+ const quads = (await jsonldToQuads(jsonld)) as [Quad, Quad];
46
+
47
+ // Assert
48
+ expect(quads).toHaveLength(2);
49
+
50
+ expect(quads[0].subject.termType).toEqual('NamedNode');
51
+ expect(quads[0].subject.value).toEqual('#it');
52
+ expect(quads[0].predicate.termType).toEqual('NamedNode');
53
+ expect(quads[0].predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
54
+ expect(quads[0].object.termType).toEqual('NamedNode');
55
+ expect(quads[0].object.value).toEqual('https://schema.org/Movie');
56
+
57
+ expect(quads[1].subject.termType).toEqual('NamedNode');
58
+ expect(quads[1].subject.value).toEqual('#it');
59
+ expect(quads[1].predicate.termType).toEqual('NamedNode');
60
+ expect(quads[1].predicate.value).toEqual('https://schema.org/name');
61
+ expect(quads[1].object.termType).toEqual('Literal');
62
+ expect(quads[1].object.value).toEqual('Spirited Away');
63
+ });
64
+
65
+ it('parses jsonld graphs', async () => {
66
+ // Arrange
67
+ const jsonld = {
68
+ '@graph': [
69
+ {
70
+ '@context': { '@vocab': 'https://schema.org/' },
71
+ '@id': 'solid://movies/spirited-away',
72
+ '@type': 'Movie',
73
+ 'name': 'Spirited Away',
74
+ },
75
+ {
76
+ '@context': { '@vocab': 'https://schema.org/' },
77
+ '@id': 'solid://movies/spirited-away',
78
+ '@type': 'Movie',
79
+ 'name': 'Spirited Away',
80
+ },
81
+ ],
82
+ };
83
+
84
+ // Act
85
+ const quads = await jsonldToQuads(jsonld);
86
+
87
+ // Assert
88
+ expect(quads).toHaveLength(4);
89
+
90
+ [0, 2].forEach((index) => {
91
+ expect(quads[index]?.subject.termType).toEqual('NamedNode');
92
+ expect(quads[index]?.subject.value).toEqual('solid://movies/spirited-away');
93
+ expect(quads[index]?.predicate.termType).toEqual('NamedNode');
94
+ expect(quads[index]?.predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
95
+ expect(quads[index]?.object.termType).toEqual('NamedNode');
96
+ expect(quads[index]?.object.value).toEqual('https://schema.org/Movie');
97
+ });
98
+
99
+ [1, 3].forEach((index) => {
100
+ expect(quads[index]?.subject.termType).toEqual('NamedNode');
101
+ expect(quads[index]?.subject.value).toEqual('solid://movies/spirited-away');
102
+ expect(quads[index]?.predicate.termType).toEqual('NamedNode');
103
+ expect(quads[index]?.predicate.value).toEqual('https://schema.org/name');
104
+ expect(quads[index]?.object.termType).toEqual('Literal');
105
+ expect(quads[index]?.object.value).toEqual('Spirited Away');
106
+ });
107
+ });
108
+
109
+ it('normalizes sparql', () => {
110
+ // Arrange
111
+ const insertTurtle = `
112
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
113
+
114
+ <#me>
115
+ foaf:name "Amy" ;
116
+ foaf:lastName "Doe" .
117
+ `;
118
+ const deleteTurtle = `
119
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
120
+
121
+ <#me> foaf:name "John Doe" .
122
+ `;
123
+ const sparql = `
124
+ INSERT DATA { ${insertTurtle} } ;
125
+ DELETE DATA { ${deleteTurtle} }
126
+ `;
127
+
128
+ // Act
129
+ const normalized = normalizeSparql(sparql);
130
+
131
+ // Assert
132
+ expect(normalized).toEqual(
133
+ [
134
+ 'INSERT DATA {',
135
+ ' <#me> <http://xmlns.com/foaf/0.1/lastName> "Doe" .',
136
+ ' <#me> <http://xmlns.com/foaf/0.1/name> "Amy" .',
137
+ '} ;',
138
+ 'DELETE DATA {',
139
+ ' <#me> <http://xmlns.com/foaf/0.1/name> "John Doe" .',
140
+ '}',
141
+ ].join('\n'),
142
+ );
143
+ });
144
+
145
+ it('parses sparql', () => {
146
+ // Arrange
147
+ const insertTurtle = `
148
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
149
+
150
+ <#me> foaf:name "Amy Doe" .
151
+ `;
152
+ const deleteTurtle = `
153
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
154
+
155
+ <#me> foaf:name "John Doe" .
156
+ `;
157
+ const sparql = `
158
+ INSERT DATA { ${insertTurtle} } ;
159
+ DELETE DATA { ${deleteTurtle} }
160
+ `;
161
+
162
+ // Act
163
+ const quads = sparqlToQuadsSync(sparql);
164
+
165
+ // Assert
166
+ expect(Object.keys(quads)).toHaveLength(2);
167
+
168
+ expect(quads.insert).toEqual(turtleToQuadsSync(insertTurtle));
169
+ expect(quads.delete).toEqual(turtleToQuadsSync(deleteTurtle));
170
+ });
171
+
172
+ it('parses turtle synchronously', () => {
173
+ // Arrange
174
+ const turtle = `
175
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
176
+
177
+ <#me>
178
+ a foaf:Person ;
179
+ foaf:name "John Doe" .
180
+ `;
181
+
182
+ // Act
183
+ const quads = turtleToQuadsSync(turtle) as [Quad, Quad];
184
+
185
+ // Assert
186
+ expect(quads).toHaveLength(2);
187
+
188
+ expect(quads[0].subject.termType).toEqual('NamedNode');
189
+ expect(quads[0].subject.value).toEqual('#me');
190
+ expect(quads[0].predicate.termType).toEqual('NamedNode');
191
+ expect(quads[0].predicate.value).toEqual('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
192
+ expect(quads[0].object.termType).toEqual('NamedNode');
193
+ expect(quads[0].object.value).toEqual('http://xmlns.com/foaf/0.1/Person');
194
+
195
+ expect(quads[1].subject.termType).toEqual('NamedNode');
196
+ expect(quads[1].subject.value).toEqual('#me');
197
+ expect(quads[1].predicate.termType).toEqual('NamedNode');
198
+ expect(quads[1].predicate.value).toEqual('http://xmlns.com/foaf/0.1/name');
199
+ expect(quads[1].object.termType).toEqual('Literal');
200
+ expect(quads[1].object.value).toEqual('John Doe');
201
+ });
202
+
203
+ it('converts quads to jsonld', async () => {
204
+ // Arrange
205
+ const quads = turtleToQuadsSync(`
206
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
207
+
208
+ <#me>
209
+ a foaf:Person ;
210
+ foaf:name "John Doe" .
211
+ `);
212
+
213
+ // Act
214
+ const jsonld = await quadsToJsonLD(quads);
215
+
216
+ // Assert
217
+ expect(jsonld).toEqual({
218
+ '@graph': [
219
+ {
220
+ '@id': '#me',
221
+ '@type': ['http://xmlns.com/foaf/0.1/Person'],
222
+ 'http://xmlns.com/foaf/0.1/name': [{ '@value': 'John Doe' }],
223
+ },
224
+ ],
225
+ });
226
+ });
227
+
228
+ });