X-Git-Url: https://git.ladys.computer/Etiquette/blobdiff_plain/6623cbf39b85515219a3ab012e27d97884ea8aa3..f8903c5b3bd12d02af174e5043d906d63da0b0d1:/model.test.js?ds=sidebyside diff --git a/model.test.js b/model.test.js index 7944238..b3a1b1b 100644 --- a/model.test.js +++ b/model.test.js @@ -1,11 +1,14 @@ -// šŸ“§šŸ·ļø Ɖtiquette ∷ model.test.js -// ==================================================================== -// -// Copyright Ā© 2023 Lady [@ Lady’s Computer]. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at . +// SPDX-FileCopyrightText: 2023, 2025 Lady +// SPDX-License-Identifier: MPL-2.0 +/** + * ⁌ šŸ“§šŸ·ļø Ɖtiquette ∷ model.test.js + * + * Copyright Ā© 2023, 2025 Lady [@ Ladys Computer]. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . + */ import { assert, @@ -105,13 +108,13 @@ describe("TagSystem", () => { }); it("[[Construct]] defaults the preferred label to the empty string", () => { - assertStrictEquals(new Tag().prefLabel, ""); + assertEquals({ ...new Tag().prefLabel }, { "@value": "" }); }); it("[[Construct]] correctly sets the preferred label to a simple string", () => { - assertStrictEquals( - new Tag("RelationshipTag", "Shadow, Me").prefLabel, - "Shadow, Me", + assertEquals( + { ...new Tag("RelationshipTag", "Shadow, Me").prefLabel }, + { "@value": "Shadow, Me" }, ); }); @@ -164,6 +167,12 @@ describe("TagSystem", () => { }); }); + describe(".constructor", () => { + it("[[Get]] is `Function`", () => { + assertStrictEquals(Tag.constructor, Function); + }); + }); + describe(".fromIRI", () => { it("[[Call]] returns the persisted tag with the given IĀ·RĀ·I", () => { const tag = new Tag(); @@ -177,17 +186,19 @@ describe("TagSystem", () => { assertStrictEquals(retrieved.identifier, identifier); }); - it("[[Call]] returns null if no tag with the given IĀ·RĀ·I has been persisted", () => { + it("[[Call]] returns undefined if no tag with the given IĀ·RĀ·I has been persisted", () => { assertStrictEquals( Tag.fromIRI( `https://${system.authorityName}/tag:${system.taggingEntity}:000-0000`, ), - null, + undefined, ); }); - it("[[Call]] returns null if passed an invalid IĀ·RĀ·I", () => { - assertStrictEquals(Tag.fromIRI(`bad iri`), null); + it("[[Call]] throws if passed an invalid IĀ·RĀ·I", () => { + assertThrows(() => { + Tag.fromIRI(`bad iri`); + }); }); }); @@ -204,8 +215,8 @@ describe("TagSystem", () => { assertStrictEquals(retrieved.identifier, identifier); }); - it("[[Call]] returns null if no tag with the given identifier has been persisted", () => { - assertStrictEquals(Tag.fromIdentifier("000-0000"), null); + it("[[Call]] returns undefined if no tag with the given identifier has been persisted", () => { + assertStrictEquals(Tag.fromIdentifier("000-0000"), undefined); }); it("[[Call]] throws if passed an invalid identifier", () => { @@ -231,14 +242,14 @@ describe("TagSystem", () => { assertStrictEquals(retrieved.identifier, identifier); }); - it("[[Call]] returns null if no tag with the given Tag UĀ·RĀ·I has been persisted", () => { + it("[[Call]] returns undefined if no tag with the given Tag UĀ·RĀ·I has been persisted", () => { assertStrictEquals( - Tag.fromIRI(`tag:${system.taggingEntity}:`), - null, + Tag.fromTagURI(`tag:${system.taggingEntity}:`), + undefined, ); assertStrictEquals( - Tag.fromIRI(`tag:${system.taggingEntity}:000-0000`), - null, + Tag.fromTagURI(`tag:${system.taggingEntity}:000-0000`), + undefined, ); }); @@ -254,12 +265,6 @@ describe("TagSystem", () => { }); }); - describe(".getSystem", () => { - it("[[Has]] is not present", () => { - assertFalse("getSystem" in Tag); - }); - }); - describe(".identifiers", () => { it("[[Call]] yields all the persisted identifiers", () => { const tags = new Set(function* () { @@ -278,6 +283,12 @@ describe("TagSystem", () => { }); }); + describe(".system", () => { + it("[[Call]] returns the `TagSystem`", () => { + assertEquals(Tag.system, system); + }); + }); + // `.[Storage.toInstance]` is tested by `.fromIdentifier`. describe("::addAltLabel", () => { @@ -295,17 +306,19 @@ describe("TagSystem", () => { { "@value": "three", "@language": "en" }, ); assertEquals( - Array.from( - tag.altLabels(), - ($) => typeof $ == "string" ? $ : { ...$ }, - ), + Array.from(tag.altLabels(), ($) => ({ ...$ })), [ - "one", - "two", + { "@value": "one" }, + { "@value": "two" }, { "@value": "three", "@language": "en" }, ], ); }); + + it("[[Call]] returns this", () => { + const tag = new Tag(); + assertStrictEquals(tag.addAltLabel(), tag); + }); }); describe("::addBroaderTag", () => { @@ -328,6 +341,11 @@ describe("TagSystem", () => { ); }); + it("[[Call]] returns this", () => { + const tag = new Tag(); + assertStrictEquals(tag.addBroaderTag(), tag); + }); + it("[[Call]] throws when adding a non‐persisted tag", () => { const tag = new Tag(); assertThrows(() => { @@ -361,17 +379,19 @@ describe("TagSystem", () => { { "@value": "three", "@language": "en" }, ); assertEquals( - Array.from( - tag.hiddenLabels(), - ($) => typeof $ == "string" ? $ : { ...$ }, - ), + Array.from(tag.hiddenLabels(), ($) => ({ ...$ })), [ - "one", - "two", + { "@value": "one" }, + { "@value": "two" }, { "@value": "three", "@language": "en" }, ], ); }); + + it("[[Call]] returns this", () => { + const tag = new Tag(); + assertStrictEquals(tag.addHiddenLabel(), tag); + }); }); describe("::addInCanonTag", () => { @@ -394,9 +414,16 @@ describe("TagSystem", () => { ); }); + it("[[Call]] returns this", () => { + const tag = new Tag("EntityTag"); + assertStrictEquals(tag.addInCanonTag(), tag); + }); + it("[[Call]] throws when this is not a tag which can be placed in canon", () => { + const canon = new Tag("CanonTag"); + canon.persist(); assertThrows(() => { - new Tag().addInCanonTag(); + new Tag().addInCanonTag(canon); }); }); @@ -447,9 +474,16 @@ describe("TagSystem", () => { ); }); + it("[[Call]] returns this", () => { + const tag = new Tag("ConceptualTag"); + assertStrictEquals(tag.addInvolvesTag(), tag); + }); + it("[[Call]] throws when this is not a conceptual tag", () => { + const involved = new Tag(); + involved.persist(); assertThrows(() => { - new Tag().addInvolvesTag(); + new Tag().addInvolvesTag(involved); }); }); @@ -528,7 +562,10 @@ describe("TagSystem", () => { const tag = new Tag(); tag.addAltLabel("etaoin"); tag.deleteAltLabel(); - assertEquals([...tag.altLabels()], ["etaoin"]); + assertEquals( + Array.from(tag.altLabels(), ($) => ({ ...$ })), + [{ "@value": "etaoin" }], + ); }); it("[[Call]] deletes only the provided hidden labels", () => { @@ -545,7 +582,15 @@ describe("TagSystem", () => { { "@value": "three", "@language": "en" }, { "@value": "four", "@language": "en" }, ); - assertEquals([...tag.altLabels()], ["four"]); + assertEquals( + Array.from(tag.altLabels(), ($) => ({ ...$ })), + [{ "@value": "four" }], + ); + }); + + it("[[Call]] returns this", () => { + const tag = new Tag(); + assertStrictEquals(tag.deleteAltLabel(), tag); }); }); @@ -579,6 +624,11 @@ describe("TagSystem", () => { [broader2.identifier], ); }); + + it("[[Call]] returns this", () => { + const tag = new Tag(); + assertStrictEquals(tag.deleteBroaderTag(), tag); + }); }); describe("::deleteHiddenLabel", () => { @@ -586,7 +636,10 @@ describe("TagSystem", () => { const tag = new Tag(); tag.addHiddenLabel("etaoin"); tag.deleteHiddenLabel(); - assertEquals([...tag.hiddenLabels()], ["etaoin"]); + assertEquals( + Array.from(tag.hiddenLabels(), ($) => ({ ...$ })), + [{ "@value": "etaoin" }], + ); }); it("[[Call]] deletes only the provided alternative labels", () => { @@ -603,7 +656,15 @@ describe("TagSystem", () => { { "@value": "three", "@language": "en" }, { "@value": "four", "@language": "en" }, ); - assertEquals([...tag.hiddenLabels()], ["four"]); + assertEquals( + Array.from(tag.hiddenLabels(), ($) => ({ ...$ })), + [{ "@value": "four" }], + ); + }); + + it("[[Call]] returns this", () => { + const tag = new Tag(); + assertStrictEquals(tag.deleteHiddenLabel(), tag); }); }); @@ -633,6 +694,11 @@ describe("TagSystem", () => { [canon2.identifier], ); }); + + it("[[Call]] returns this", () => { + const tag = new Tag("EntityTag"); + assertStrictEquals(tag.deleteInCanonTag(), tag); + }); }); describe("::deleteInvolvesTag", () => { @@ -665,6 +731,11 @@ describe("TagSystem", () => { [involved2.identifier], ); }); + + it("[[Call]] returns this", () => { + const tag = new Tag("ConceptualTag"); + assertStrictEquals(tag.deleteInvolvesTag(), tag); + }); }); describe("::hasInCanonTags", () => { @@ -713,6 +784,8 @@ describe("TagSystem", () => { // `::iri` is tested by a `.fromIRI`. + // `::iriSpace` is tested by a `.fromIRI`. + // `::kind` is tested by the constructor. describe("::narrowerTags", () => { @@ -849,15 +922,24 @@ describe("TagSystem", () => { const activity = tag.persist(); assertStrictEquals(activity, null); }); + + it("[[Call]] returns undefined for a silent persist", () => { + const broader = new Tag(); + broader.persist(); + const tag = new Tag(); + tag.prefLabel = "etaoin"; + tag.addBroaderTag(broader); + assertStrictEquals(tag.persist(true), undefined); + }); }); describe("::prefLabel", () => { it("[[Set]] sets the preferred label", () => { const tag = new Tag(); tag.prefLabel = "one"; - assertStrictEquals(tag.prefLabel, "one"); + assertEquals({ ...tag.prefLabel }, { "@value": "one" }); tag.prefLabel = { "@value": "two" }; - assertStrictEquals(tag.prefLabel, "two"); + assertEquals({ ...tag.prefLabel }, { "@value": "two" }); tag.prefLabel = { "@value": "three", "@language": "en" }; assertEquals( { ...tag.prefLabel }, @@ -866,6 +948,12 @@ describe("TagSystem", () => { }); }); + describe("::system", () => { + it("[[Get]] returns the tag system", () => { + assertStrictEquals(new Tag().system, system); + }); + }); + // `::tagURI` is tested by a `.fromTagURI`. describe("::taggingEntity", () => { @@ -888,6 +976,208 @@ describe("TagSystem", () => { // `::[Storage.toObject]` is tested by `::persist`. }); + describe("::apply", () => { + let system; + let Tag; + + beforeEach(() => { + system = new TagSystem("example", "1972-12-31"); + Tag = system.Tag; + }); + + it("[[Call]] throws if no activity is provided", () => { + assertThrows(() => { + system.apply(); + }); + }); + + it("[[Call]] throws with an invalid activity", () => { + assertThrows(() => { + system.apply({}); + }); + }); + + it("[[Call]] throws when specifying an invalid object", () => { + assertThrows(() => { + system.apply({ + object: "", + }); + }); + assertThrows(() => { + system.apply({ + object: `${system.iriSpace}000-0000`, + }); + }); + }); + + it("[[Call]] returns the tag being modified", () => { + const tag = new Tag(); + tag.persist(true); + const applied = system.apply({ object: tag.iri }); + assertStrictEquals( + Object.getPrototypeOf(applied), + system.Tag.prototype, + ); + assertStrictEquals(tag.identifier, applied.identifier); + }); + + it("[[Call]] applies the changes", () => { + const broaderTag = new Tag(); + const broaderActivity = broaderTag.persist(); + const otherBroaderTag = new Tag(); + const otherBroaderActivity = otherBroaderTag.persist(); + const tag = new Tag("EntityTag", "my pref label"); + tag.addHiddenLabel("label"); + tag.addBroaderTag(broaderTag); + const createActivity = tag.persist(); + tag.prefLabel = "new pref label"; + tag.addAltLabel("alternative label"); + tag.deleteHiddenLabel("label"); + tag.addBroaderTag(otherBroaderTag); + tag.deleteBroaderTag(broaderTag); + const updateActivity = tag.persist(); + const otherSystem = new TagSystem( + system.authorityName, + system.date, + ); + otherSystem.apply(broaderActivity); + otherSystem.apply(otherBroaderActivity); + const appliedCreate = otherSystem.apply(createActivity); + assertStrictEquals(appliedCreate.kind, "EntityTag"); + assertStrictEquals(appliedCreate.identifier, tag.identifier); + assertEquals( + { ...appliedCreate.prefLabel }, + { "@value": "my pref label" }, + ); + assertEquals( + [...appliedCreate.hiddenLabels()].map(($) => ({ ...$ })), + [{ "@value": "label" }], + ); + assertEquals( + [...appliedCreate.broaderTags()].map(($) => $.identifier), + [broaderTag.identifier], + ); + const appliedUpdate = otherSystem.apply(updateActivity); + assertEquals( + { ...appliedUpdate.prefLabel }, + { "@value": "new pref label" }, + ); + assertEquals( + [...appliedUpdate.altLabels()].map(($) => ({ ...$ })), + [{ "@value": "alternative label" }], + ); + assertEquals([...appliedUpdate.hiddenLabels()], []); + assertEquals( + [...appliedUpdate.broaderTags()].map(($) => $.identifier), + [otherBroaderTag.identifier], + ); + }); + + it("[[Call]] silently fails deleting preflabels", () => { + const tag = new system.Tag("Tag", "my pref label"); + tag.persist(true); + const applied = system.apply({ + object: tag.iri, + unstates: [{ + predicate: "prefLabel", + object: "my pref label", + }], + }); + assertEquals( + { ...applied.prefLabel }, + { "@value": "my pref label" }, + ); + }); + + it("[[Call]] silently fails deleting unrecognized statements", () => { + const tag = new Tag(); + tag.persist(true); + const otherTag = new Tag(); + otherTag.persist(true); + const applied = system.apply({ + object: tag.iri, + unstates: [{ + predicate: "bad_statement", + object: otherTag.iri, + }], + }); + assert(applied); + }); + + it("[[Call]] silently fails deleting immutable statements", () => { + const tag = new Tag(); + tag.persist(true); + const applied = system.apply({ + object: tag.iri, + unstates: [{ predicate: "a", object: "Tag" }], + }); + assertStrictEquals(applied.kind, "Tag"); + }); + + it("[[Call]] silently fails deleting inverse statements", () => { + const tag = new Tag(); + tag.persist(true); + const otherTag = new Tag(); + otherTag.addBroaderTag(tag); + otherTag.persist(true); + const applied = system.apply({ + object: tag.iri, + unstates: [{ predicate: "narrower", object: otherTag.iri }], + }); + assertStrictEquals( + [...applied.narrowerTags()][0].identifier, + otherTag.identifier, + ); + }); + + it("[[Call]] sets preflabels", () => { + const tag = new Tag("Tag", "my pref label"); + tag.persist(true); + const applied = system.apply({ + object: tag.iri, + states: [{ predicate: "prefLabel", object: "new pref label" }], + }); + assertEquals( + { ...applied.prefLabel }, + { "@value": "new pref label" }, + ); + }); + + it("[[Call]] silently fails setting unrecognized statements", () => { + const tag = new Tag(); + tag.persist(true); + const otherTag = new Tag(); + otherTag.persist(true); + const applied = system.apply({ + object: tag.iri, + states: [{ predicate: "bad_statement", object: otherTag.iri }], + }); + assert(applied); + }); + + it("[[Call]] silently fails setting immutable statements", () => { + const tag = new Tag(); + tag.persist(true); + const applied = system.apply({ + object: tag.iri, + states: [{ predicate: "a", object: "RelationshipTag" }], + }); + assertStrictEquals(applied.kind, "Tag"); + }); + + it("[[Call]] silently fails setting inverse statements", () => { + const tag = new Tag(); + tag.persist(true); + const otherTag = new Tag(); + otherTag.persist(true); + const applied = system.apply({ + object: tag.iri, + unstates: [{ predicate: "narrower", object: otherTag.iri }], + }); + assertEquals([...applied.narrowerTags()], []); + }); + }); + describe("::authorityName", () => { it("[[Get]] returns the authority name", () => { const system = new TagSystem("etaoin.example", "1972-12-31"); @@ -969,6 +1259,25 @@ describe("TagSystem", () => { }); }); + describe("::iriSpace", () => { + it("[[Get]] returns the IĀ·RĀ·I space", () => { + const system = new TagSystem("etaoin.example", "1972-12-31"); + assertStrictEquals( + system.iriSpace, + "https://etaoin.example/tag:etaoin.example,1972-12-31:", + ); + const system2 = new TagSystem( + "etaoin.example", + "1972-12-31", + "etaoin", + ); + assertStrictEquals( + system2.iriSpace, + "https://etaoin.example/tag:etaoin.example,1972-12-31:", + ); + }); + }); + describe("::tagURI", () => { it("[[Get]] returns the Tag UĀ·RĀ·I", () => { const system = new TagSystem("etaoin.example", "1972-12-31");