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