UNPKG

@inrupt/solid-client

Version:

Make your web apps work with Solid Pods.

1,781 lines (1,585 loc) • 94 kB
// Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // import { describe, it, expect } from "@jest/globals"; import { dataset } from "@rdfjs/dataset"; import { DataFactory } from "n3"; import type { IriString, Thing, ThingPersisted, UrlString, } from "../interfaces"; import { removeAll, removeUrl, removeBoolean, removeDatetime, removeDate, removeTime, removeDecimal, removeInteger, removeStringEnglish, removeStringWithLocale, removeStringNoLocale, removeLiteral, removeNamedNode, } from "./remove"; import { mockThingFrom } from "./mock"; import { ValidPropertyUrlExpectedError, ValidValueUrlExpectedError, } from "./thing"; import { localNodeSkolemPrefix } from "../rdf.internal"; function getMockThingWithLiteralFor( predicate: IriString, literalValue: string, literalType: | "string" | "integer" | "decimal" | "boolean" | "dateTime" | "date" | "time", ): Thing { return { type: "Subject", url: "https://arbitrary.vocab/subject", predicates: { [predicate]: { literals: { [`http://www.w3.org/2001/XMLSchema#${literalType}`]: [literalValue], }, }, }, }; } function getMockThingWithNamedNode( predicate: IriString, object: IriString, ): Thing { return { type: "Subject", url: "https://arbitrary.vocab/subject", predicates: { [predicate]: { namedNodes: [object], }, }, }; } describe("removeAll", () => { it("removes all values for the given Predicate", () => { const thingWithStringAndIri = getMockThingWithLiteralFor( "https://some.vocab/predicate", "Arbitrary string value", "string", ); (thingWithStringAndIri.predicates["https://some.vocab/predicate"] .namedNodes as UrlString[]) = ["https://arbitrary.vocab/predicate"]; const updatedThing = removeAll( thingWithStringAndIri, "https://some.vocab/predicate", ); expect( updatedThing.predicates["https://some.vocab/predicate"], ).toBeUndefined(); }); it("accepts Properties as Named Nodes", () => { const thingWithString = getMockThingWithLiteralFor( "https://some.vocab/predicate", "Arbitrary string value", "string", ); const updatedThing = removeAll( thingWithString, DataFactory.namedNode("https://some.vocab/predicate"), ); expect( updatedThing.predicates["https://some.vocab/predicate"], ).toBeUndefined(); }); it("does not modify the input Thing", () => { const thingWithString = getMockThingWithLiteralFor( "https://some.vocab/predicate", "Arbitrary string value", "string", ); const updatedThing = removeAll( thingWithString, DataFactory.namedNode("https://some.vocab/predicate"), ); expect(thingWithString).not.toStrictEqual(updatedThing); expect( thingWithString.predicates["https://some.vocab/predicate"], ).toBeDefined(); expect( updatedThing.predicates["https://some.vocab/predicate"], ).toBeUndefined(); }); it("does nothing if there was nothing to remove", () => { const thingWithString = getMockThingWithLiteralFor( "https://some.vocab/predicate", "Arbitrary string value", "string", ); const updatedThing = removeAll( thingWithString, DataFactory.namedNode("https://some.vocab/other-predicate"), ); expect(thingWithString).toStrictEqual(updatedThing); expect( updatedThing.predicates["https://some.vocab/predicate"], ).toBeDefined(); }); it("also works on ThingLocals", () => { const thingLocal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "Arbitrary string value", "string", ); (thingLocal.url as string) = `${localNodeSkolemPrefix}arbitrary-subject-name`; const updatedThing = removeAll(thingLocal, "https://some.vocab/predicate"); expect( updatedThing.predicates["https://some.vocab/predicate"], ).toBeUndefined(); }); it("removes multiple instances of the same value for the same Predicate", () => { const thingWithDuplicateIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://arbitrary.pod/resource#name", ); ( thingWithDuplicateIri.predicates["https://some.vocab/predicate"] .namedNodes as UrlString[] ).push("https://arbitrary.pod/resource#name"); const updatedThing = removeAll( thingWithDuplicateIri, "https://some.vocab/predicate", ); expect( updatedThing.predicates["https://some.vocab/predicate"], ).toBeUndefined(); }); it("does not remove Quads with different Predicates", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://arbitrary.pod/resource#name", ); (thingWithIri.predicates["https://some-other.vocab/predicate"] as { namedNodes: UrlString[]; }) = { namedNodes: ["https://arbitrary.pod/resource#name"], }; const updatedThing = removeAll( thingWithIri, "https://some.vocab/predicate", ); expect( updatedThing.predicates["https://some-other.vocab/predicate"], ).toBeDefined(); }); it("throws an error when passed something other than a Thing", () => { expect(() => removeAll(null as unknown as Thing, "https://arbitrary.vocab/predicate"), ).toThrow("Expected a Thing, but received: [null]."); }); it("throws an error when passed an invalid property URL", () => { expect(() => removeAll( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", ), ).toThrow( "Expected a valid URL to identify a property, but received: [not-a-url].", ); }); it("throws an instance of ValidPropertyUrlExpectedError when passed an invalid property URL", () => { let thrownError; try { removeAll( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", ); } catch (e) { thrownError = e; } expect(thrownError).toBeInstanceOf(ValidPropertyUrlExpectedError); }); }); describe("removeIri", () => { it("removes the given IRI value for the given Predicate", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); const updatedThing = removeUrl( thingWithIri, "https://some.vocab/predicate", "https://some.pod/resource#name", ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual([]); }); it("accepts Properties as Named Nodes", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); const updatedThing = removeUrl( thingWithIri, DataFactory.namedNode("https://some.vocab/predicate"), "https://some.pod/resource#name", ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual([]); }); it("accepts IRI's as Named Nodes", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); const updatedThing = removeUrl( thingWithIri, "https://some.vocab/predicate", DataFactory.namedNode("https://some.pod/resource#name"), ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual([]); }); it("does not modify the input Thing", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); const updatedThing = removeUrl( thingWithIri, "https://some.vocab/predicate", "https://some.pod/resource#name", ); expect(thingWithIri).not.toStrictEqual(updatedThing); expect( thingWithIri.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual(["https://some.pod/resource#name"]); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual([]); }); it("does nothing if the URL to remove was not found", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); const updatedThing = removeUrl( thingWithIri, "https://some.vocab/other-predicate", "https://some.pod/other-resource#name", ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toEqual(["https://some.pod/resource#name"]); }); it("also works on ThingLocals", () => { const thingLocal = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); (thingLocal.url as string) = `${localNodeSkolemPrefix}arbitrary-subject-name`; const updatedThing = removeUrl( thingLocal, "https://some.vocab/predicate", "https://some.pod/resource#name", ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual([]); }); it("removes multiple instances of the same IRI for the same Predicate", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); ( thingWithIri.predicates["https://some.vocab/predicate"] .namedNodes as UrlString[] ).push("https://some.pod/resource#name"); const updatedThing = removeUrl( thingWithIri, "https://some.vocab/predicate", "https://some.pod/resource#name", ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual([]); }); it("does not remove Quads with different Predicates or Objects", () => { const thingWithIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#name", ); ( thingWithIri.predicates["https://some.vocab/predicate"] .namedNodes as UrlString[] ).push("https://some-other.pod/resource#name"); (thingWithIri.predicates["https://some-other.vocab/predicate"] as { namedNodes: UrlString[]; }) = { namedNodes: ["https://arbitrary.pod/resource#name"], }; const updatedThing = removeUrl( thingWithIri, "https://some.vocab/predicate", "https://some.pod/resource#name", ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual(["https://some-other.pod/resource#name"]); expect( updatedThing.predicates["https://some-other.vocab/predicate"].namedNodes, ).toStrictEqual(["https://arbitrary.pod/resource#name"]); }); it("does not remove Quads with non-IRI Objects", () => { const thingWithIri = getMockThingWithLiteralFor( "https://some.vocab/predicate", "Some non-IRI Object", "string", ); (thingWithIri.predicates["https://some.vocab/predicate"] .namedNodes as UrlString[]) = ["https://some.pod/resource#name"]; const updatedThing = removeUrl( thingWithIri, "https://some.vocab/predicate", "https://some.pod/resource#name", ); expect( updatedThing.predicates["https://some.vocab/predicate"], ).toStrictEqual({ literals: { "http://www.w3.org/2001/XMLSchema#string": ["Some non-IRI Object"], }, namedNodes: [], }); }); it("resolves ThingPersisteds", () => { const thingPersisted: ThingPersisted = { type: "Subject", url: "https://some.pod/resource#thing", predicates: {}, }; const thingWithThingPersistedIri = getMockThingWithNamedNode( "https://some.vocab/predicate", "https://some.pod/resource#thing", ); const updatedThing = removeUrl( thingWithThingPersistedIri, "https://some.vocab/predicate", thingPersisted, ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual([]); }); it("throws an error when passed something other than a Thing", () => { expect(() => removeUrl( null as unknown as Thing, "https://arbitrary.vocab/predicate", "https://arbitrary.url", ), ).toThrow("Expected a Thing, but received: [null]."); }); it("throws an error when passed an invalid property URL", () => { expect(() => removeUrl( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", "https://arbitrary.url", ), ).toThrow( "Expected a valid URL to identify a property, but received: [not-a-url].", ); }); it("throws an instance of ValidPropertyUrlExpectedError when passed an invalid property URL", () => { let thrownError; try { removeUrl( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", "https://arbitrary.url", ); } catch (e) { thrownError = e; } expect(thrownError).toBeInstanceOf(ValidPropertyUrlExpectedError); }); it("throws an error when passed an invalid URL value", () => { expect(() => removeUrl( mockThingFrom("https://arbitrary.pod/resource#thing"), "https://arbitrary.vocab/predicate", "not-a-url", ), ).toThrow("Expected a valid URL value, but received: [not-a-url]."); }); it("throws an instance of ValidValueUrlExpectedError when passed an invalid property URL", () => { let thrownError; try { removeUrl( mockThingFrom("https://arbitrary.pod/resource#thing"), "https://arbitrary.vocab/predicate", "not-a-url", ); } catch (e) { thrownError = e; } expect(thrownError).toBeInstanceOf(ValidValueUrlExpectedError); }); }); describe("removeBoolean", () => { it("removes the given boolean value for the given Predicate", () => { const thingWithBoolean = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); const updatedThing = removeBoolean( thingWithBoolean, "https://some.vocab/predicate", true, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); }); it("removes equivalent booleans with different serialisations", () => { const thingWithSerialised1 = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); const thingWithSerialised0 = getMockThingWithLiteralFor( "https://some.vocab/predicate", "0", "boolean", ); const thingWithSerialisedTrue = getMockThingWithLiteralFor( "https://some.vocab/predicate", "true", "boolean", ); const thingWithSerialisedFalse = getMockThingWithLiteralFor( "https://some.vocab/predicate", "false", "boolean", ); const updatedThingWithoutSerialised1 = removeBoolean( thingWithSerialised1, "https://some.vocab/predicate", true, ); const updatedThingWithoutSerialised0 = removeBoolean( thingWithSerialised0, "https://some.vocab/predicate", false, ); const updatedThingWithoutSerialisedTrue = removeBoolean( thingWithSerialisedTrue, "https://some.vocab/predicate", true, ); const updatedThingWithoutSerialisedFalse = removeBoolean( thingWithSerialisedFalse, "https://some.vocab/predicate", false, ); expect( updatedThingWithoutSerialised1.predicates["https://some.vocab/predicate"] .literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); expect( updatedThingWithoutSerialised0.predicates["https://some.vocab/predicate"] .literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); expect( updatedThingWithoutSerialisedTrue.predicates[ "https://some.vocab/predicate" ].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); expect( updatedThingWithoutSerialisedFalse.predicates[ "https://some.vocab/predicate" ].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); }); it("accepts Properties as Named Nodes", () => { const thingWithBoolean = getMockThingWithLiteralFor( "https://some.vocab/predicate", "0", "boolean", ); const updatedThing = removeBoolean( thingWithBoolean, DataFactory.namedNode("https://some.vocab/predicate"), false, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); }); it("does not modify the input Thing", () => { const thingWithBoolean = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); const updatedThing = removeBoolean( thingWithBoolean, "https://some.vocab/predicate", true, ); expect(thingWithBoolean).not.toStrictEqual(updatedThing); expect( thingWithBoolean.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": ["1"], }); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); }); it("does nothing if the boolean to remove was not found", () => { const thingWithBoolean = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); const updatedThing = removeBoolean( thingWithBoolean, "https://some.vocab/other-predicate", false, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toEqual({ "http://www.w3.org/2001/XMLSchema#boolean": ["1"], }); }); it("also works on ThingLocals", () => { const thingLocal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); (thingLocal.url as string) = `${localNodeSkolemPrefix}arbitrary-subject-name`; const updatedThing = removeBoolean( thingLocal, "https://some.vocab/predicate", true, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); }); it("removes multiple instances of the same boolean for the same Predicate", () => { const thingWithDuplicateBoolean = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); ( thingWithDuplicateBoolean.predicates["https://some.vocab/predicate"] .literals!["http://www.w3.org/2001/XMLSchema#boolean"] as string[] ).push("1"); const updatedThing = removeBoolean( thingWithDuplicateBoolean, "https://some.vocab/predicate", true, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": [], }); }); it("does not remove Quads with different Predicates or Objects", () => { const thingWithOtherQuads = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); ( thingWithOtherQuads.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#boolean" ] as string[] ).push("0"); (thingWithOtherQuads.predicates[ "https://some-other.vocab/predicate" ] as any) = { literals: { "http://www.w3.org/2001/XMLSchema#boolean": ["1"], }, }; const updatedThing = removeBoolean( thingWithOtherQuads, "https://some.vocab/predicate", true, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": ["0"] }); expect( updatedThing.predicates["https://some-other.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#boolean": ["1"] }); }); it("does not remove Quads with non-boolean Objects", () => { const thingWithIri = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1", "boolean", ); (thingWithIri.predicates["https://some.vocab/predicate"] .namedNodes as UrlString[]) = ["1"]; const updatedThing = removeBoolean( thingWithIri, "https://some.vocab/predicate", true, ); expect( updatedThing.predicates["https://some.vocab/predicate"].namedNodes, ).toStrictEqual(["1"]); }); it("throws an error when passed something other than a Thing", () => { expect(() => removeBoolean( null as unknown as Thing, "https://arbitrary.vocab/predicate", true, ), ).toThrow("Expected a Thing, but received: [null]."); }); it("throws an error when passed an invalid property URL", () => { expect(() => removeBoolean( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", true, ), ).toThrow( "Expected a valid URL to identify a property, but received: [not-a-url].", ); }); it("throws an instance of ValidPropertyUrlExpectedError when passed an invalid property URL", () => { let thrownError; try { removeBoolean( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", true, ); } catch (e) { thrownError = e; } expect(thrownError).toBeInstanceOf(ValidPropertyUrlExpectedError); }); }); describe("removeDatetime", () => { it("removes the given datetime value for the given Predicate", () => { const thingWithDatetime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); const updatedThing = removeDatetime( thingWithDatetime, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], }); }); it("removes equivalent Datetimes with different serialisations", () => { const thingWithRoundedDatetime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); const thingWithSpecificDatetime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T12:37:42.000+01:00", "dateTime", ); const updatedThingWithoutRoundedDatetime = removeDatetime( thingWithRoundedDatetime, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); const updatedThingWithoutSpecificDatetime = removeDatetime( thingWithSpecificDatetime, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect( updatedThingWithoutRoundedDatetime.predicates[ "https://some.vocab/predicate" ].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], }); expect( updatedThingWithoutSpecificDatetime.predicates[ "https://some.vocab/predicate" ].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], }); }); it("accepts Properties as Named Nodes", () => { const thingWithDatetime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); const updatedThing = removeDatetime( thingWithDatetime, DataFactory.namedNode("https://some.vocab/predicate"), new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], }); }); it("does not modify the input Thing", () => { const thingWithDatetime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); const updatedThing = removeDatetime( thingWithDatetime, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect(thingWithDatetime).not.toStrictEqual(updatedThing); expect( thingWithDatetime.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": ["1990-11-12T13:37:42Z"], }); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], }); }); it("also works on ThingLocals", () => { const thingLocal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); (thingLocal.url as string) = `${localNodeSkolemPrefix}arbitrary-subject-name`; const updatedThing = removeDatetime( thingLocal, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], }); }); it("removes multiple instances of the same datetime for the same Predicate", () => { const thingWithDuplicateDatetime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); ( thingWithDuplicateDatetime.predicates["https://some.vocab/predicate"] .literals!["http://www.w3.org/2001/XMLSchema#dateTime"] as string[] ).push("1990-11-12T13:37:42Z"); const updatedThing = removeDatetime( thingWithDuplicateDatetime, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], }); }); it("does not remove Quads with different Predicates or Objects", () => { const thingWithOtherQuads = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); // A different Object: ( thingWithOtherQuads.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#dateTime" ] as string[] ).push("1955-06-08T13:37:42Z"); // An invalid object ( thingWithOtherQuads.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#dateTime" ] as string[] ).push("not-a-datetime"); // A different predicate (thingWithOtherQuads.predicates[ "https://some-other.vocab/predicate" ] as any) = { literals: { "http://www.w3.org/2001/XMLSchema#dateTime": ["1990-11-12T13:37:42Z"], }, }; const updatedThing = removeDatetime( thingWithOtherQuads, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect(updatedThing.predicates).toStrictEqual({ "https://some.vocab/predicate": { literals: { "http://www.w3.org/2001/XMLSchema#dateTime": [ "1955-06-08T13:37:42Z", "not-a-datetime", ], }, }, "https://some-other.vocab/predicate": { literals: { "http://www.w3.org/2001/XMLSchema#dateTime": ["1990-11-12T13:37:42Z"], }, }, }); }); it("does not remove Quads with non-datetime Objects", () => { const thingWithString = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12T13:37:42Z", "dateTime", ); (thingWithString.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#string" ] as string[]) = ["Some string value"]; const updatedThing = removeDatetime( thingWithString, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#dateTime": [], "http://www.w3.org/2001/XMLSchema#string": ["Some string value"], }); }); it("throws an error when passed something other than a Thing", () => { expect(() => removeDatetime( null as unknown as Thing, "https://arbitrary.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ), ).toThrow("Expected a Thing, but received: [null]."); }); it("throws an error when passed an invalid property URL", () => { expect(() => removeDatetime( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ), ).toThrow( "Expected a valid URL to identify a property, but received: [not-a-url].", ); }); it("throws an instance of ValidPropertyUrlExpectedError when passed an invalid property URL", () => { let thrownError; try { removeDatetime( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", new Date(Date.UTC(1990, 10, 12, 13, 37, 42, 0)), ); } catch (e) { thrownError = e; } expect(thrownError).toBeInstanceOf(ValidPropertyUrlExpectedError); }); }); describe("removeDate", () => { it("removes the given date value for the given Predicate", () => { const thingWithDate = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); const updatedThing = removeDate( thingWithDate, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": [], }); }); it("removes the given date value for the given Predicate regardless of the time value set", () => { const thingWithDate = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); const updatedThing = removeDate( thingWithDate, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12, 13, 37, 42)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": [], }); }); it("accepts Properties as Named Nodes", () => { const thingWithDate = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); const updatedThing = removeDate( thingWithDate, DataFactory.namedNode("https://some.vocab/predicate"), new Date(Date.UTC(1990, 10, 12)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": [], }); }); it("does not modify the input Thing", () => { const thingWithDate = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); const updatedThing = removeDate( thingWithDate, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12)), ); expect(thingWithDate).not.toStrictEqual(updatedThing); expect( thingWithDate.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": ["1990-11-12Z"], }); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": [], }); }); it("also works on ThingLocals", () => { const thingLocal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); (thingLocal.url as string) = `${localNodeSkolemPrefix}arbitrary-subject-name`; const updatedThing = removeDate( thingLocal, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": [], }); }); it("removes multiple instances of the same date for the same Predicate", () => { const thingWithDuplicateDate = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); ( thingWithDuplicateDate.predicates["https://some.vocab/predicate"] .literals!["http://www.w3.org/2001/XMLSchema#date"] as string[] ).push("1990-11-12Z"); const updatedThing = removeDate( thingWithDuplicateDate, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": [], }); }); it("does not remove Quads with different Predicates or Objects", () => { const thingWithOtherQuads = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); // A different Object: ( thingWithOtherQuads.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#date" ] as string[] ).push("1955-06-08Z"); // An invalid object ( thingWithOtherQuads.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#date" ] as string[] ).push("not-a-date"); // A different predicate (thingWithOtherQuads.predicates[ "https://some-other.vocab/predicate" ] as any) = { literals: { "http://www.w3.org/2001/XMLSchema#date": ["1990-11-12Z"], }, }; const updatedThing = removeDate( thingWithOtherQuads, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12)), ); expect(updatedThing.predicates).toStrictEqual({ "https://some.vocab/predicate": { literals: { "http://www.w3.org/2001/XMLSchema#date": [ "1955-06-08Z", "not-a-date", ], }, }, "https://some-other.vocab/predicate": { literals: { "http://www.w3.org/2001/XMLSchema#date": ["1990-11-12Z"], }, }, }); }); it("does not remove Quads with non-date Objects", () => { const thingWithString = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1990-11-12Z", "date", ); (thingWithString.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#string" ] as string[]) = ["Some string value"]; const updatedThing = removeDate( thingWithString, "https://some.vocab/predicate", new Date(Date.UTC(1990, 10, 12)), ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#date": [], "http://www.w3.org/2001/XMLSchema#string": ["Some string value"], }); }); it("throws an error when passed something other than a Thing", () => { expect(() => removeDate( null as unknown as Thing, "https://arbitrary.vocab/predicate", new Date(Date.UTC(1990, 10, 12)), ), ).toThrow("Expected a Thing, but received: [null]."); }); it("throws an error when passed an invalid property URL", () => { expect(() => removeDate( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", new Date(Date.UTC(1990, 10, 12)), ), ).toThrow( "Expected a valid URL to identify a property, but received: [not-a-url].", ); }); it("throws an instance of ValidPropertyUrlExpectedError when passed an invalid property URL", () => { let thrownError; try { removeDate( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", new Date(Date.UTC(1990, 10, 12)), ); } catch (e) { thrownError = e; } expect(thrownError).toBeInstanceOf(ValidPropertyUrlExpectedError); }); }); describe("removeTime", () => { it("removes the given time value for the given Predicate", () => { const thingWithTime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); const updatedThing = removeTime( thingWithTime, "https://some.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], }); }); it("removes equivalent times with different serialisations", () => { const thingWithRoundedTime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); const thingWithSpecificTime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "12:37:42+01:00", "time", ); const updatedThingWithoutRoundedTime = removeTime( thingWithRoundedTime, "https://some.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ); const updatedThingWithoutSpecificTime = removeTime( thingWithSpecificTime, "https://some.vocab/predicate", { hour: 12, minute: 37, second: 42, timezoneHourOffset: 1, timezoneMinuteOffset: 0, }, ); expect( updatedThingWithoutRoundedTime.predicates["https://some.vocab/predicate"] .literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], }); expect( updatedThingWithoutSpecificTime.predicates["https://some.vocab/predicate"] .literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], }); }); it("accepts Properties as Named Nodes", () => { const thingWithTime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); const updatedThing = removeTime( thingWithTime, DataFactory.namedNode("https://some.vocab/predicate"), { hour: 13, minute: 37, second: 42, }, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], }); }); it("does not modify the input Thing", () => { const thingWithTime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); const updatedThing = removeTime( thingWithTime, "https://some.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ); expect(thingWithTime).not.toStrictEqual(updatedThing); expect( thingWithTime.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": ["13:37:42"], }); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], }); }); it("also works on ThingLocals", () => { const thingLocal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); (thingLocal.url as string) = `${localNodeSkolemPrefix}arbitrary-subject-name`; const updatedThing = removeTime( thingLocal, "https://some.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], }); }); it("removes multiple instances of the same time for the same Predicate", () => { const thingWithDuplicateTime = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); ( thingWithDuplicateTime.predicates["https://some.vocab/predicate"] .literals!["http://www.w3.org/2001/XMLSchema#time"] as string[] ).push("13:37:42"); const updatedThing = removeTime( thingWithDuplicateTime, "https://some.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], }); }); it("does not remove Quads with different Predicates or Objects", () => { const thingWithOtherQuads = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); // A different Object: ( thingWithOtherQuads.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#time" ] as string[] ).push("14:37:42"); // An invalid object ( thingWithOtherQuads.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#time" ] as string[] ).push("not-a-time"); // A different predicate (thingWithOtherQuads.predicates[ "https://some-other.vocab/predicate" ] as any) = { literals: { "http://www.w3.org/2001/XMLSchema#time": ["13:37:42"], }, }; const updatedThing = removeTime( thingWithOtherQuads, "https://some.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ); expect(updatedThing.predicates).toStrictEqual({ "https://some.vocab/predicate": { literals: { "http://www.w3.org/2001/XMLSchema#time": ["14:37:42", "not-a-time"], }, }, "https://some-other.vocab/predicate": { literals: { "http://www.w3.org/2001/XMLSchema#time": ["13:37:42"], }, }, }); }); it("does not remove Quads with non-time Objects", () => { const thingWithString = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13:37:42", "time", ); (thingWithString.predicates["https://some.vocab/predicate"].literals![ "http://www.w3.org/2001/XMLSchema#string" ] as string[]) = ["Some string value"]; const updatedThing = removeTime( thingWithString, "https://some.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#time": [], "http://www.w3.org/2001/XMLSchema#string": ["Some string value"], }); }); it("throws an error when passed something other than a Thing", () => { expect(() => removeTime( null as unknown as Thing, "https://arbitrary.vocab/predicate", { hour: 13, minute: 37, second: 42, }, ), ).toThrow("Expected a Thing, but received: [null]."); }); it("throws an error when passed an invalid property URL", () => { expect(() => removeTime( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", { hour: 13, minute: 37, second: 42, }, ), ).toThrow( "Expected a valid URL to identify a property, but received: [not-a-url].", ); }); it("throws an instance of ValidPropertyUrlExpectedError when passed an invalid property URL", () => { let thrownError; try { removeTime( mockThingFrom("https://arbitrary.pod/resource#thing"), "not-a-url", { hour: 13, minute: 37, second: 42, }, ); } catch (e) { thrownError = e; } expect(thrownError).toBeInstanceOf(ValidPropertyUrlExpectedError); }); }); describe("removeDecimal", () => { it("removes the given decimal value for the given Predicate", () => { const thingWithDecimal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13.37", "decimal", ); const updatedThing = removeDecimal( thingWithDecimal, "https://some.vocab/predicate", 13.37, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#decimal": [], }); }); it("removes equivalent Decimals with different serialisations", () => { const thingWithPlainDecimal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "1337", "decimal", ); const thingWithSignedDecimal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "+1337", "decimal", ); const thingWithZeroedDecimal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "01337.0", "decimal", ); const updatedThingWithoutPlainDecimal = removeDecimal( thingWithPlainDecimal, "https://some.vocab/predicate", 1337, ); const updatedThingWithoutSignedDecimal = removeDecimal( thingWithSignedDecimal, "https://some.vocab/predicate", 1337, ); const updatedThingWithoutZeroedDecimal = removeDecimal( thingWithZeroedDecimal, "https://some.vocab/predicate", 1337, ); expect( updatedThingWithoutPlainDecimal.predicates["https://some.vocab/predicate"] .literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#decimal": [], }); expect( updatedThingWithoutSignedDecimal.predicates[ "https://some.vocab/predicate" ].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#decimal": [], }); expect( updatedThingWithoutZeroedDecimal.predicates[ "https://some.vocab/predicate" ].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#decimal": [], }); }); it("accepts Properties as Named Nodes", () => { const thingWithDecimal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13.37", "decimal", ); const updatedThing = removeDecimal( thingWithDecimal, DataFactory.namedNode("https://some.vocab/predicate"), 13.37, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#decimal": [], }); }); it("does not modify the input Thing", () => { const thingWithDecimal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13.37", "decimal", ); const updatedThing = removeDecimal( thingWithDecimal, "https://some.vocab/predicate", 13.37, ); expect(thingWithDecimal).not.toStrictEqual(updatedThing); expect( thingWithDecimal.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#decimal": ["13.37"], }); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toStrictEqual({ "http://www.w3.org/2001/XMLSchema#decimal": [], }); }); it("does nothing if the decimal to remove could not be found", () => { const thingWithDecimal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13.37", "decimal", ); const updatedThing = removeDecimal( thingWithDecimal, "https://some.vocab/other-predicate", 4.2, ); expect( updatedThing.predicates["https://some.vocab/predicate"].literals, ).toEqual({ "http://www.w3.org/2001/XMLSchema#decimal": ["13.37"], }); }); it("also works on ThingLocals", () => { const thingLocal = getMockThingWithLiteralFor( "https://some.vocab/predicate", "13.37", "decimal", ); (thingLocal.url as string) = `${localNodeSkolemPrefix}arbitrary-subject-n