]>
Lady’s Gitweb - Etiquette/blob - model.test.js
a648d4f736b8e6fb44dcb06c681c6bf739615a59
1 // 📧🏷️ Étiquette ∷ model.test.js
2 // ====================================================================
4 // Copyright © 2023 Lady [@ Lady’s Computer].
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
21 } from "./dev-deps.js";
22 import { TagSystem
} from "./model.js";
24 describe("TagSystem", () => {
25 it("[[Call]] throws", () => {
31 it("[[Construct]] creates a new TagSystem", () => {
33 Object
.getPrototypeOf(new TagSystem("example", "1972-12-31")),
38 it("[[Construct]] uses the identifier if provided", () => {
40 new TagSystem("example", "1972-12-31", "etaoin").identifier
,
45 it("[[Construct]] uses an empty identifier if none is provided", () => {
47 new TagSystem("example", "1972-12-31").identifier
,
52 it("[[Construct]] throws if provided an invalid domain", () => {
54 new TagSystem("example@example", "1972-12-31");
57 new TagSystem("0.0.0.0", "1972-12-31");
61 it("[[Construct]] throws if provided an invalid date", () => {
63 new TagSystem("example", "1969");
66 new TagSystem("example", "1972-12-31T00:00:00Z");
70 describe("::Tag", () => {
75 system
= new TagSystem("example", "1972-12-31");
79 it("[[Get]] returns the same value every time", () => {
80 assertStrictEquals(Tag
, system
.Tag
);
83 it("[[Call]] throws", () => {
89 it("[[Construct]] returns a new Tag", () => {
91 Object
.getPrototypeOf(new Tag()),
96 it('[[Construct]] defaults the kind to "Tag"', () => {
97 assertStrictEquals(new Tag().kind
, "Tag");
100 it("[[Construct]] correctly sets the tag kind", () => {
102 new Tag("RelationshipTag").kind
,
107 it("[[Construct]] defaults the preferred label to the empty string", () => {
108 assertStrictEquals(new Tag().prefLabel
, "");
111 it("[[Construct]] correctly sets the preferred label to a simple string", () => {
113 new Tag("RelationshipTag", "Shadow, Me").prefLabel
,
118 it("[[Construct]] initializes tag identifiers to null", () => {
120 new Tag().identifier
,
125 it("[[Construct]] correctly sets the preferred label to a language‐tagged string", () => {
128 ...new Tag("RelationshipTag", {
129 "@value": "Shadow, Me",
133 { "@value": "Shadow, Me", "@language": "en" },
137 it("[[Construct]] throws if the tag kind is not recognized", () => {
143 describe(".all", () => {
144 it("[[Call]] yields all the persisted tags", () => {
145 const tags
= new Set(function* () {
148 // Generate 5 tags and remember their identifiers.
149 const tag
= new Tag();
151 yield tag
.identifier
;
154 for (const tag
of Tag
.all()) {
156 Object
.getPrototypeOf(tag
),
161 new Set(Array
.from(Tag
.all(), (tag
) => tag
.identifier
)),
167 describe(".fromIRI", () => {
168 it("[[Call]] returns the persisted tag with the given I·R·I", () => {
169 const tag
= new Tag();
171 const { identifier
, iri
} = tag
;
172 const retrieved
= Tag
.fromIRI(iri
);
174 Object
.getPrototypeOf(retrieved
),
177 assertStrictEquals(retrieved
.identifier
, identifier
);
180 it("[[Call]] returns null if no tag with the given I·R·I has been persisted", () => {
183 `https://${system.authorityName}/tag:${system.taggingEntity}:000-0000`,
189 it("[[Call]] returns null if passed an invalid I·R·I", () => {
190 assertStrictEquals(Tag
.fromIRI(`bad iri`), null);
194 describe(".fromIdentifier", () => {
195 it("[[Call]] returns the persisted tag with the given identifier", () => {
196 const tag
= new Tag();
198 const { identifier
} = tag
;
199 const retrieved
= Tag
.fromIdentifier(identifier
);
201 Object
.getPrototypeOf(retrieved
),
204 assertStrictEquals(retrieved
.identifier
, identifier
);
207 it("[[Call]] returns null if no tag with the given identifier has been persisted", () => {
208 assertStrictEquals(Tag
.fromIdentifier("000-0000"), null);
211 it("[[Call]] throws if passed an invalid identifier", () => {
213 Tag
.fromIdentifier(""); // wrong format
216 Tag
.fromIdentifier("100-0000"); // bad checksum
221 describe(".fromTagURI", () => {
222 it("[[Call]] returns the persisted tag with the given Tag U·R·I", () => {
223 const tag
= new Tag();
225 const { identifier
, tagURI
} = tag
;
226 const retrieved
= Tag
.fromTagURI(tagURI
);
228 Object
.getPrototypeOf(retrieved
),
231 assertStrictEquals(retrieved
.identifier
, identifier
);
234 it("[[Call]] returns null if no tag with the given Tag U·R·I has been persisted", () => {
236 Tag
.fromIRI(`tag:${system.taggingEntity}:`),
240 Tag
.fromIRI(`tag:${system.taggingEntity}:000-0000`),
245 it("[[Call]] throws if passed an invalid Tag U·R·I", () => {
247 Tag
.fromTagURI(""); // wrong format
251 "tag:unexample,1970-01-01:Z", // incorrect tagging entity
257 describe(".getSystem", () => {
258 it("[[Has]] is not present", () => {
259 assertFalse("getSystem" in Tag
);
263 describe(".identifiers", () => {
264 it("[[Call]] yields all the persisted identifiers", () => {
265 const tags
= new Set(function* () {
268 // Generate 5 tags and remember their identifiers.
269 const tag
= new Tag();
271 yield tag
.identifier
;
275 new Set(Tag
.identifiers()),
281 // `.[Storage.toInstance]` is tested by `.fromIdentifier`.
283 describe("::addAltLabel", () => {
284 it("[[Call]] does nothing if called with no arguments", () => {
285 const tag
= new Tag();
287 assertEquals([...tag
.altLabels()], []);
290 it("[[Call]] adds the provided alternative labels", () => {
291 const tag
= new Tag();
295 { "@value": "three", "@language": "en" },
300 ($) => typeof $ == "string" ? $ : { ...$ },
305 { "@value": "three", "@language": "en" },
311 describe("::addBroaderTag", () => {
312 it("[[Call]] does nothing if called with no arguments", () => {
313 const tag
= new Tag();
315 assertEquals([...tag
.broaderTags()], []);
318 it("[[Call]] adds the provided broader tags", () => {
319 const broader
= new Tag();
321 const broader2
= new Tag();
323 const tag
= new Tag();
324 tag
.addBroaderTag(broader
, broader2
);
326 Array
.from(tag
.broaderTags(), ($) => $.identifier
),
327 [broader
.identifier
, broader2
.identifier
],
331 it("[[Call]] throws when adding a non‐persisted tag", () => {
332 const tag
= new Tag();
334 tag
.addBroaderTag(new Tag());
338 it("[[Call]] throws when adding an unrecognized identifier", () => {
339 const tag
= new Tag();
341 tag
.addBroaderTag("000-0000"); // not persisted
344 tag
.addBroaderTag(""); // bad format
349 describe("::addHiddenLabel", () => {
350 it("[[Call]] does nothing if called with no arguments", () => {
351 const tag
= new Tag();
352 tag
.addHiddenLabel();
353 assertEquals([...tag
.hiddenLabels()], []);
356 it("[[Call]] adds the provided hidden labels", () => {
357 const tag
= new Tag();
361 { "@value": "three", "@language": "en" },
366 ($) => typeof $ == "string" ? $ : { ...$ },
371 { "@value": "three", "@language": "en" },
377 describe("::addInCanonTag", () => {
378 it("[[Call]] does nothing if called with no arguments", () => {
379 const tag
= new Tag("EntityTag");
381 assertEquals([...tag
.inCanonTags()], []);
384 it("[[Call]] adds the provided canon tags", () => {
385 const canon
= new Tag("CanonTag");
387 const canon2
= new Tag("CanonTag");
389 const tag
= new Tag("EntityTag");
390 tag
.addInCanonTag(canon
, canon2
);
392 Array
.from(tag
.inCanonTags(), ($) => $.identifier
),
393 [canon
.identifier
, canon2
.identifier
],
397 it("[[Call]] throws when this is not a tag which can be placed in canon", () => {
399 new Tag().addInCanonTag();
403 it("[[Call]] throws when provided with a non‐canon tag", () => {
404 const notCanon
= new Tag();
406 const tag
= new Tag("EntityTag");
408 tag
.addInCanonTag(notCanon
);
412 it("[[Call]] throws when adding a non‐persisted tag", () => {
413 const tag
= new Tag("EntityTag");
415 tag
.addInCanonTag(new Tag("CanonTag"));
419 it("[[Call]] throws when adding an unrecognized identifier", () => {
420 const tag
= new Tag("EntityTag");
422 tag
.addInCanonTag("000-0000"); // not persisted
425 tag
.addInCanonTag(""); // bad format
430 describe("::addInvolvesTag", () => {
431 it("[[Call]] does nothing if called with no arguments", () => {
432 const tag
= new Tag("ConceptualTag");
433 tag
.addInvolvesTag();
434 assertEquals([...tag
.involvesTags()], []);
437 it("[[Call]] adds the provided tags", () => {
438 const involved
= new Tag();
440 const involved2
= new Tag();
442 const tag
= new Tag("ConceptualTag");
443 tag
.addInvolvesTag(involved
, involved2
);
445 Array
.from(tag
.involvesTags(), ($) => $.identifier
),
446 [involved
.identifier
, involved2
.identifier
],
450 it("[[Call]] throws when this is not a conceptual tag", () => {
452 new Tag().addInvolvesTag();
456 it("[[Call]] throws when this is a relationship tag and provided with a non‐involvable tag", () => {
457 const notInvolved
= new Tag();
458 notInvolved
.persist();
459 const tag
= new Tag("RelationshipTag");
461 tag
.addInvolvesTag(notInvolved
);
465 it("[[Call]] throws when adding a non‐persisted tag", () => {
466 const tag
= new Tag("ConceptualTag");
468 tag
.addInvolvesTag(new Tag());
472 it("[[Call]] throws when adding an unrecognized identifier", () => {
473 const tag
= new Tag("ConceptualTag");
475 tag
.addInvolvesTag("000-0000"); // not persisted
478 tag
.addInvolvesTag(""); // bad format
483 // `::altLabels` is tested by `::addAltLabel`.
485 describe("::authorityName", () => {
486 it("[[Get]] returns the authority name of the tag system", () => {
488 new Tag().authorityName
,
489 system
.authorityName
,
494 // `::broaderTags` is tested by `::addBroaderTag`.
496 describe("::broaderTransitiveTags", () => {
497 it("[[Call]] returns broader tags transitively", () => {
498 const superBroad
= new Tag();
499 superBroad
.persist();
500 const broad
= new Tag();
501 broad
.addBroaderTag(superBroad
);
503 const tag
= new Tag();
504 tag
.addBroaderTag(broad
);
506 Array
.from(tag
.broaderTransitiveTags(), ($) => $.identifier
),
507 [broad
.identifier
, superBroad
.identifier
],
511 it("[[Call]] cannot recurse infinitely", () => {
512 const tag
= new Tag();
514 const broad
= new Tag();
515 broad
.addBroaderTag(tag
);
517 tag
.addBroaderTag(broad
);
520 Array
.from(tag
.broaderTransitiveTags(), ($) => $.identifier
),
521 [broad
.identifier
, tag
.identifier
],
526 describe("::deleteAltLabel", () => {
527 it("[[Call]] does nothing if called with no arguments", () => {
528 const tag
= new Tag();
529 tag
.addAltLabel("etaoin");
530 tag
.deleteAltLabel();
531 assertEquals([...tag
.altLabels()], ["etaoin"]);
534 it("[[Call]] deletes only the provided hidden labels", () => {
535 const tag
= new Tag();
539 { "@value": "three", "@language": "en" },
545 { "@value": "three", "@language": "en" },
546 { "@value": "four", "@language": "en" },
548 assertEquals([...tag
.altLabels()], ["four"]);
552 describe("::deleteBroaderTag", () => {
553 it("[[Call]] does nothing if called with no arguments", () => {
554 const broader
= new Tag();
556 const tag
= new Tag();
557 tag
.addBroaderTag(broader
);
558 tag
.deleteBroaderTag();
560 Array
.from(tag
.broaderTags(), ($) => $.identifier
),
561 [broader
.identifier
],
565 it("[[Call]] deletes only the provided broader tags", () => {
566 const superBroader
= new Tag();
567 superBroader
.persist();
568 const broader
= new Tag();
569 broader
.addBroaderTag(superBroader
);
571 const broader2
= new Tag();
572 broader2
.addBroaderTag(superBroader
);
574 const tag
= new Tag();
575 tag
.addBroaderTag(broader
, broader2
);
576 tag
.deleteBroaderTag(broader
, superBroader
, "000-0000", "");
578 Array
.from(tag
.broaderTags(), ($) => $.identifier
),
579 [broader2
.identifier
],
584 describe("::deleteHiddenLabel", () => {
585 it("[[Call]] does nothing if called with no arguments", () => {
586 const tag
= new Tag();
587 tag
.addHiddenLabel("etaoin");
588 tag
.deleteHiddenLabel();
589 assertEquals([...tag
.hiddenLabels()], ["etaoin"]);
592 it("[[Call]] deletes only the provided alternative labels", () => {
593 const tag
= new Tag();
597 { "@value": "three", "@language": "en" },
600 tag
.deleteHiddenLabel(
603 { "@value": "three", "@language": "en" },
604 { "@value": "four", "@language": "en" },
606 assertEquals([...tag
.hiddenLabels()], ["four"]);
610 describe("::deleteInCanonTag", () => {
611 it("[[Call]] does nothing if called with no arguments", () => {
612 const canon
= new Tag("CanonTag");
614 const tag
= new Tag("EntityTag");
615 tag
.addInCanonTag(canon
);
616 tag
.deleteInCanonTag();
618 Array
.from(tag
.inCanonTags(), ($) => $.identifier
),
623 it("[[Call]] deletes only the provided canon tags", () => {
624 const canon
= new Tag("CanonTag");
626 const canon2
= new Tag("CanonTag");
628 const tag
= new Tag("EntityTag");
629 tag
.addInCanonTag(canon
, canon2
);
630 tag
.deleteInCanonTag(canon
, "000-0000", "");
632 Array
.from(tag
.inCanonTags(), ($) => $.identifier
),
638 describe("::deleteInvolvesTag", () => {
639 it("[[Call]] does nothing if called with no arguments", () => {
640 const involved
= new Tag();
642 const tag
= new Tag("ConceptualTag");
643 tag
.addInvolvesTag(involved
);
644 tag
.deleteInvolvesTag();
646 Array
.from(tag
.involvesTags(), ($) => $.identifier
),
647 [involved
.identifier
],
651 it("[[Call]] deletes only the provided involved tags", () => {
652 const character
= new Tag("CharacterTag");
654 const involved
= new Tag("RelationshipTag");
655 involved
.addInvolvesTag(character
);
657 const involved2
= new Tag("RelationshipTag");
658 involved2
.addInvolvesTag(character
);
660 const tag
= new Tag("RelationshipTag");
661 tag
.addInvolvesTag(involved
, involved2
);
662 tag
.deleteInvolvesTag(involved
, character
, "000-0000", "");
664 Array
.from(tag
.involvesTags(), ($) => $.identifier
),
665 [involved2
.identifier
],
670 describe("::hasInCanonTags", () => {
671 it("[[Call]] yields the persisted tags which have this tag in canon", () => {
672 const canon
= new Tag("CanonTag");
674 const entity
= new Tag("EntityTag");
675 entity
.addInCanonTag(canon
);
677 const entity2
= new Tag("EntityTag");
678 entity2
.addInCanonTag(canon
);
680 const tag
= Tag
.fromIdentifier(canon
.identifier
); // reload
682 Array
.from(tag
.hasInCanonTags(), ($) => $.identifier
),
683 [entity
.identifier
, entity2
.identifier
],
688 // `::hiddenLabels` is tested by `::addHiddenLabel`.
690 // `::identifier` is tested by a `.fromIdentifier`.
692 // `::inCanonTags` is tested by `::addInCanonTag`.
694 describe("::involvedInTags", () => {
695 it("[[Call]] yields the persisted tags which involve this tag", () => {
696 const involved
= new Tag();
698 const conceptual
= new Tag("ConceptualTag");
699 conceptual
.addInvolvesTag(involved
);
700 conceptual
.persist();
701 const conceptual2
= new Tag("ConceptualTag");
702 conceptual2
.addInvolvesTag(involved
);
703 conceptual2
.persist();
704 const tag
= Tag
.fromIdentifier(involved
.identifier
); // reload
706 Array
.from(tag
.involvedInTags(), ($) => $.identifier
),
707 [conceptual
.identifier
, conceptual2
.identifier
],
712 // `::involvesTags` is tested by `::addInvolvesTag`.
714 // `::iri` is tested by a `.fromIRI`.
716 // `::kind` is tested by the constructor.
718 describe("::narrowerTags", () => {
719 it("[[Call]] yields the persisted tags which are narrower than this tag", () => {
720 const broader
= new Tag();
722 const narrower
= new Tag();
723 narrower
.addBroaderTag(broader
);
725 const narrower2
= new Tag();
726 narrower2
.addBroaderTag(broader
);
728 const tag
= Tag
.fromIdentifier(broader
.identifier
); // reload
730 Array
.from(tag
.narrowerTags(), ($) => $.identifier
),
731 [narrower
.identifier
, narrower2
.identifier
],
736 describe("::narrowerTransitiveTags", () => {
737 it("[[Call]] returns narrower tags transitively", () => {
738 const broad
= new Tag();
740 const narrow
= new Tag();
741 narrow
.addBroaderTag(broad
);
743 const superNarrow
= new Tag();
744 superNarrow
.addBroaderTag(narrow
);
745 superNarrow
.persist();
746 const tag
= Tag
.fromIdentifier(broad
.identifier
); // reload
749 tag
.narrowerTransitiveTags(),
752 [narrow
.identifier
, superNarrow
.identifier
],
756 it("[[Call]] cannot recurse infinitely", () => {
757 const tag
= new Tag();
759 const broad
= new Tag();
760 broad
.addBroaderTag(tag
);
762 tag
.addBroaderTag(broad
);
765 Array
.from(tag
.broaderTransitiveTags(), ($) => $.identifier
),
766 [broad
.identifier
, tag
.identifier
],
771 describe("::persist", () => {
772 it("[[Call]] returns an object with expected properties if there were changes", () => {
773 const tag
= new Tag();
774 const activity
= tag
.persist();
779 "https://ns.1024.gdn/Tagging/discovery.context.jsonld",
784 assertArrayIncludes(activity
["@type"], ["TagActivity"]);
785 assert("endTime" in activity
);
788 it("[[Call]] returns a Create activity with a type predicate for new objects", () => {
789 const activity
= new Tag().persist();
790 assertEquals(activity
["@type"], ["TagActivity", "Create"]);
791 assertArrayIncludes(activity
.states
, [{
797 it("[[Call]] returns an Update activity for old objects", () => {
798 const tag
= new Tag();
800 tag
.prefLabel
= "etaoin";
801 const activity
= tag
.persist();
802 assertEquals(activity
["@type"], ["TagActivity", "Update"]);
805 it("[[Call]] states and unstates changes", () => {
806 const broader1
= new Tag();
808 const broader2
= new Tag();
810 const tag
= new Tag();
811 tag
.addBroaderTag(broader1
);
813 tag
.prefLabel
= "etaoin";
814 tag
.deleteBroaderTag(broader1
);
815 tag
.addBroaderTag(broader2
);
816 const activity
= tag
.persist();
817 assertObjectMatch(activity
, {
819 { predicate
: "prefLabel", object
: { "@value": "" } },
820 { predicate
: "broader", object
: broader1
.iri
},
823 { predicate
: "prefLabel", object
: { "@value": "etaoin" } },
824 { predicate
: "broader", object
: broader2
.iri
},
829 it("[[Call]] doesn’t state if there are no additions", () => {
830 const tag
= new Tag();
831 tag
.addAltLabel("etaoin");
833 tag
.deleteAltLabel("etaoin");
834 const activity
= tag
.persist();
835 assertFalse("state" in activity
);
838 it("[[Call]] doesn’t unstate if there are no removals", () => {
839 const tag
= new Tag();
841 tag
.addAltLabel("etaoin");
842 const activity
= tag
.persist();
843 assertFalse("unstate" in activity
);
846 it("[[Call]] returns null if no meaningful changes were made", () => {
847 const tag
= new Tag();
849 const activity
= tag
.persist();
850 assertStrictEquals(activity
, null);
853 it("[[Call]] returns undefined for a silent persist", () => {
854 const broader
= new Tag();
856 const tag
= new Tag();
857 tag
.prefLabel
= "etaoin";
858 tag
.addBroaderTag(broader
);
859 assertStrictEquals(tag
.persist(true), undefined);
863 describe("::prefLabel", () => {
864 it("[[Set]] sets the preferred label", () => {
865 const tag
= new Tag();
866 tag
.prefLabel
= "one";
867 assertStrictEquals(tag
.prefLabel
, "one");
868 tag
.prefLabel
= { "@value": "two" };
869 assertStrictEquals(tag
.prefLabel
, "two");
870 tag
.prefLabel
= { "@value": "three", "@language": "en" };
872 { ...tag
.prefLabel
},
873 { "@value": "three", "@language": "en" },
878 // `::tagURI` is tested by a `.fromTagURI`.
880 describe("::taggingEntity", () => {
881 it("[[Get]] returns the tagging entity of the tag system", () => {
883 new Tag().taggingEntity
,
884 system
.taggingEntity
,
889 describe("::toString", () => {
890 it("[[Get]] returns the string value of the preferred label", () => {
891 const tag
= new Tag();
892 tag
.prefLabel
= { "@value": "etaoin", "@language": "zxx" };
893 assertStrictEquals(tag
.toString(), "etaoin");
897 // `::[Storage.toObject]` is tested by `::persist`.
900 describe("::authorityName", () => {
901 it("[[Get]] returns the authority name", () => {
902 const system
= new TagSystem("etaoin.example", "1972-12-31");
903 assertStrictEquals(system
.authorityName
, "etaoin.example");
907 describe("::authorityName", () => {
908 it("[[Get]] returns the date", () => {
909 const system
= new TagSystem("etaoin.example", "1972-12-31");
910 assertStrictEquals(system
.date
, "1972-12-31");
914 describe("::identifiers", () => {
915 it("[[Get]] yields the extant entities", () => {
916 const system
= new TagSystem("etaoin.example", "1972-12-31");
917 const tags
= new Set(function* () {
920 // Generate 5 tags and remember their identifiers.
921 const tag
= new system
.Tag();
923 yield tag
.identifier
;
927 new Set(Array
.from(system
.entities(), ($) => $.identifier
)),
933 describe("::identifier", () => {
934 it("[[Get]] returns the identifier", () => {
935 const system
= new TagSystem("etaoin.example", "1972-12-31");
936 assertStrictEquals(system
.identifier
, "");
937 const system2
= new TagSystem(
942 assertStrictEquals(system2
.identifier
, "etaoin");
946 describe("::identifiers", () => {
947 it("[[Get]] yields the identifiers in use", () => {
948 const system
= new TagSystem("etaoin.example", "1972-12-31");
949 const tags
= new Set(function* () {
952 // Generate 5 tags and remember their identifiers.
953 const tag
= new system
.Tag();
955 yield tag
.identifier
;
958 assertEquals(new Set(system
.identifiers()), tags
);
962 describe("::iri", () => {
963 it("[[Get]] returns the I·R·I", () => {
964 const system
= new TagSystem("etaoin.example", "1972-12-31");
967 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
969 const system2
= new TagSystem(
976 "https://etaoin.example/tag:etaoin.example,1972-12-31:etaoin",
981 describe("::tagURI", () => {
982 it("[[Get]] returns the Tag U·R·I", () => {
983 const system
= new TagSystem("etaoin.example", "1972-12-31");
986 "tag:etaoin.example,1972-12-31:",
988 const system2
= new TagSystem(
995 "tag:etaoin.example,1972-12-31:etaoin",
1000 describe("::taggingEntity", () => {
1001 it("[[Get]] returns the tagging entity", () => {
1002 const system
= new TagSystem("etaoin.example", "1972-12-31");
1004 system
.taggingEntity
,
1005 "etaoin.example,1972-12-31",
This page took 0.179325 seconds and 3 git commands to generate.