]> Lady’s Gitweb - Etiquette/blob - model.test.js
15a2f54cfa955831457a64d8dcd215e486937ed4
[Etiquette] / model.test.js
1 // 📧🏷️ Étiquette ∷ model.test.js
2 // ====================================================================
3 //
4 // Copyright © 2023 Lady [@ Lady’s Computer].
5 //
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/>.
9
10 import {
11 assert,
12 assertArrayIncludes,
13 assertEquals,
14 assertFalse,
15 assertObjectMatch,
16 assertStrictEquals,
17 assertThrows,
18 beforeEach,
19 describe,
20 it,
21 } from "./dev-deps.js";
22 import { TagSystem } from "./model.js";
23
24 describe("TagSystem", () => {
25 it("[[Call]] throws", () => {
26 assertThrows(() => {
27 TagSystem();
28 });
29 });
30
31 it("[[Construct]] creates a new TagSystem", () => {
32 assertStrictEquals(
33 Object.getPrototypeOf(new TagSystem("example", "1972-12-31")),
34 TagSystem.prototype,
35 );
36 });
37
38 it("[[Construct]] uses the identifier if provided", () => {
39 assertStrictEquals(
40 new TagSystem("example", "1972-12-31", "etaoin").identifier,
41 "etaoin",
42 );
43 });
44
45 it("[[Construct]] uses an empty identifier if none is provided", () => {
46 assertStrictEquals(
47 new TagSystem("example", "1972-12-31").identifier,
48 "",
49 );
50 });
51
52 it("[[Construct]] throws if provided an invalid domain", () => {
53 assertThrows(() => {
54 new TagSystem("example@example", "1972-12-31");
55 });
56 assertThrows(() => {
57 new TagSystem("0.0.0.0", "1972-12-31");
58 });
59 });
60
61 it("[[Construct]] throws if provided an invalid date", () => {
62 assertThrows(() => {
63 new TagSystem("example", "1969");
64 });
65 assertThrows(() => {
66 new TagSystem("example", "1972-12-31T00:00:00Z");
67 });
68 });
69
70 describe("::Tag", () => {
71 let Tag;
72 let system;
73
74 beforeEach(() => {
75 system = new TagSystem("example", "1972-12-31");
76 Tag = system.Tag;
77 });
78
79 it("[[Get]] returns the same value every time", () => {
80 assertStrictEquals(Tag, system.Tag);
81 });
82
83 it("[[Call]] throws", () => {
84 assertThrows(() => {
85 Tag();
86 });
87 });
88
89 it("[[Construct]] returns a new Tag", () => {
90 assertStrictEquals(
91 Object.getPrototypeOf(new Tag()),
92 Tag.prototype,
93 );
94 });
95
96 it('[[Construct]] defaults the kind to "Tag"', () => {
97 assertStrictEquals(new Tag().kind, "Tag");
98 });
99
100 it("[[Construct]] correctly sets the tag kind", () => {
101 assertStrictEquals(
102 new Tag("RelationshipTag").kind,
103 "RelationshipTag",
104 );
105 });
106
107 it("[[Construct]] defaults the preferred label to the empty string", () => {
108 assertStrictEquals(new Tag().prefLabel, "");
109 });
110
111 it("[[Construct]] correctly sets the preferred label to a simple string", () => {
112 assertStrictEquals(
113 new Tag("RelationshipTag", "Shadow, Me").prefLabel,
114 "Shadow, Me",
115 );
116 });
117
118 it("[[Construct]] initializes tag identifiers to null", () => {
119 assertStrictEquals(
120 new Tag().identifier,
121 null,
122 );
123 });
124
125 it("[[Construct]] correctly sets the preferred label to a language‐tagged string", () => {
126 assertEquals(
127 {
128 ...new Tag("RelationshipTag", {
129 "@value": "Shadow, Me",
130 "@language": "en",
131 }).prefLabel,
132 },
133 { "@value": "Shadow, Me", "@language": "en" },
134 );
135 });
136
137 it("[[Construct]] throws if the tag kind is not recognized", () => {
138 assertThrows(() => {
139 new Tag("NotATag");
140 });
141 });
142
143 describe(".all", () => {
144 it("[[Call]] yields all the persisted tags", () => {
145 const tags = new Set(function* () {
146 let i = 0;
147 while (i++ < 5) {
148 // Generate 5 tags and remember their identifiers.
149 const tag = new Tag();
150 tag.persist();
151 yield tag.identifier;
152 }
153 }());
154 for (const tag of Tag.all()) {
155 assertStrictEquals(
156 Object.getPrototypeOf(tag),
157 Tag.prototype,
158 );
159 }
160 assertEquals(
161 new Set(Array.from(Tag.all(), (tag) => tag.identifier)),
162 tags,
163 );
164 });
165 });
166
167 describe(".fromIRI", () => {
168 it("[[Call]] returns the persisted tag with the given I·R·I", () => {
169 const tag = new Tag();
170 tag.persist();
171 const { identifier, iri } = tag;
172 const retrieved = Tag.fromIRI(iri);
173 assertStrictEquals(
174 Object.getPrototypeOf(retrieved),
175 Tag.prototype,
176 );
177 assertStrictEquals(retrieved.identifier, identifier);
178 });
179
180 it("[[Call]] returns null if no tag with the given I·R·I has been persisted", () => {
181 assertStrictEquals(
182 Tag.fromIRI(
183 `https://${system.authorityName}/tag:${system.taggingEntity}:000-0000`,
184 ),
185 null,
186 );
187 });
188
189 it("[[Call]] throws if passed an invalid I·R·I", () => {
190 assertThrows(() => {Tag.fromIRI(`bad iri`)});
191 });
192 });
193
194 describe(".fromIdentifier", () => {
195 it("[[Call]] returns the persisted tag with the given identifier", () => {
196 const tag = new Tag();
197 tag.persist();
198 const { identifier } = tag;
199 const retrieved = Tag.fromIdentifier(identifier);
200 assertStrictEquals(
201 Object.getPrototypeOf(retrieved),
202 Tag.prototype,
203 );
204 assertStrictEquals(retrieved.identifier, identifier);
205 });
206
207 it("[[Call]] returns null if no tag with the given identifier has been persisted", () => {
208 assertStrictEquals(Tag.fromIdentifier("000-0000"), null);
209 });
210
211 it("[[Call]] throws if passed an invalid identifier", () => {
212 assertThrows(() => {
213 Tag.fromIdentifier(""); // wrong format
214 });
215 assertThrows(() => {
216 Tag.fromIdentifier("100-0000"); // bad checksum
217 });
218 });
219 });
220
221 describe(".fromTagURI", () => {
222 it("[[Call]] returns the persisted tag with the given Tag U·R·I", () => {
223 const tag = new Tag();
224 tag.persist();
225 const { identifier, tagURI } = tag;
226 const retrieved = Tag.fromTagURI(tagURI);
227 assertStrictEquals(
228 Object.getPrototypeOf(retrieved),
229 Tag.prototype,
230 );
231 assertStrictEquals(retrieved.identifier, identifier);
232 });
233
234 it("[[Call]] returns null if no tag with the given Tag U·R·I has been persisted", () => {
235 assertStrictEquals(
236 Tag.fromTagURI(`tag:${system.taggingEntity}:`),
237 null,
238 );
239 assertStrictEquals(
240 Tag.fromTagURI(`tag:${system.taggingEntity}:000-0000`),
241 null,
242 );
243 });
244
245 it("[[Call]] throws if passed an invalid Tag U·R·I", () => {
246 assertThrows(() => {
247 Tag.fromTagURI(""); // wrong format
248 });
249 assertThrows(() => {
250 Tag.fromTagURI(
251 "tag:unexample,1970-01-01:Z", // incorrect tagging entity
252 );
253 });
254 });
255 });
256
257 describe(".getSystem", () => {
258 it("[[Has]] is not present", () => {
259 assertFalse("getSystem" in Tag);
260 });
261 });
262
263 describe(".identifiers", () => {
264 it("[[Call]] yields all the persisted identifiers", () => {
265 const tags = new Set(function* () {
266 let i = 0;
267 while (i++ < 5) {
268 // Generate 5 tags and remember their identifiers.
269 const tag = new Tag();
270 tag.persist();
271 yield tag.identifier;
272 }
273 }());
274 assertEquals(
275 new Set(Tag.identifiers()),
276 tags,
277 );
278 });
279 });
280
281 // `.[Storage.toInstance]` is tested by `.fromIdentifier`.
282
283 describe("::addAltLabel", () => {
284 it("[[Call]] does nothing if called with no arguments", () => {
285 const tag = new Tag();
286 tag.addAltLabel();
287 assertEquals([...tag.altLabels()], []);
288 });
289
290 it("[[Call]] adds the provided alternative labels", () => {
291 const tag = new Tag();
292 tag.addAltLabel(
293 "one",
294 { "@value": "two" },
295 { "@value": "three", "@language": "en" },
296 );
297 assertEquals(
298 Array.from(
299 tag.altLabels(),
300 ($) => typeof $ == "string" ? $ : { ...$ },
301 ),
302 [
303 "one",
304 "two",
305 { "@value": "three", "@language": "en" },
306 ],
307 );
308 });
309
310 it("[[Call]] returns this", () => {
311 const tag = new Tag();
312 assertStrictEquals(tag.addAltLabel(), tag);
313 });
314 });
315
316 describe("::addBroaderTag", () => {
317 it("[[Call]] does nothing if called with no arguments", () => {
318 const tag = new Tag();
319 tag.addBroaderTag();
320 assertEquals([...tag.broaderTags()], []);
321 });
322
323 it("[[Call]] adds the provided broader tags", () => {
324 const broader = new Tag();
325 broader.persist();
326 const broader2 = new Tag();
327 broader2.persist();
328 const tag = new Tag();
329 tag.addBroaderTag(broader, broader2);
330 assertEquals(
331 Array.from(tag.broaderTags(), ($) => $.identifier),
332 [broader.identifier, broader2.identifier],
333 );
334 });
335
336 it("[[Call]] returns this", () => {
337 const tag = new Tag();
338 assertStrictEquals(tag.addBroaderTag(), tag);
339 });
340
341 it("[[Call]] throws when adding a non‐persisted tag", () => {
342 const tag = new Tag();
343 assertThrows(() => {
344 tag.addBroaderTag(new Tag());
345 });
346 });
347
348 it("[[Call]] throws when adding an unrecognized identifier", () => {
349 const tag = new Tag();
350 assertThrows(() => {
351 tag.addBroaderTag("000-0000"); // not persisted
352 });
353 assertThrows(() => {
354 tag.addBroaderTag(""); // bad format
355 });
356 });
357 });
358
359 describe("::addHiddenLabel", () => {
360 it("[[Call]] does nothing if called with no arguments", () => {
361 const tag = new Tag();
362 tag.addHiddenLabel();
363 assertEquals([...tag.hiddenLabels()], []);
364 });
365
366 it("[[Call]] adds the provided hidden labels", () => {
367 const tag = new Tag();
368 tag.addHiddenLabel(
369 "one",
370 { "@value": "two" },
371 { "@value": "three", "@language": "en" },
372 );
373 assertEquals(
374 Array.from(
375 tag.hiddenLabels(),
376 ($) => typeof $ == "string" ? $ : { ...$ },
377 ),
378 [
379 "one",
380 "two",
381 { "@value": "three", "@language": "en" },
382 ],
383 );
384 });
385
386 it("[[Call]] returns this", () => {
387 const tag = new Tag();
388 assertStrictEquals(tag.addHiddenLabel(), tag);
389 });
390 });
391
392 describe("::addInCanonTag", () => {
393 it("[[Call]] does nothing if called with no arguments", () => {
394 const tag = new Tag("EntityTag");
395 tag.addInCanonTag();
396 assertEquals([...tag.inCanonTags()], []);
397 });
398
399 it("[[Call]] adds the provided canon tags", () => {
400 const canon = new Tag("CanonTag");
401 canon.persist();
402 const canon2 = new Tag("CanonTag");
403 canon2.persist();
404 const tag = new Tag("EntityTag");
405 tag.addInCanonTag(canon, canon2);
406 assertEquals(
407 Array.from(tag.inCanonTags(), ($) => $.identifier),
408 [canon.identifier, canon2.identifier],
409 );
410 });
411
412 it("[[Call]] returns this", () => {
413 const tag = new Tag("EntityTag");
414 assertStrictEquals(tag.addInCanonTag(), tag);
415 });
416
417 it("[[Call]] throws when this is not a tag which can be placed in canon", () => {
418 assertThrows(() => {
419 new Tag().addInCanonTag();
420 });
421 });
422
423 it("[[Call]] throws when provided with a non‐canon tag", () => {
424 const notCanon = new Tag();
425 notCanon.persist();
426 const tag = new Tag("EntityTag");
427 assertThrows(() => {
428 tag.addInCanonTag(notCanon);
429 });
430 });
431
432 it("[[Call]] throws when adding a non‐persisted tag", () => {
433 const tag = new Tag("EntityTag");
434 assertThrows(() => {
435 tag.addInCanonTag(new Tag("CanonTag"));
436 });
437 });
438
439 it("[[Call]] throws when adding an unrecognized identifier", () => {
440 const tag = new Tag("EntityTag");
441 assertThrows(() => {
442 tag.addInCanonTag("000-0000"); // not persisted
443 });
444 assertThrows(() => {
445 tag.addInCanonTag(""); // bad format
446 });
447 });
448 });
449
450 describe("::addInvolvesTag", () => {
451 it("[[Call]] does nothing if called with no arguments", () => {
452 const tag = new Tag("ConceptualTag");
453 tag.addInvolvesTag();
454 assertEquals([...tag.involvesTags()], []);
455 });
456
457 it("[[Call]] adds the provided tags", () => {
458 const involved = new Tag();
459 involved.persist();
460 const involved2 = new Tag();
461 involved2.persist();
462 const tag = new Tag("ConceptualTag");
463 tag.addInvolvesTag(involved, involved2);
464 assertEquals(
465 Array.from(tag.involvesTags(), ($) => $.identifier),
466 [involved.identifier, involved2.identifier],
467 );
468 });
469
470 it("[[Call]] returns this", () => {
471 const tag = new Tag("ConceptualTag");
472 assertStrictEquals(tag.addInvolvesTag(), tag);
473 });
474
475 it("[[Call]] throws when this is not a conceptual tag", () => {
476 assertThrows(() => {
477 new Tag().addInvolvesTag();
478 });
479 });
480
481 it("[[Call]] throws when this is a relationship tag and provided with a non‐involvable tag", () => {
482 const notInvolved = new Tag();
483 notInvolved.persist();
484 const tag = new Tag("RelationshipTag");
485 assertThrows(() => {
486 tag.addInvolvesTag(notInvolved);
487 });
488 });
489
490 it("[[Call]] throws when adding a non‐persisted tag", () => {
491 const tag = new Tag("ConceptualTag");
492 assertThrows(() => {
493 tag.addInvolvesTag(new Tag());
494 });
495 });
496
497 it("[[Call]] throws when adding an unrecognized identifier", () => {
498 const tag = new Tag("ConceptualTag");
499 assertThrows(() => {
500 tag.addInvolvesTag("000-0000"); // not persisted
501 });
502 assertThrows(() => {
503 tag.addInvolvesTag(""); // bad format
504 });
505 });
506 });
507
508 // `::altLabels` is tested by `::addAltLabel`.
509
510 describe("::authorityName", () => {
511 it("[[Get]] returns the authority name of the tag system", () => {
512 assertStrictEquals(
513 new Tag().authorityName,
514 system.authorityName,
515 );
516 });
517 });
518
519 // `::broaderTags` is tested by `::addBroaderTag`.
520
521 describe("::broaderTransitiveTags", () => {
522 it("[[Call]] returns broader tags transitively", () => {
523 const superBroad = new Tag();
524 superBroad.persist();
525 const broad = new Tag();
526 broad.addBroaderTag(superBroad);
527 broad.persist();
528 const tag = new Tag();
529 tag.addBroaderTag(broad);
530 assertEquals(
531 Array.from(tag.broaderTransitiveTags(), ($) => $.identifier),
532 [broad.identifier, superBroad.identifier],
533 );
534 });
535
536 it("[[Call]] cannot recurse infinitely", () => {
537 const tag = new Tag();
538 tag.persist();
539 const broad = new Tag();
540 broad.addBroaderTag(tag);
541 broad.persist();
542 tag.addBroaderTag(broad);
543 tag.persist();
544 assertEquals(
545 Array.from(tag.broaderTransitiveTags(), ($) => $.identifier),
546 [broad.identifier, tag.identifier],
547 );
548 });
549 });
550
551 describe("::deleteAltLabel", () => {
552 it("[[Call]] does nothing if called with no arguments", () => {
553 const tag = new Tag();
554 tag.addAltLabel("etaoin");
555 tag.deleteAltLabel();
556 assertEquals([...tag.altLabels()], ["etaoin"]);
557 });
558
559 it("[[Call]] deletes only the provided hidden labels", () => {
560 const tag = new Tag();
561 tag.addAltLabel(
562 "one",
563 "two",
564 { "@value": "three", "@language": "en" },
565 "four",
566 );
567 tag.deleteAltLabel(
568 "one",
569 { "@value": "two" },
570 { "@value": "three", "@language": "en" },
571 { "@value": "four", "@language": "en" },
572 );
573 assertEquals([...tag.altLabels()], ["four"]);
574 });
575
576 it("[[Call]] returns this", () => {
577 const tag = new Tag();
578 assertStrictEquals(tag.deleteAltLabel(), tag);
579 });
580 });
581
582 describe("::deleteBroaderTag", () => {
583 it("[[Call]] does nothing if called with no arguments", () => {
584 const broader = new Tag();
585 broader.persist();
586 const tag = new Tag();
587 tag.addBroaderTag(broader);
588 tag.deleteBroaderTag();
589 assertEquals(
590 Array.from(tag.broaderTags(), ($) => $.identifier),
591 [broader.identifier],
592 );
593 });
594
595 it("[[Call]] deletes only the provided broader tags", () => {
596 const superBroader = new Tag();
597 superBroader.persist();
598 const broader = new Tag();
599 broader.addBroaderTag(superBroader);
600 broader.persist();
601 const broader2 = new Tag();
602 broader2.addBroaderTag(superBroader);
603 broader2.persist();
604 const tag = new Tag();
605 tag.addBroaderTag(broader, broader2);
606 tag.deleteBroaderTag(broader, superBroader, "000-0000", "");
607 assertEquals(
608 Array.from(tag.broaderTags(), ($) => $.identifier),
609 [broader2.identifier],
610 );
611 });
612
613 it("[[Call]] returns this", () => {
614 const tag = new Tag();
615 assertStrictEquals(tag.deleteBroaderTag(), tag);
616 });
617 });
618
619 describe("::deleteHiddenLabel", () => {
620 it("[[Call]] does nothing if called with no arguments", () => {
621 const tag = new Tag();
622 tag.addHiddenLabel("etaoin");
623 tag.deleteHiddenLabel();
624 assertEquals([...tag.hiddenLabels()], ["etaoin"]);
625 });
626
627 it("[[Call]] deletes only the provided alternative labels", () => {
628 const tag = new Tag();
629 tag.addHiddenLabel(
630 "one",
631 "two",
632 { "@value": "three", "@language": "en" },
633 "four",
634 );
635 tag.deleteHiddenLabel(
636 "one",
637 { "@value": "two" },
638 { "@value": "three", "@language": "en" },
639 { "@value": "four", "@language": "en" },
640 );
641 assertEquals([...tag.hiddenLabels()], ["four"]);
642 });
643
644 it("[[Call]] returns this", () => {
645 const tag = new Tag();
646 assertStrictEquals(tag.deleteHiddenLabel(), tag);
647 });
648 });
649
650 describe("::deleteInCanonTag", () => {
651 it("[[Call]] does nothing if called with no arguments", () => {
652 const canon = new Tag("CanonTag");
653 canon.persist();
654 const tag = new Tag("EntityTag");
655 tag.addInCanonTag(canon);
656 tag.deleteInCanonTag();
657 assertEquals(
658 Array.from(tag.inCanonTags(), ($) => $.identifier),
659 [canon.identifier],
660 );
661 });
662
663 it("[[Call]] deletes only the provided canon tags", () => {
664 const canon = new Tag("CanonTag");
665 canon.persist();
666 const canon2 = new Tag("CanonTag");
667 canon2.persist();
668 const tag = new Tag("EntityTag");
669 tag.addInCanonTag(canon, canon2);
670 tag.deleteInCanonTag(canon, "000-0000", "");
671 assertEquals(
672 Array.from(tag.inCanonTags(), ($) => $.identifier),
673 [canon2.identifier],
674 );
675 });
676
677 it("[[Call]] returns this", () => {
678 const tag = new Tag("EntityTag");
679 assertStrictEquals(tag.deleteInCanonTag(), tag);
680 });
681 });
682
683 describe("::deleteInvolvesTag", () => {
684 it("[[Call]] does nothing if called with no arguments", () => {
685 const involved = new Tag();
686 involved.persist();
687 const tag = new Tag("ConceptualTag");
688 tag.addInvolvesTag(involved);
689 tag.deleteInvolvesTag();
690 assertEquals(
691 Array.from(tag.involvesTags(), ($) => $.identifier),
692 [involved.identifier],
693 );
694 });
695
696 it("[[Call]] deletes only the provided involved tags", () => {
697 const character = new Tag("CharacterTag");
698 character.persist();
699 const involved = new Tag("RelationshipTag");
700 involved.addInvolvesTag(character);
701 involved.persist();
702 const involved2 = new Tag("RelationshipTag");
703 involved2.addInvolvesTag(character);
704 involved2.persist();
705 const tag = new Tag("RelationshipTag");
706 tag.addInvolvesTag(involved, involved2);
707 tag.deleteInvolvesTag(involved, character, "000-0000", "");
708 assertEquals(
709 Array.from(tag.involvesTags(), ($) => $.identifier),
710 [involved2.identifier],
711 );
712 });
713
714 it("[[Call]] returns this", () => {
715 const tag = new Tag("ConceptualTag");
716 assertStrictEquals(tag.deleteInvolvesTag(), tag);
717 });
718 });
719
720 describe("::hasInCanonTags", () => {
721 it("[[Call]] yields the persisted tags which have this tag in canon", () => {
722 const canon = new Tag("CanonTag");
723 canon.persist();
724 const entity = new Tag("EntityTag");
725 entity.addInCanonTag(canon);
726 entity.persist();
727 const entity2 = new Tag("EntityTag");
728 entity2.addInCanonTag(canon);
729 entity2.persist();
730 const tag = Tag.fromIdentifier(canon.identifier); // reload
731 assertEquals(
732 Array.from(tag.hasInCanonTags(), ($) => $.identifier),
733 [entity.identifier, entity2.identifier],
734 );
735 });
736 });
737
738 // `::hiddenLabels` is tested by `::addHiddenLabel`.
739
740 // `::identifier` is tested by a `.fromIdentifier`.
741
742 // `::inCanonTags` is tested by `::addInCanonTag`.
743
744 describe("::involvedInTags", () => {
745 it("[[Call]] yields the persisted tags which involve this tag", () => {
746 const involved = new Tag();
747 involved.persist();
748 const conceptual = new Tag("ConceptualTag");
749 conceptual.addInvolvesTag(involved);
750 conceptual.persist();
751 const conceptual2 = new Tag("ConceptualTag");
752 conceptual2.addInvolvesTag(involved);
753 conceptual2.persist();
754 const tag = Tag.fromIdentifier(involved.identifier); // reload
755 assertEquals(
756 Array.from(tag.involvedInTags(), ($) => $.identifier),
757 [conceptual.identifier, conceptual2.identifier],
758 );
759 });
760 });
761
762 // `::involvesTags` is tested by `::addInvolvesTag`.
763
764 // `::iri` is tested by a `.fromIRI`.
765
766 // `::iriSpace` is tested by a `.fromIRI`.
767
768 // `::kind` is tested by the constructor.
769
770 describe("::narrowerTags", () => {
771 it("[[Call]] yields the persisted tags which are narrower than this tag", () => {
772 const broader = new Tag();
773 broader.persist();
774 const narrower = new Tag();
775 narrower.addBroaderTag(broader);
776 narrower.persist();
777 const narrower2 = new Tag();
778 narrower2.addBroaderTag(broader);
779 narrower2.persist();
780 const tag = Tag.fromIdentifier(broader.identifier); // reload
781 assertEquals(
782 Array.from(tag.narrowerTags(), ($) => $.identifier),
783 [narrower.identifier, narrower2.identifier],
784 );
785 });
786 });
787
788 describe("::narrowerTransitiveTags", () => {
789 it("[[Call]] returns narrower tags transitively", () => {
790 const broad = new Tag();
791 broad.persist();
792 const narrow = new Tag();
793 narrow.addBroaderTag(broad);
794 narrow.persist();
795 const superNarrow = new Tag();
796 superNarrow.addBroaderTag(narrow);
797 superNarrow.persist();
798 const tag = Tag.fromIdentifier(broad.identifier); // reload
799 assertEquals(
800 Array.from(
801 tag.narrowerTransitiveTags(),
802 ($) => $.identifier,
803 ),
804 [narrow.identifier, superNarrow.identifier],
805 );
806 });
807
808 it("[[Call]] cannot recurse infinitely", () => {
809 const tag = new Tag();
810 tag.persist();
811 const broad = new Tag();
812 broad.addBroaderTag(tag);
813 broad.persist();
814 tag.addBroaderTag(broad);
815 tag.persist();
816 assertEquals(
817 Array.from(tag.broaderTransitiveTags(), ($) => $.identifier),
818 [broad.identifier, tag.identifier],
819 );
820 });
821 });
822
823 describe("::persist", () => {
824 it("[[Call]] returns an object with expected properties if there were changes", () => {
825 const tag = new Tag();
826 const activity = tag.persist();
827 assertObjectMatch(
828 activity,
829 {
830 "@context":
831 "https://ns.1024.gdn/Tagging/discovery.context.jsonld",
832 context: system.iri,
833 object: tag.iri,
834 },
835 );
836 assertArrayIncludes(activity["@type"], ["TagActivity"]);
837 assert("endTime" in activity);
838 });
839
840 it("[[Call]] returns a Create activity with a type predicate for new objects", () => {
841 const activity = new Tag().persist();
842 assertEquals(activity["@type"], ["TagActivity", "Create"]);
843 assertArrayIncludes(activity.states, [{
844 predicate: "a",
845 object: "Tag",
846 }]);
847 });
848
849 it("[[Call]] returns an Update activity for old objects", () => {
850 const tag = new Tag();
851 tag.persist();
852 tag.prefLabel = "etaoin";
853 const activity = tag.persist();
854 assertEquals(activity["@type"], ["TagActivity", "Update"]);
855 });
856
857 it("[[Call]] states and unstates changes", () => {
858 const broader1 = new Tag();
859 broader1.persist();
860 const broader2 = new Tag();
861 broader2.persist();
862 const tag = new Tag();
863 tag.addBroaderTag(broader1);
864 tag.persist();
865 tag.prefLabel = "etaoin";
866 tag.deleteBroaderTag(broader1);
867 tag.addBroaderTag(broader2);
868 const activity = tag.persist();
869 assertObjectMatch(activity, {
870 unstates: [
871 { predicate: "prefLabel", object: { "@value": "" } },
872 { predicate: "broader", object: broader1.iri },
873 ],
874 states: [
875 { predicate: "prefLabel", object: { "@value": "etaoin" } },
876 { predicate: "broader", object: broader2.iri },
877 ],
878 });
879 });
880
881 it("[[Call]] doesn’t state if there are no additions", () => {
882 const tag = new Tag();
883 tag.addAltLabel("etaoin");
884 tag.persist();
885 tag.deleteAltLabel("etaoin");
886 const activity = tag.persist();
887 assertFalse("state" in activity);
888 });
889
890 it("[[Call]] doesn’t unstate if there are no removals", () => {
891 const tag = new Tag();
892 tag.persist();
893 tag.addAltLabel("etaoin");
894 const activity = tag.persist();
895 assertFalse("unstate" in activity);
896 });
897
898 it("[[Call]] returns null if no meaningful changes were made", () => {
899 const tag = new Tag();
900 tag.persist();
901 const activity = tag.persist();
902 assertStrictEquals(activity, null);
903 });
904
905 it("[[Call]] returns undefined for a silent persist", () => {
906 const broader = new Tag();
907 broader.persist();
908 const tag = new Tag();
909 tag.prefLabel = "etaoin";
910 tag.addBroaderTag(broader);
911 assertStrictEquals(tag.persist(true), undefined);
912 });
913 });
914
915 describe("::prefLabel", () => {
916 it("[[Set]] sets the preferred label", () => {
917 const tag = new Tag();
918 tag.prefLabel = "one";
919 assertStrictEquals(tag.prefLabel, "one");
920 tag.prefLabel = { "@value": "two" };
921 assertStrictEquals(tag.prefLabel, "two");
922 tag.prefLabel = { "@value": "three", "@language": "en" };
923 assertEquals(
924 { ...tag.prefLabel },
925 { "@value": "three", "@language": "en" },
926 );
927 });
928 });
929
930 // `::tagURI` is tested by a `.fromTagURI`.
931
932 describe("::taggingEntity", () => {
933 it("[[Get]] returns the tagging entity of the tag system", () => {
934 assertStrictEquals(
935 new Tag().taggingEntity,
936 system.taggingEntity,
937 );
938 });
939 });
940
941 describe("::toString", () => {
942 it("[[Get]] returns the string value of the preferred label", () => {
943 const tag = new Tag();
944 tag.prefLabel = { "@value": "etaoin", "@language": "zxx" };
945 assertStrictEquals(tag.toString(), "etaoin");
946 });
947 });
948
949 // `::[Storage.toObject]` is tested by `::persist`.
950 });
951
952 describe("::authorityName", () => {
953 it("[[Get]] returns the authority name", () => {
954 const system = new TagSystem("etaoin.example", "1972-12-31");
955 assertStrictEquals(system.authorityName, "etaoin.example");
956 });
957 });
958
959 describe("::authorityName", () => {
960 it("[[Get]] returns the date", () => {
961 const system = new TagSystem("etaoin.example", "1972-12-31");
962 assertStrictEquals(system.date, "1972-12-31");
963 });
964 });
965
966 describe("::identifiers", () => {
967 it("[[Get]] yields the extant entities", () => {
968 const system = new TagSystem("etaoin.example", "1972-12-31");
969 const tags = new Set(function* () {
970 let i = 0;
971 while (i++ < 5) {
972 // Generate 5 tags and remember their identifiers.
973 const tag = new system.Tag();
974 tag.persist();
975 yield tag.identifier;
976 }
977 }());
978 assertEquals(
979 new Set(Array.from(system.entities(), ($) => $.identifier)),
980 tags,
981 );
982 });
983 });
984
985 describe("::identifier", () => {
986 it("[[Get]] returns the identifier", () => {
987 const system = new TagSystem("etaoin.example", "1972-12-31");
988 assertStrictEquals(system.identifier, "");
989 const system2 = new TagSystem(
990 "etaoin.example",
991 "1972-12-31",
992 "etaoin",
993 );
994 assertStrictEquals(system2.identifier, "etaoin");
995 });
996 });
997
998 describe("::identifiers", () => {
999 it("[[Get]] yields the identifiers in use", () => {
1000 const system = new TagSystem("etaoin.example", "1972-12-31");
1001 const tags = new Set(function* () {
1002 let i = 0;
1003 while (i++ < 5) {
1004 // Generate 5 tags and remember their identifiers.
1005 const tag = new system.Tag();
1006 tag.persist();
1007 yield tag.identifier;
1008 }
1009 }());
1010 assertEquals(new Set(system.identifiers()), tags);
1011 });
1012 });
1013
1014 describe("::iri", () => {
1015 it("[[Get]] returns the I·R·I", () => {
1016 const system = new TagSystem("etaoin.example", "1972-12-31");
1017 assertStrictEquals(
1018 system.iri,
1019 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
1020 );
1021 const system2 = new TagSystem(
1022 "etaoin.example",
1023 "1972-12-31",
1024 "etaoin",
1025 );
1026 assertStrictEquals(
1027 system2.iri,
1028 "https://etaoin.example/tag:etaoin.example,1972-12-31:etaoin",
1029 );
1030 });
1031 });
1032
1033 describe("::iriSpace", () => {
1034 it("[[Get]] returns the I·R·I space", () => {
1035 const system = new TagSystem("etaoin.example", "1972-12-31");
1036 assertStrictEquals(
1037 system.iriSpace,
1038 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
1039 );
1040 const system2 = new TagSystem(
1041 "etaoin.example",
1042 "1972-12-31",
1043 "etaoin",
1044 );
1045 assertStrictEquals(
1046 system2.iriSpace,
1047 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
1048 );
1049 });
1050 });
1051
1052 describe("::tagURI", () => {
1053 it("[[Get]] returns the Tag U·R·I", () => {
1054 const system = new TagSystem("etaoin.example", "1972-12-31");
1055 assertStrictEquals(
1056 system.tagURI,
1057 "tag:etaoin.example,1972-12-31:",
1058 );
1059 const system2 = new TagSystem(
1060 "etaoin.example",
1061 "1972-12-31",
1062 "etaoin",
1063 );
1064 assertStrictEquals(
1065 system2.tagURI,
1066 "tag:etaoin.example,1972-12-31:etaoin",
1067 );
1068 });
1069 });
1070
1071 describe("::taggingEntity", () => {
1072 it("[[Get]] returns the tagging entity", () => {
1073 const system = new TagSystem("etaoin.example", "1972-12-31");
1074 assertStrictEquals(
1075 system.taggingEntity,
1076 "etaoin.example,1972-12-31",
1077 );
1078 });
1079 });
1080 });
This page took 0.193564 seconds and 3 git commands to generate.