You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1559 lines
67 KiB

import { createDataset, serializedToDataset } from "@ldo/dataset";
import type {
JsonldDatasetProxyBuilder,
LanguageSet,
LdSet,
} from "../src/index.js";
import {
graphOf,
jsonldDatasetProxy,
languagesOf,
setLanguagePreferences,
write,
_getUnderlyingDataset,
_getUnderlyingMatch,
_getUnderlyingNode,
_isSubjectOriented,
_proxyContext,
_writeGraphs,
set,
SetProxy,
} from "../src/index.js";
import type { ObservationShape, PatientShape } from "./patientExampleData.js";
import {
patientData,
tinyPatientData,
tinyArrayPatientData,
patientDataWithBlankNodes,
tinyPatientDataWithBlankNodes,
tinyPatientDataWithLanguageTags,
patientUnnestedContext,
patientNestedContext,
} from "./patientExampleData.js";
import {
namedNode,
quad,
literal,
defaultGraph,
blankNode,
} from "@ldo/rdf-utils";
import type { Dataset, NamedNode } from "@rdfjs/types";
import type { ContextDefinition } from "jsonld";
import type { LdoJsonldContext } from "../src/LdoJsonldContext.js";
import {
scopedContext,
scopedData,
type Avatar,
type Bender,
} from "./scopedExampleData.js";
import { WildcardSubjectSetProxy } from "../src/setProxy/WildcardSubjectSetProxy.js";
global.console.warn = () => {};
const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
async function getLoadedDataset(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(patientData);
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Observation1")),
builder,
];
}
async function getLoadedDatasetWithBlankNodes(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(patientDataWithBlankNodes);
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Observation1")),
builder,
];
}
async function getTinyLoadedDataset(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(tinyPatientData);
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Observation1")),
builder,
];
}
async function getGraphLoadedDataset(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
const tempDataset = await serializedToDataset(patientData);
const dataset = createDataset();
const subjectGraphMap: Record<string, NamedNode> = {
"http://example.com/Observation1": namedNode(
"http://example.com/Observation1Doc",
),
"http://example.com/Patient1": namedNode(
"http://example.com/Patient1Doc",
),
"http://example.com/Patient2": namedNode(
"http://example.com/Patient2Doc",
),
"http://example.com/Patient3": namedNode(
"http://example.com/Patient3Doc",
),
};
tempDataset.forEach((tempQuad) => {
dataset.add(
quad(
tempQuad.subject,
tempQuad.predicate,
tempQuad.object,
subjectGraphMap[tempQuad.subject.value],
),
);
});
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Observation1")),
builder,
];
}
async function getTinyLoadedDatasetWithBlankNodes(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(tinyPatientDataWithBlankNodes);
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Observation1")),
builder,
];
}
async function getTinyLoadedDatasetWithLanguageTags(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(tinyPatientDataWithLanguageTags);
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Observation1")),
builder,
];
}
async function getArrayLoadedDataset(): Promise<
[Dataset, PatientShape, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(tinyArrayPatientData);
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Patient1")),
builder,
];
}
async function getEmptyObservationDataset(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
const dataset = await createDataset();
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Observation1")),
builder,
];
}
async function getEmptyPatientDataset(): Promise<
[Dataset, PatientShape, JsonldDatasetProxyBuilder]
> {
const dataset = await createDataset();
const builder = await jsonldDatasetProxy(dataset, patientContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Patient1")),
builder,
];
}
async function getScopedDataset(): Promise<
[Dataset, Bender, Avatar, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(scopedData);
const builder = await jsonldDatasetProxy(dataset, scopedContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Katara")),
builder.fromSubject(namedNode("http://example.com/Aang")),
builder,
];
}
describe("read", () => {
it("retreives a primitive", async () => {
const [, observation] = await getLoadedDataset();
expect(observation["@id"]).toBe("http://example.com/Observation1");
expect(observation.notes).toBe("Cool Notes");
});
it("retreives a primitive with blank nodes", async () => {
const [, observation] = await getLoadedDatasetWithBlankNodes();
expect(observation.subject?.age).toBe(35);
});
it("retrieves a nested primitive", async () => {
const [, observation] = await getLoadedDataset();
expect(observation?.subject && observation.subject["@id"]).toBe(
"http://example.com/Patient1",
);
expect(observation?.subject?.age).toBe(35);
expect(observation?.subject?.birthdate).toBe("1986-01-01");
expect(observation?.subject?.isHappy).toBe(true);
});
it("retrieves a nested primitive with a blank node", async () => {
const [, observation] = await getLoadedDatasetWithBlankNodes();
expect(observation?.subject?.roommate?.map((obj) => obj.age)).toContain(
34,
);
});
it("retreives a @type value as rdf:type", async () => {
const [, observation] = await getLoadedDataset();
expect(observation.subject?.type?.["@id"]).toBe("Patient");
});
it("simulates the getter behavior of an array of primitives", async () => {
const [, observation] = await getLoadedDataset();
const set = observation!.subject!.name!;
expect(set.size).toBe(3);
expect(set).toContain("Garrett");
expect(set).toContain("Bobby");
expect(set).toContain("Ferguson");
const entriesIterator = set.entries();
expect(entriesIterator.next()).toEqual({
value: ["Garrett", "Garrett"],
done: false,
});
expect(entriesIterator.next()).toEqual({
value: ["Bobby", "Bobby"],
done: false,
});
expect(entriesIterator.next()).toEqual({
value: ["Ferguson", "Ferguson"],
done: false,
});
expect(entriesIterator.next()).toEqual({
value: undefined,
done: true,
});
expect(set.every((val) => val.length > 2)).toBe(true);
expect(set.every((val) => val.length > 6)).toBe(false);
const filteredSet = set.filter((val) => val.length > 6);
expect(filteredSet.size).toBe(2);
expect(filteredSet).toContain("Garrett");
expect(filteredSet).toContain("Ferguson");
let concatTest = "";
set.forEach((value) => (concatTest += value));
expect(concatTest).toBe("GarrettBobbyFerguson");
expect(set.has("Bobby")).toBe(true);
const keysIterator = set.keys();
expect(keysIterator.next()).toEqual({
value: "Garrett",
done: false,
});
expect(keysIterator.next()).toEqual({
value: "Bobby",
done: false,
});
expect(keysIterator.next()).toEqual({
value: "Ferguson",
done: false,
});
expect(keysIterator.next()).toEqual({
value: undefined,
done: true,
});
expect(set.map((val) => val.toUpperCase())).toEqual([
"GARRETT",
"BOBBY",
"FERGUSON",
]);
expect(set.reduce((agg, val) => agg + val, "")).toBe(
"GarrettBobbyFerguson",
);
expect(set.some((val) => val.startsWith("G"))).toBe(true);
const valuesIterator = set.values();
expect(valuesIterator.next()).toEqual({
value: "Garrett",
done: false,
});
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(set)).toBe(`["Garrett","Bobby","Ferguson"]`);
expect(set.toString()).toBe("[object LdSet]");
});
it("can traverse a circular graph", async () => {
const [, observation] = await getLoadedDataset();
expect(
observation.subject?.roommate
?.toArray()[0]
.roommate?.toArray()[0]
?.name?.toArray()[0],
).toBe("Garrett");
});
it("simulates getter object properties", async () => {
const [, observation] = await getLoadedDataset();
const obj = observation.subject as PatientShape;
expect(obj["@id"]).toEqual("http://example.com/Patient1");
expect(obj.type).toEqual({ "@id": "Patient" });
expect(obj.name).toContain("Garrett");
expect(obj.name).toContain("Bobby");
expect(obj.name).toContain("Ferguson");
expect(obj.birthdate).toEqual("1986-01-01");
expect(obj.age).toEqual(35);
expect(obj.isHappy).toEqual(true);
const entries = Object.entries(obj);
expect(entries[0][0]).toBe("@id");
expect(entries[0][1]).toBe("http://example.com/Patient1");
expect(entries[1][0]).toBe("type");
expect(entries[1][1]).toEqual({ "@id": "Patient" });
expect(entries[2][0]).toBe("name");
expect(entries[2][1]).toContain("Garrett");
expect(entries[2][1]).toContain("Bobby");
expect(entries[2][1]).toContain("Ferguson");
expect(entries[3][0]).toBe("birthdate");
expect(entries[3][1]).toBe("1986-01-01");
expect(entries[4][0]).toBe("age");
expect(entries[4][1]).toBe(35);
expect(entries[5][0]).toBe("isHappy");
expect(entries[5][1]).toBe(true);
expect(entries[6][0]).toEqual("roommate");
expect(Object.keys(obj)).toEqual([
"@id",
"type",
"name",
"birthdate",
"age",
"isHappy",
"roommate",
]);
const values = Object.values(obj);
expect(values[0]).toEqual("http://example.com/Patient1");
expect(values[1]).toEqual({ "@id": "Patient" });
expect(values[2]).toContain("Garrett");
expect(values[2]).toContain("Bobby");
expect(values[2]).toContain("Ferguson");
expect(values[3]).toEqual("1986-01-01");
expect(values[4]).toEqual(35);
expect(values[5]).toEqual(true);
});
it("handles stringification of a non circular object", async () => {
const [, observation] = await getLoadedDataset();
const obj = observation.subject!.roommate!.toArray()[1];
expect(obj.toString()).toBe("[object Object]");
expect(JSON.stringify(obj)).toBe(
`{"@id":"http://example.com/Patient3","type":{"@id":"Patient"},"name":["Amy"],"birthdate":"1988-01-01","age":33,"isHappy":true}`,
);
});
it("Returns an array for required array fields even if no data is in the dataset", async () => {
const [, observation] = await getLoadedDataset();
const obj = observation.subject!.roommate!.toArray()[1];
expect(obj.roommate).toBeInstanceOf(SetProxy);
expect(obj.roommate?.size).toBe(0);
});
it("updates when the dataset is updated", async () => {
const [dataset, observation] = await getLoadedDataset();
expect(observation.notes).toBe("Cool Notes");
dataset.delete(
quad(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/notes"),
literal("Cool Notes", "http://www.w3.org/2001/XMLSchema#string"),
),
);
dataset.add(
quad(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/notes"),
literal("Bad Notes", "http://www.w3.org/2001/XMLSchema#string"),
),
);
expect(observation.notes).toBe("Bad Notes");
});
it("handles stringfication of a circular object", async () => {
const [, observation] = await getLoadedDataset();
const obj = observation.subject as PatientShape;
expect(obj.toString()).toBe("[object Object]");
expect(() => JSON.stringify(obj)).toThrow(
"Converting circular structure to JSON",
);
});
it("returns undefined if called with an unrecognized symbol", async () => {
const [, observation] = await getLoadedDataset();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(observation[Symbol.toPrimitive]).toBe(undefined);
});
it("If a container is not a set, but multiple triples exist, it should still return only 1.", async () => {
const dataset = await serializedToDataset(patientData);
const fakePatientSContext: ContextDefinition = {
name: {
"@id": "http://hl7.org/fhir/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
};
const builder = jsonldDatasetProxy(dataset, fakePatientSContext);
const patient = builder.fromSubject(
namedNode("http://example.com/Patient1"),
);
expect(patient.name).toBe("Garrett");
});
it("returns context when the @context key is called", async () => {
const [, observation] = await getLoadedDataset();
expect(observation["@context"]).toEqual(patientContext);
});
it("reads an array for collections, but a var for non collections", async () => {
const [, bender, avatar] = await getScopedDataset();
expect(avatar.element.map((obj) => obj["@id"])).toContain(
"http://example.com/Air",
);
expect(avatar.element.map((obj) => obj["@id"])).toContain(
"http://example.com/Water",
);
expect(bender.element["@id"]).toBe("http://example.com/Water");
});
});
describe("write", () => {
it("sets a primitive value that doesn't exist yet", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
observation.type = { "@id": "Observation" };
observation.notes = "Cool Notes";
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/notes> "Cool Notes" .\n',
);
});
it("sets primitive number and boolean values", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.age = 35;
patient.isHappy = true;
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/age> "35"^^<http://www.w3.org/2001/XMLSchema#integer> .\n<http://example.com/Patient1> <http://hl7.org/fhir/isHappy> "true"^^<http://www.w3.org/2001/XMLSchema#boolean> .\n',
);
});
it("sets a @type value as rdf:type", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
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",
);
});
it("replaces a primitive value that currently exists", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
dataset.addAll([
quad(
namedNode("http://example.com/Observation1"),
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
namedNode("http://hl7.org/fhir/Observation"),
),
quad(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/notes"),
literal("Cool Notes"),
),
]);
observation.notes = "Lame Notes";
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/notes> "Lame Notes" .\n',
);
});
it("adds all quads from a set object", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
const patient: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
birthdate: "2001-01-01",
};
observation.type = { "@id": "Observation" };
observation.subject = patient;
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/birthdate> "2001-01-01"^^<http://www.w3.org/2001/XMLSchema#date> .\n',
);
});
it("sets a retrieved blank node object", async () => {
const [, observation] = await getTinyLoadedDatasetWithBlankNodes();
const patient2 = observation.subject?.roommate?.toArray()[0];
observation.subject = patient2;
expect(observation.subject?.name).toContain("Rob");
expect(observation.subject?.roommate?.toArray()[0].name).toContain(
"Garrett",
);
expect(
observation.subject?.roommate?.toArray()[0]?.roommate?.toArray()[0]
.name,
).toContain("Rob");
});
it("only removes the connection when a value is set to undefined", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
observation.subject = undefined;
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/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("Creates a blank node if the id is blank during set", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
observation.type = { "@id": "Observation" };
observation.subject = { type: { "@id": "Patient" }, name: set("Joe") };
expect(observation.subject?.["@id"]).toBeUndefined();
expect(observation.subject.name).toContain("Joe");
expect(
dataset
.match(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/subject"),
)
.toArray()[0].object.termType,
).toBe("BlankNode");
});
it("adds all quads from a set object that includes an array", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
const patient: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
birthdate: "2001-01-01",
name: set("Jon", "Bon", "Jovi"),
};
observation.type = { "@id": "Observation" };
observation.subject = patient;
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/birthdate> "2001-01-01"^^<http://www.w3.org/2001/XMLSchema#date> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Jon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Jovi" .\n',
);
});
it("does not infinitely recurse if there is a loop when setting an object", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
const patient1: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
name: set("jon"),
};
const patient2: PatientShape = {
"@id": "http://example.com/patient2",
type: { "@id": "Patient" },
name: set("jane"),
roommate: set(patient1),
};
patient1.roommate = set(patient2);
observation.type = { "@id": "Observation" };
observation.subject = patient1;
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> "jon" .\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> "jane" .\n<http://example.com/patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
it("adds a proxy object to the array", async () => {
const [, , builder] = await getTinyLoadedDataset();
const patient3: PatientShape = builder.fromSubject(
namedNode("http://example.com/Patient3"),
);
patient3.type = { "@id": "Patient" };
const patient1: PatientShape = builder.fromSubject(
namedNode("http://example.com/Patient1"),
);
patient3.roommate?.add(patient1);
});
it("sets an array", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = set("Joe", "Mama");
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> "Joe" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Mama" .\n',
);
});
it("Does not remove the full object when it is replaced on an object", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = {
"@id": "http://example.com/ReplacementPatient",
type: { "@id": "Patient" },
name: set("Jackson"),
};
observation.subject = 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/ReplacementPatient> .\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<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',
);
});
it("Does not overwrite the full object when a partial object is provided", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
observation.subject = {
"@id": "http://example.com/Patient2",
type: { "@id": "Patient" },
};
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/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',
);
});
it("allows instances of proxies to be set", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
const patient2 = observation.subject!.roommate!.toArray()[0];
observation.subject = patient2;
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/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',
);
});
it("Changes the subject name if the @id is changed", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
const patient = observation?.subject as PatientShape;
patient["@id"] = "http://example.com/RenamedPatient";
expect(patient["@id"]).toBe("http://example.com/RenamedPatient");
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/RenamedPatient> .\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/RenamedPatient> .\n<http://example.com/RenamedPatient> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/RenamedPatient> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/RenamedPatient> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n',
);
});
it("converts a node to a blank node when @id is set to undefined", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
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("treats deleting a field as setting that field to undefined", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject;
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/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("treats deleting a field to a collection as setting that field to undefined", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.name;
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("Removes connecting triples when the delete method is called on a set", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
const firstRoommate = observation.subject!.roommate!.toArray()[0];
observation.subject!.roommate!.delete(firstRoommate);
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/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 connecting triples when the delete method is called on a set with blank nodes", async () => {
const [dataset, observation] = await getTinyLoadedDatasetWithBlankNodes();
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 method", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
observation.subject!.name!.delete("Garrett");
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("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 () => {
const [dataset, observation] = await getTinyLoadedDataset();
delete observation["@id"];
expect(observation).toEqual({ "@id": "http://example.com/Observation1" });
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/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("Does nothing when deleting triples that don't exist", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
delete observation.subject;
expect(dataset.toString()).toBe("");
});
it("Does nothing when deleting context", async () => {
const [, observation] = await getTinyLoadedDataset();
delete observation["@context"];
expect(observation["@context"]).toEqual(patientContext);
});
it("Does nothing when deleting toString", async () => {
const [, observation] = await getTinyLoadedDataset();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete observation.toString;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete observation[Symbol.toStringTag];
expect(typeof observation.toString).toBe("function");
});
it("Does nothing when deleting any symbol", async () => {
const [, observation] = await getTinyLoadedDataset();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete observation[Symbol.search];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(observation[Symbol.search]).toBe(undefined);
});
it("Only replaces referenced triples on a node that has the same id as the one it replaced", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
name: set("Mister Sneaky"),
};
observation.subject = 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> "Mister Sneaky" .\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("handles Object.assign", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
Object.assign(observation.subject!, {
age: 35,
isHappy: true,
});
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/Patient2> .\n<http://example.com/Patient1> <http://hl7.org/fhir/age> "35"^^<http://www.w3.org/2001/XMLSchema#integer> .\n<http://example.com/Patient1> <http://hl7.org/fhir/isHappy> "true"^^<http://www.w3.org/2001/XMLSchema#boolean> .\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("Adds elements to the array even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = set("Joe", "Blow");
dataset.add(
quad(
namedNode("http://example.com/Patient1"),
namedNode("http://hl7.org/fhir/name"),
literal("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 set even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = set("Joe", "Blow");
dataset.delete(
quad(
namedNode("http://example.com/Patient1"),
namedNode("http://hl7.org/fhir/name"),
literal("Blow"),
),
);
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 () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = set("Joe", "Blow");
dataset.delete(
quad(
namedNode("http://example.com/Patient1"),
namedNode("http://hl7.org/fhir/name"),
literal("Blow"),
),
);
dataset.add(
quad(
namedNode("http://example.com/Patient1"),
namedNode("http://hl7.org/fhir/name"),
literal("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.size).toBe(3);
expect(arr).toContain("Garrett");
expect(arr).toContain("Bobby");
expect(arr).toContain("Ferguson");
});
it("Prevents duplicates for Objects", async () => {
const [, observation] = await getLoadedDataset();
const roommates = observation.subject!.roommate!;
roommates.add({
"@id": "http://example.com/Patient3",
type: { "@id": "Patient" },
});
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");
});
it("allows rdf namedNodes to be added to a set", async () => {
const [, observation] = await getTinyLoadedDataset();
observation.subject?.roommate?.add(
// @ts-expect-error This isn't technically allowed by the generated types
namedNode("http://example.com/Patient3"),
);
expect(observation.subject?.roommate?.map((r) => r["@id"])).toContain(
"http://example.com/Patient3",
);
});
it("allows rdf bankNodes to be added to a set", async () => {
const [, observation] = await getTinyLoadedDataset();
const blank = blankNode();
observation.subject?.roommate?.add(
// @ts-expect-error This isn't technically allowed by the generated types
blank,
);
expect(
observation.subject?.roommate?.map((r) => r[_getUnderlyingNode]),
).toContain(blank);
});
it("Can set a triple object named node with just a string", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
observation.type = { "@id": "Observation" };
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
observation.subject = "http://example.com/Patient1";
expect(observation.subject).toEqual({
"@id": "http://example.com/Patient1",
});
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",
);
});
});
describe("underlying data", () => {
it("retrieves underlying data", async () => {
const dataset = await serializedToDataset(patientData);
const entryNode = namedNode("http://example.com/Observation1");
const context = patientContext;
const builder = jsonldDatasetProxy(dataset, context);
const observation = builder.fromSubject(entryNode);
expect(observation[_getUnderlyingDataset]).toBe(dataset);
expect(observation[_getUnderlyingNode].value).toBe(
"http://example.com/Observation1",
);
expect(observation[_writeGraphs][0].termType).toBe("DefaultGraph");
expect(observation[_proxyContext].writeGraphs[0].termType).toBe(
"DefaultGraph",
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const roommateArr = observation.subject!.roommate!;
expect(roommateArr[_getUnderlyingDataset]).toBe(dataset);
expect(roommateArr[_isSubjectOriented]).toBe(false);
const match = roommateArr[_getUnderlyingMatch];
expect(match[0].value).toBe("http://example.com/Patient1");
expect(match[1].value).toBe("http://hl7.org/fhir/roommate");
expect(match[2]).toBe(null);
expect(match[3]).toBe(null);
});
});
describe("matchSubject", () => {
let patients: LdSet<PatientShape>;
let dataset: Dataset;
beforeEach(async () => {
const [receivedDataset, , builder] = await getLoadedDataset();
dataset = receivedDataset;
patients = builder.matchSubject<PatientShape>(
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
namedNode("http://hl7.org/fhir/Patient"),
);
});
it("creates a list of subjects that match a certain pattern", async () => {
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 () => {
patients.add({
"@id": "http://example.com/Patient4",
type: { "@id": "Patient" },
name: set("Dippy"),
age: 2,
});
expect(
dataset
.match(
null,
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
namedNode("http://hl7.org/fhir/Patient"),
)
.some((quad) => {
return quad.subject.value === "http://example.com/Patient4";
}),
).toBe(true);
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 () => {
dataset.add(
quad(
namedNode("http://example.com/Patient4"),
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
namedNode("http://hl7.org/fhir/Patient"),
),
);
dataset.add(
quad(
namedNode("http://example.com/Patient4"),
namedNode("http://hl7.org/fhir/name"),
literal("Dippy"),
),
);
expect(
dataset
.match(
null,
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
namedNode("http://hl7.org/fhir/Patient"),
)
.some((quad) => {
return quad.subject.value === "http://example.com/Patient4";
}),
).toBe(true);
expect(patients.toArray()[3].name?.toArray()[0]).toBe("Dippy");
});
it("errors if an object is added without the correct parameters", async () => {
expect(() =>
patients.add({
"@id": "http://example.com/Patient4",
// @ts-expect-error This object is purposely wrong
name: ["Dippy"],
age: 2,
}),
).toThrowError(
`Cannot add value to collection. This must contain a quad that matches (null, namedNode(http://www.w3.org/1999/02/22-rdf-syntax-ns#type), namedNode(http://hl7.org/fhir/Patient), null)`,
);
});
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.add("some string")).toThrowError(
`Cannot add a literal "some string"(string) to a subject-oriented collection.`,
);
});
it("creates a collection that matches only collections in a certain graph", async () => {
const [, , builder] = await getGraphLoadedDataset();
patients = builder.matchSubject<PatientShape>(
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
namedNode("http://hl7.org/fhir/Patient"),
namedNode("http://example.com/Patient1Doc"),
);
expect(patients.size).toBe(1);
expect(patients.map((o) => o["@id"])).toContain(
"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", () => {
let patients: LdSet<PatientShape>;
let builder: JsonldDatasetProxyBuilder;
let dataset: Dataset;
beforeEach(async () => {
const [recievedDataset, , receivedBuilder] = await getLoadedDataset();
dataset = recievedDataset;
builder = receivedBuilder;
patients = builder.matchObject<PatientShape>(
null,
namedNode("http://hl7.org/fhir/roommate"),
null,
);
});
it("create a collection that matches the null, predicate, null pattern", async () => {
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", () => {
patients.add({
"@id": "http://example.com/Patient4",
type: { "@id": "Patient" },
});
expect(dataset.match(namedNode("http://example.com/Patient4")).size).toBe(
0,
);
});
it("creates a collection that matches the subject, null, null pattern", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hodgePodge = builder.matchObject<any>(
namedNode("http://example.com/Patient3"),
null,
null,
);
expect(hodgePodge.size).toBe(5);
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);
});
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", () => {
it("initializes a patient using the fromJSON method", async () => {
const [, , builder] = await getEmptyPatientDataset();
const patient = builder.fromJson<PatientShape>({
type: { "@id": "Patient" },
name: set("Jack", "Horner"),
birthdate: "1725/11/03",
age: 298,
roommate: set({
type: { "@id": "Patient" },
name: set("Ethical", "Bug"),
}),
});
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?.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 () => {
const [, , builder] = await getEmptyPatientDataset();
const patient = builder.fromJson<PatientShape>({
"@id": "http://example.com/Patient13",
type: { "@id": "Patient" },
name: set("Jack", "Horner"),
birthdate: "1725/11/03",
age: 298,
roommate: set({
type: { "@id": "Patient" },
name: set("Ethical", "Bug"),
}),
});
expect(patient["@id"]).toBe("http://example.com/Patient13");
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?.toArray()[0].name).toContain("Ethical");
expect(patient.roommate?.toArray()[0].name).toContain("Bug");
});
});
describe("Graph Methods", () => {
describe("builder", () => {
it("sets write graph", async () => {
const [dataset, , builder] = await getEmptyObservationDataset();
const patient4 = builder
.write(namedNode("http://example.com/Patient4Doc"))
.fromSubject<PatientShape>(namedNode("https://example.com/Patient4"));
patient4.type = { "@id": "Patient" };
patient4.name = set("Jackson");
expect(dataset.toString()).toBe(
'<https://example.com/Patient4> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> <http://example.com/Patient4Doc> .\n<https://example.com/Patient4> <http://hl7.org/fhir/name> "Jackson" <http://example.com/Patient4Doc> .\n',
);
});
});
describe("graphOf", () => {
it("detects the graph of a single value", async () => {
const [, observation] = await getGraphLoadedDataset();
expect(graphOf(observation, "subject").map((n) => n.value)).toContain(
"http://example.com/Observation1Doc",
);
expect(
graphOf(observation, "subject", observation.subject)[0].value,
).toBe("http://example.com/Observation1Doc");
expect(
graphOf(observation.subject as PatientShape, "age")[0].value,
).toBe("http://example.com/Patient1Doc");
});
it("detects the graph of an array value", async () => {
const [, observation] = await getGraphLoadedDataset();
const patient1 = observation.subject!;
expect(
graphOf(patient1, "name", "Garrett").map((n) => n.value),
).toContain("http://example.com/Patient1Doc");
expect(
graphOf(patient1, "roommate", {
"@id": "http://example.com/Patient2",
} as PatientShape).map((n) => n.value),
).toContain("http://example.com/Patient1Doc");
});
it("detects the graph of a value in multiple graphs", async () => {
const [dataset, observation] = await getGraphLoadedDataset();
dataset.add(
quad(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/subject"),
namedNode("http://example.com/Patient1"),
namedNode("http://example.com/SomeOtherDoc"),
),
);
expect(graphOf(observation, "subject")[0].value).toBe(
"http://example.com/Observation1Doc",
);
expect(graphOf(observation, "subject")[1].value).toBe(
"http://example.com/SomeOtherDoc",
);
});
});
describe("write method", () => {
it("changes the write graph", async () => {
const [, observation] = await getGraphLoadedDataset();
write(namedNode("http://example.com/SomeOtherDoc")).using(observation);
observation.notes = "Cool Notes";
expect(graphOf(observation, "notes")[0].value).toBe(
"http://example.com/SomeOtherDoc",
);
});
it("allows the write graph to be reset", async () => {
const doc1 = namedNode("http://example.com/Doc1");
const doc2 = namedNode("http://example.com/Doc2");
const doc3 = namedNode("http://example.com/Doc3");
const [, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name?.add("default");
const end1 = write(doc1).using(patient);
patient.name?.add("1");
const end2 = write(doc2).using(patient);
patient.name?.add("2");
const end3 = write(doc3).using(patient);
patient.name?.add("3");
end3();
patient.name?.add("2 again");
end2();
patient.name?.add("1 again");
end1();
patient.name?.add("default again");
expect(graphOf(patient, "name", "default")[0].value).toBe(
defaultGraph().value,
);
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", "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,
);
});
it("copies the proxy and changes the write graphs without modifying the original", async () => {
const doc1 = namedNode("http://example.com/Doc1");
const [, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name?.add("Default");
const [patientOnDoc1] = write(doc1).usingCopy(patient);
patientOnDoc1.name?.add("Doc1");
expect(graphOf(patient, "name", "Default")[0].value).toBe(
defaultGraph().value,
);
expect(graphOf(patient, "name", "Doc1")[0].value).toBe(doc1.value);
});
it("works with set proxies", async () => {
const [, , builder] = await getTinyLoadedDataset();
const allRoommates = builder.matchObject<PatientShape>(
namedNode("http://example.com/Patient1"),
namedNode("http://hl7.org/fhir/roommate"),
);
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",
);
});
});
});
describe("languageTag Support", () => {
it("Retrieves the proper language given the languageOrdering", async () => {
const [, , builder] = await getTinyLoadedDatasetWithLanguageTags();
const observation = builder
.setLanguagePreferences("fr", "en")
.fromSubject<ObservationShape>(
namedNode("http://example.com/Observation1"),
);
const patient = observation.subject as PatientShape;
expect(observation.langNotes).toBe("Notes Sympas");
expect(patient.langName?.toArray()[0]).toBe("Jean");
setLanguagePreferences("ru", "zh").using(observation, patient);
expect(observation.langNotes).toBeUndefined();
expect(patient.langName?.size).toBe(0);
setLanguagePreferences("@other", "fr").using(observation, patient);
expect(observation.langNotes).not.toBe("Notes Sympas");
expect(patient.langName?.toArray()[0]).not.toBe("Jean");
setLanguagePreferences().using(observation, patient);
expect(observation.langNotes).toBe(undefined);
expect(patient.langName?.size).toBe(0);
});
it("sets language strings based on the default language", async () => {
const [, , builder] = await getTinyLoadedDatasetWithLanguageTags();
const observation = builder
.setLanguagePreferences("fr", "en")
.fromSubject<ObservationShape>(
namedNode("http://example.com/Observation1"),
);
observation.langNotes = "quelques notes";
expect(languagesOf(observation, "langNotes")).toEqual({
fr: "quelques notes",
"@none": "Cool Notes",
en: "Cooler Notes",
es: "Notas Geniales",
});
const patient = observation.subject as PatientShape;
patient.langName?.add("Luc");
expect(languagesOf(patient, "langName").fr?.has("Jean")).toBe(true);
expect(languagesOf(patient, "langName").fr?.has("Luc")).toBe(true);
expect(languagesOf(patient, "langName")["@none"]?.has("Jon")).toBe(true);
expect(languagesOf(patient, "langName").en?.has("John")).toBe(true);
expect(languagesOf(patient, "langName").es?.has("Juan")).toBe(true);
// Skips other in favor of setting the next language
setLanguagePreferences("@other", "es").using(observation, patient);
observation.langNotes = "algunas notas";
expect(languagesOf(observation, "langNotes")).toEqual({
fr: "quelques notes",
"@none": "Cool Notes",
en: "Cooler Notes",
es: "algunas notas",
});
// Does not set a language if only other
setLanguagePreferences("@other").using(observation, patient);
observation.langNotes = "Some Notes that will never be written";
expect(languagesOf(observation, "langNotes")).toEqual({
fr: "quelques notes",
"@none": "Cool Notes",
en: "Cooler Notes",
es: "algunas notas",
});
// Does not set a language if empty
setLanguagePreferences().using(observation, patient);
observation.langNotes = "Some Notes that will never be written";
expect(languagesOf(observation, "langNotes")).toEqual({
fr: "quelques notes",
"@none": "Cool Notes",
en: "Cooler Notes",
es: "algunas notas",
});
// Sets @none
setLanguagePreferences("@none").using(observation, patient);
observation.langNotes = "Other notes";
expect(languagesOf(observation, "langNotes")).toEqual({
fr: "quelques notes",
"@none": "Other notes",
en: "Cooler Notes",
es: "algunas notas",
});
});
it("uses languageOf to make a languageMap", async () => {
const [, observation] = await getTinyLoadedDatasetWithLanguageTags();
const languageMap = languagesOf(observation, "langNotes");
expect(languageMap).toEqual({
"@none": "Cool Notes",
en: "Cooler Notes",
es: "Notas Geniales",
fr: "Notes Sympas",
});
});
it("uses languageOf to set values on a languageMap", async () => {
const [dataset, observation] =
await getTinyLoadedDatasetWithLanguageTags();
const languageMap = languagesOf(observation, "langNotes");
languageMap.zh = "很酷的笔记";
languageMap.fr = "notes plus fraîches";
expect(languageMap).toEqual({
"@none": "Cool Notes",
en: "Cooler Notes",
es: "Notas Geniales",
fr: "notes plus fraîches",
zh: "很酷的笔记",
});
const langNoteQuads = dataset.match(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/langNotes"),
);
expect(langNoteQuads.size).toBe(5);
expect(
langNoteQuads.some(
(quad) =>
quad.object.termType === "Literal" &&
quad.object.language === "fr" &&
quad.object.value === "notes plus fraîches",
),
).toBe(true);
expect(
langNoteQuads.some(
(quad) =>
quad.object.termType === "Literal" &&
quad.object.language === "zh" &&
quad.object.value === "很酷的笔记",
),
).toBe(true);
});
it("uses languageOf to delete values on a languageMap", async () => {
const [dataset, observation] =
await getTinyLoadedDatasetWithLanguageTags();
const languageMap = languagesOf(observation, "langNotes");
delete languageMap.fr;
expect(languageMap).toEqual({
"@none": "Cool Notes",
en: "Cooler Notes",
es: "Notas Geniales",
});
const langNoteQuads = dataset.match(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/langNotes"),
);
expect(langNoteQuads.size).toBe(3);
expect(
langNoteQuads.every(
(quad) =>
!(
quad.object.termType === "Literal" &&
quad.object.language === "fr"
),
),
).toBe(true);
});
it("executes the methods of the LanguageSet", async () => {
const [dataset, observation] =
await getTinyLoadedDatasetWithLanguageTags();
const subject = namedNode("http://example.com/Patient1");
const predicate = namedNode("http://hl7.org/fhir/langName");
const patient = observation.subject as PatientShape;
const enSet = languagesOf(patient, "langName").en as LanguageSet;
expect(enSet.size).toBe(1);
enSet.add("Doe");
expect(enSet.size).toBe(2);
expect(enSet.has("Doe")).toBe(true);
expect(dataset.has(quad(subject, predicate, literal("Doe", "en")))).toBe(
true,
);
const callbackMock = jest.fn();
enSet.forEach(callbackMock);
expect(callbackMock).toHaveBeenCalledTimes(2);
expect(callbackMock).toHaveBeenCalledWith("John", "John", enSet);
const entries = enSet.entries();
const entriesVal1 = entries.next();
const entriesVal2 = entries.next();
const entriesVal3 = entries.next();
expect(entriesVal1.value).toEqual(["John", "John"]);
expect(entriesVal2.value).toEqual(["Doe", "Doe"]);
expect(entriesVal3.done).toBe(true);
const keys = enSet.keys();
const keysVal1 = keys.next();
const keysVal2 = keys.next();
const keysVal3 = keys.next();
expect(keysVal1.value).toBe("John");
expect(keysVal2.value).toBe("Doe");
expect(keysVal3.done).toBe(true);
const values = enSet.values();
const valuesVal1 = values.next();
const valuesVal2 = values.next();
const valuesVal3 = values.next();
expect(valuesVal1.value).toBe("John");
expect(valuesVal2.value).toBe("Doe");
expect(valuesVal3.done).toBe(true);
enSet.delete("John");
expect(enSet.size).toBe(1);
expect(enSet.has("John")).toBe(false);
expect(dataset.has(quad(subject, predicate, literal("John", "en")))).toBe(
false,
);
enSet.clear();
expect(enSet.size).toBe(0);
});
});
};
describe(
"jsonldDatasetProxy - unnested context",
testJsonldDatasetProxy(patientUnnestedContext),
);
describe(
"jsonldDatasetProxy - nested context",
testJsonldDatasetProxy(patientNestedContext),
);