From 503e69687a87c8cc8b9dc0bd8cf1f8a1420b5981 Mon Sep 17 00:00:00 2001 From: Jackson Morgan Date: Mon, 24 Feb 2025 13:32:00 -0500 Subject: [PATCH] Completed tests other than language --- packages/jsonld-dataset-proxy/package.json | 2 +- packages/jsonld-dataset-proxy/src/graphOf.ts | 8 +- .../src/setProxy/ObjectSetProxy.ts | 22 ++ .../src/setProxy/SetProxy.ts | 42 ++-- .../src/setProxy/SubjectSetProxy.ts | 54 +++- .../src/setProxy/WildcardObjectSetProxy.ts | 39 ++- .../src/setProxy/WildcardSubjectSetProxy.ts | 11 +- .../src/setProxy/ldSet/BasicLdSet.ts | 16 +- .../src/util/addObjectToDataset.ts | 3 +- .../test/jsonldDatasetProxy.test.ts | 235 +++++++----------- 10 files changed, 255 insertions(+), 177 deletions(-) diff --git a/packages/jsonld-dataset-proxy/package.json b/packages/jsonld-dataset-proxy/package.json index 65e4029..d4e0a49 100644 --- a/packages/jsonld-dataset-proxy/package.json +++ b/packages/jsonld-dataset-proxy/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "tsc --project tsconfig.build.json", "build:watch": "tsc-watch", - "test": "jest --coverage", + "test": "NODE_NO_WARNINGS=1 jest --coverage", "prepublishOnly": "npm run test && npm run build", "start": "ts-node ./example/example.ts", "start:lang": "ts-node ./example/languageExample.ts", diff --git a/packages/jsonld-dataset-proxy/src/graphOf.ts b/packages/jsonld-dataset-proxy/src/graphOf.ts index 22de920..806a3dd 100644 --- a/packages/jsonld-dataset-proxy/src/graphOf.ts +++ b/packages/jsonld-dataset-proxy/src/graphOf.ts @@ -9,6 +9,7 @@ import { _proxyContext, } from "./types"; import type { LdSet } from "./setProxy/ldSet/LdSet"; +import { getNodeFromRawValue } from "./util/getNodeFromRaw"; /** * Returns the graph for which a defined triple is a member @@ -36,8 +37,11 @@ export function graphOf( if (object == null) { objectNode = null; } else { - const objectProxy = getSubjectProxyFromObject(object); - objectNode = objectProxy[_getUnderlyingNode]; + const datatype = proxyContext.contextUtil.getDataType( + predicate as string, + proxyContext.getRdfType(subjectNode), + ); + objectNode = getNodeFromRawValue(object, proxyContext, datatype) ?? null; } const quads = subjectProxy[_getUnderlyingDataset].match( subjectNode, diff --git a/packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts index 456fd3f..9af9f4b 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts @@ -38,4 +38,26 @@ export class ObjectSetProxy< ); return this; } + + /** + * Clears the set of all values + */ + clear(): void { + for (const value of this) { + this.delete(value); + } + } + + /** + * Deletes an item for the set + * @param value the item to delete + * @returns true if the item was present before deletion + */ + delete(value: T): boolean { + const { dataset } = this.context; + const { subject, predicate, object, graph } = this.getSPOG(value); + const didDelete = dataset.match(subject, predicate, object, graph).size > 0; + dataset.deleteMatches(subject, predicate, object, graph); + return didDelete; + } } diff --git a/packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts index 45e3a32..4eb4950 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts @@ -43,10 +43,11 @@ export abstract class SetProxy< /** * Gets the subject, predicate and object for this set */ - protected abstract getSPO(value?: T): { + protected abstract getSPOG(value?: T): { subject?: SubjectNode; predicate?: PredicateNode; object?: ObjectNode; + graph?: GraphNode; }; protected abstract getNodeOfFocus(quad: Quad): SubjectNode | ObjectNode; @@ -62,29 +63,38 @@ export abstract class SetProxy< return this; } + /** + * The clear method on an abstract set does nothing. + * @deprecated You cannot clear data from an abstract set as it is simply a proxy to an underlying dataset + */ clear(): void { - for (const value of this) { - this.delete(value); - } + console.warn( + 'You\'ve attempted to call "clear" on an abstract set. You cannot clear data from an abstract set as it is simply a proxy to an underlying dataset', + ); + return; } - delete(value: T): boolean { - const { dataset } = this.context; - const { subject, predicate, object } = this.getSPO(value); - dataset.deleteMatches(subject, predicate, object); - return true; + /** + * The delete method on an abstract set does nothing. + * @deprecated You cannot delete data from an abstract set as it is simply a proxy to an underlying dataset + */ + delete(_value: T): boolean { + console.warn( + 'You\'ve attempted to call "clear" on an abstract set. You cannot delete data from an abstract set as it is simply a proxy to an underlying dataset', + ); + return false; } has(value: T): boolean { const { dataset } = this.context; - const { subject, predicate, object } = this.getSPO(value); - return dataset.match(subject, predicate, object).size > 0; + const { subject, predicate, object, graph } = this.getSPOG(value); + return dataset.match(subject, predicate, object, graph).size > 0; } get size() { const { dataset } = this.context; - const { subject, predicate, object } = this.getSPO(); - return dataset.match(subject, predicate, object).size; + const { subject, predicate, object, graph } = this.getSPOG(); + return dataset.match(subject, predicate, object, graph).size; } entries(): IterableIterator<[T, T]> { @@ -105,8 +115,8 @@ export abstract class SetProxy< [Symbol.iterator](): IterableIterator { const { dataset } = this.context; - const { subject, predicate, object } = this.getSPO(); - const quads = dataset.match(subject, predicate, object); + const { subject, predicate, object, graph } = this.getSPOG(); + const quads = dataset.match(subject, predicate, object, graph); const collection: T[] = quads.toArray().map((quad) => { const quadSubject = this.getNodeOfFocus(quad); return nodeToJsonldRepresentation(quadSubject, this.context) as T; @@ -138,4 +148,6 @@ export abstract class SetProxy< get [_writeGraphs](): GraphNode[] { return this.context.writeGraphs; } + + abstract get [_isSubjectOriented](): boolean; } diff --git a/packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts index f867c5f..9e18035 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts @@ -1,10 +1,21 @@ -import type { GraphNode, ObjectNode, PredicateNode } from "@ldo/rdf-utils"; +import { + type GraphNode, + type ObjectNode, + type PredicateNode, +} from "@ldo/rdf-utils"; import type { RawObject } from "../util/RawObject"; import { addObjectToDataset } from "../util/addObjectToDataset"; import type { ProxyContext } from "../ProxyContext"; import { WildcardSubjectSetProxy } from "./WildcardSubjectSetProxy"; import { _getUnderlyingNode } from "../types"; -import { quad } from "@rdfjs/data-model"; +import { defaultGraph, quad } from "@rdfjs/data-model"; +import { + createTransactionDatasetFactory, + TransactionDataset, +} from "@ldo/subscribable-dataset"; +import { createDatasetFactory } from "@ldo/dataset"; +import { getNodeFromRawObject } from "../util/getNodeFromRaw"; +import { nodeToString } from "../util/NodeSet"; export type SubjectSetProxyQuadMatch = [ undefined | null, @@ -27,6 +38,45 @@ export class SubjectSetProxy< * Appends a new element with a specified value to the end of the Set. */ add(value: T): this { + // Undefined is fine no matter what + if (value === undefined) { + return this; + } + if (typeof value !== "object") { + throw new Error( + `Cannot add a literal "${value}"(${typeof value}) to a subject-oriented collection.`, + ); + } + // Create a test dataset to see if the inputted data is valid + const testDataset = new TransactionDataset( + this.context.dataset, + createDatasetFactory(), + createTransactionDatasetFactory(), + ); + addObjectToDataset( + value, + false, + this.context.duplicate({ + writeGraphs: [defaultGraph()], + }), + ); + const isValidAddition = + testDataset.match( + getNodeFromRawObject(value, this.context.contextUtil), + this.quadMatch[1], + this.quadMatch[2], + ).size !== 0; + if (!isValidAddition) { + throw new Error( + `Cannot add value to collection. This must contain a quad that matches (${nodeToString( + this.quadMatch[0], + )}, ${nodeToString(this.quadMatch[1])}, ${nodeToString( + this.quadMatch[2], + )}, ${nodeToString(this.quadMatch[3])})`, + ); + } + + // Add the object if everything's okay const added = addObjectToDataset(value as RawObject, false, this.context); const addedNode = added[_getUnderlyingNode]; this.context.writeGraphs.forEach((graph) => { diff --git a/packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts index e30493f..beb23ab 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts @@ -9,6 +9,7 @@ import type { RawValue } from "../util/RawObject"; import { SetProxy } from "./SetProxy"; import type { ProxyContext } from "../ProxyContext"; import { getNodeFromRawValue } from "../util/getNodeFromRaw"; +import { _isSubjectOriented } from "../types"; export type WildcardObjectSetProxyQuadMatch = [ SubjectNode | undefined | null, @@ -35,14 +36,16 @@ export class WildcardObjectSetProxy< this.quadMatch = quadMatch; } - protected getSPO(value?: T | undefined): { + protected getSPOG(value?: T | undefined): { subject?: SubjectNode; predicate?: PredicateNode; object?: ObjectNode; + graph?: GraphNode; } { // Get the RDF Node that represents the value, skip is no value const subject = this.quadMatch[0] ?? undefined; const predicate = this.quadMatch[1] ?? undefined; + const graph = this.quadMatch[3] ?? undefined; if (value) { // Get datatype if applicable let datatype: string | undefined = undefined; @@ -56,6 +59,7 @@ export class WildcardObjectSetProxy< subject, predicate, object: valueNode, + graph, }; } // SPO for no value @@ -63,6 +67,7 @@ export class WildcardObjectSetProxy< subject, predicate, object: undefined, + graph, }; } @@ -73,11 +78,17 @@ export class WildcardObjectSetProxy< private manuallyMatchWithUnknownObjectNode( subject: SubjectNode | undefined, predicate: PredicateNode | undefined, + graph: GraphNode | undefined, value: T, ): Dataset { // If there's not an object, that means that we don't know the object node // and need to find it manually. - const matchingQuads = this.context.dataset.match(subject, predicate, null); + const matchingQuads = this.context.dataset.match( + subject, + predicate, + null, + graph, + ); return matchingQuads.filter( (quad) => quad.object.termType === "Literal" && quad.object.value === value, @@ -86,32 +97,42 @@ export class WildcardObjectSetProxy< delete(value: T): boolean { const { dataset } = this.context; - const { subject, predicate, object } = this.getSPO(value); + const { subject, predicate, object, graph } = this.getSPOG(value); if (!object) { const matchedQuads = this.manuallyMatchWithUnknownObjectNode( subject, predicate, + graph, value, ); matchedQuads.forEach((quad) => dataset.delete(quad)); return matchedQuads.size > 0; } else { - const willDelete = dataset.match(subject, predicate, object).size > 0; - dataset.deleteMatches(subject, predicate, object); + const willDelete = + dataset.match(subject, predicate, object, graph).size > 0; + dataset.deleteMatches(subject, predicate, object, graph); return willDelete; } } has(value: T): boolean { const { dataset } = this.context; - const { subject, predicate, object } = this.getSPO(value); + const { subject, predicate, object, graph } = this.getSPOG(value); if (!object) { return ( - this.manuallyMatchWithUnknownObjectNode(subject, predicate, value) - .size > 0 + this.manuallyMatchWithUnknownObjectNode( + subject, + predicate, + graph, + value, + ).size > 0 ); } else { - return dataset.match(subject, predicate, object).size > 0; + return dataset.match(subject, predicate, object, graph).size > 0; } } + + get [_isSubjectOriented](): false { + return false; + } } diff --git a/packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts index 415d794..43a97a2 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts @@ -9,6 +9,7 @@ import type { RawObject } from "../util/RawObject"; import { SetProxy } from "./SetProxy"; import type { ProxyContext } from "../ProxyContext"; import { getNodeFromRawObject } from "../util/getNodeFromRaw"; +import { _isSubjectOriented } from "../types"; export type WildcardSubjectSetProxyQuadMatch = [ undefined | null, @@ -33,20 +34,23 @@ export class WildcardSubjectSetProxy extends SetProxy { this.quadMatch = quadMatch; } - protected getSPO(value?: T | undefined): { + protected getSPOG(value?: T | undefined): { subject?: SubjectNode; predicate?: PredicateNode; object?: ObjectNode; + graph?: GraphNode; } { // Get the RDF Node that represents the value, skip is no value const predicate = this.quadMatch[1] ?? undefined; const object = this.quadMatch[2] ?? undefined; + const graph = this.quadMatch[3] ?? undefined; if (value) { const valueNode = getNodeFromRawObject(value, this.context.contextUtil); return { subject: valueNode, predicate, object, + graph, }; } // SPO for no value @@ -54,10 +58,15 @@ export class WildcardSubjectSetProxy extends SetProxy { subject: undefined, predicate, object, + graph, }; } protected getNodeOfFocus(quad: Quad): SubjectNode { return quad.subject as SubjectNode; } + + get [_isSubjectOriented](): true { + return true; + } } diff --git a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts index 2d77fe1..c3ef348 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts @@ -9,20 +9,28 @@ export class BasicLdSet = NonNullable> extends Set implements LdSet { - private hashMap = new Map(); + private hashMap: Map; constructor(values?: Iterable | null) { - super(values); + super(); + this.hashMap = new Map(); + if (values) { + for (const value of values) { + this.add(value); + } + } } - private hashFn(value: T) { + private hashFn(value: T): string { if (typeof value !== "object") return value.toString(); if (value[_getUnderlyingNode]) { return (value[_getUnderlyingNode] as NamedNode | BlankNode).value; } else if (!value["@id"]) { return blankNode().value; - } else { + } else if (typeof value["@id"] === "string") { return value["@id"]; + } else { + return value["@id"].value; } } diff --git a/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts b/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts index 96040ed..37a876c 100644 --- a/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts +++ b/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts @@ -12,6 +12,7 @@ import { languageDeleteMatch, languageKeyToLiteralLanguage, } from "../language/languageUtils"; +import { BasicLdSet } from "../setProxy/ldSet/BasicLdSet"; export function addRawValueToDatasetRecursive( subject: NamedNode | BlankNode, @@ -110,7 +111,7 @@ export function addRawObjectToDatasetRecursive( dataset.deleteMatches(subject, predicate); } } - if (Array.isArray(value)) { + if (value instanceof BasicLdSet) { value.forEach((valueItem) => { addRawValueToDatasetRecursive( subject, diff --git a/packages/jsonld-dataset-proxy/test/jsonldDatasetProxy.test.ts b/packages/jsonld-dataset-proxy/test/jsonldDatasetProxy.test.ts index f60e79e..4168682 100644 --- a/packages/jsonld-dataset-proxy/test/jsonldDatasetProxy.test.ts +++ b/packages/jsonld-dataset-proxy/test/jsonldDatasetProxy.test.ts @@ -37,6 +37,8 @@ import { type Bender, } from "./scopedExampleData"; +global.console.warn = () => {}; + const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { async function getLoadedDataset(): Promise< [Dataset, ObservationShape, JsonldDatasetProxyBuilder] @@ -544,13 +546,9 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { it("Creates a blank node if the id is blank during set", async () => { const [dataset, observation] = await getEmptyObservationDataset(); observation.type = { "@id": "Observation" }; - console.log("1"); observation.subject = { type: { "@id": "Patient" }, name: set("Joe") }; - console.log("2"); expect(observation.subject?.["@id"]).toBeUndefined(); - console.log("here"); expect(observation.subject.name).toContain("Joe"); - console.log("there"); expect( dataset .match( @@ -707,6 +705,10 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { ); }); + // TODO changes to blank node when set undefined on ID + + // TODO deletes the object if delete on an ID + it("Removes all adjoining triples when garbage collection is indicated via the delete operator on an object", async () => { const [dataset, observation] = await getTinyLoadedDataset(); delete observation.subject; @@ -723,25 +725,33 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { ); }); - it("Removes all adjoining triples when garbage collection is indicated via the delete operator on an array", async () => { + it("Removes connecting triples when the delete method is called on a set", async () => { const [dataset, observation] = await getTinyLoadedDataset(); - delete observation.subject?.roommate?.[0]; + const firstRoommate = observation.subject!.roommate!.toArray()[0]; + observation.subject!.roommate!.delete(firstRoommate); expect(dataset.toString()).toBe( - ' .\n .\n .\n "Garrett" .\n', + ' .\n .\n .\n "Garrett" .\n .\n "Rob" .\n .\n', ); }); - it("Removes all adjoining triples when garbage collection is indicated via the delete operator on an array with blank nodes", async () => { + it("Removes connecting triples when the delete method is called on a set with blank nodes", async () => { const [dataset, observation] = await getTinyLoadedDatasetWithBlankNodes(); - const deletedBlankNode = - observation.subject?.roommate?.[0][_getUnderlyingNode]; - delete observation.subject?.roommate?.[0]; - expect(dataset.match(deletedBlankNode).size).toBe(0); + const originalDatasetSize = dataset.size; + const roommate1 = observation.subject!.roommate!.toArray()[0]; + observation.subject!.roommate!.delete(roommate1); + expect(dataset.size).toBe(originalDatasetSize - 1); + expect( + dataset.match( + observation.subject![_getUnderlyingNode], + null, + roommate1[_getUnderlyingNode], + ).size, + ).toBe(0); }); - it("Removes a literal in an array when using the delete operator", async () => { + it("Removes a literal in an array when using the delete method", async () => { const [dataset, observation] = await getTinyLoadedDataset(); - delete observation.subject?.name?.[0]; + observation.subject!.name!.delete("Garrett"); expect(dataset.toString()).toBe( ' .\n .\n .\n .\n .\n "Rob" .\n .\n', ); @@ -826,10 +836,13 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { literal("Tow"), ), ); - expect(patient.name).toEqual(["Joe", "Blow", "Tow"]); + expect(patient.name.size).toBe(3); + expect(patient.name).toContain("Joe"); + expect(patient.name).toContain("Blow"); + expect(patient.name).toContain("Tow"); }); - it("Removes elements from the array even if they were modified by the datastore", async () => { + it("Removes elements from the set even if they were modified by the datastore", async () => { const [dataset, patient] = await getEmptyPatientDataset(); patient.type = { "@id": "Patient" }; patient.name = set("Joe", "Blow"); @@ -840,7 +853,8 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { literal("Blow"), ), ); - expect(patient.name).toEqual(["Joe"]); + expect(patient.name.size).toBe(1); + expect(patient.name).toContain("Joe"); }); it("Removes and adds from the array even if they were modified by the datastore", async () => { @@ -861,14 +875,19 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { literal("Tow"), ), ); - expect(patient.name).toEqual(["Joe", "Tow"]); + expect(patient.name.size).toBe(2); + expect(patient.name).toContain("Joe"); + expect(patient.name).toContain("Tow"); }); it("Prevents duplicates from being added to the array", async () => { const [, patient] = await getArrayLoadedDataset(); const arr = patient.name!; arr.add("Garrett"); - expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]); + expect(arr.size).toBe(3); + expect(arr).toContain("Garrett"); + expect(arr).toContain("Bobby"); + expect(arr).toContain("Ferguson"); }); // TODO: Check if can delete @@ -886,8 +905,12 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { "@id": "http://example.com/Patient3", type: { "@id": "Patient" }, }); - expect(roommates.size).toBe(1); - expect(roommates.map((obj) => obj.name)).toContain("Amy"); + expect(roommates.size).toBe(2); + const roommateNames = roommates.reduce((s, obj) => { + obj.name?.forEach((n) => s.add(n)); + return s; + }, set()); + expect(roommateNames).toContain("Amy"); }); // TODO: check @@ -1143,18 +1166,8 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { const match = roommateArr[_getUnderlyingMatch]; expect(match[0].value).toBe("http://example.com/Patient1"); expect(match[1].value).toBe("http://hl7.org/fhir/roommate"); - // TODO: Alt tests - // expect(roommateArr[_getNodeAtIndex](0).value).toBe( - // "http://example.com/Patient2", - // ); - // expect(roommateArr[_getNodeAtIndex](10)).toBe(undefined); - // expect(observation.subject.name[_getNodeAtIndex](0).value).toBe( - // "Garrett", - // ); - const underlyingArrayTarget = roommateArr[_getUnderlyingMatch]; - expect(underlyingArrayTarget[0].value).toBe( - "http://example.com/Patient2", - ); + expect(match[2]).toBe(null); + expect(match[3]).toBe(null); }); }); @@ -1172,9 +1185,9 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { }); it("creates a list of subjects that match a certain pattern", async () => { - expect(patients[0].name?.[0]).toBe("Garrett"); - expect(patients[1].name?.[0]).toBe("Rob"); - expect(patients[2].name?.[0]).toBe("Amy"); + expect(patients.toArray()[0].name?.toArray()[0]).toBe("Garrett"); + expect(patients.toArray()[1].name?.toArray()[0]).toBe("Rob"); + expect(patients.toArray()[2].name?.toArray()[0]).toBe("Amy"); }); it("Successfully adds a node to the list", async () => { @@ -1195,7 +1208,7 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { return quad.subject.value === "http://example.com/Patient4"; }), ).toBe(true); - expect(patients[3].name?.[0]).toBe("Dippy"); + expect(patients.toArray()[3].name?.toArray()[0]).toBe("Dippy"); }); it("will read a new object if something has been added to the dataset after object creation", async () => { @@ -1225,15 +1238,14 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { return quad.subject.value === "http://example.com/Patient4"; }), ).toBe(true); - expect(patients[3].name?.[0]).toBe("Dippy"); + expect(patients.toArray()[3].name?.toArray()[0]).toBe("Dippy"); }); it("errors if an object is added without the correct parameters", async () => { expect(() => - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - patients.push({ + patients.add({ "@id": "http://example.com/Patient4", + // @ts-expect-error This object is purposely wrong name: ["Dippy"], age: 2, }), @@ -1244,66 +1256,15 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { it("errors if a literal is added to the collection", async () => { // @ts-expect-error Purposely pushing an incorrect value to trigger an error - expect(() => patients.push("some string")).toThrowError( + expect(() => patients.add("some string")).toThrowError( `Cannot add a literal "some string"(string) to a subject-oriented collection.`, ); }); - it("Removes all an object and replaces in upon set", async () => { - patients[0] = { - "@id": "http://example.com/Patient4", - type: { "@id": "Patient" }, - name: ["Dippy"], - age: 2, - }; - - expect(dataset.match(namedNode("http://example.com/Patient1")).size).toBe( - 0, - ); - expect(patients[0].name?.[0]).toBe("Dippy"); - }); - - it("Removes an object and replaces it upon splice", async () => { - // TODO adjust test - // patients.splice( - // 1, - // 1, - // { - // "@id": "http://example.com/Patient4", - // type: { "@id": "Patient" }, - // name: ["Dippy"], - // age: 2, - // }, - // { - // "@id": "http://example.com/Patient5", - // type: { "@id": "Patient" }, - // name: ["Licky"], - // age: 3, - // }, - // ); - // expect(dataset.match(namedNode("http://example.com/Patient2")).size).toBe( - // 0, - // ); - // expect(patients[1].name?.[0]).toBe("Dippy"); - // expect(patients[2].name?.[0]).toBe("Licky"); - }); - - it("Removes an object completely when assigning it to undefined", async () => { - // TODO adjust - // patients[0] = undefined; - // expect(dataset.match(namedNode("http://example.com/Patient1")).size).toBe( - // 0, - // ); - // expect(patients[0].name?.[0]).toBe("Rob"); - }); - - it("Removes an object completely when using the delete parameter", async () => { - delete patients[0]; - - expect(dataset.match(namedNode("http://example.com/Patient1")).size).toBe( - 0, - ); - expect(patients[0].name?.[0]).toBe("Rob"); + it("Removes an object completely when using the delete method", async () => { + const firstPatient = patients.toArray()[0]; + patients.delete(firstPatient); + expect(patients.has(firstPatient)).toBe(true); }); it("creates a collection that matches only collections in a certain graph", async () => { @@ -1323,9 +1284,11 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { describe("matchObject", () => { let patients: LdSet; let builder: JsonldDatasetProxyBuilder; + let dataset: Dataset; beforeEach(async () => { - const [, , receivedBuilder] = await getLoadedDataset(); + const [recievedDataset, , receivedBuilder] = await getLoadedDataset(); + dataset = recievedDataset; builder = receivedBuilder; patients = builder.matchObject( null, @@ -1335,20 +1298,18 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { }); it("create a collection that matches the null, predicate, null pattern", async () => { - expect(patients[0].name?.[0]).toBe("Garrett"); - expect(patients[1].name?.[0]).toBe("Amy"); - expect(patients[2].name?.[0]).toBe("Rob"); + expect(patients.toArray()[0].name?.toArray()[0]).toBe("Garrett"); + expect(patients.toArray()[1].name?.toArray()[0]).toBe("Amy"); + expect(patients.toArray()[2].name?.toArray()[0]).toBe("Rob"); }); it("cannot write to a collection that matches the null, predicate, null pattern", () => { - expect( - () => - (patients[1] = { - "@id": "http://example.com/Patient4", - type: { "@id": "Patient" }, - }), - ).toThrow( - "A collection that does not specify a match for both a subject or predicate cannot be modified directly.", + patients.add({ + "@id": "http://example.com/Patient4", + type: { "@id": "Patient" }, + }); + expect(dataset.match(namedNode("http://example.com/Patient4")).size).toBe( + 0, ); }); @@ -1360,11 +1321,11 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { null, ); expect(hodgePodge.size).toBe(5); - expect(hodgePodge[0]["@id"]).toBe("Patient"); - expect(hodgePodge[1]).toBe("Amy"); - expect(hodgePodge[2]).toBe("1988-01-01"); - expect(hodgePodge[3]).toBe(33); - expect(hodgePodge[4]).toBe(true); + expect(hodgePodge.toArray()[0]["@id"]).toBe("Patient"); + expect(hodgePodge.toArray()[1]).toBe("Amy"); + expect(hodgePodge.toArray()[2]).toBe("1988-01-01"); + expect(hodgePodge.toArray()[3]).toBe(33); + expect(hodgePodge.toArray()[4]).toBe(true); }); }); @@ -1381,12 +1342,12 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { name: set("Ethical", "Bug"), }), }); - expect(patient.name?.[0]).toBe("Jack"); - expect(patient.name?.[1]).toBe("Horner"); + expect(patient.name).toContain("Jack"); + expect(patient.name).toContain("Horner"); expect(patient.birthdate).toBe("1725/11/03"); expect(patient.age).toBe(298); - expect(patient.roommate?.[0].name?.[0]).toBe("Ethical"); - expect(patient.roommate?.[0].name?.[1]).toBe("Bug"); + expect(patient.roommate?.toArray()[0].name).toContain("Ethical"); + expect(patient.roommate?.toArray()[0].name).toContain("Bug"); }); it("initializes a patient using the fromJSON method with a named node", async () => { @@ -1403,12 +1364,12 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { }), }); expect(patient["@id"]).toBe("http://example.com/Patient13"); - expect(patient.name?.[0]).toBe("Jack"); - expect(patient.name?.[1]).toBe("Horner"); + expect(patient.name).toContain("Jack"); + expect(patient.name).toContain("Horner"); expect(patient.birthdate).toBe("1725/11/03"); expect(patient.age).toBe(298); - expect(patient.roommate?.[0].name?.[0]).toBe("Ethical"); - expect(patient.roommate?.[0].name?.[1]).toBe("Bug"); + expect(patient.roommate?.toArray()[0].name).toContain("Ethical"); + expect(patient.roommate?.toArray()[0].name).toContain("Bug"); }); }); @@ -1443,7 +1404,7 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { it("detects the graph of an array value", async () => { const [, observation] = await getGraphLoadedDataset(); - const patient1 = observation.subject as PatientShape; + const patient1 = observation.subject!; expect( graphOf(patient1, "name", "Garrett").map((n) => n.value), ).toContain("http://example.com/Patient1Doc"); @@ -1471,14 +1432,6 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { "http://example.com/SomeOtherDoc", ); }); - - it("throws an error if a number is provided as an object and the object is not an array", async () => { - const [, observation] = await getGraphLoadedDataset(); - // @ts-expect-error this should not be allowed - expect(() => graphOf(observation, "subject", 0)).toThrowError( - `Key "subject" of [object Object] is not an array.`, - ); - }); }); describe("write method", () => { @@ -1518,8 +1471,8 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { expect(graphOf(patient, "name", "1")[0].value).toBe(doc1.value); expect(graphOf(patient, "name", "2")[0].value).toBe(doc2.value); expect(graphOf(patient, "name", "3")[0].value).toBe(doc3.value); - expect(graphOf(patient, "name", "4")[0].value).toBe(doc2.value); - expect(graphOf(patient, "name", "5")[0].value).toBe(doc1.value); + expect(graphOf(patient, "name", "2 again")[0].value).toBe(doc2.value); + expect(graphOf(patient, "name", "1 again")[0].value).toBe(doc1.value); expect(graphOf(patient, "name", "default again")[0].value).toBe( defaultGraph().value, ); @@ -1539,18 +1492,16 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { expect(graphOf(patient, "name", "Doc1")[0].value).toBe(doc1.value); }); - it("works with array proxies", async () => { + it("works with set proxies", async () => { const [, , builder] = await getTinyLoadedDataset(); const allRoommates = builder.matchObject( namedNode("http://example.com/Patient1"), namedNode("http://hl7.org/fhir/roommate"), ); - write(namedNode("http://example.com/SomeGraph")).using( - allRoommates, - allRoommates, - ); - allRoommates[0].age = 20; - expect(graphOf(allRoommates[0], "age")[0].value).toBe( + write(namedNode("http://example.com/SomeGraph")).using(allRoommates); + const firstRoommate = allRoommates.toArray()[0]; + firstRoommate.age = 20; + expect(graphOf(firstRoommate, "age")[0].value).toBe( "http://example.com/SomeGraph", ); }); @@ -1570,7 +1521,7 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { const patient = observation.subject as PatientShape; expect(observation.langNotes).toBe("Notes Sympas"); - expect(patient.langName?.[0]).toBe("Jean"); + expect(patient.langName?.toArray()[0]).toBe("Jean"); setLanguagePreferences("ru", "zh").using(observation, patient); @@ -1579,7 +1530,7 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => { setLanguagePreferences("@other", "fr").using(observation, patient); expect(observation.langNotes).not.toBe("Notes Sympas"); - expect(patient.langName?.[0]).not.toBe("Jean"); + expect(patient.langName?.toArray()[0]).not.toBe("Jean"); setLanguagePreferences().using(observation, patient); expect(observation.langNotes).toBe(undefined);