Full test coverage for set refactor

main
Jackson Morgan 6 months ago
parent 2deae89b37
commit e86b552f24
  1. 4
      packages/jsonld-dataset-proxy/src/JsonldDatasetProxyBuilder.ts
  2. 4
      packages/jsonld-dataset-proxy/src/graphOf.ts
  3. 25
      packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts
  4. 6
      packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts
  5. 14
      packages/jsonld-dataset-proxy/src/subjectProxy/createSubjectHandler.ts
  6. 37
      packages/jsonld-dataset-proxy/src/subjectProxy/deleteFromDataset.ts
  7. 1
      packages/jsonld-dataset-proxy/src/types.ts
  8. 12
      packages/jsonld-dataset-proxy/src/util/NodeSet.ts
  9. 3
      packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts
  10. 5
      packages/jsonld-dataset-proxy/src/util/nodeToJsonldRepresentation.ts
  11. 567
      packages/jsonld-dataset-proxy/test/jsonldDatasetProxy.test.ts

@ -3,7 +3,7 @@ import type { BlankNode, NamedNode } from "@rdfjs/types";
import type { GraphNode, QuadMatch } from "@ldo/rdf-utils"; import type { GraphNode, QuadMatch } from "@ldo/rdf-utils";
import type { LanguageOrdering } from "./language/languageTypes"; import type { LanguageOrdering } from "./language/languageTypes";
import type { ProxyContext } from "./ProxyContext"; import type { ProxyContext } from "./ProxyContext";
import type { ObjectLike } from "./types"; import type { LiteralLike, ObjectLike } from "./types";
import type { LdSet } from "./setProxy/ldSet/LdSet"; import type { LdSet } from "./setProxy/ldSet/LdSet";
/** /**
@ -74,7 +74,7 @@ export class JsonldDatasetProxyBuilder {
* @param predicate The predicate to match * @param predicate The predicate to match
* @param graph The graph to match * @param graph The graph to match
*/ */
matchObject<T extends ObjectLike>( matchObject<T extends ObjectLike | LiteralLike>(
subject?: QuadMatch[0] | undefined | null, subject?: QuadMatch[0] | undefined | null,
predicate?: QuadMatch[1], predicate?: QuadMatch[1],
graph?: QuadMatch[3] | undefined | null, graph?: QuadMatch[3] | undefined | null,

@ -33,7 +33,7 @@ export function graphOf<Subject extends ObjectLike, Key extends keyof Subject>(
proxyContext.getRdfType(subjectNode), proxyContext.getRdfType(subjectNode),
), ),
); );
let objectNode: ObjectNode | null; let objectNode: ObjectNode | undefined | null;
if (object == null) { if (object == null) {
objectNode = null; objectNode = null;
} else { } else {
@ -41,7 +41,7 @@ export function graphOf<Subject extends ObjectLike, Key extends keyof Subject>(
predicate as string, predicate as string,
proxyContext.getRdfType(subjectNode), proxyContext.getRdfType(subjectNode),
); );
objectNode = getNodeFromRawValue(object, proxyContext, datatype) ?? null; objectNode = getNodeFromRawValue(object, proxyContext, datatype);
} }
const quads = subjectProxy[_getUnderlyingDataset].match( const quads = subjectProxy[_getUnderlyingDataset].match(
subjectNode, subjectNode,

@ -38,10 +38,6 @@ export class SubjectSetProxy<
* Appends a new element with a specified value to the end of the Set. * Appends a new element with a specified value to the end of the Set.
*/ */
add(value: T): this { add(value: T): this {
// Undefined is fine no matter what
if (value === undefined) {
return this;
}
if (typeof value !== "object") { if (typeof value !== "object") {
throw new Error( throw new Error(
`Cannot add a literal "${value}"(${typeof value}) to a subject-oriented collection.`, `Cannot add a literal "${value}"(${typeof value}) to a subject-oriented collection.`,
@ -86,25 +82,4 @@ export class SubjectSetProxy<
}); });
return this; 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 quads = this.getQuads(value);
quads.forEach((quad) => dataset.delete(quad));
return quads.size > 0;
}
} }

@ -37,9 +37,9 @@ export class WildcardSubjectSetProxy<T extends RawObject> extends SetProxy<T> {
protected getQuads(value?: T | undefined): Dataset<Quad, Quad> { protected getQuads(value?: T | undefined): Dataset<Quad, Quad> {
const { dataset } = this.context; const { dataset } = this.context;
// Get the RDF Node that represents the value, skip is no value // Get the RDF Node that represents the value, skip is no value
const predicate = this.quadMatch[1] ?? undefined; const predicate = this.quadMatch[1];
const object = this.quadMatch[2] ?? undefined; const object = this.quadMatch[2];
const graph = this.quadMatch[3] ?? undefined; const graph = this.quadMatch[3];
if (value) { if (value) {
const valueNode = getNodeFromRawObject(value, this.context.contextUtil); const valueNode = getNodeFromRawObject(value, this.context.contextUtil);
return dataset.match(valueNode, predicate, object, graph); return dataset.match(valueNode, predicate, object, graph);

@ -1,4 +1,4 @@
import { namedNode, quad } from "@rdfjs/data-model"; import { blankNode, namedNode, quad } from "@rdfjs/data-model";
import type { BlankNode, NamedNode } from "@rdfjs/types"; import type { BlankNode, NamedNode } from "@rdfjs/types";
import { addObjectToDataset } from "../util/addObjectToDataset"; import { addObjectToDataset } from "../util/addObjectToDataset";
import { deleteValueFromDataset } from "./deleteFromDataset"; import { deleteValueFromDataset } from "./deleteFromDataset";
@ -62,14 +62,18 @@ export function createSubjectHandler(
proxyContext = value; proxyContext = value;
return true; return true;
} }
if (key === "@id" && typeof value === "string") { if (
key === "@id" &&
(typeof value === "string" || typeof value == "undefined")
) {
const newSubjectNode = value ? namedNode(value) : blankNode();
// Replace Subject Quads // Replace Subject Quads
const currentSubjectQuads = proxyContext.dataset const currentSubjectQuads = proxyContext.dataset
.match(target["@id"]) .match(target["@id"])
.toArray(); .toArray();
const newSubjectQuads = currentSubjectQuads.map((curQuad) => const newSubjectQuads = currentSubjectQuads.map((curQuad) =>
quad( quad(
namedNode(value), newSubjectNode,
curQuad.predicate, curQuad.predicate,
curQuad.object, curQuad.object,
curQuad.graph, curQuad.graph,
@ -87,7 +91,7 @@ export function createSubjectHandler(
quad( quad(
curQuad.subject, curQuad.subject,
curQuad.predicate, curQuad.predicate,
namedNode(value), newSubjectNode,
curQuad.graph, curQuad.graph,
), ),
); );
@ -95,7 +99,7 @@ export function createSubjectHandler(
proxyContext.dataset.delete(curQuad), proxyContext.dataset.delete(curQuad),
); );
proxyContext.dataset.addAll(newObjectQuads); proxyContext.dataset.addAll(newObjectQuads);
target["@id"] = namedNode(value); target["@id"] = newSubjectNode;
} }
addObjectToDataset( addObjectToDataset(
{ "@id": target["@id"], [key]: value }, { "@id": target["@id"], [key]: value },

@ -1,14 +1,12 @@
import { namedNode, quad } from "@rdfjs/data-model";
import type { Term } from "@rdfjs/types";
import type { SubjectProxyTarget } from "./createSubjectHandler"; import type { SubjectProxyTarget } from "./createSubjectHandler";
import type { ProxyContext } from "../ProxyContext"; import type { ProxyContext } from "../ProxyContext";
import { addObjectToDataset } from "../util/addObjectToDataset";
export function deleteValueFromDataset( export function deleteValueFromDataset(
target: SubjectProxyTarget, target: SubjectProxyTarget,
key: string | symbol, key: string | symbol,
proxyContext: ProxyContext, proxyContext: ProxyContext,
) { ) {
const nodesToRemove: Term[] = [];
if (key === "@context") { if (key === "@context") {
return true; return true;
} }
@ -18,29 +16,18 @@ export function deleteValueFromDataset(
if (typeof key === "symbol") { if (typeof key === "symbol") {
return true; return true;
} }
const subject = target["@id"]; // Remove this node completely if delete on ID
const predicate = namedNode(
proxyContext.contextUtil.keyToIri(key, proxyContext.getRdfType(subject)),
);
if (key === "@id") { if (key === "@id") {
nodesToRemove.push(target["@id"]); const thisNode = target["@id"];
} else { proxyContext.dataset.deleteMatches(thisNode, undefined, undefined);
const objectDataset = proxyContext.dataset.match(subject, predicate); proxyContext.dataset.deleteMatches(undefined, undefined, thisNode);
if (objectDataset.size === 0) { return true;
return true;
} else {
nodesToRemove.push(...objectDataset.toArray().map((quad) => quad.object));
}
} }
nodesToRemove.forEach((term) => { // Otherwise, this is essentially treated like setting a key to undefined.
if (term.termType === "Literal") { addObjectToDataset(
proxyContext.dataset.delete(quad(subject, predicate, term)); { "@id": target["@id"], [key]: undefined },
return true; true,
} else if (term.termType === "NamedNode") { proxyContext,
proxyContext.dataset.deleteMatches(term, undefined, undefined); );
proxyContext.dataset.deleteMatches(undefined, undefined, term);
return true;
}
});
return true; return true;
} }

@ -8,3 +8,4 @@ export const _writeGraphs = Symbol("_writeGraphs");
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ObjectLike = Record<string | number | symbol, any>; export type ObjectLike = Record<string | number | symbol, any>;
export type LiteralLike = string | number | boolean;

@ -32,16 +32,4 @@ export class NodeSet {
has(node: ObjectNode): boolean { has(node: ObjectNode): boolean {
return this.set.has(nodeToString(node)); return this.set.has(nodeToString(node));
} }
delete(node: ObjectNode) {
const key = nodeToString(node);
delete this.map[key];
return this.set.delete(nodeToString(node));
}
toArray() {
return Array.from(this.set).map((stringVal) => {
return this.map[stringVal];
});
}
} }

@ -82,9 +82,6 @@ export function addRawObjectToDatasetRecursive(
shouldDeleteOldTriples: boolean, shouldDeleteOldTriples: boolean,
proxyContext: ProxyContext, proxyContext: ProxyContext,
): SubjectProxy { ): SubjectProxy {
if (isSubjectProxy(item)) {
return item as SubjectProxy;
}
const { dataset } = proxyContext; const { dataset } = proxyContext;
const subject = getNodeFromRawObject(item, proxyContext.contextUtil); const subject = getNodeFromRawObject(item, proxyContext.contextUtil);
const rdfType = proxyContext.getRdfType(subject); const rdfType = proxyContext.getRdfType(subject);

@ -1,8 +1,9 @@
import type { Literal, Quad_Object } from "@rdfjs/types"; import type { Literal, Quad_Object } from "@rdfjs/types";
import type { ProxyContext } from "../ProxyContext"; import type { ProxyContext } from "../ProxyContext";
import type { SubjectProxy } from "../subjectProxy/SubjectProxy"; import type { SubjectProxy } from "../subjectProxy/SubjectProxy";
import type { LiteralLike } from "../types";
export type ObjectJsonRepresentation = string | number | boolean | SubjectProxy; export type ObjectJsonRepresentation = LiteralLike | SubjectProxy;
export function literalToJsonldRepresentation(literal: Literal) { export function literalToJsonldRepresentation(literal: Literal) {
switch (literal.datatype.value) { switch (literal.datatype.value) {
@ -66,7 +67,7 @@ export function literalToJsonldRepresentation(literal: Literal) {
export function nodeToJsonldRepresentation( export function nodeToJsonldRepresentation(
node: Quad_Object, node: Quad_Object,
proxyContext: ProxyContext, proxyContext: ProxyContext,
): string | number | boolean | SubjectProxy { ): ObjectJsonRepresentation {
if (node.termType === "Literal") { if (node.termType === "Literal") {
return literalToJsonldRepresentation(node); return literalToJsonldRepresentation(node);
} else if (node.termType === "NamedNode" || node.termType === "BlankNode") { } else if (node.termType === "NamedNode" || node.termType === "BlankNode") {

@ -26,7 +26,13 @@ import {
patientUnnestedContext, patientUnnestedContext,
patientNestedContext, patientNestedContext,
} from "./patientExampleData"; } from "./patientExampleData";
import { namedNode, quad, literal, defaultGraph } from "@rdfjs/data-model"; import {
namedNode,
quad,
literal,
defaultGraph,
blankNode,
} from "@rdfjs/data-model";
import type { Dataset, NamedNode } from "@rdfjs/types"; import type { Dataset, NamedNode } from "@rdfjs/types";
import type { ContextDefinition } from "jsonld"; import type { ContextDefinition } from "jsonld";
import type { LdoJsonldContext } from "../src/LdoJsonldContext"; import type { LdoJsonldContext } from "../src/LdoJsonldContext";
@ -36,6 +42,7 @@ import {
type Avatar, type Avatar,
type Bender, type Bender,
} from "./scopedExampleData"; } from "./scopedExampleData";
import { WildcardSubjectSetProxy } from "../src/setProxy/WildcardSubjectSetProxy";
global.console.warn = () => {}; global.console.warn = () => {};
@ -227,93 +234,78 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
expect(set).toContain("Garrett"); expect(set).toContain("Garrett");
expect(set).toContain("Bobby"); expect(set).toContain("Bobby");
expect(set).toContain("Ferguson"); expect(set).toContain("Ferguson");
// TODO const entriesIterator = set.entries();
// expect(arr.concat(["Mimoey"])).toEqual([ expect(entriesIterator.next()).toEqual({
// "Garrett", value: ["Garrett", "Garrett"],
// "Bobby", done: false,
// "Ferguson", });
// "Mimoey", expect(entriesIterator.next()).toEqual({
// ]); value: ["Bobby", "Bobby"],
// const entriesIterator = arr.entries(); done: false,
// expect(entriesIterator.next()).toEqual({ });
// value: [0, "Garrett"], expect(entriesIterator.next()).toEqual({
// done: false, value: ["Ferguson", "Ferguson"],
// }); done: false,
// expect(entriesIterator.next()).toEqual({ });
// value: [1, "Bobby"], expect(entriesIterator.next()).toEqual({
// done: false, value: undefined,
// }); done: true,
// expect(entriesIterator.next()).toEqual({ });
// value: [2, "Ferguson"], expect(set.every((val) => val.length > 2)).toBe(true);
// done: false, expect(set.every((val) => val.length > 6)).toBe(false);
// }); const filteredSet = set.filter((val) => val.length > 6);
// expect(entriesIterator.next()).toEqual({ expect(filteredSet.size).toBe(2);
// value: undefined, expect(filteredSet).toContain("Garrett");
// done: true, expect(filteredSet).toContain("Ferguson");
// }); let concatTest = "";
// expect(arr.every((val) => val.length > 2)).toBe(true); set.forEach((value) => (concatTest += value));
// expect(arr.every((val) => val.length > 6)).toBe(false); expect(concatTest).toBe("GarrettBobbyFerguson");
// expect(arr.filter((val) => val.length > 6)).toEqual([ expect(set.has("Bobby")).toBe(true);
// "Garrett", const keysIterator = set.keys();
// "Ferguson", expect(keysIterator.next()).toEqual({
// ]); value: "Garrett",
// expect(arr.find((val) => val.length < 6)).toBe("Bobby"); done: false,
// expect(arr.findIndex((val) => val.length < 6)).toBe(1); });
// // arr.flat (Not included because there should never be nested arrays) expect(keysIterator.next()).toEqual({
// let concatTest = ""; value: "Bobby",
// arr.forEach((value) => (concatTest += value)); done: false,
// expect(concatTest).toBe("GarrettBobbyFerguson"); });
// expect(arr.includes("Bobby")).toBe(true); expect(keysIterator.next()).toEqual({
// expect(arr.indexOf("Bobby")).toBe(1); value: "Ferguson",
// expect(arr.join("-")).toBe("Garrett-Bobby-Ferguson"); done: false,
// const keysIterator = arr.keys(); });
// expect(keysIterator.next()).toEqual({ expect(keysIterator.next()).toEqual({
// value: 0, value: undefined,
// done: false, done: true,
// }); });
// expect(keysIterator.next()).toEqual({ expect(set.map((val) => val.toUpperCase())).toEqual([
// value: 1, "GARRETT",
// done: false, "BOBBY",
// }); "FERGUSON",
// expect(keysIterator.next()).toEqual({ ]);
// value: 2, expect(set.reduce((agg, val) => agg + val, "")).toBe(
// done: false, "GarrettBobbyFerguson",
// }); );
// expect(keysIterator.next()).toEqual({ expect(set.some((val) => val.startsWith("G"))).toBe(true);
// value: undefined, const valuesIterator = set.values();
// done: true, expect(valuesIterator.next()).toEqual({
// }); value: "Garrett",
// expect(arr.lastIndexOf("Bobby")).toBe(1); done: false,
// expect(arr.map((val) => val.toUpperCase())).toEqual([ });
// "GARRETT", expect(valuesIterator.next()).toEqual({
// "BOBBY", value: "Bobby",
// "FERGUSON", done: false,
// ]); });
// expect(arr.reduce((agg, val) => agg + val, "")).toBe( expect(valuesIterator.next()).toEqual({
// "GarrettBobbyFerguson", value: "Ferguson",
// ); done: false,
// expect(arr.slice(2)).toEqual(["Ferguson"]); });
// expect(arr.some((val) => val.startsWith("G"))).toBe(true); expect(valuesIterator.next()).toEqual({
// expect(arr.toString()).toBe("Garrett,Bobby,Ferguson"); value: undefined,
// const valuesIterator = arr.values(); done: true,
// expect(valuesIterator.next()).toEqual({ });
// value: "Garrett", expect(JSON.stringify(set)).toBe(`["Garrett","Bobby","Ferguson"]`);
// done: false, expect(set.toString()).toBe("[object LdSet]");
// });
// expect(valuesIterator.next()).toEqual({
// value: "Bobby",
// done: false,
// });
// expect(valuesIterator.next()).toEqual({
// value: "Ferguson",
// done: false,
// });
// expect(valuesIterator.next()).toEqual({
// value: undefined,
// done: true,
// });
// expect(JSON.stringify(arr)).toBe(`["Garrett","Bobby","Ferguson"]`);
// expect(arr.toString()).toBe("Garrett,Bobby,Ferguson");
}); });
it("can traverse a circular graph", async () => { it("can traverse a circular graph", async () => {
@ -607,33 +599,6 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
patient3.roommate?.add(patient1); patient3.roommate?.add(patient1);
}); });
// TODO: Confirm commenting this out is fine
// it("sets a primitive on an array", async () => {
// const [dataset, patient] = await getEmptyPatientDataset();
// patient.type = { "@id": "Patient" };
// patient.name[0] = "jon";
// expect(dataset.toString()).toBe(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "jon" .\n',
// );
// });
// TODO: Confirm that commenting this out is fine
// it("sets a primitive on an array and overwrites one that already is there", async () => {
// const [dataset, patient] = await getEmptyPatientDataset();
// patient.type = { "@id": "Patient" };
// dataset.add(
// quad(
// namedNode("http://example.com/Patient1"),
// namedNode("http://hl7.org/fhir/name"),
// literal("jon", "http://www.w3.org/2001/XMLSchema#string"),
// ),
// );
// (patient.name as string[])[0] = "not jon";
// expect(dataset.toString()).toBe(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "not jon" .\n',
// );
// });
it("sets an array", async () => { it("sets an array", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" }; patient.type = { "@id": "Patient" };
@ -667,33 +632,14 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
); );
}); });
// TODO: Check that commenting this out is fine it("allows instances of proxies to be set", async () => {
// it("Does not remove the full object when it is replaced on an array", async () => { const [dataset, observation] = await getTinyLoadedDataset();
// const [dataset, observation] = await getTinyLoadedDataset(); const patient2 = observation.subject!.roommate!.toArray()[0];
// const replacementPatient: PatientShape = { observation.subject = patient2;
// "@id": "http://example.com/ReplacementPatient", expect(dataset.toString()).toBe(
// type: { "@id": "Patient" }, '<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient2> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
// name: set("Jackson"), );
// }; });
// const roommateArr = observation!.subject!.roommate;
// roommateArr[0] = replacementPatient;
// expect(dataset.toString()).toBe(
// '<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/ReplacementPatient> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n<http://example.com/ReplacementPatient> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/ReplacementPatient> <http://hl7.org/fhir/name> "Jackson" .\n',
// );
// });
// TODO check
// it("Keeps the correct array index when setting an index", async () => {
// const [, observation] = await getLoadedDataset();
// const roommateArr = observation.subject?.roommate as PatientShape[];
// roommateArr[0] = {
// "@id": "http://example.com/ReplacementPatient",
// type: { "@id": "Patient" },
// name: ["Jackson"],
// };
// expect(roommateArr.length).toBe(2);
// expect(roommateArr[0].name?.[0]).toBe("Jackson");
// });
it("Changes the subject name if the @id is changed", async () => { it("Changes the subject name if the @id is changed", async () => {
const [dataset, observation] = await getTinyLoadedDataset(); const [dataset, observation] = await getTinyLoadedDataset();
@ -705,19 +651,35 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
); );
}); });
// TODO changes to blank node when set undefined on ID it("converts a node to a blank node when @id is set to undefined", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
// TODO deletes the object if delete on an ID const patient1 = observation.subject!;
patient1["@id"] = undefined;
const underlyingNode = patient1[_getUnderlyingNode];
expect(underlyingNode.termType).toBe("BlankNode");
expect(dataset.match(underlyingNode).size).toBe(3);
expect(patient1.name).toContain("Garrett");
expect(patient1.roommate?.toArray()[0].name).toContain("Rob");
const roommatesRoommate = patient1
.roommate!.toArray()[0]!
.roommate!.toArray()[0];
expect(roommatesRoommate.name).toContain("Garrett");
expect(roommatesRoommate[_getUnderlyingNode].termType).toBe("BlankNode");
expect(roommatesRoommate[_getUnderlyingNode].equals(underlyingNode)).toBe(
true,
);
});
it("Removes all adjoining triples when garbage collection is indicated via the delete operator on an object", async () => { it("treats deleting a field as setting that field to undefined", async () => {
const [dataset, observation] = await getTinyLoadedDataset(); const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject; delete observation.subject;
expect(dataset.toString()).toBe( expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n', '<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
); );
}); });
it("Removes all adjoining triples in an array when garbage collection is indicated via the delete operator on an object", async () => { it("treats deleting a field to a collection as setting that field to undefined", async () => {
const [dataset, observation] = await getTinyLoadedDataset(); const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.name; delete observation.subject?.name;
expect(dataset.toString()).toBe( expect(dataset.toString()).toBe(
@ -757,10 +719,16 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
); );
}); });
it("removes all literals in a set using the clear method", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
observation.subject!.name!.clear();
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
it("Deletes itself if @id is deleted", async () => { it("Deletes itself if @id is deleted", async () => {
const [dataset, observation] = await getTinyLoadedDataset(); const [dataset, observation] = await getTinyLoadedDataset();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete observation["@id"]; delete observation["@id"];
expect(observation).toEqual({ "@id": "http://example.com/Observation1" }); expect(observation).toEqual({ "@id": "http://example.com/Observation1" });
expect(dataset.toString()).toBe( expect(dataset.toString()).toBe(
@ -890,14 +858,6 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
expect(arr).toContain("Ferguson"); expect(arr).toContain("Ferguson");
}); });
// TODO: Check if can delete
// it("Prevents duplicates from being added when a value is overwritten", async () => {
// const [, patient] = await getArrayLoadedDataset();
// const arr = patient.name!;
// arr[1] = "Garrett";
// expect(arr).toEqual(["Garrett", "Ferguson"]);
// });
it("Prevents duplicates for Objects", async () => { it("Prevents duplicates for Objects", async () => {
const [, observation] = await getLoadedDataset(); const [, observation] = await getLoadedDataset();
const roommates = observation.subject!.roommate!; const roommates = observation.subject!.roommate!;
@ -913,38 +873,28 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
expect(roommateNames).toContain("Amy"); expect(roommateNames).toContain("Amy");
}); });
// TODO: check it("allows rdf namedNodes to be added to a set", async () => {
// it("Does nothing when you try to set a symbol on an array", async () => { const [, observation] = await getTinyLoadedDataset();
// const [, patient] = await getArrayLoadedDataset(); observation.subject?.roommate?.add(
// const arr = patient.name!; // @ts-expect-error This isn't technically allowed by the generated types
// expect(() => { namedNode("http://example.com/Patient3"),
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment );
// // @ts-ignore expect(observation.subject?.roommate?.map((r) => r["@id"])).toContain(
// arr[Symbol.search] = "Cool"; "http://example.com/Patient3",
// }).not.toThrowError(); );
// }); });
// TODO: check it("allows rdf bankNodes to be added to a set", async () => {
// it("Does nothing when you try to delete a symbol on an array", async () => { const [, observation] = await getTinyLoadedDataset();
// const [, patient] = await getArrayLoadedDataset(); const blank = blankNode();
// const arr = patient.name as string[]; observation.subject?.roommate?.add(
// expect(() => { // @ts-expect-error This isn't technically allowed by the generated types
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment blank,
// // @ts-ignore );
// delete arr[Symbol.search]; expect(
// }).not.toThrowError(); observation.subject?.roommate?.map((r) => r[_getUnderlyingNode]),
// }); ).toContain(blank);
});
// TODO check
// it("Does nothing when you try to delete an index of the array that doesn't exist", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// delete arr[5];
// expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
it("Can set a triple object named node with just a string", async () => { it("Can set a triple object named node with just a string", async () => {
const [dataset, observation] = await getEmptyObservationDataset(); const [dataset, observation] = await getEmptyObservationDataset();
@ -959,189 +909,6 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
"<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n", "<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n",
); );
}); });
// TODO check
// describe("Array Methods", () => {
// it("handles copyWithin", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name!;
// arr.copyWithin(0, 2, 3);
// expect(arr).toEqual(["Ferguson", "Bobby"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
// it("handles copyWithin with the optional end variable missing", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// arr.copyWithin(0, 2);
// expect(arr).toEqual(["Ferguson", "Bobby"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
// it("handles copyWithin with the optional start variable missing", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// // @ts-ignore
// expect(() => arr.copyWithin(0, undefined, 2)).not.toThrowError();
// expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
// it("handles fill", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// arr.fill("Beepy", 2, 5);
// expect(arr).toEqual(["Garrett", "Bobby", "Beepy"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
// );
// });
// it("handles pop", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// expect(arr.pop()).toBe("Ferguson");
// expect(arr).toEqual(["Garrett", "Bobby"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n',
// );
// });
// it("returns undefined for pop on an empty collection", async () => {
// const [, patient] = await getArrayLoadedDataset();
// patient.name = [];
// expect(patient.name.pop()).toBe(undefined);
// });
// it("handles push", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// arr.push("Beepy");
// expect(arr).toEqual(["Garrett", "Bobby", "Ferguson", "Beepy"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
// );
// });
// it("handles reverse", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// patient.name?.reverse();
// expect(patient.name).toEqual(["Ferguson", "Bobby", "Garrett"]);
// expect(dataset.toString()).toBe(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
// it("handles shift", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// expect(arr.shift()).toEqual("Garrett");
// expect(arr).toEqual(["Bobby", "Ferguson"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
// it("returns undefined for shift on an empty collection", async () => {
// const [, patient] = await getArrayLoadedDataset();
// patient.name = [];
// expect(patient.name.shift()).toBe(undefined);
// });
// it("handles sort", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// patient.name?.sort((a, b) => {
// return a.length - b.length;
// });
// expect(patient.name).toEqual(["Bobby", "Garrett", "Ferguson"]);
// expect(dataset.toString()).toBe(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
// it("handles sort without a sort function", async () => {
// const [, patient] = await getArrayLoadedDataset();
// patient.name?.sort();
// expect(patient.name).toEqual(["Bobby", "Ferguson", "Garrett"]);
// });
// it("handles sort without a sort function and there are two equal values", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// dataset.add(
// quad(
// namedNode("http://example.com/Patient1"),
// namedNode("http://hl7.org/fhir/name"),
// literal(
// "Bobby",
// namedNode("http://www.w3.org/2001/XMLSchema#token"),
// ),
// ),
// );
// patient.name?.sort();
// expect(patient.name).toEqual(["Bobby", "Bobby", "Ferguson", "Garrett"]);
// });
// it("handles splice", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// arr.splice(1, 0, "Beepy");
// expect(arr).toEqual(["Garrett", "Beepy", "Bobby", "Ferguson"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
// );
// });
// it("handles splice with objects", async () => {
// const [, observation] = await getLoadedDataset();
// const roommates = observation.subject?.roommate as PatientShape[];
// roommates.splice(
// 0,
// 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(roommates[0].name?.[0]).toBe("Dippy");
// expect(roommates[1].name?.[0]).toBe("Licky");
// expect(roommates[2].name?.[0]).toBe("Amy");
// });
// it("handles splice with only two params", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// arr.splice(1, 1);
// expect(arr).toEqual(["Garrett", "Ferguson"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
// );
// });
// it("handles unshift", async () => {
// const [dataset, patient] = await getArrayLoadedDataset();
// const arr = patient.name as string[];
// arr.unshift("Beepy");
// expect(arr).toEqual(["Beepy", "Garrett", "Bobby", "Ferguson"]);
// expect(dataset.toString()).toEqual(
// '<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
// );
// });
// });
}); });
describe("underlying data", () => { describe("underlying data", () => {
@ -1261,12 +1028,6 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
); );
}); });
it("Removes an object completely when using the delete method", async () => {
const firstPatient = patients.toArray()[0];
patients.delete(firstPatient);
expect(patients.has(firstPatient)).toBe(false);
});
it("creates a collection that matches only collections in a certain graph", async () => { it("creates a collection that matches only collections in a certain graph", async () => {
const [, , builder] = await getGraphLoadedDataset(); const [, , builder] = await getGraphLoadedDataset();
patients = builder.matchSubject<PatientShape>( patients = builder.matchSubject<PatientShape>(
@ -1279,6 +1040,38 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
"http://example.com/Patient1", "http://example.com/Patient1",
); );
}); });
it("creates a WildcardSubjectSetProxy when providing wildcard subject matches", async () => {
const [, , builder] = await getLoadedDataset();
patients = builder.matchSubject<PatientShape>(
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
);
expect(patients).toBeInstanceOf(WildcardSubjectSetProxy);
expect(patients[_isSubjectOriented]).toBe(true);
expect(
patients.has({
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
}),
).toBe(true);
});
it("does nothing when attempting to modify an abstract set", async () => {
const [dataset, , builder] = await getLoadedDataset();
patients = builder.matchSubject<PatientShape>(
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
);
const unmodifiedDataset = dataset.toString();
patients.add({ "@id": "dontaddthisguy", type: { "@id": "Patient" } });
expect(dataset.toString()).toBe(unmodifiedDataset);
patients.delete({
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
});
expect(dataset.toString()).toBe(unmodifiedDataset);
patients.clear();
expect(dataset.toString()).toBe(unmodifiedDataset);
});
}); });
describe("matchObject", () => { describe("matchObject", () => {
@ -1327,6 +1120,22 @@ const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
expect(hodgePodge.toArray()[3]).toBe(33); expect(hodgePodge.toArray()[3]).toBe(33);
expect(hodgePodge.toArray()[4]).toBe(true); expect(hodgePodge.toArray()[4]).toBe(true);
}); });
it("can match object when the object is a literal.", () => {
const allNames = builder.matchObject<string>(
null,
namedNode("http://hl7.org/fhir/name"),
null,
);
expect(allNames.size).toBe(5);
expect(allNames).toContain("Garrett");
expect(allNames).toContain("Bobby");
expect(allNames).toContain("Ferguson");
expect(allNames).toContain("Rob");
expect(allNames).toContain("Amy");
expect(allNames.has("Bobby")).toBe(true);
expect(allNames.has("WrongName")).toBe(false);
});
}); });
describe("fromJson", () => { describe("fromJson", () => {

Loading…
Cancel
Save