@arpproject/recrate 0.1.2

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 (286) hide show
  1. package/.eslintrc.json +37 -0
  2. package/.storybook/main.ts +40 -0
  3. package/.storybook/preview.tsx +46 -0
  4. package/LICENCE +18 -0
  5. package/README.md +83 -0
  6. package/babel.config.json +5 -0
  7. package/dist/app/App.d.ts +2 -0
  8. package/dist/app/EmbeddedComponent.d.ts +3 -0
  9. package/dist/app/index.d.ts +0 -0
  10. package/dist/app/lookup.d.ts +82 -0
  11. package/dist/crate-builder/CrateManager/contexts.d.ts +6 -0
  12. package/dist/crate-builder/CrateManager/crate-manager-benchmarking.spec.d.ts +1 -0
  13. package/dist/crate-builder/CrateManager/crate-manager-loading-exporting.spec.d.ts +1 -0
  14. package/dist/crate-builder/CrateManager/crate-manager-operations.spec.d.ts +0 -0
  15. package/dist/crate-builder/CrateManager/crate-manager.d.ts +641 -0
  16. package/dist/crate-builder/CrateManager/crate-manager.spec.d.ts +1 -0
  17. package/dist/crate-builder/CrateManager/lib.d.ts +26 -0
  18. package/dist/crate-builder/CrateManager/lib.spec.d.ts +1 -0
  19. package/dist/crate-builder/CrateManager/profile-manager.d.ts +143 -0
  20. package/dist/crate-builder/CrateManager/profile-manager.spec.d.ts +1 -0
  21. package/dist/crate-builder/CrateManager/schema-type-definitions.json.d.ts +35125 -0
  22. package/dist/crate-builder/CrateManager/validate-identifier.d.ts +10 -0
  23. package/dist/crate-builder/CrateManager/validate-identifier.spec.d.ts +1 -0
  24. package/dist/crate-builder/RenderEntity/Add.d.ts +26 -0
  25. package/dist/crate-builder/RenderEntity/AddControl.d.ts +14 -0
  26. package/dist/crate-builder/RenderEntity/AddControl.stories.d.ts +29 -0
  27. package/dist/crate-builder/RenderEntity/AutoComplete.d.ts +12 -0
  28. package/dist/crate-builder/RenderEntity/BulkAdd.d.ts +10 -0
  29. package/dist/crate-builder/RenderEntity/DeleteProperty.d.ts +8 -0
  30. package/dist/crate-builder/RenderEntity/DialogAddProperty.d.ts +9 -0
  31. package/dist/crate-builder/RenderEntity/DialogBrowseEntities.d.ts +8 -0
  32. package/dist/crate-builder/RenderEntity/DialogEditContext.d.ts +8 -0
  33. package/dist/crate-builder/RenderEntity/DialogPreviewCrate.d.ts +7 -0
  34. package/dist/crate-builder/RenderEntity/DialogSaveCrateAsTemplate.d.ts +13 -0
  35. package/dist/crate-builder/RenderEntity/DialogSaveEntityTemplate.d.ts +14 -0
  36. package/dist/crate-builder/RenderEntity/DisplayPropertyName.d.ts +8 -0
  37. package/dist/crate-builder/RenderEntity/DisplayPropertyName.stories.d.ts +6 -0
  38. package/dist/crate-builder/RenderEntity/EntityId.d.ts +13 -0
  39. package/dist/crate-builder/RenderEntity/EntityName.d.ts +13 -0
  40. package/dist/crate-builder/RenderEntity/EntityProperty.d.ts +19 -0
  41. package/dist/crate-builder/RenderEntity/EntityPropertyInstance.d.ts +25 -0
  42. package/dist/crate-builder/RenderEntity/EntityType.d.ts +12 -0
  43. package/dist/crate-builder/RenderEntity/ItemLink.d.ts +14 -0
  44. package/dist/crate-builder/RenderEntity/PaginateLinkedEntities.d.ts +23 -0
  45. package/dist/crate-builder/RenderEntity/PaginateLinkedEntities.stories.d.ts +7 -0
  46. package/dist/crate-builder/RenderEntity/PropertyHelp.d.ts +6 -0
  47. package/dist/crate-builder/RenderEntity/RenderControls.d.ts +22 -0
  48. package/dist/crate-builder/RenderEntity/RenderLinkedItem.d.ts +20 -0
  49. package/dist/crate-builder/RenderEntity/RenderPropertyHelp.d.ts +6 -0
  50. package/dist/crate-builder/RenderEntity/RenderReverseConnections.d.ts +16 -0
  51. package/dist/crate-builder/RenderEntity/RenderTypes.d.ts +6 -0
  52. package/dist/crate-builder/RenderEntity/Shell2.d.ts +14 -0
  53. package/dist/crate-builder/RenderEntity/UnlinkEntity.d.ts +14 -0
  54. package/dist/crate-builder/RenderEntity/auto-complete.lib.d.ts +25 -0
  55. package/dist/crate-builder/RenderEntity/keys.d.ts +4 -0
  56. package/dist/crate-builder/RenderEntity/layout.d.ts +14 -0
  57. package/dist/crate-builder/RenderEntity/layout.spec.d.ts +1 -0
  58. package/dist/crate-builder/Shell.d.ts +40 -0
  59. package/dist/crate-builder/editor-state.d.ts +72 -0
  60. package/dist/crate-builder/helpers.d.ts +9 -0
  61. package/dist/crate-builder/i18n.d.ts +2 -0
  62. package/dist/crate-builder/lib/validate-iri.d.ts +6 -0
  63. package/dist/crate-builder/locales/en.d.ts +79 -0
  64. package/dist/crate-builder/locales/hu.d.ts +79 -0
  65. package/dist/crate-builder/primitives/Boolean.d.ts +11 -0
  66. package/dist/crate-builder/primitives/Boolean.stories.d.ts +7 -0
  67. package/dist/crate-builder/primitives/Date.d.ts +11 -0
  68. package/dist/crate-builder/primitives/Date.stories.d.ts +5 -0
  69. package/dist/crate-builder/primitives/DateTime.d.ts +11 -0
  70. package/dist/crate-builder/primitives/DateTime.stories.d.ts +5 -0
  71. package/dist/crate-builder/primitives/Geo.d.ts +12 -0
  72. package/dist/crate-builder/primitives/Geo.stories.d.ts +26 -0
  73. package/dist/crate-builder/primitives/Map.SelectArea.d.ts +9 -0
  74. package/dist/crate-builder/primitives/Map.d.ts +15 -0
  75. package/dist/crate-builder/primitives/Map.stories.d.ts +6 -0
  76. package/dist/crate-builder/primitives/Number.d.ts +16 -0
  77. package/dist/crate-builder/primitives/Number.stories.d.ts +10 -0
  78. package/dist/crate-builder/primitives/Select.d.ts +15 -0
  79. package/dist/crate-builder/primitives/Select.stories.d.ts +9 -0
  80. package/dist/crate-builder/primitives/SelectObject.d.ts +13 -0
  81. package/dist/crate-builder/primitives/SelectObject.stories.d.ts +5 -0
  82. package/dist/crate-builder/primitives/SelectUrl.d.ts +14 -0
  83. package/dist/crate-builder/primitives/SelectUrl.stories.d.ts +7 -0
  84. package/dist/crate-builder/primitives/Text.d.ts +19 -0
  85. package/dist/crate-builder/primitives/Text.stories.d.ts +13 -0
  86. package/dist/crate-builder/primitives/Time.d.ts +11 -0
  87. package/dist/crate-builder/primitives/Time.stories.d.ts +7 -0
  88. package/dist/crate-builder/primitives/Url.d.ts +11 -0
  89. package/dist/crate-builder/primitives/Url.stories.d.ts +7 -0
  90. package/dist/crate-builder/primitives/Value.d.ts +7 -0
  91. package/dist/crate-builder/primitives/Value.stories.d.ts +7 -0
  92. package/dist/crate-builder/primitives/date-libs.d.ts +1 -0
  93. package/dist/crate-builder/property-definitions.d.ts +78 -0
  94. package/dist/crate-builder/store.d.ts +8 -0
  95. package/dist/favicon.ico +0 -0
  96. package/dist/index.d.ts +3 -0
  97. package/dist/index.html +43 -0
  98. package/dist/logo192.png +0 -0
  99. package/dist/logo512.png +0 -0
  100. package/dist/manifest.json +25 -0
  101. package/dist/marker-icon.png +0 -0
  102. package/dist/marker-shadow.png +0 -0
  103. package/dist/recrate.es.js +158699 -0
  104. package/dist/recrate.umd.js +158717 -0
  105. package/dist/robots.txt +3 -0
  106. package/dist/style.css +1721 -0
  107. package/dist/types.d.ts +42 -0
  108. package/docker-compose.yml +30 -0
  109. package/docs/.nojekyll +1 -0
  110. package/docs/assets/highlight.css +99 -0
  111. package/docs/assets/icons.js +18 -0
  112. package/docs/assets/icons.svg +1 -0
  113. package/docs/assets/main.js +60 -0
  114. package/docs/assets/navigation.js +1 -0
  115. package/docs/assets/search.js +1 -0
  116. package/docs/assets/style.css +1448 -0
  117. package/docs/classes/src_crate_builder_CrateManager_crate_manager.CrateManager.html +240 -0
  118. package/docs/classes/src_crate_builder_CrateManager_profile_manager.ProfileManager.html +42 -0
  119. package/docs/classes/src_crate_builder_editor_state.EditorState.html +28 -0
  120. package/docs/classes/src_crate_builder_types.CrateManagerType.html +57 -0
  121. package/docs/classes/src_crate_builder_types.ProfileManagerType.html +13 -0
  122. package/docs/functions/src_crate_builder_CrateManager_lib.isURL.html +2 -0
  123. package/docs/functions/src_crate_builder_CrateManager_lib.mintNewCrate.html +3 -0
  124. package/docs/functions/src_crate_builder_CrateManager_lib.normalise.html +5 -0
  125. package/docs/functions/src_crate_builder_CrateManager_lib.normaliseEntityType.html +1 -0
  126. package/docs/index.html +58 -0
  127. package/docs/interfaces/src_crate_builder_types.NormalisedCrate.html +3 -0
  128. package/docs/interfaces/src_crate_builder_types.NormalisedEntityDefinition.html +4 -0
  129. package/docs/interfaces/src_crate_builder_types.NormalisedProfile.html +9 -0
  130. package/docs/interfaces/src_crate_builder_types.ProfileLayout.html +2 -0
  131. package/docs/interfaces/src_crate_builder_types.ProfileLayoutGroup.html +9 -0
  132. package/docs/interfaces/src_crate_builder_types.UnverifiedCrate.html +3 -0
  133. package/docs/interfaces/src_crate_builder_types.UnverifiedEntityDefinition.html +4 -0
  134. package/docs/modules/src_crate_builder_CrateManager_crate_manager.html +2 -0
  135. package/docs/modules/src_crate_builder_CrateManager_lib.html +6 -0
  136. package/docs/modules/src_crate_builder_CrateManager_profile_manager.html +2 -0
  137. package/docs/modules/src_crate_builder_editor_state.html +2 -0
  138. package/docs/modules/src_crate_builder_types.html +16 -0
  139. package/docs/types/src_crate_builder_types.EntityReference.html +1 -0
  140. package/docs/types/src_crate_builder_types.NormalisedContext.html +1 -0
  141. package/docs/types/src_crate_builder_types.PrimitiveType.html +1 -0
  142. package/docs/types/src_crate_builder_types.ProfileAssociation.html +1 -0
  143. package/docs/types/src_crate_builder_types.ProfileInput.html +1 -0
  144. package/docs/types/src_crate_builder_types.UnverifiedContext.html +1 -0
  145. package/docs/variables/src_crate_builder_CrateManager_lib.urlProtocols.html +1 -0
  146. package/index.html +13 -0
  147. package/load-data-packs.cjs +38 -0
  148. package/package.json +135 -0
  149. package/postcss.config.cjs +6 -0
  150. package/public/favicon.ico +0 -0
  151. package/public/index.html +43 -0
  152. package/public/logo192.png +0 -0
  153. package/public/logo512.png +0 -0
  154. package/public/manifest.json +25 -0
  155. package/public/marker-icon.png +0 -0
  156. package/public/marker-shadow.png +0 -0
  157. package/public/robots.txt +3 -0
  158. package/react-app-env.d.ts +1 -0
  159. package/rollup.config.js +26 -0
  160. package/src/app/App.tsx +13 -0
  161. package/src/app/EmbeddedComponent.tsx +432 -0
  162. package/src/app/index.html +20 -0
  163. package/src/app/index.tsx +19 -0
  164. package/src/app/lookup.ts +141 -0
  165. package/src/app/override-styles.css +96 -0
  166. package/src/crate-builder/CrateManager/contexts/1.1-context.jsonld +2660 -0
  167. package/src/crate-builder/CrateManager/contexts/1.2-DRAFT-context.jsonld +2918 -0
  168. package/src/crate-builder/CrateManager/contexts.ts +42 -0
  169. package/src/crate-builder/CrateManager/crate-manager-benchmarking.spec.ts +31 -0
  170. package/src/crate-builder/CrateManager/crate-manager-loading-exporting.spec.ts +431 -0
  171. package/src/crate-builder/CrateManager/crate-manager-operations.spec.ts +298 -0
  172. package/src/crate-builder/CrateManager/crate-manager.spec.ts +2336 -0
  173. package/src/crate-builder/CrateManager/crate-manager.ts +2111 -0
  174. package/src/crate-builder/CrateManager/lib.spec.ts +133 -0
  175. package/src/crate-builder/CrateManager/lib.ts +170 -0
  176. package/src/crate-builder/CrateManager/profile-manager.spec.ts +593 -0
  177. package/src/crate-builder/CrateManager/profile-manager.ts +367 -0
  178. package/src/crate-builder/CrateManager/schema-type-definitions.json +35122 -0
  179. package/src/crate-builder/CrateManager/validate-identifier.spec.ts +82 -0
  180. package/src/crate-builder/CrateManager/validate-identifier.ts +65 -0
  181. package/src/crate-builder/RenderEntity/Add.tsx +249 -0
  182. package/src/crate-builder/RenderEntity/AddControl.stories.tsx +126 -0
  183. package/src/crate-builder/RenderEntity/AddControl.tsx +84 -0
  184. package/src/crate-builder/RenderEntity/AutoComplete.tsx +215 -0
  185. package/src/crate-builder/RenderEntity/BulkAdd.tsx +136 -0
  186. package/src/crate-builder/RenderEntity/DeleteProperty.tsx +33 -0
  187. package/src/crate-builder/RenderEntity/DialogAddProperty.tsx +83 -0
  188. package/src/crate-builder/RenderEntity/DialogBrowseEntities.tsx +136 -0
  189. package/src/crate-builder/RenderEntity/DialogEditContext.tsx +107 -0
  190. package/src/crate-builder/RenderEntity/DialogPreviewCrate.tsx +54 -0
  191. package/src/crate-builder/RenderEntity/DialogSaveCrateAsTemplate.tsx +65 -0
  192. package/src/crate-builder/RenderEntity/DialogSaveEntityTemplate.tsx +87 -0
  193. package/src/crate-builder/RenderEntity/DisplayPropertyName.stories.tsx +30 -0
  194. package/src/crate-builder/RenderEntity/DisplayPropertyName.tsx +21 -0
  195. package/src/crate-builder/RenderEntity/EntityId.tsx +75 -0
  196. package/src/crate-builder/RenderEntity/EntityName.tsx +49 -0
  197. package/src/crate-builder/RenderEntity/EntityProperty.tsx +188 -0
  198. package/src/crate-builder/RenderEntity/EntityPropertyInstance.tsx +255 -0
  199. package/src/crate-builder/RenderEntity/EntityType.tsx +95 -0
  200. package/src/crate-builder/RenderEntity/ItemLink.tsx +37 -0
  201. package/src/crate-builder/RenderEntity/PaginateLinkedEntities.stories.tsx +43 -0
  202. package/src/crate-builder/RenderEntity/PaginateLinkedEntities.tsx +141 -0
  203. package/src/crate-builder/RenderEntity/PropertyHelp.tsx +39 -0
  204. package/src/crate-builder/RenderEntity/RenderControls.tsx +278 -0
  205. package/src/crate-builder/RenderEntity/RenderLinkedItem.tsx +139 -0
  206. package/src/crate-builder/RenderEntity/RenderPropertyHelp.tsx +41 -0
  207. package/src/crate-builder/RenderEntity/RenderReverseConnections.tsx +150 -0
  208. package/src/crate-builder/RenderEntity/RenderTypes.tsx +102 -0
  209. package/src/crate-builder/RenderEntity/Shell2.tsx +576 -0
  210. package/src/crate-builder/RenderEntity/UnlinkEntity.tsx +30 -0
  211. package/src/crate-builder/RenderEntity/auto-complete.lib.ts +184 -0
  212. package/src/crate-builder/RenderEntity/keys.ts +4 -0
  213. package/src/crate-builder/RenderEntity/layout.spec.js +593 -0
  214. package/src/crate-builder/RenderEntity/layout.ts +220 -0
  215. package/src/crate-builder/Shell.tsx +323 -0
  216. package/src/crate-builder/component.css +65 -0
  217. package/src/crate-builder/editor-state.ts +114 -0
  218. package/src/crate-builder/helpers.ts +16 -0
  219. package/src/crate-builder/i18n.ts +22 -0
  220. package/src/crate-builder/lib/validate-iri.js +69 -0
  221. package/src/crate-builder/lib/validate-iri.ts +57 -0
  222. package/src/crate-builder/locales/en.js +149 -0
  223. package/src/crate-builder/locales/hu.js +147 -0
  224. package/src/crate-builder/primitives/Boolean.stories.tsx +33 -0
  225. package/src/crate-builder/primitives/Boolean.tsx +67 -0
  226. package/src/crate-builder/primitives/Date.stories.tsx +32 -0
  227. package/src/crate-builder/primitives/Date.tsx +58 -0
  228. package/src/crate-builder/primitives/DateTime.stories.tsx +32 -0
  229. package/src/crate-builder/primitives/DateTime.tsx +64 -0
  230. package/src/crate-builder/primitives/Geo.stories.tsx +57 -0
  231. package/src/crate-builder/primitives/Geo.tsx +225 -0
  232. package/src/crate-builder/primitives/Map.SelectArea.js +359 -0
  233. package/src/crate-builder/primitives/Map.stories.tsx +61 -0
  234. package/src/crate-builder/primitives/Map.tsx +124 -0
  235. package/src/crate-builder/primitives/Number.stories.tsx +74 -0
  236. package/src/crate-builder/primitives/Number.tsx +166 -0
  237. package/src/crate-builder/primitives/Select.stories.tsx +66 -0
  238. package/src/crate-builder/primitives/Select.tsx +122 -0
  239. package/src/crate-builder/primitives/SelectObject.stories.tsx +29 -0
  240. package/src/crate-builder/primitives/SelectObject.tsx +105 -0
  241. package/src/crate-builder/primitives/SelectUrl.stories.tsx +42 -0
  242. package/src/crate-builder/primitives/SelectUrl.tsx +110 -0
  243. package/src/crate-builder/primitives/Text.stories.tsx +106 -0
  244. package/src/crate-builder/primitives/Text.tsx +197 -0
  245. package/src/crate-builder/primitives/Time.stories.tsx +38 -0
  246. package/src/crate-builder/primitives/Time.tsx +71 -0
  247. package/src/crate-builder/primitives/Url.stories.tsx +43 -0
  248. package/src/crate-builder/primitives/Url.tsx +75 -0
  249. package/src/crate-builder/primitives/Value.stories.tsx +37 -0
  250. package/src/crate-builder/primitives/Value.tsx +30 -0
  251. package/src/crate-builder/primitives/date-libs.ts +12 -0
  252. package/src/crate-builder/profile-schema.json +145 -0
  253. package/src/crate-builder/property-definitions.ts +78 -0
  254. package/src/crate-builder/store.ts +14 -0
  255. package/src/crate-builder/tailwind.css +7 -0
  256. package/src/crate-builder/types.d.ts +318 -0
  257. package/src/examples/collection/collections-entity-example.json +131 -0
  258. package/src/examples/collection/crate-builder-entity-example.json +33 -0
  259. package/src/examples/item/complex-collection/ro-crate-metadata.json +174 -0
  260. package/src/examples/item/complex-item/ro-crate-metadata.json +769 -0
  261. package/src/examples/item/crate-with-language.json +38 -0
  262. package/src/examples/item/empty/ro-crate-metadata.json +20 -0
  263. package/src/examples/item/item-with-relationship-and-action/ro-crate-metadata.json +66 -0
  264. package/src/examples/item/large-crate/ro-crate-metadata.json +5762 -0
  265. package/src/examples/item/multiple-types/ro-crate-metadata.json +20 -0
  266. package/src/examples/item/ridiculously-big-collection/ro-crate-metadata.json +162977 -0
  267. package/src/examples/profile/aroma.complex.profile.json +11098 -0
  268. package/src/examples/profile/aroma.profile.json +9158 -0
  269. package/src/examples/profile/nyingarn-item-profile.json +426 -0
  270. package/src/examples/profile/profile-to-test-inverse-associations.json +73 -0
  271. package/src/examples/profile/profile-to-test-multiple-types.json +31 -0
  272. package/src/examples/profile/profile-with-all-primitives-and-groups.json +207 -0
  273. package/src/examples/profile/profile-with-all-primitives.json +244 -0
  274. package/src/examples/profile/profile-with-constraints.json +446 -0
  275. package/src/examples/profile/profile-with-resolve.json +57 -0
  276. package/src/examples/profile/vocabulary-creation-profile.json +231 -0
  277. package/src/images.d.ts +5 -0
  278. package/src/index.ts +10 -0
  279. package/src/types.ts +104 -0
  280. package/tailwind.config.js +20 -0
  281. package/tsconfig.app.json +31 -0
  282. package/tsconfig.json +26 -0
  283. package/typedoc.json +11 -0
  284. package/update-deps.sh +4 -0
  285. package/vite-env.d.ts +1 -0
  286. package/vite.config.ts +41 -0
@@ -0,0 +1,2336 @@
1
+ import { describe, test, expect, beforeAll, beforeEach, vi } from "vitest";
2
+ import { CrateManager } from "./crate-manager";
3
+ import { ProfileManager } from "./profile-manager";
4
+ import { readJSON } from "fs-extra";
5
+ import Chance from "chance";
6
+ const chance = Chance();
7
+ import type {
8
+ CrateManagerType,
9
+ EntityReference,
10
+ NormalisedEntityDefinition,
11
+ NormalisedProfile,
12
+ ProfileManagerType,
13
+ UnverifiedCrate,
14
+ UnverifiedEntityDefinition,
15
+ } from "../types";
16
+
17
+ describe("Test interacting with the crate", () => {
18
+ let crate: UnverifiedCrate, cm: CrateManagerType;
19
+ beforeAll(() => {
20
+ vi.spyOn(console, "debug").mockImplementation(() => {});
21
+ });
22
+ beforeEach(async () => {
23
+ crate = getBaseCrate();
24
+ crate["@graph"].push({
25
+ "@id": "./",
26
+ "@type": "Dataset",
27
+ name: "root dataset",
28
+ text: "something",
29
+ });
30
+ cm = new CrateManager({ crate });
31
+ });
32
+ test("get root dataset", () => {
33
+ let rd = cm.getRootDataset();
34
+ expect(rd["@id"]).toEqual("./");
35
+ });
36
+ test("normalising contexts with varying shapes", () => {
37
+ let context: any = "https://w3id.org/ro/crate/1.1/context";
38
+ let normalisedContext = cm.__normaliseContext(context);
39
+ expect(normalisedContext).toEqual(["https://w3id.org/ro/crate/1.1/context"]);
40
+
41
+ context = ["https://w3id.org/ro/crate/1.1/context"];
42
+ normalisedContext = cm.__normaliseContext(context);
43
+ expect(normalisedContext).toEqual(["https://w3id.org/ro/crate/1.1/context"]);
44
+
45
+ context = [
46
+ "https://w3id.org/ro/crate/1.1/context",
47
+ { hasPart: "https://schema.org/hasPart" },
48
+ { schema: "https://schema.org" },
49
+ ];
50
+ normalisedContext = cm.__normaliseContext(context);
51
+ expect(normalisedContext).toEqual([
52
+ "https://w3id.org/ro/crate/1.1/context",
53
+ {
54
+ hasPart: "https://schema.org/hasPart",
55
+ schema: "https://schema.org",
56
+ },
57
+ ]);
58
+ });
59
+ test("get context", () => {
60
+ let context = cm.getContext();
61
+ expect(context).toEqual(["https://w3id.org/ro/crate/1.1/context"]);
62
+ });
63
+ test("set context", () => {
64
+ cm.setContext({} as any);
65
+ expect(cm.getContext()).toEqual([]);
66
+ });
67
+ test("set profile manager", () => {
68
+ const profile: any = {
69
+ metadata: {},
70
+ classes: {},
71
+ };
72
+ const pm: ProfileManagerType = new ProfileManager({ profile }) as ProfileManagerType;
73
+ expect(pm).toMatchObject({ profile: { metadata: {}, classes: {} } });
74
+
75
+ cm.setProfileManager(pm);
76
+ expect(cm.pm).toMatchObject({ profile: { metadata: {}, classes: {} } });
77
+ });
78
+ test(`getting entities from the crate`, () => {
79
+ // no id provided
80
+ try {
81
+ let match = cm.getEntity({} as any);
82
+ } catch (error) {
83
+ expect((error as Error).message).toEqual("An id must be provided");
84
+ }
85
+
86
+ // materialise an entity
87
+ let match = cm.getEntity({ id: chance.url() });
88
+ expect(match).toMatchObject({ "@type": ["URL"] });
89
+
90
+ // matching entity
91
+ match = cm.getEntity({ id: "./" });
92
+ expect(match).toMatchObject({
93
+ "@id": "./",
94
+ "@type": ["Dataset"],
95
+ name: "root dataset",
96
+ text: ["something"],
97
+ });
98
+
99
+ // matching entity - stub only
100
+ match = cm.getEntity({ id: "./", stub: true });
101
+ expect(match).toMatchObject({
102
+ "@id": "./",
103
+ "@type": ["Dataset"],
104
+ name: "root dataset",
105
+ });
106
+ });
107
+ test("add a simple entity to the crate", () => {
108
+ // test 1
109
+ let entity: UnverifiedEntityDefinition = {
110
+ "@id": chance.url(),
111
+ "@type": "Person",
112
+ name: chance.sentence(),
113
+ };
114
+ let r = cm.addEntity(entity);
115
+ let ec = cm.exportCrate();
116
+ expect(ec["@graph"].length).toEqual(3);
117
+ expect(r).toBeTruthy;
118
+
119
+ // test 2
120
+ entity = {
121
+ "@id": "something",
122
+ "@type": "Thing",
123
+ };
124
+ r = cm.addEntity(entity);
125
+ ec = cm.exportCrate();
126
+ expect(ec["@graph"].length).toEqual(4);
127
+ expect(r).toBeTruthy;
128
+
129
+ // test 3
130
+ entity = {
131
+ "@id": "something",
132
+ "@type": "Person",
133
+ };
134
+ r = cm.addEntity(entity);
135
+ ec = cm.exportCrate();
136
+ expect(ec["@graph"].length).toEqual(5);
137
+ expect(r).toBeTruthy;
138
+
139
+ // test 4
140
+ try {
141
+ entity = {
142
+ "@id": "http://something.com",
143
+ };
144
+ r = cm.addEntity(entity);
145
+ } catch (error) {
146
+ expect((error as Error).message).toEqual(
147
+ `You can't add an entity without defining the type : '@type'.`
148
+ );
149
+ }
150
+
151
+ // test 5
152
+ try {
153
+ entity = {
154
+ name: "some thing",
155
+ };
156
+ r = cm.addEntity({ entity } as UnverifiedEntityDefinition);
157
+ } catch (error) {
158
+ expect((error as Error).message).toEqual(
159
+ `You can't add an entity without an identifier: '@id'.`
160
+ );
161
+ }
162
+
163
+ // test 6
164
+ try {
165
+ entity = {
166
+ "@id": "./",
167
+ "@type": "Dataset",
168
+ };
169
+ r = cm.addEntity(entity);
170
+ } catch (error) {
171
+ expect((error as Error).message).toEqual(
172
+ `You can't add an entity with id: './' as that will clash with the root dataset.`
173
+ );
174
+ }
175
+
176
+ // test 7
177
+ try {
178
+ entity = {
179
+ "@id": "#person",
180
+ };
181
+ r = cm.addEntity({ entity } as UnverifiedEntityDefinition);
182
+ } catch (error) {
183
+ expect((error as Error).message).toEqual(
184
+ `You can't add an entity without an identifier: '@id'.`
185
+ );
186
+ }
187
+ });
188
+ test(`prevent adding entities with id's that clash with root dataset or descriptor`, () => {
189
+ try {
190
+ cm.addEntity({ "@id": "./", "@type": "Dataset" });
191
+ } catch (error) {
192
+ expect((error as Error).message).toEqual(
193
+ `You can't add an entity with id: './' as that will clash with the root dataset.`
194
+ );
195
+ }
196
+ try {
197
+ cm.addEntity({ "@id": "ro-crate-metadata.json", "@type": "CreativeWork" });
198
+ } catch (error) {
199
+ expect((error as Error).message).toEqual(
200
+ `You can't add an entity with id: 'ro-crate-metadata.json' as that will clash with the root descriptor.`
201
+ );
202
+ }
203
+ // ensure it fails even when the type is different
204
+ try {
205
+ cm.addEntity({ "@id": "ro-crate-metadata.json", "@type": "Person" });
206
+ } catch (error) {
207
+ expect((error as Error).message).toEqual(
208
+ `You can't add an entity with id: 'ro-crate-metadata.json' as that will clash with the root descriptor.`
209
+ );
210
+ }
211
+ });
212
+ test("add a simple entity to the crate and export as a template", () => {
213
+ let entity = {
214
+ "@id": "#person",
215
+ "@type": "Person",
216
+ name: "person",
217
+ data: "value",
218
+ };
219
+ let e = cm.addEntity(entity);
220
+
221
+ let template = cm.exportEntityTemplate({ id: e["@id"], resolveDepth: 1 });
222
+ expect(template).toMatchObject({
223
+ "@id": "#person",
224
+ "@type": ["Person"],
225
+ name: "person",
226
+ data: "value",
227
+ });
228
+ });
229
+ test(`Ensure no id clashes when entity type is different`, () => {
230
+ let topic = {
231
+ "@id": "#1234",
232
+ "@type": "Topic",
233
+ name: "topic",
234
+ };
235
+ let e = cm.addEntity(topic);
236
+ expect(e).toEqual({ "@id": "#1234", "@type": ["Topic"], name: "topic" });
237
+
238
+ let theme = {
239
+ "@id": "#1234",
240
+ "@type": "Theme",
241
+ name: "theme",
242
+ };
243
+ e = cm.addEntity(theme);
244
+ expect(e).toEqual({ "@id": "#e4", "@type": ["Theme"], name: "theme" });
245
+
246
+ e = cm.addEntity(e);
247
+ expect(e).toMatchObject({ "@id": "#e4", "@type": ["Theme"], name: "theme" });
248
+ });
249
+ test("add a complex entity to the crate and export as a template", () => {
250
+ let entity: UnverifiedEntityDefinition = {
251
+ "@id": "#person",
252
+ "@type": "Person",
253
+ name: "person",
254
+ data: "value",
255
+ };
256
+ let e = cm.addEntity(entity);
257
+ entity = {
258
+ "@id": "#organisation",
259
+ "@type": "Organisation",
260
+ name: "organisation",
261
+ ceo: { "@id": "http://person.name" },
262
+ };
263
+ e = cm.addEntity(entity);
264
+ cm.linkEntity({
265
+ id: "#person",
266
+ property: "organisation",
267
+ value: { "@id": "#organisation" },
268
+ });
269
+
270
+ let template = cm.exportEntityTemplate({
271
+ id: "#person",
272
+ resolveDepth: 1,
273
+ });
274
+ expect(template).toEqual({
275
+ "@id": "#person",
276
+ "@type": ["Person"],
277
+ name: "person",
278
+ data: "value",
279
+ organisation: {
280
+ "@id": "#organisation",
281
+ "@type": ["Organisation"],
282
+ name: "organisation",
283
+ },
284
+ });
285
+ });
286
+ test(`fail template export - bad resolve depth`, () => {
287
+ // test bad resolveDepth
288
+ try {
289
+ let _template = cm.exportEntityTemplate({
290
+ id: "#person",
291
+ resolveDepth: 4,
292
+ });
293
+ } catch (error) {
294
+ expect((error as Error).message).toEqual(`resolveDepth can only be 0 or 1`);
295
+ }
296
+ });
297
+ test("add a complex entity to the crate", () => {
298
+ let url = chance.url();
299
+ let entity = {
300
+ "@id": url,
301
+ "@type": "Person",
302
+ name: "a person",
303
+ text: "some text",
304
+ author: [{ "@id": url }],
305
+ };
306
+ let e = cm.addEntity(entity);
307
+ let match = cm.getEntity({ id: e["@id"] });
308
+ expect(match).toMatchObject({
309
+ "@id": url,
310
+ "@type": ["Person"],
311
+ name: "a person",
312
+ text: ["some text"],
313
+ author: [{ "@id": url }],
314
+ });
315
+ });
316
+ test("update entity name", () => {
317
+ const url = chance.url();
318
+ let entity = {
319
+ "@id": url,
320
+ "@type": "Person",
321
+ name: chance.sentence(),
322
+ };
323
+ let e: NormalisedEntityDefinition = cm.addEntity(entity);
324
+ expect(e).toBeTruthy;
325
+
326
+ let r = cm.updateProperty({
327
+ id: entity["@id"],
328
+ idx: 0,
329
+ property: "name",
330
+ value: "something else",
331
+ });
332
+ expect(r).toBeTruthy;
333
+ e = cm.getEntity({ id: entity["@id"] }) as NormalisedEntityDefinition;
334
+ expect(e.name).toEqual("something else");
335
+ });
336
+ test("update entity @type", () => {
337
+ const url = chance.url();
338
+ let entity = {
339
+ "@id": url,
340
+ "@type": "Person",
341
+ name: chance.sentence(),
342
+ };
343
+ let e: NormalisedEntityDefinition = cm.addEntity(entity);
344
+ expect(e).toBeTruthy;
345
+
346
+ // test 1
347
+ let r = cm.updateProperty({
348
+ id: entity["@id"],
349
+ property: "@type",
350
+ idx: 0,
351
+ value: ["Person", "Adult"],
352
+ });
353
+ expect(r).toBeTruthy;
354
+ e = cm.getEntity({ id: entity["@id"] }) as NormalisedEntityDefinition;
355
+ expect(e["@type"]).toEqual(["Person", "Adult"]);
356
+
357
+ // test 2
358
+ r = cm.updateProperty({
359
+ id: entity["@id"],
360
+ property: "@type",
361
+ value: ["Adult", "Person", "Adult"],
362
+ });
363
+ expect(r).toBeTruthy;
364
+ e = cm.getEntity({ id: entity["@id"] }) as NormalisedEntityDefinition;
365
+ expect(e["@type"]).toEqual(["Adult", "Person"]);
366
+ });
367
+ test("update entity '@id'", () => {
368
+ crate = getBaseCrate();
369
+
370
+ const authorId = "http://schema.org/person";
371
+ const newAuthorId = "http://schema.org/author";
372
+ crate["@graph"].push({
373
+ "@id": "./",
374
+ "@type": ["Dataset"],
375
+ name: "rd",
376
+ author: [{ "@id": authorId }],
377
+ });
378
+ crate["@graph"].push({
379
+ "@id": authorId,
380
+ "@type": ["Person"],
381
+ name: "a person",
382
+ group: [{ "@id": "#g1" }],
383
+ });
384
+ crate["@graph"].push({
385
+ "@id": "#g1",
386
+ "@type": ["Group"],
387
+ name: "a group",
388
+ });
389
+ cm = new CrateManager({ crate });
390
+
391
+ cm.updateProperty({
392
+ id: authorId,
393
+ property: "@id",
394
+ value: newAuthorId,
395
+ });
396
+
397
+ let ec = cm.exportCrate();
398
+
399
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
400
+ expect(ec["@graph"]).toMatchObject([
401
+ { "@id": "ro-crate-metadata.json" },
402
+ { "@id": "./", author: { "@id": newAuthorId } },
403
+ {
404
+ "@id": newAuthorId,
405
+ "@reverse": {
406
+ author: {
407
+ "@id": "./",
408
+ },
409
+ },
410
+ },
411
+ {
412
+ "@id": "#g1",
413
+ "@reverse": {
414
+ group: {
415
+ "@id": "http://schema.org/author",
416
+ },
417
+ },
418
+ },
419
+ ]);
420
+ });
421
+ test(`trying to set a property on a non-existent entity`, () => {
422
+ let result = cm.updateProperty({
423
+ id: "http://schema.org/person",
424
+ property: "@id",
425
+ value: "new",
426
+ });
427
+ expect(result).toEqual(undefined);
428
+ });
429
+ test("delete an entity", () => {
430
+ const f1 = cm.addFile("/file1.txt");
431
+ const f2 = cm.addFile("/file2.txt");
432
+ let r1 = cm.addEntity({ "@id": "_:r1", "@type": "Relationship", name: "r1" });
433
+ let r2 = cm.addEntity({ "@id": "_:r2", "@type": "Relationship", name: "r2" });
434
+
435
+ // cm.linkEntity({ id: "./", property: "hasPart", value: r1 });
436
+ // cm.linkEntity({ id: "./", property: "hasPart", value: r2 });
437
+
438
+ // delete an entity that is not linked to anything
439
+ cm.deleteEntity({ id: r1["@id"] });
440
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
441
+ { "@id": "ro-crate-metadata.json" },
442
+ { "@id": "./" },
443
+ { "@id": "file1.txt" },
444
+ { "@id": "file2.txt" },
445
+ { "@id": "_:r2" },
446
+ ]);
447
+
448
+ // console.log(JSON.stringify(cm.exportCrate()["@graph"], null, 2));
449
+
450
+ // re-add the entity and link a file to it
451
+ r1 = cm.addEntity({ "@id": "_:r1", "@type": "Relationship", name: "r1" });
452
+ cm.linkEntity({ id: "./", property: "hasPart", value: r1 });
453
+ cm.setProperty({
454
+ id: r1["@id"],
455
+ property: "object",
456
+ propertyId: "",
457
+ value: f1,
458
+ });
459
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
460
+ { "@id": "ro-crate-metadata.json" },
461
+ { "@id": "./" },
462
+ { "@id": "file1.txt" },
463
+ { "@id": "file2.txt" },
464
+ { "@id": "_:r2" },
465
+ { "@id": "_:r1", object: { "@id": "file1.txt" } },
466
+ ]);
467
+
468
+ // now delete it and check everything else is still there
469
+ cm.deleteEntity({ id: r1["@id"] });
470
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
471
+ { "@id": "ro-crate-metadata.json" },
472
+ { "@id": "./" },
473
+ { "@id": "file1.txt" },
474
+ { "@id": "file2.txt" },
475
+ { "@id": "_:r2" },
476
+ ]);
477
+ // console.log(JSON.stringify(cm.exportCrate()["@graph"], null, 2));
478
+
479
+ // re-add the entity and link a file to it
480
+ r1 = cm.addEntity({ "@id": "_:r1", "@type": "Relationship", name: "r1" });
481
+ // cm.linkEntity({ id: "./", property: "hasPart", value: r1 });
482
+ cm.setProperty({
483
+ id: r1["@id"],
484
+ property: "object",
485
+ propertyId: "",
486
+ value: f1,
487
+ });
488
+
489
+ // link the same file to a different entity
490
+ cm.setProperty({
491
+ id: r2["@id"],
492
+ property: "object",
493
+ propertyId: "",
494
+ value: f1,
495
+ });
496
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
497
+ { "@id": "ro-crate-metadata.json" },
498
+ { "@id": "./" },
499
+ { "@id": "file1.txt" },
500
+ { "@id": "file2.txt" },
501
+ { "@id": "_:r2", object: { "@id": "file1.txt" } },
502
+ { "@id": "_:r1", object: { "@id": "file1.txt" } },
503
+ ]);
504
+
505
+ // now delete it and check everything else is still there
506
+ cm.deleteEntity({ id: r1["@id"] });
507
+ // console.log(JSON.stringify(cm.exportCrate()["@graph"], null, 2));
508
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
509
+ { "@id": "ro-crate-metadata.json" },
510
+ { "@id": "./" },
511
+ { "@id": "file1.txt" },
512
+ { "@id": "file2.txt" },
513
+ { "@id": "_:r2" },
514
+ ]);
515
+ });
516
+ test("prevent deleting the root dataset and the root descriptor", () => {
517
+ try {
518
+ cm.deleteEntity({ id: "./" });
519
+ } catch (error) {
520
+ expect((error as Error).message).toEqual(
521
+ `You can't delete the root dataset or the root descriptor.`
522
+ );
523
+ }
524
+ try {
525
+ cm.deleteEntity({ id: "ro-crate-metadata.json" });
526
+ } catch (error) {
527
+ expect((error as Error).message).toEqual(
528
+ `You can't delete the root dataset or the root descriptor.`
529
+ );
530
+ }
531
+ });
532
+ test(`fail - property updates `, () => {
533
+ // fail updating core property
534
+ try {
535
+ cm.setProperty({
536
+ id: "./",
537
+ property: "@id",
538
+ value: "something else",
539
+ });
540
+ } catch (error) {
541
+ expect((error as Error).message).toEqual(
542
+ `This method does not operate on @id, @type, @reverse, name`
543
+ );
544
+ }
545
+ // fail bad value
546
+ try {
547
+ cm.setProperty({
548
+ id: "./",
549
+ property: "new",
550
+ value: {} as EntityReference,
551
+ });
552
+ } catch (error) {
553
+ expect((error as Error).message).toEqual(
554
+ `value must be a string, number, boolean or object with '@id'`
555
+ );
556
+ }
557
+ try {
558
+ cm.setProperty({
559
+ id: "./",
560
+ property: "new",
561
+ value: [] as any,
562
+ });
563
+ } catch (error) {
564
+ expect((error as Error).message).toEqual(
565
+ `value must be a string, number, boolean or object with '@id'`
566
+ );
567
+ }
568
+ // fail - wrong method
569
+ try {
570
+ cm.updateProperty({
571
+ id: "./",
572
+ property: "@id",
573
+ value: [],
574
+ });
575
+ } catch (error) {
576
+ expect((error as Error).message).toEqual(
577
+ `'@id' property must be a string or not defined at all`
578
+ );
579
+ }
580
+ });
581
+ test("adding a property to an entity", () => {
582
+ const url = chance.url();
583
+ let entity = {
584
+ "@id": url,
585
+ "@type": "Person",
586
+ name: chance.sentence(),
587
+ };
588
+ let e = cm.addEntity(entity);
589
+
590
+ cm.setProperty({
591
+ id: e["@id"],
592
+ property: "author",
593
+ value: "something else",
594
+ });
595
+ e = cm.getEntity({ id: e["@id"] }) as NormalisedEntityDefinition;
596
+ expect(e.author.length).toEqual(1);
597
+ expect(e.author[0]).toEqual("something else");
598
+ });
599
+ test("a sequence of operations around adding and manipulating properties on an entity", () => {
600
+ cm.setProperty({ id: "./", property: "new", value: "some text" });
601
+ const authorId = chance.url();
602
+ cm.setProperty({ id: "./", property: "author", value: { "@id": authorId } });
603
+ cm.setProperty({ id: "./", property: "author", value: "text" });
604
+ cm.setProperty({ id: "./", property: "author", value: 3 });
605
+
606
+ let entity = cm.getEntity({ id: "./" });
607
+ expect(entity).toMatchObject({
608
+ "@id": "./",
609
+ new: ["some text"],
610
+ author: [{ "@id": authorId }, "text", 3],
611
+ });
612
+ // console.log(entity);
613
+
614
+ entity = cm.getEntity({ id: "./" });
615
+ expect(entity).toMatchObject({
616
+ "@id": "./",
617
+ new: ["some text"],
618
+ author: [{ "@id": authorId }, "text", 3],
619
+ });
620
+
621
+ cm.updateProperty({ id: "./", property: "author", idx: 1, value: "new" });
622
+ entity = cm.getEntity({ id: "./" });
623
+ expect(entity).toMatchObject({
624
+ "@id": "./",
625
+ author: [{ "@id": authorId }, "new", 3],
626
+ });
627
+
628
+ cm.setProperty({ id: "./", property: "author", value: 3 });
629
+ cm.updateProperty({ id: "./", property: "author", idx: 1, value: "new" });
630
+ entity = cm.getEntity({ id: "./" });
631
+ expect(entity).toMatchObject({
632
+ "@id": "./",
633
+ author: [{ "@id": authorId }, "new", 3, 3],
634
+ });
635
+ // console.log(entity);
636
+ });
637
+ test("delete a instance of data attached to a property", () => {
638
+ const url = chance.url();
639
+ let entity = {
640
+ "@id": url,
641
+ "@type": "Person",
642
+ name: chance.sentence(),
643
+ text: "some text",
644
+ };
645
+ let e = cm.addEntity(entity);
646
+ e = cm.getEntity({ id: e["@id"] }) as NormalisedEntityDefinition;
647
+
648
+ cm.deleteProperty({
649
+ id: e["@id"],
650
+ property: "text",
651
+ idx: 0,
652
+ });
653
+
654
+ e = cm.getEntity({ id: e["@id"] }) as NormalisedEntityDefinition;
655
+ expect(e).not.toHaveProperty("text");
656
+ });
657
+ test("delete all data connected to a property", () => {
658
+ let entity: UnverifiedEntityDefinition = {
659
+ "@id": "#person1",
660
+ "@type": "Person",
661
+ name: "person1",
662
+ text: ["some text", "some other text"],
663
+ };
664
+ let e1 = cm.addEntity(entity);
665
+ e1 = cm.getEntity({ id: e1["@id"] }) as NormalisedEntityDefinition;
666
+
667
+ // link it to another entity
668
+ entity = {
669
+ "@id": "#person2",
670
+ "@type": "Person",
671
+ name: "person2",
672
+ };
673
+ let e2 = cm.addEntity(entity);
674
+ e2 = cm.getEntity({ id: e2["@id"] }) as NormalisedEntityDefinition;
675
+
676
+ cm.linkEntity({ id: e1["@id"], property: "knows", value: e2 });
677
+
678
+ let crate = cm.exportCrate();
679
+ expect(crate["@graph"]).toMatchObject([
680
+ {
681
+ "@id": "ro-crate-metadata.json",
682
+ },
683
+ {
684
+ "@id": "./",
685
+ },
686
+ {
687
+ "@id": "#person1",
688
+ "@type": "Person",
689
+ name: "person1",
690
+ text: ["some text", "some other text"],
691
+ knows: { "@id": "#person2" },
692
+ },
693
+ {
694
+ "@id": "#person2",
695
+ "@type": "Person",
696
+ name: "person2",
697
+ },
698
+ ]);
699
+
700
+ // delete a property without associations
701
+ cm.deleteProperty({
702
+ id: e1["@id"],
703
+ property: "text",
704
+ });
705
+
706
+ e1 = cm.getEntity({ id: e1["@id"] }) as NormalisedEntityDefinition;
707
+ expect(e1).not.toHaveProperty("text");
708
+
709
+ // ensure it doesn't fail when that same prop doesn't exist
710
+ cm.deleteProperty({
711
+ id: e1["@id"],
712
+ property: "text",
713
+ });
714
+ e1 = cm.getEntity({ id: e1["@id"] }) as NormalisedEntityDefinition;
715
+ expect(e1).not.toHaveProperty("text");
716
+
717
+ expect(crate["@graph"]).toMatchObject([
718
+ {
719
+ "@id": "ro-crate-metadata.json",
720
+ },
721
+ {
722
+ "@id": "./",
723
+ },
724
+ {
725
+ "@id": "#person1",
726
+ "@type": "Person",
727
+ name: "person1",
728
+ knows: { "@id": "#person2" },
729
+ },
730
+ {
731
+ "@id": "#person2",
732
+ "@type": "Person",
733
+ name: "person2",
734
+ },
735
+ ]);
736
+
737
+ // delete a property with associations
738
+ cm.deleteProperty({
739
+ id: e1["@id"],
740
+ property: "knows",
741
+ });
742
+ e1 = cm.getEntity({ id: e1["@id"] }) as NormalisedEntityDefinition;
743
+ expect(e1).not.toHaveProperty("text");
744
+
745
+ crate = cm.exportCrate();
746
+ expect(crate["@graph"]).toMatchObject([
747
+ {
748
+ "@id": "ro-crate-metadata.json",
749
+ },
750
+ {
751
+ "@id": "./",
752
+ },
753
+ {
754
+ "@id": "#person1",
755
+ },
756
+ {
757
+ "@id": "#person2",
758
+ },
759
+ ]);
760
+ });
761
+ test("a sequence of operations around deleting a property on an entity", () => {
762
+ cm.setProperty({ id: "./", property: "new", value: "some text" });
763
+ const authorId = chance.url();
764
+ cm.setProperty({ id: "./", property: "author", value: { "@id": authorId } });
765
+ cm.setProperty({ id: "./", property: "author", value: "text" });
766
+ cm.setProperty({ id: "./", property: "author", value: 3 });
767
+
768
+ let entity = cm.getEntity({ id: "./" });
769
+ expect(entity).toMatchObject({
770
+ "@id": "./",
771
+ new: ["some text"],
772
+ author: [{ "@id": authorId }, "text", 3],
773
+ });
774
+
775
+ cm.deleteProperty({ id: "./", property: "author", idx: 1 });
776
+ entity = cm.getEntity({ id: "./" });
777
+ expect(entity).toMatchObject({
778
+ "@id": "./",
779
+ author: [{ "@id": authorId }, 3],
780
+ });
781
+
782
+ cm.setProperty({ id: "./", property: "author", value: 3 });
783
+ entity = cm.getEntity({ id: "./" });
784
+ cm.deleteProperty({ id: "./", property: "author", idx: 0 });
785
+ cm.deleteProperty({ id: "./", property: "author", idx: 1 });
786
+ entity = cm.getEntity({ id: "./" });
787
+ expect(entity).toMatchObject({
788
+ "@id": "./",
789
+ author: [3],
790
+ });
791
+ });
792
+ test("link two entities", () => {
793
+ const url = chance.url();
794
+ let entity = {
795
+ "@id": url,
796
+ "@type": "Person",
797
+ name: chance.sentence(),
798
+ text: "some text",
799
+ };
800
+ let e = cm.addEntity(entity);
801
+
802
+ cm.linkEntity({
803
+ id: "./",
804
+ property: "author",
805
+ value: { "@id": e["@id"] },
806
+ });
807
+
808
+ let ec = cm.exportCrate();
809
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
810
+ expect(ec["@graph"]).toMatchObject([
811
+ {
812
+ "@id": "ro-crate-metadata.json",
813
+ },
814
+ {
815
+ "@id": "./",
816
+ author: {
817
+ "@id": e["@id"],
818
+ },
819
+ },
820
+ {
821
+ "@id": e["@id"],
822
+ "@reverse": {
823
+ author: {
824
+ "@id": "./",
825
+ },
826
+ },
827
+ },
828
+ ]);
829
+ });
830
+ test("unlink two entities", () => {
831
+ const url = chance.url();
832
+ let entity = {
833
+ "@id": url,
834
+ "@type": "Person",
835
+ name: chance.sentence(),
836
+ text: "some text",
837
+ };
838
+ let e = cm.addEntity(entity);
839
+
840
+ cm.linkEntity({
841
+ id: "./",
842
+ property: "author",
843
+ value: { "@id": e["@id"] },
844
+ });
845
+
846
+ cm.unlinkEntity({
847
+ id: "./",
848
+ property: "author",
849
+ value: {
850
+ "@id": e["@id"],
851
+ },
852
+ });
853
+
854
+ let ec = cm.exportCrate();
855
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
856
+
857
+ expect(ec["@graph"]).toMatchObject([
858
+ { "@id": "ro-crate-metadata.json" },
859
+ { "@id": "./" },
860
+ { "@id": url },
861
+ ]);
862
+ expect(ec["@graph"][1]).not.toHaveProperty("author");
863
+ });
864
+ test("fail - fail linking / unlinking entities", () => {
865
+ try {
866
+ cm.linkEntity({
867
+ id: "./",
868
+ property: "author",
869
+ value: "string" as any,
870
+ });
871
+ } catch (error) {
872
+ expect((error as Error).message).toEqual(`value must be an object with '@id' defined`);
873
+ }
874
+
875
+ try {
876
+ cm.unlinkEntity({
877
+ id: "./",
878
+ property: "author",
879
+ value: "string" as any,
880
+ });
881
+ } catch (error) {
882
+ expect((error as Error).message).toEqual(`value must be an object with '@id' defined`);
883
+ }
884
+ });
885
+ test("linking / unlinking two entities and handling inverse property associations", () => {
886
+ const profile: NormalisedProfile = {
887
+ metadata: {} as any,
888
+ classes: [] as any,
889
+ propertyAssociations: [
890
+ {
891
+ property: "keywords",
892
+ propertyId: "https://schema.org/keywords",
893
+ inverse: {
894
+ property: "isKeywordOf",
895
+ propertyId: "https://schema.org/isKeywordOf",
896
+ },
897
+ },
898
+ {
899
+ property: "hasMember",
900
+ propertyId: "https://schema.org/hasMember",
901
+ inverse: {
902
+ property: "isMemberOf",
903
+ propertyId: "https://schema.org/isMemberOf",
904
+ },
905
+ },
906
+ ],
907
+ };
908
+
909
+ const pm = new ProfileManager({ profile }) as ProfileManagerType;
910
+ cm.setProfileManager(pm);
911
+
912
+ let entity = {
913
+ "@id": "#term",
914
+ "@type": "DefinedTerm",
915
+ name: "a term",
916
+ };
917
+ const e = cm.addEntity(entity);
918
+
919
+ // link to root dataset and check
920
+ cm.linkEntity({
921
+ id: "./",
922
+ property: "keywords",
923
+ propertyId: "http://schema.org/keywords",
924
+ value: { "@id": e["@id"] },
925
+ });
926
+ let crate = cm.exportCrate();
927
+ // console.log(JSON.stringify(crate["@graph"], null, 2));
928
+ expect(crate["@graph"][1]).toMatchObject({
929
+ "@id": "./",
930
+ keywords: {
931
+ "@id": "#term",
932
+ },
933
+ "@reverse": {
934
+ isKeywordOf: {
935
+ "@id": "#term",
936
+ },
937
+ },
938
+ });
939
+ expect(crate["@graph"][2]).toMatchObject({
940
+ "@id": "#term",
941
+ isKeywordOf: {
942
+ "@id": "./",
943
+ },
944
+ "@reverse": {
945
+ keywords: {
946
+ "@id": "./",
947
+ },
948
+ },
949
+ });
950
+
951
+ cm.unlinkEntity({
952
+ id: "./",
953
+ property: "keywords",
954
+ value: { "@id": e["@id"] },
955
+ });
956
+ crate = cm.exportCrate();
957
+ expect(crate["@graph"][1]).toMatchObject({
958
+ "@id": "./",
959
+ "@reverse": {},
960
+ });
961
+ expect(crate["@graph"][2]).toMatchObject({
962
+ "@id": "#term",
963
+ "@reverse": {},
964
+ });
965
+ // console.log(JSON.stringify(crate["@graph"], null, 2));
966
+ });
967
+ test("more complex:: linking / unlinking two entities and handling inverse property associations", () => {
968
+ const profile = {
969
+ metadata: {} as any,
970
+ classes: [] as any,
971
+ propertyAssociations: [
972
+ {
973
+ property: "keywords",
974
+ propertyId: "https://schema.org/keywords",
975
+ inverse: {
976
+ property: "isKeywordOf",
977
+ propertyId: "https://schema.org/isKeywordOf",
978
+ },
979
+ },
980
+ {
981
+ property: "hasMember",
982
+ propertyId: "https://schema.org/hasMember",
983
+ inverse: {
984
+ property: "isMemberOf",
985
+ propertyId: "https://schema.org/isMemberOf",
986
+ },
987
+ },
988
+ ],
989
+ };
990
+
991
+ const pm = new ProfileManager({ profile }) as ProfileManagerType;
992
+ cm.setProfileManager(pm);
993
+
994
+ let entity = {
995
+ "@id": "#term",
996
+ "@type": "DefinedTerm",
997
+ name: "a term",
998
+ };
999
+ const e = cm.addEntity(entity);
1000
+
1001
+ // link to root dataset and check
1002
+ cm.linkEntity({
1003
+ id: "./",
1004
+ property: "keywords",
1005
+ propertyId: "http://schema.org/keywords",
1006
+ value: { "@id": e["@id"] },
1007
+ });
1008
+ let crate = cm.exportCrate();
1009
+ // console.log(JSON.stringify(crate["@graph"], null, 2));
1010
+ expect(crate["@context"][1]).toMatchObject({
1011
+ isKeywordOf: "https://schema.org/isKeywordOf",
1012
+ });
1013
+ expect(crate["@graph"][1].keywords).toEqual({ "@id": "#term" });
1014
+ expect((crate["@graph"][1]["@reverse"] as any).isKeywordOf).toEqual({ "@id": "#term" });
1015
+ expect(crate["@graph"][2].isKeywordOf).toEqual({ "@id": "./" });
1016
+ expect((crate["@graph"][2]["@reverse"] as any).keywords).toEqual({ "@id": "./" });
1017
+
1018
+ // link to itself and check
1019
+ cm.linkEntity({
1020
+ id: e["@id"],
1021
+ property: "keywords",
1022
+ value: { "@id": e["@id"] },
1023
+ });
1024
+ crate = cm.exportCrate();
1025
+ // console.log(JSON.stringify(crate["@graph"], null, 2));
1026
+ expect(crate["@graph"][2]).toMatchObject({
1027
+ isKeywordOf: [{ "@id": "./" }, { "@id": "#term" }],
1028
+ keywords: { "@id": "#term" },
1029
+ "@reverse": {
1030
+ keywords: [{ "@id": "./" }, { "@id": "#term" }],
1031
+ isKeywordOf: { "@id": "#term" },
1032
+ },
1033
+ });
1034
+
1035
+ // link to root dataset via an inverse association
1036
+ cm.linkEntity({
1037
+ id: e["@id"],
1038
+ property: "isMemberOf",
1039
+ value: { "@id": "./" },
1040
+ });
1041
+ crate = cm.exportCrate();
1042
+
1043
+ expect(crate["@graph"][2]).toMatchObject({
1044
+ isMemberOf: { "@id": "./" },
1045
+ "@reverse": {
1046
+ keywords: [{ "@id": "./" }, { "@id": "#term" }],
1047
+ isKeywordOf: { "@id": "#term" },
1048
+ hasMember: { "@id": "./" },
1049
+ },
1050
+ });
1051
+ expect(crate["@graph"][1]).toMatchObject({
1052
+ hasMember: { "@id": "#term" },
1053
+ "@reverse": { isMemberOf: { "@id": "#term" } },
1054
+ });
1055
+ // console.log(JSON.stringify(crate["@graph"], null, 2));
1056
+
1057
+ cm.unlinkEntity({
1058
+ id: e["@id"],
1059
+ property: "keywords",
1060
+ value: {
1061
+ "@id": e["@id"],
1062
+ },
1063
+ });
1064
+ crate = cm.exportCrate();
1065
+ // console.log(JSON.stringify(crate["@graph"][1], null, 2));
1066
+ // console.log(JSON.stringify(crate["@graph"], null, 2));
1067
+ expect(crate["@graph"][1]).toMatchObject({
1068
+ "@id": "./",
1069
+ "@type": "Dataset",
1070
+ keywords: {
1071
+ "@id": "#term",
1072
+ },
1073
+ hasMember: {
1074
+ "@id": "#term",
1075
+ },
1076
+ "@reverse": {
1077
+ about: {
1078
+ "@id": "ro-crate-metadata.json",
1079
+ },
1080
+ isKeywordOf: {
1081
+ "@id": "#term",
1082
+ },
1083
+ isMemberOf: {
1084
+ "@id": "#term",
1085
+ },
1086
+ },
1087
+ });
1088
+ expect(crate["@graph"][2]).toMatchObject({
1089
+ "@id": "#term",
1090
+ "@type": "DefinedTerm",
1091
+ name: "a term",
1092
+ isKeywordOf: {
1093
+ "@id": "./",
1094
+ },
1095
+ isMemberOf: {
1096
+ "@id": "./",
1097
+ },
1098
+ "@reverse": {
1099
+ keywords: {
1100
+ "@id": "./",
1101
+ },
1102
+ hasMember: {
1103
+ "@id": "./",
1104
+ },
1105
+ },
1106
+ });
1107
+
1108
+ // cm.deleteEntity({ id: e["@id"] });
1109
+
1110
+ // console.log("BEFORE");
1111
+ // crate = cm.exportCrate();
1112
+ // // console.log(JSON.stringify(crate["@graph"], null, 2));
1113
+ // expect(crate["@graph"].length).toEqual(2);
1114
+
1115
+ // // add it back in and link it to root dataset via keywords
1116
+ // cm.addEntity(entity);
1117
+ // cm.linkEntity({
1118
+ // id: "./",
1119
+ // property: "keywords",
1120
+ // value: { "@id": e["@id"] },
1121
+ // });
1122
+ // crate = cm.exportCrate();
1123
+ // // console.log(JSON.stringify(crate["@graph"], null, 2));
1124
+
1125
+ // // now unlink from the root dataset via keywords prop
1126
+ // cm.unlinkEntity({
1127
+ // id: "./",
1128
+ // property: "keywords",
1129
+ // value: {
1130
+ // "@id": e["@id"],
1131
+ // },
1132
+ // });
1133
+ // crate = cm.exportCrate();
1134
+ // expect(crate["@graph"][2]).toMatchObject({
1135
+ // "@id": "#term",
1136
+ // "@type": "DefinedTerm",
1137
+ // name: "a term",
1138
+ // "@reverse": {},
1139
+ // });
1140
+ });
1141
+ test("a sequence of complex '@id' updates across the crate", () => {
1142
+ let entity = {
1143
+ "@id": "an id",
1144
+ "@type": "Person",
1145
+ name: "person",
1146
+ };
1147
+ let e1 = cm.addEntity(entity);
1148
+ cm.linkEntity({ id: "./", property: "author", value: { "@id": e1["@id"] } });
1149
+
1150
+ e1 = cm.getEntity({ id: e1["@id"] }) as NormalisedEntityDefinition;
1151
+ expect(e1).toMatchObject({ "@id": "#an%20id", "@type": ["Person"], name: "person" });
1152
+
1153
+ let entityChild = {
1154
+ "@id": "child id",
1155
+ "@type": "Person",
1156
+ name: "child",
1157
+ };
1158
+ let e2 = cm.addEntity(entityChild);
1159
+ cm.linkEntity({ id: "#an%20id", property: "child", value: { "@id": e2["@id"] } });
1160
+ e2 = cm.getEntity({ id: e2["@id"] }) as NormalisedEntityDefinition;
1161
+ expect(e2).toMatchObject({ "@id": "#child%20id", "@type": ["Person"], name: "child" });
1162
+
1163
+ let ec = cm.exportCrate();
1164
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1165
+ expect(ec["@graph"]).toMatchObject([
1166
+ { "@id": "ro-crate-metadata.json" },
1167
+ { "@id": "./", author: { "@id": "#an%20id" } },
1168
+ {
1169
+ "@id": "#an%20id",
1170
+ child: {
1171
+ "@id": "#child%20id",
1172
+ },
1173
+ "@reverse": {
1174
+ author: {
1175
+ "@id": "./",
1176
+ },
1177
+ },
1178
+ },
1179
+ {
1180
+ "@id": "#child%20id",
1181
+ "@reverse": {
1182
+ child: {
1183
+ "@id": "#an%20id",
1184
+ },
1185
+ },
1186
+ },
1187
+ ]);
1188
+
1189
+ // change the id to #a new id
1190
+ cm.updateProperty({ id: e1["@id"], property: "@id", value: "a new id" });
1191
+ ec = cm.exportCrate();
1192
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1193
+ expect(ec["@graph"]).toMatchObject([
1194
+ { "@id": "ro-crate-metadata.json" },
1195
+ { "@id": "./", author: { "@id": "#a%20new%20id" } },
1196
+ {
1197
+ "@id": "#a%20new%20id",
1198
+ },
1199
+ {
1200
+ "@id": "#child%20id",
1201
+ "@reverse": {
1202
+ child: {
1203
+ "@id": "#a%20new%20id",
1204
+ },
1205
+ },
1206
+ },
1207
+ ]);
1208
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1209
+
1210
+ // change the id to http://schema.org/person
1211
+ cm.updateProperty({
1212
+ id: "#a%20new%20id",
1213
+ property: "@id",
1214
+ value: "http://schema.org/person",
1215
+ });
1216
+ ec = cm.exportCrate();
1217
+ expect(ec["@graph"]).toMatchObject([
1218
+ { "@id": "ro-crate-metadata.json" },
1219
+ { "@id": "./", author: { "@id": "http://schema.org/person" } },
1220
+ {
1221
+ "@id": "http://schema.org/person",
1222
+ },
1223
+ {
1224
+ "@id": "#child%20id",
1225
+ "@reverse": {
1226
+ child: {
1227
+ "@id": "http://schema.org/person",
1228
+ },
1229
+ },
1230
+ },
1231
+ ]);
1232
+
1233
+ // change the id to #a new id
1234
+ cm.updateProperty({
1235
+ id: "http://schema.org/person",
1236
+ property: "@id",
1237
+ value: "a new id",
1238
+ });
1239
+ ec = cm.exportCrate();
1240
+ expect(ec["@graph"]).toMatchObject([
1241
+ { "@id": "ro-crate-metadata.json" },
1242
+ { "@id": "./", author: { "@id": "#a%20new%20id" } },
1243
+ {
1244
+ "@id": "#a%20new%20id",
1245
+ },
1246
+ {
1247
+ "@id": "#child%20id",
1248
+ "@reverse": {
1249
+ child: {
1250
+ "@id": "#a%20new%20id",
1251
+ },
1252
+ },
1253
+ },
1254
+ ]);
1255
+
1256
+ // change the id to http://schema.org/person
1257
+ cm.updateProperty({
1258
+ id: "#a%20new%20id",
1259
+ property: "@id",
1260
+ value: "http://schema.org/person",
1261
+ });
1262
+ ec = cm.exportCrate();
1263
+ expect(ec["@graph"]).toMatchObject([
1264
+ { "@id": "ro-crate-metadata.json" },
1265
+ { "@id": "./", author: { "@id": "http://schema.org/person" } },
1266
+ {
1267
+ "@id": "http://schema.org/person",
1268
+ },
1269
+ {
1270
+ "@id": "#child%20id",
1271
+ "@reverse": {
1272
+ child: {
1273
+ "@id": "http://schema.org/person",
1274
+ },
1275
+ },
1276
+ },
1277
+ ]);
1278
+ });
1279
+ test("adding a property that is a link to another entity in the crate", () => {
1280
+ crate = getBaseCrate();
1281
+ crate["@graph"].push({
1282
+ "@id": "./",
1283
+ "@type": "Dataset",
1284
+ name: "rd",
1285
+ });
1286
+ crate["@graph"].push({
1287
+ "@id": "#person",
1288
+ "@type": "Person",
1289
+ name: "author",
1290
+ });
1291
+
1292
+ let cm = new CrateManager({ crate });
1293
+ cm.setProperty({ id: "./", property: "author", value: { "@id": "#person" } });
1294
+
1295
+ let ec = cm.exportCrate();
1296
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1297
+ expect(ec["@graph"]).toMatchObject([
1298
+ { "@id": "ro-crate-metadata.json" },
1299
+ { "@id": "./", author: { "@id": "#person" } },
1300
+ { "@id": "#person", "@reverse": { author: { "@id": "./" } } },
1301
+ ]);
1302
+ });
1303
+ test("adding a property that is not a link to another entity in the crate", () => {
1304
+ crate = getBaseCrate();
1305
+ crate["@graph"].push({
1306
+ "@id": "./",
1307
+ "@type": "Dataset",
1308
+ name: "rd",
1309
+ });
1310
+
1311
+ let cm = new CrateManager({ crate });
1312
+ cm.setProperty({ id: "./", property: "author", value: { "@id": "#person" } });
1313
+
1314
+ let ec = cm.exportCrate();
1315
+ expect(ec["@graph"]).toMatchObject([
1316
+ { "@id": "ro-crate-metadata.json" },
1317
+ { "@id": "./", author: { "@id": "#person" } },
1318
+ ]);
1319
+ });
1320
+ test("add an entity to the crate and then get it back", () => {
1321
+ let entity = {
1322
+ "@id": chance.url(),
1323
+ "@type": "Person",
1324
+ name: chance.sentence(),
1325
+ };
1326
+ let e = cm.addEntity(entity);
1327
+ let match = cm.getEntity({ id: entity["@id"] });
1328
+ expect(match).toMatchObject(e);
1329
+ });
1330
+ test("find entities by id, type, name", () => {
1331
+ let id = chance.url();
1332
+ let name = chance.sentence();
1333
+ let entity = {
1334
+ "@id": id,
1335
+ "@type": "Person",
1336
+ name: name,
1337
+ };
1338
+ let e = cm.addEntity(entity);
1339
+ let match = [...cm.getEntities({ query: id })];
1340
+ expect(match.length).toEqual(1);
1341
+
1342
+ match = [...cm.getEntities({ query: id, type: "Person" })];
1343
+ expect(match.length).toEqual(1);
1344
+
1345
+ match = [...cm.getEntities({ query: name.slice(0, 3), type: "Person" })];
1346
+ expect(match.length).toEqual(1);
1347
+
1348
+ match = [...cm.getEntities({ type: "Pers" })];
1349
+ expect(match.length).toEqual(1);
1350
+
1351
+ match = [
1352
+ ...cm.getEntities({
1353
+ limit: 0,
1354
+ type: "Perso",
1355
+ query: name.slice(0, 3),
1356
+ }),
1357
+ ];
1358
+ expect(match.length).toEqual(1);
1359
+
1360
+ try {
1361
+ [...cm.getEntities({ query: {} as any })];
1362
+ } catch (error) {
1363
+ expect((error as Error).message).toEqual(`query must be a string`);
1364
+ }
1365
+ try {
1366
+ [...cm.getEntities({ type: {} as any })];
1367
+ } catch (error) {
1368
+ expect((error as Error).message).toEqual(`type must be a string`);
1369
+ }
1370
+ });
1371
+ test("add an entity then delete it and confirm it's gone", () => {
1372
+ let entity = {
1373
+ "@id": chance.url(),
1374
+ "@type": "Person",
1375
+ name: chance.sentence(),
1376
+ };
1377
+ let e = cm.addEntity(entity);
1378
+ cm.deleteEntity({ id: e["@id"] });
1379
+ let match = cm.getEntity({ id: entity["@id"] });
1380
+ expect(match).toBeUndefined;
1381
+ });
1382
+ test("exporting a simple crate file without unlinked entities", async () => {
1383
+ let crate = getBaseCrate();
1384
+ crate = addRootDataset({ crate });
1385
+
1386
+ let cm = new CrateManager({ crate });
1387
+ let entity = {
1388
+ "@id": chance.url(),
1389
+ "@type": "Person",
1390
+ name: chance.sentence(),
1391
+ };
1392
+ let e = cm.addEntity(entity);
1393
+ cm.deleteEntity({ id: e["@id"] });
1394
+
1395
+ let ec = cm.exportCrate();
1396
+ expect(ec["@graph"]).toMatchObject([
1397
+ {
1398
+ "@id": "ro-crate-metadata.json",
1399
+ },
1400
+ {
1401
+ "@id": "./",
1402
+ },
1403
+ ]);
1404
+ });
1405
+ test("crate exporting", async () => {
1406
+ let crate = getBaseCrate();
1407
+ crate = addRootDataset({ crate });
1408
+
1409
+ let cm = new CrateManager({ crate });
1410
+
1411
+ // export base crate
1412
+ let ec = cm.exportCrate();
1413
+ expect(ec["@graph"]).toMatchObject([{ "@id": "ro-crate-metadata.json" }, { "@id": "./" }]);
1414
+
1415
+ // add entity and export
1416
+ let entity = {
1417
+ "@id": chance.url(),
1418
+ "@type": "Person",
1419
+ name: chance.sentence(),
1420
+ };
1421
+ cm.addEntity(entity);
1422
+ ec = cm.exportCrate();
1423
+ expect(ec["@graph"]).toMatchObject([
1424
+ { "@id": "ro-crate-metadata.json" },
1425
+ { "@id": "./" },
1426
+ { "@id": entity["@id"] },
1427
+ ]);
1428
+
1429
+ // link ./ -> entity and export
1430
+ cm.setProperty({ id: "./", property: "author", value: entity });
1431
+ ec = cm.exportCrate();
1432
+ expect(ec["@graph"]).toMatchObject([
1433
+ { "@id": "ro-crate-metadata.json" },
1434
+ { "@id": "./", author: { "@id": entity["@id"] } },
1435
+ { "@id": entity["@id"], "@reverse": { author: { "@id": "./" } } },
1436
+ ]);
1437
+
1438
+ // link entity -> ./ and export - ie ensure it can handle circular refs
1439
+ cm.setProperty({ id: entity["@id"], property: "isAuthorOf", value: { "@id": "./" } });
1440
+ ec = cm.exportCrate();
1441
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1442
+ expect(ec["@graph"]).toMatchObject([
1443
+ { "@id": "ro-crate-metadata.json" },
1444
+ { "@id": "./", "@reverse": { isAuthorOf: { "@id": entity["@id"] } } },
1445
+ { "@id": entity["@id"], isAuthorOf: { "@id": "./" } },
1446
+ ]);
1447
+ });
1448
+ test("fail flattening", () => {
1449
+ try {
1450
+ let flattened = cm.flatten([] as any);
1451
+ } catch (error) {
1452
+ expect((error as Error).message).toEqual(`flatten only takes an object.`);
1453
+ }
1454
+ });
1455
+ test(`should be able to flatten a complex entity - like one coming from a datapack`, async () => {
1456
+ const json = {
1457
+ "@id": "http://some.thing",
1458
+ "@type": "Thing",
1459
+ name: "level1",
1460
+ level: {
1461
+ "@id": "http://2.some.thing",
1462
+ "@type": "Thing",
1463
+ name: "level2",
1464
+ level: {
1465
+ "@id": "http://3.some.thing",
1466
+ "@type": "Thing",
1467
+ name: "level3",
1468
+ },
1469
+ other: [
1470
+ {
1471
+ "@id": "http://4.some.thing",
1472
+ "@type": "Thing",
1473
+ name: "level4",
1474
+ },
1475
+ {
1476
+ "@id": "http://5.some.thing",
1477
+ "@type": "Thing",
1478
+ name: "level5",
1479
+ },
1480
+ {
1481
+ "@id": "http://5.some.thing",
1482
+ "@type": "Thing",
1483
+ name: "level5",
1484
+ },
1485
+ ],
1486
+ },
1487
+ };
1488
+ let flattened = cm.flatten(json);
1489
+ // console.log(JSON.stringify(flattened, null, 2));
1490
+
1491
+ expect(flattened).toMatchObject([
1492
+ {
1493
+ "@id": "http://some.thing",
1494
+ "@type": "Thing",
1495
+ name: "level1",
1496
+ level: [
1497
+ {
1498
+ "@id": "http://2.some.thing",
1499
+ },
1500
+ ],
1501
+ },
1502
+ {
1503
+ "@id": "http://2.some.thing",
1504
+ "@type": "Thing",
1505
+ name: "level2",
1506
+ level: [
1507
+ {
1508
+ "@id": "http://3.some.thing",
1509
+ },
1510
+ ],
1511
+ other: [
1512
+ {
1513
+ "@id": "http://4.some.thing",
1514
+ },
1515
+ {
1516
+ "@id": "http://5.some.thing",
1517
+ },
1518
+ {
1519
+ "@id": "http://5.some.thing",
1520
+ },
1521
+ ],
1522
+ },
1523
+ {
1524
+ "@id": "http://3.some.thing",
1525
+ "@type": "Thing",
1526
+ name: "level3",
1527
+ },
1528
+ {
1529
+ "@id": "http://4.some.thing",
1530
+ "@type": "Thing",
1531
+ name: "level4",
1532
+ },
1533
+ {
1534
+ "@id": "http://5.some.thing",
1535
+ "@type": "Thing",
1536
+ name: "level5",
1537
+ },
1538
+ {
1539
+ "@id": "http://5.some.thing",
1540
+ "@type": "Thing",
1541
+ name: "level5",
1542
+ },
1543
+ ]);
1544
+
1545
+ cm.ingestAndLink({
1546
+ id: "./",
1547
+ property: "language",
1548
+ propertyId: "https://schema.org/languageId",
1549
+ json,
1550
+ });
1551
+ let ec = cm.exportCrate();
1552
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1553
+ expect(ec["@graph"]).toMatchObject([
1554
+ {
1555
+ "@id": "ro-crate-metadata.json",
1556
+ },
1557
+ {
1558
+ "@id": "./",
1559
+ language: {
1560
+ "@id": "http://some.thing",
1561
+ },
1562
+ },
1563
+ {
1564
+ "@id": "http://some.thing",
1565
+ name: "level1",
1566
+ level: {
1567
+ "@id": "http://2.some.thing",
1568
+ },
1569
+ "@reverse": {
1570
+ language: { "@id": "./" },
1571
+ },
1572
+ },
1573
+ {
1574
+ "@id": "http://2.some.thing",
1575
+ name: "level2",
1576
+ level: {
1577
+ "@id": "http://3.some.thing",
1578
+ },
1579
+ other: [
1580
+ {
1581
+ "@id": "http://4.some.thing",
1582
+ },
1583
+ {
1584
+ "@id": "http://5.some.thing",
1585
+ },
1586
+ {
1587
+ "@id": "http://5.some.thing",
1588
+ },
1589
+ ],
1590
+ "@reverse": {
1591
+ level: { "@id": "http://some.thing" },
1592
+ },
1593
+ },
1594
+ {
1595
+ "@id": "http://3.some.thing",
1596
+ name: "level3",
1597
+ "@reverse": {
1598
+ level: { "@id": "http://2.some.thing" },
1599
+ },
1600
+ },
1601
+ {
1602
+ "@id": "http://4.some.thing",
1603
+ name: "level4",
1604
+ "@reverse": {
1605
+ other: { "@id": "http://2.some.thing" },
1606
+ },
1607
+ },
1608
+ {
1609
+ "@id": "http://5.some.thing",
1610
+ name: "level5",
1611
+ "@reverse": {
1612
+ other: { "@id": "http://2.some.thing" },
1613
+ },
1614
+ },
1615
+ ]);
1616
+ });
1617
+ test(`it should handle ingesting json objects with text arrays`, async () => {
1618
+ let json = {
1619
+ "@id": "http://some.thing",
1620
+ "@type": "Thing",
1621
+ alternateName: ["name1", "name2", "name3"],
1622
+ };
1623
+ cm.ingestAndLink({
1624
+ id: "./",
1625
+ property: "language",
1626
+ propertyId: "https://schema.org/languageId",
1627
+ json,
1628
+ });
1629
+ let ec = cm.exportCrate();
1630
+ expect(ec["@graph"]).toMatchObject([
1631
+ {
1632
+ "@id": "ro-crate-metadata.json",
1633
+ },
1634
+ {
1635
+ "@id": "./",
1636
+ language: {
1637
+ "@id": "http://some.thing",
1638
+ },
1639
+ },
1640
+ {
1641
+ "@id": "http://some.thing",
1642
+ "@reverse": {
1643
+ language: {
1644
+ "@id": "./",
1645
+ },
1646
+ },
1647
+ },
1648
+ ]);
1649
+ });
1650
+ test(`it should handle ingesting json objects whilst ignoring empty properties`, async () => {
1651
+ let json = {
1652
+ "@id": "http://some.thing",
1653
+ "@type": "Thing",
1654
+ alternateName: "",
1655
+ };
1656
+ cm.ingestAndLink({
1657
+ id: "./",
1658
+ property: "language",
1659
+ propertyId: "https://schema.org/languageId",
1660
+ json,
1661
+ });
1662
+ let ec = cm.exportCrate();
1663
+ expect(ec["@graph"]).toMatchObject([
1664
+ {
1665
+ "@id": "ro-crate-metadata.json",
1666
+ },
1667
+ {
1668
+ "@id": "./",
1669
+ language: {
1670
+ "@id": "http://some.thing",
1671
+ },
1672
+ },
1673
+ {
1674
+ "@id": "http://some.thing",
1675
+ "@reverse": {
1676
+ language: {
1677
+ "@id": "./",
1678
+ },
1679
+ },
1680
+ },
1681
+ ]);
1682
+ expect(ec["@graph"][2]).not.toHaveProperty("alternateName");
1683
+ });
1684
+ test(`it should be able to handle self links`, async () => {
1685
+ const json = {
1686
+ "@id": "http://some.thing",
1687
+ "@type": "Thing",
1688
+ name: "level1",
1689
+ level: {
1690
+ "@id": "http://some.thing",
1691
+ "@type": "Thing",
1692
+ name: "level2",
1693
+ },
1694
+ };
1695
+ cm.ingestAndLink({
1696
+ id: "./",
1697
+ property: "author",
1698
+ propertyId: "https://schema.org/author",
1699
+ json,
1700
+ });
1701
+ let ec = cm.exportCrate();
1702
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1703
+
1704
+ // delete the author property from the root dataset
1705
+ cm.deleteProperty({
1706
+ id: "./",
1707
+ property: "author",
1708
+ idx: 0,
1709
+ });
1710
+
1711
+ ec = cm.exportCrate();
1712
+ // console.log(JSON.stringify(ec["@graph"], null, 2));
1713
+
1714
+ expect(ec["@graph"].length).toEqual(3);
1715
+ expect(ec["@graph"]).toMatchObject([
1716
+ {
1717
+ "@id": "ro-crate-metadata.json",
1718
+ },
1719
+ {
1720
+ "@id": "./",
1721
+ "@type": "Dataset",
1722
+ },
1723
+ {
1724
+ "@id": "http://some.thing",
1725
+ level: {
1726
+ "@id": "http://some.thing",
1727
+ },
1728
+ },
1729
+ ]);
1730
+ });
1731
+ test(`it should be able to ingest a complex entity, unlink it, and remove all descendants`, async () => {
1732
+ const json = {
1733
+ "@id": "http://some.thing",
1734
+ "@type": "Thing",
1735
+ name: "level1",
1736
+ level: {
1737
+ "@id": "http://some.other.thing",
1738
+ "@type": "Thing",
1739
+ name: "level2",
1740
+ },
1741
+ };
1742
+ cm.ingestAndLink({
1743
+ id: "./",
1744
+ property: "author",
1745
+ propertyId: "https://schema.org/author",
1746
+ json,
1747
+ });
1748
+
1749
+ // delete the author property from the root dataset
1750
+ cm.deleteProperty({
1751
+ id: "./",
1752
+ property: "author",
1753
+ idx: 0,
1754
+ });
1755
+
1756
+ cm.purgeUnlinkedEntities();
1757
+ let ec = cm.exportCrate();
1758
+ expect(ec["@graph"]).toMatchObject([{ "@id": "ro-crate-metadata.json" }, { "@id": "./" }]);
1759
+ });
1760
+ test(`check purging unlinked nodes; confirm it handles linking type entities like relationships`, () => {
1761
+ // add a blank node not linked to anything
1762
+ cm.addBlankNode("Relationship");
1763
+
1764
+ // and an entity linked to root dataset
1765
+ cm.ingestAndLink({
1766
+ id: "./",
1767
+ property: "people",
1768
+ propertyId: "https://schema.org/author",
1769
+ json: { "@id": "#1", "@type": "Person", name: "#1" },
1770
+ });
1771
+
1772
+ // confirm they are both there
1773
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
1774
+ { "@id": "ro-crate-metadata.json" },
1775
+ { "@id": "./" },
1776
+ { "@id": "_:Relationship1" },
1777
+ { "@id": "#1" },
1778
+ ]);
1779
+
1780
+ // purge unlinked and confirm blank node gone
1781
+ cm.purgeUnlinkedEntities();
1782
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
1783
+ { "@id": "ro-crate-metadata.json" },
1784
+ { "@id": "./" },
1785
+ { "@id": "#1" },
1786
+ ]);
1787
+
1788
+ // re add blank node and link to the entity in the graph
1789
+ const e = cm.addBlankNode("Relationship");
1790
+ cm.setProperty({
1791
+ id: e["@id"],
1792
+ property: "object",
1793
+ propertyId: "https://schema.org/object",
1794
+ value: { "@id": "#1" },
1795
+ });
1796
+ // console.log(cm.exportCrate()["@graph"]);
1797
+ cm.purgeUnlinkedEntities();
1798
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
1799
+ { "@id": "ro-crate-metadata.json" },
1800
+ { "@id": "./" },
1801
+ { "@id": "#1" },
1802
+ { "@id": "_:Relationship2" },
1803
+ ]);
1804
+ // console.log(cm.exportCrate()["@graph"]);
1805
+ });
1806
+ test("resolve entity associations", async () => {
1807
+ crate = getBaseCrate();
1808
+ crate["@graph"].push({
1809
+ "@id": "./",
1810
+ "@type": "Dataset",
1811
+ name: "root dataset",
1812
+ text: "something",
1813
+ });
1814
+ cm = new CrateManager({ crate });
1815
+ let profile = await readJSON("./src/examples/profile/profile-with-resolve.json");
1816
+
1817
+ let entity: any = {
1818
+ "@id": "#createAction1",
1819
+ "@type": ["CreateAction"],
1820
+ name: "A very long named create action to demonstrate what happens with display of long names",
1821
+ object: { "@id": "#person2" },
1822
+ participant: { "@id": "#participant1" },
1823
+ agent: { "@id": "#agent1" },
1824
+ };
1825
+ let associations = cm.resolveLinkedEntityAssociations({ entity, profile });
1826
+ expect(associations).toMatchObject([
1827
+ {
1828
+ property: "object",
1829
+ "@id": "#person2",
1830
+ },
1831
+ {
1832
+ property: "participant",
1833
+ "@id": "#participant1",
1834
+ },
1835
+ {
1836
+ property: "agent",
1837
+ "@id": "#agent1",
1838
+ },
1839
+ ]);
1840
+
1841
+ entity = {
1842
+ "@id": "#relationship",
1843
+ "@type": ["Relationship", "RelatedEntity"],
1844
+ source: [{ "@id": "#person1" }, { "@id": "#person2" }],
1845
+ target: { "@id": "#thing1" } as any,
1846
+ };
1847
+ associations = cm.resolveLinkedEntityAssociations({ entity, profile });
1848
+ expect(associations).toMatchObject([
1849
+ {
1850
+ property: "source",
1851
+ "@id": "#person1",
1852
+ },
1853
+ {
1854
+ property: "source",
1855
+ "@id": "#person2",
1856
+ },
1857
+ {
1858
+ property: "target",
1859
+ "@id": "#thing1",
1860
+ },
1861
+ ]);
1862
+ });
1863
+ test(`context handling - pass in context to be used as is`, () => {
1864
+ cm = new CrateManager({
1865
+ crate,
1866
+ context: "https://www.researchobject.org/ro-crate/1.1/context.jsonld",
1867
+ });
1868
+ let context = cm.getContext();
1869
+ // console.log(context);
1870
+ expect(context).toEqual(["https://www.researchobject.org/ro-crate/1.1/context.jsonld"]);
1871
+
1872
+ cm = new CrateManager({
1873
+ crate,
1874
+ context: ["https://www.researchobject.org/ro-crate/1.1/context.jsonld"],
1875
+ });
1876
+ context = cm.getContext();
1877
+ expect(context).toEqual(["https://www.researchobject.org/ro-crate/1.1/context.jsonld"]);
1878
+
1879
+ cm = new CrateManager({
1880
+ crate,
1881
+ context: [
1882
+ "https://w3id.org/ro/crate/1.1/context",
1883
+ { foaf: "http://xmlns.com/foaf/0.1" },
1884
+ { dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/" },
1885
+ ],
1886
+ });
1887
+ context = cm.getContext();
1888
+ expect(context).toEqual([
1889
+ "https://w3id.org/ro/crate/1.1/context",
1890
+ {
1891
+ foaf: "http://xmlns.com/foaf/0.1",
1892
+ dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/",
1893
+ },
1894
+ ]);
1895
+ });
1896
+ test(`context handling - adding properties to the crate`, () => {
1897
+ crate["@context"] = [
1898
+ "https://w3id.org/ro/crate/1.1/context",
1899
+ "https://my.domain.com/ontology",
1900
+ { foaf: "http://xmlns.com/foaf/0.1/" },
1901
+ { dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/" },
1902
+ {
1903
+ somePropertyInYourDomain:
1904
+ "https://your.domain/path/to/definition#somePropertyInYourDomain",
1905
+ },
1906
+ ];
1907
+ let cm = new CrateManager({ crate });
1908
+ let context = cm.getContext();
1909
+ // console.log(context);
1910
+ expect(context).toMatchObject([
1911
+ "https://w3id.org/ro/crate/1.1/context",
1912
+ "https://my.domain.com/ontology",
1913
+ {
1914
+ foaf: "http://xmlns.com/foaf/0.1/",
1915
+ dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/",
1916
+ somePropertyInYourDomain:
1917
+ "https://your.domain/path/to/definition#somePropertyInYourDomain",
1918
+ },
1919
+ ]);
1920
+
1921
+ // property already defined in context - not added
1922
+ cm.setProperty({
1923
+ id: "./",
1924
+ property: "model",
1925
+ propertyId: "http://schema.org/Person",
1926
+ value: { "@id": "#model" },
1927
+ });
1928
+ context = cm.getContext();
1929
+ // console.log(context);
1930
+ expect(context).toMatchObject([
1931
+ "https://w3id.org/ro/crate/1.1/context",
1932
+ "https://my.domain.com/ontology",
1933
+ {
1934
+ foaf: "http://xmlns.com/foaf/0.1/",
1935
+ dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/",
1936
+ somePropertyInYourDomain:
1937
+ "https://your.domain/path/to/definition#somePropertyInYourDomain",
1938
+ },
1939
+ ]);
1940
+
1941
+ // property not defined - is added
1942
+ cm.setProperty({
1943
+ id: "./",
1944
+ property: "model",
1945
+ propertyId: "http://www.w3.org/ns/prov#actedOnBehalfOf",
1946
+ value: { "@id": "#model" },
1947
+ });
1948
+ context = cm.getContext();
1949
+ // console.log(context);
1950
+ expect(context).toMatchObject([
1951
+ "https://w3id.org/ro/crate/1.1/context",
1952
+ "https://my.domain.com/ontology",
1953
+ {
1954
+ foaf: "http://xmlns.com/foaf/0.1/",
1955
+ dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/",
1956
+ somePropertyInYourDomain:
1957
+ "https://your.domain/path/to/definition#somePropertyInYourDomain",
1958
+ model: "http://www.w3.org/ns/prov#actedOnBehalfOf",
1959
+ },
1960
+ ]);
1961
+
1962
+ // property now defined - not re-added
1963
+ cm.setProperty({
1964
+ id: "./",
1965
+ property: "model",
1966
+ propertyId: "http://www.w3.org/ns/prov#actedOnBehalfOf",
1967
+ value: { "@id": "#model" },
1968
+ });
1969
+ context = cm.getContext();
1970
+ expect(context).toMatchObject([
1971
+ "https://w3id.org/ro/crate/1.1/context",
1972
+ "https://my.domain.com/ontology",
1973
+ {
1974
+ foaf: "http://xmlns.com/foaf/0.1/",
1975
+ dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/",
1976
+ somePropertyInYourDomain:
1977
+ "https://your.domain/path/to/definition#somePropertyInYourDomain",
1978
+ model: "http://www.w3.org/ns/prov#actedOnBehalfOf",
1979
+ },
1980
+ ]);
1981
+
1982
+ let ec = cm.exportCrate();
1983
+ // console.log(JSON.stringify(ec, null, 2));
1984
+ expect(ec).toMatchObject({
1985
+ "@context": [
1986
+ "https://w3id.org/ro/crate/1.1/context",
1987
+ "https://my.domain.com/ontology",
1988
+ {
1989
+ foaf: "http://xmlns.com/foaf/0.1/",
1990
+ dcterms: "https://www.dublincore.org/specifications/dublin-core/dcmi-terms/",
1991
+ somePropertyInYourDomain:
1992
+ "https://your.domain/path/to/definition#somePropertyInYourDomain",
1993
+ model: "http://www.w3.org/ns/prov#actedOnBehalfOf",
1994
+ },
1995
+ ],
1996
+ "@graph": [
1997
+ {
1998
+ "@id": "ro-crate-metadata.json",
1999
+ },
2000
+ {
2001
+ "@id": "./",
2002
+ "@type": "Dataset",
2003
+ model: {
2004
+ "@id": "#model",
2005
+ },
2006
+ },
2007
+ ],
2008
+ });
2009
+ });
2010
+ test(`storing / removing entity types for lookups`, () => {
2011
+ let cm = new CrateManager({ crate });
2012
+
2013
+ expect(cm.entityTypes).toEqual({ CreativeWork: 1, Dataset: 1 });
2014
+ expect(cm.getEntityTypes()).toEqual(["CreativeWork", "Dataset"]);
2015
+
2016
+ let entity = {
2017
+ "@id": "file1.jpg",
2018
+ "@type": "File",
2019
+ name: "file1.jpg",
2020
+ };
2021
+ let r = cm.addEntity(entity);
2022
+ expect(cm.getEntityTypes()).toEqual(["CreativeWork", "Dataset", "File"]);
2023
+
2024
+ cm.updateProperty({ id: r["@id"], property: "@type", idx: 0, value: "Cow" });
2025
+ expect(cm.getEntityTypes()).toEqual(["Cow", "CreativeWork", "Dataset"]);
2026
+
2027
+ cm.deleteEntity({ id: r["@id"] });
2028
+ expect(cm.getEntityTypes()).toEqual(["CreativeWork", "Dataset"]);
2029
+ });
2030
+ test("test creating blank nodes", () => {
2031
+ let cm = new CrateManager({ crate });
2032
+
2033
+ let e = cm.addBlankNode("Relationship");
2034
+ expect(e).toEqual({
2035
+ "@id": "_:Relationship1",
2036
+ "@type": ["Relationship"],
2037
+ name: "_:Relationship1",
2038
+ });
2039
+
2040
+ e = cm.addBlankNode("Relationship");
2041
+ expect(e).toEqual({
2042
+ "@id": "_:Relationship2",
2043
+ "@type": ["Relationship"],
2044
+ name: "_:Relationship2",
2045
+ });
2046
+
2047
+ e = cm.addBlankNode("GeoShape");
2048
+ expect(e).toEqual({
2049
+ "@id": "_:GeoShape1",
2050
+ "@type": ["GeoShape"],
2051
+ name: "_:GeoShape1",
2052
+ });
2053
+
2054
+ e = cm.addBlankNode("CreateAction");
2055
+ expect(e).toEqual({
2056
+ "@id": "_:CreateAction1",
2057
+ "@type": ["CreateAction"],
2058
+ name: "_:CreateAction1",
2059
+ });
2060
+
2061
+ // ensure we don't clash with pre-existing blank nodes
2062
+ e = cm.addBlankNode("Relationship");
2063
+ expect(e).toMatchObject({
2064
+ "@id": "_:Relationship3",
2065
+ "@type": ["Relationship"],
2066
+ name: "_:Relationship3",
2067
+ });
2068
+ });
2069
+ test(`test locating entities - strict matching`, () => {
2070
+ let cm = new CrateManager({ crate });
2071
+
2072
+ const r1 = cm.addBlankNode("Relationship");
2073
+ const r2 = cm.addBlankNode("Relationship");
2074
+ const e1 = cm.addEntity({ "@id": "/a/b/file1.txt", "@type": "File", name: "/file1.txt" });
2075
+ const e2 = cm.addEntity({ "@id": "/file2.txt", "@type": "File", name: "/file2.txt" });
2076
+ const e3 = cm.addFile("/a/c/d/file.png");
2077
+ const e4 = cm.addFile("/a/c/d/file with spaces.png");
2078
+
2079
+ // link e1 and e2 entities to r1
2080
+ cm.linkEntity({
2081
+ id: r1["@id"],
2082
+ property: "object",
2083
+ propertyId: "https://schema.org/object",
2084
+ value: { "@id": e1["@id"] },
2085
+ });
2086
+
2087
+ cm.linkEntity({
2088
+ id: r1["@id"],
2089
+ property: "object",
2090
+ propertyId: "https://schema.org/object",
2091
+ value: { "@id": e2["@id"] },
2092
+ });
2093
+
2094
+ // link all of the entities to r2
2095
+ cm.linkEntity({
2096
+ id: r2["@id"],
2097
+ property: "object",
2098
+ propertyId: "https://schema.org/object",
2099
+ value: { "@id": e2["@id"] },
2100
+ });
2101
+ cm.linkEntity({
2102
+ id: r2["@id"],
2103
+ property: "object",
2104
+ propertyId: "https://schema.org/object",
2105
+ value: { "@id": e3["@id"] },
2106
+ });
2107
+ cm.linkEntity({
2108
+ id: r2["@id"],
2109
+ property: "object",
2110
+ propertyId: "https://schema.org/object",
2111
+ value: { "@id": e4["@id"] },
2112
+ });
2113
+
2114
+ // console.log(JSON.stringify(cm.exportCrate()["@graph"], null, 2));
2115
+
2116
+ // won't find a matching entity
2117
+ let matches = cm.locateEntity({ entityIds: ["file1.txt", "/file2.txt"] });
2118
+ expect(matches).toEqual(undefined);
2119
+
2120
+ // will not find a matching entity - not an exact match
2121
+ matches = cm.locateEntity({ entityIds: ["/file1.txt"] });
2122
+ expect(matches).toEqual(undefined);
2123
+
2124
+ // will find a matching entity - an exact match
2125
+ matches = cm.locateEntity({ entityIds: ["/a/b/file1.txt", "/file2.txt"] });
2126
+ expect(matches).toMatchObject([
2127
+ {
2128
+ "@id": "_:Relationship1",
2129
+ "@type": ["Relationship"],
2130
+ name: "_:Relationship1",
2131
+ },
2132
+ ]);
2133
+
2134
+ // will find a matching entity - an exact match
2135
+ matches = cm.locateEntity({ entityIds: ["/file2.txt", "/a/b/file1.txt"] });
2136
+ expect(matches).toMatchObject([
2137
+ {
2138
+ "@id": "_:Relationship1",
2139
+ "@type": ["Relationship"],
2140
+ name: "_:Relationship1",
2141
+ },
2142
+ ]);
2143
+
2144
+ // will not find a matching entity - no match
2145
+ matches = cm.locateEntity({ entityIds: ["/file2.pdf", "/file1.txt"] });
2146
+ expect(matches).toEqual(undefined);
2147
+
2148
+ // will find a matching entity - an exact match
2149
+ matches = cm.locateEntity({
2150
+ entityIds: ["a/c/d/file.png", "/file2.txt", "a/c/d/file with spaces.png"],
2151
+ });
2152
+ expect(matches).toMatchObject([
2153
+ {
2154
+ "@id": "_:Relationship2",
2155
+ "@type": ["Relationship"],
2156
+ name: "_:Relationship2",
2157
+ },
2158
+ ]);
2159
+
2160
+ matches = cm.locateEntity({
2161
+ entityIds: ["/file2.txt", "a/c/d/file.png", "a/c/d/file with spaces.png"],
2162
+ });
2163
+ expect(matches).toMatchObject([
2164
+ {
2165
+ "@id": "_:Relationship2",
2166
+ "@type": ["Relationship"],
2167
+ name: "_:Relationship2",
2168
+ },
2169
+ ]);
2170
+
2171
+ matches = cm.locateEntity({
2172
+ entityIds: ["/file2.txt", "a/c/d/file.png", "a/c/d/file with spaces.png"],
2173
+ });
2174
+ expect(matches).toMatchObject([
2175
+ {
2176
+ "@id": "_:Relationship2",
2177
+ "@type": ["Relationship"],
2178
+ name: "_:Relationship2",
2179
+ },
2180
+ ]);
2181
+ });
2182
+ test(`test locating entities - subset matching`, () => {
2183
+ let cm = new CrateManager({ crate });
2184
+
2185
+ const r1 = cm.addBlankNode("Relationship");
2186
+ const r2 = cm.addBlankNode("Relationship");
2187
+ const e1 = cm.addEntity({ "@id": "/a/b/file1.txt", "@type": "File", name: "/file1.txt" });
2188
+ const e2 = cm.addEntity({ "@id": "/file2.txt", "@type": "File", name: "/file2.txt" });
2189
+ const e3 = cm.addFile("/a/c/d/file.png");
2190
+ const e4 = cm.addFile("/a/c/d/file with spaces.png");
2191
+
2192
+ // link e1 and e2 entities to r1
2193
+ cm.linkEntity({
2194
+ id: r1["@id"],
2195
+ property: "object",
2196
+ propertyId: "https://schema.org/object",
2197
+ value: { "@id": e1["@id"] },
2198
+ });
2199
+
2200
+ cm.linkEntity({
2201
+ id: r1["@id"],
2202
+ property: "object",
2203
+ propertyId: "https://schema.org/object",
2204
+ value: { "@id": e2["@id"] },
2205
+ });
2206
+
2207
+ // link e2, e3 and e4 to r2
2208
+ cm.linkEntity({
2209
+ id: r2["@id"],
2210
+ property: "object",
2211
+ propertyId: "https://schema.org/object",
2212
+ value: { "@id": e2["@id"] },
2213
+ });
2214
+ cm.linkEntity({
2215
+ id: r2["@id"],
2216
+ property: "object",
2217
+ propertyId: "https://schema.org/object",
2218
+ value: { "@id": e3["@id"] },
2219
+ });
2220
+ cm.linkEntity({
2221
+ id: r2["@id"],
2222
+ property: "object",
2223
+ propertyId: "https://schema.org/object",
2224
+ value: { "@id": e4["@id"] },
2225
+ });
2226
+
2227
+ // console.log(JSON.stringify(cm.exportCrate()["@graph"], null, 2));
2228
+
2229
+ // won't find a matching entity
2230
+ let matches = cm.locateEntity({ entityIds: ["file1.txt", "/file2.txt"] });
2231
+ expect(matches).toEqual(undefined);
2232
+
2233
+ // will find a matching entity - subset match
2234
+ matches = cm.locateEntity({ entityIds: ["/file2.txt"], strict: false });
2235
+ expect(matches).toMatchObject([{ "@id": "_:Relationship1" }, { "@id": "_:Relationship2" }]);
2236
+
2237
+ // will find a matching entity - an exact match
2238
+ matches = cm.locateEntity({ entityIds: ["/a/b/file1.txt", "/file2.txt"], strict: false });
2239
+ expect(matches).toMatchObject([
2240
+ {
2241
+ "@id": "_:Relationship1",
2242
+ },
2243
+ ]);
2244
+ });
2245
+ test("add files and folders to the crate", () => {
2246
+ let e = cm.addFile("/file.txt");
2247
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
2248
+ { "@id": "ro-crate-metadata.json" },
2249
+ { "@id": "./" },
2250
+ { "@id": "file.txt", "@type": "File" },
2251
+ ]);
2252
+ expect(e).toEqual({ "@id": "file.txt", "@type": ["File"], name: "file.txt" });
2253
+
2254
+ e = cm.addFolder("images");
2255
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
2256
+ { "@id": "ro-crate-metadata.json" },
2257
+ { "@id": "./" },
2258
+ { "@id": "file.txt", "@type": "File" },
2259
+ { "@id": "images/", "@type": "Dataset" },
2260
+ ]);
2261
+ expect(e).toEqual({ "@id": "images/", "@type": ["Dataset"], name: "images/" });
2262
+
2263
+ cm.addFile("/folder/file.txt");
2264
+ // console.log(cm.exportCrate()["@graph"]);
2265
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
2266
+ { "@id": "ro-crate-metadata.json" },
2267
+ { "@id": "./" },
2268
+ { "@id": "file.txt", "@type": "File" },
2269
+ { "@id": "images/", "@type": "Dataset" },
2270
+ { "@id": "folder/", "@type": "Dataset" },
2271
+ { "@id": "folder/file.txt", "@type": "File" },
2272
+ ]);
2273
+
2274
+ cm.addFile("/a/b/c/d/file.png");
2275
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
2276
+ { "@id": "ro-crate-metadata.json" },
2277
+ { "@id": "./" },
2278
+ { "@id": "file.txt", "@type": "File" },
2279
+ { "@id": "images/", "@type": "Dataset" },
2280
+ { "@id": "folder/", "@type": "Dataset" },
2281
+ { "@id": "folder/file.txt", "@type": "File" },
2282
+ { "@id": "a/", "@type": "Dataset" },
2283
+ { "@id": "a/b/", "@type": "Dataset" },
2284
+ { "@id": "a/b/c/", "@type": "Dataset" },
2285
+ { "@id": "a/b/c/d/", "@type": "Dataset" },
2286
+ { "@id": "a/b/c/d/file.png", "@type": "File" },
2287
+ ]);
2288
+
2289
+ e = cm.addFolder("/a/j/f/g");
2290
+ // console.log(JSON.ddstringify(cm.exportCrate()["@graph"], null, 2));
2291
+ expect(cm.exportCrate()["@graph"]).toMatchObject([
2292
+ { "@id": "ro-crate-metadata.json" },
2293
+ { "@id": "./" },
2294
+ { "@id": "file.txt", "@type": "File" },
2295
+ { "@id": "images/", "@type": "Dataset" },
2296
+ { "@id": "folder/", "@type": "Dataset" },
2297
+ { "@id": "folder/file.txt", "@type": "File" },
2298
+ { "@id": "a/", "@type": "Dataset" },
2299
+ { "@id": "a/b/", "@type": "Dataset" },
2300
+ { "@id": "a/b/c/", "@type": "Dataset" },
2301
+ { "@id": "a/b/c/d/", "@type": "Dataset" },
2302
+ { "@id": "a/b/c/d/file.png", "@type": "File" },
2303
+ { "@id": "a/j/", "@type": "Dataset" },
2304
+ { "@id": "a/j/f/", "@type": "Dataset" },
2305
+ { "@id": "a/j/f/g/", "@type": "Dataset" },
2306
+ ]);
2307
+ expect(e).toEqual({ "@id": "a/j/f/g/", "@type": ["Dataset"], name: "a/j/f/g/" });
2308
+ });
2309
+ });
2310
+
2311
+ function getBaseCrate(): UnverifiedCrate {
2312
+ return {
2313
+ "@context": ["https://w3id.org/ro/crate/1.1/context"],
2314
+ "@graph": [
2315
+ {
2316
+ "@id": "ro-crate-metadata.json",
2317
+ "@type": "CreativeWork",
2318
+ conformsTo: {
2319
+ "@id": "https://w3id.org/ro/crate/1.1/context",
2320
+ },
2321
+ about: {
2322
+ "@id": "./",
2323
+ },
2324
+ },
2325
+ ],
2326
+ };
2327
+ }
2328
+
2329
+ function addRootDataset({ crate }: { crate: UnverifiedCrate }) {
2330
+ crate["@graph"].push({
2331
+ "@id": "./",
2332
+ "@type": "Dataset",
2333
+ name: "Dataset",
2334
+ });
2335
+ return crate;
2336
+ }