]> Lady’s Gitweb - Etiquette/blob - model.test.js
a92671c52d2a4a033ab575172ff6abe7e00f2117
[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(".constructor", () => {
168 it("[[Get]] is `Function`", () => {
169 assertStrictEquals(Tag.constructor, Function);
170 });
171 });
172
173 describe(".fromIRI", () => {
174 it("[[Call]] returns the persisted tag with the given I·R·I", () => {
175 const tag = new Tag();
176 tag.persist();
177 const { identifier, iri } = tag;
178 const retrieved = Tag.fromIRI(iri);
179 assertStrictEquals(
180 Object.getPrototypeOf(retrieved),
181 Tag.prototype,
182 );
183 assertStrictEquals(retrieved.identifier, identifier);
184 });
185
186 it("[[Call]] returns null if no tag with the given I·R·I has been persisted", () => {
187 assertStrictEquals(
188 Tag.fromIRI(
189 `https://${system.authorityName}/tag:${system.taggingEntity}:000-0000`,
190 ),
191 null,
192 );
193 });
194
195 it("[[Call]] throws if passed an invalid I·R·I", () => {
196 assertThrows(() => {
197 Tag.fromIRI(`bad iri`);
198 });
199 });
200 });
201
202 describe(".fromIdentifier", () => {
203 it("[[Call]] returns the persisted tag with the given identifier", () => {
204 const tag = new Tag();
205 tag.persist();
206 const { identifier } = tag;
207 const retrieved = Tag.fromIdentifier(identifier);
208 assertStrictEquals(
209 Object.getPrototypeOf(retrieved),
210 Tag.prototype,
211 );
212 assertStrictEquals(retrieved.identifier, identifier);
213 });
214
215 it("[[Call]] returns null if no tag with the given identifier has been persisted", () => {
216 assertStrictEquals(Tag.fromIdentifier("000-0000"), null);
217 });
218
219 it("[[Call]] throws if passed an invalid identifier", () => {
220 assertThrows(() => {
221 Tag.fromIdentifier(""); // wrong format
222 });
223 assertThrows(() => {
224 Tag.fromIdentifier("100-0000"); // bad checksum
225 });
226 });
227 });
228
229 describe(".fromTagURI", () => {
230 it("[[Call]] returns the persisted tag with the given Tag U·R·I", () => {
231 const tag = new Tag();
232 tag.persist();
233 const { identifier, tagURI } = tag;
234 const retrieved = Tag.fromTagURI(tagURI);
235 assertStrictEquals(
236 Object.getPrototypeOf(retrieved),
237 Tag.prototype,
238 );
239 assertStrictEquals(retrieved.identifier, identifier);
240 });
241
242 it("[[Call]] returns null if no tag with the given Tag U·R·I has been persisted", () => {
243 assertStrictEquals(
244 Tag.fromTagURI(`tag:${system.taggingEntity}:`),
245 null,
246 );
247 assertStrictEquals(
248 Tag.fromTagURI(`tag:${system.taggingEntity}:000-0000`),
249 null,
250 );
251 });
252
253 it("[[Call]] throws if passed an invalid Tag U·R·I", () => {
254 assertThrows(() => {
255 Tag.fromTagURI(""); // wrong format
256 });
257 assertThrows(() => {
258 Tag.fromTagURI(
259 "tag:unexample,1970-01-01:Z", // incorrect tagging entity
260 );
261 });
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 const canon = new Tag("CanonTag");
415 canon.persist();
416 assertThrows(() => {
417 new Tag().addInCanonTag(canon);
418 });
419 });
420
421 it("[[Call]] throws when provided with a non‐canon tag", () => {
422 const notCanon = new Tag();
423 notCanon.persist();
424 const tag = new Tag("EntityTag");
425 assertThrows(() => {
426 tag.addInCanonTag(notCanon);
427 });
428 });
429
430 it("[[Call]] throws when adding a non‐persisted tag", () => {
431 const tag = new Tag("EntityTag");
432 assertThrows(() => {
433 tag.addInCanonTag(new Tag("CanonTag"));
434 });
435 });
436
437 it("[[Call]] throws when adding an unrecognized identifier", () => {
438 const tag = new Tag("EntityTag");
439 assertThrows(() => {
440 tag.addInCanonTag("000-0000"); // not persisted
441 });
442 assertThrows(() => {
443 tag.addInCanonTag(""); // bad format
444 });
445 });
446 });
447
448 describe("::addInvolvesTag", () => {
449 it("[[Call]] does nothing if called with no arguments", () => {
450 const tag = new Tag("ConceptualTag");
451 tag.addInvolvesTag();
452 assertEquals([...tag.involvesTags()], []);
453 });
454
455 it("[[Call]] adds the provided tags", () => {
456 const involved = new Tag();
457 involved.persist();
458 const involved2 = new Tag();
459 involved2.persist();
460 const tag = new Tag("ConceptualTag");
461 tag.addInvolvesTag(involved, involved2);
462 assertEquals(
463 Array.from(tag.involvesTags(), ($) => $.identifier),
464 [involved.identifier, involved2.identifier],
465 );
466 });
467
468 it("[[Call]] returns this", () => {
469 const tag = new Tag("ConceptualTag");
470 assertStrictEquals(tag.addInvolvesTag(), tag);
471 });
472
473 it("[[Call]] throws when this is not a conceptual tag", () => {
474 const involved = new Tag();
475 involved.persist();
476 assertThrows(() => {
477 new Tag().addInvolvesTag(involved);
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(
557 Array.from(tag.altLabels(), ($) => ({ ...$ })),
558 [{ "@value": "etaoin" }],
559 );
560 });
561
562 it("[[Call]] deletes only the provided hidden labels", () => {
563 const tag = new Tag();
564 tag.addAltLabel(
565 "one",
566 "two",
567 { "@value": "three", "@language": "en" },
568 "four",
569 );
570 tag.deleteAltLabel(
571 "one",
572 { "@value": "two" },
573 { "@value": "three", "@language": "en" },
574 { "@value": "four", "@language": "en" },
575 );
576 assertEquals(
577 Array.from(tag.altLabels(), ($) => ({ ...$ })),
578 [{ "@value": "four" }],
579 );
580 });
581
582 it("[[Call]] returns this", () => {
583 const tag = new Tag();
584 assertStrictEquals(tag.deleteAltLabel(), tag);
585 });
586 });
587
588 describe("::deleteBroaderTag", () => {
589 it("[[Call]] does nothing if called with no arguments", () => {
590 const broader = new Tag();
591 broader.persist();
592 const tag = new Tag();
593 tag.addBroaderTag(broader);
594 tag.deleteBroaderTag();
595 assertEquals(
596 Array.from(tag.broaderTags(), ($) => $.identifier),
597 [broader.identifier],
598 );
599 });
600
601 it("[[Call]] deletes only the provided broader tags", () => {
602 const superBroader = new Tag();
603 superBroader.persist();
604 const broader = new Tag();
605 broader.addBroaderTag(superBroader);
606 broader.persist();
607 const broader2 = new Tag();
608 broader2.addBroaderTag(superBroader);
609 broader2.persist();
610 const tag = new Tag();
611 tag.addBroaderTag(broader, broader2);
612 tag.deleteBroaderTag(broader, superBroader, "000-0000", "");
613 assertEquals(
614 Array.from(tag.broaderTags(), ($) => $.identifier),
615 [broader2.identifier],
616 );
617 });
618
619 it("[[Call]] returns this", () => {
620 const tag = new Tag();
621 assertStrictEquals(tag.deleteBroaderTag(), tag);
622 });
623 });
624
625 describe("::deleteHiddenLabel", () => {
626 it("[[Call]] does nothing if called with no arguments", () => {
627 const tag = new Tag();
628 tag.addHiddenLabel("etaoin");
629 tag.deleteHiddenLabel();
630 assertEquals(
631 Array.from(tag.hiddenLabels(), ($) => ({ ...$ })),
632 [{ "@value": "etaoin" }],
633 );
634 });
635
636 it("[[Call]] deletes only the provided alternative labels", () => {
637 const tag = new Tag();
638 tag.addHiddenLabel(
639 "one",
640 "two",
641 { "@value": "three", "@language": "en" },
642 "four",
643 );
644 tag.deleteHiddenLabel(
645 "one",
646 { "@value": "two" },
647 { "@value": "three", "@language": "en" },
648 { "@value": "four", "@language": "en" },
649 );
650 assertEquals(
651 Array.from(tag.hiddenLabels(), ($) => ({ ...$ })),
652 [{ "@value": "four" }],
653 );
654 });
655
656 it("[[Call]] returns this", () => {
657 const tag = new Tag();
658 assertStrictEquals(tag.deleteHiddenLabel(), tag);
659 });
660 });
661
662 describe("::deleteInCanonTag", () => {
663 it("[[Call]] does nothing if called with no arguments", () => {
664 const canon = new Tag("CanonTag");
665 canon.persist();
666 const tag = new Tag("EntityTag");
667 tag.addInCanonTag(canon);
668 tag.deleteInCanonTag();
669 assertEquals(
670 Array.from(tag.inCanonTags(), ($) => $.identifier),
671 [canon.identifier],
672 );
673 });
674
675 it("[[Call]] deletes only the provided canon tags", () => {
676 const canon = new Tag("CanonTag");
677 canon.persist();
678 const canon2 = new Tag("CanonTag");
679 canon2.persist();
680 const tag = new Tag("EntityTag");
681 tag.addInCanonTag(canon, canon2);
682 tag.deleteInCanonTag(canon, "000-0000", "");
683 assertEquals(
684 Array.from(tag.inCanonTags(), ($) => $.identifier),
685 [canon2.identifier],
686 );
687 });
688
689 it("[[Call]] returns this", () => {
690 const tag = new Tag("EntityTag");
691 assertStrictEquals(tag.deleteInCanonTag(), tag);
692 });
693 });
694
695 describe("::deleteInvolvesTag", () => {
696 it("[[Call]] does nothing if called with no arguments", () => {
697 const involved = new Tag();
698 involved.persist();
699 const tag = new Tag("ConceptualTag");
700 tag.addInvolvesTag(involved);
701 tag.deleteInvolvesTag();
702 assertEquals(
703 Array.from(tag.involvesTags(), ($) => $.identifier),
704 [involved.identifier],
705 );
706 });
707
708 it("[[Call]] deletes only the provided involved tags", () => {
709 const character = new Tag("CharacterTag");
710 character.persist();
711 const involved = new Tag("RelationshipTag");
712 involved.addInvolvesTag(character);
713 involved.persist();
714 const involved2 = new Tag("RelationshipTag");
715 involved2.addInvolvesTag(character);
716 involved2.persist();
717 const tag = new Tag("RelationshipTag");
718 tag.addInvolvesTag(involved, involved2);
719 tag.deleteInvolvesTag(involved, character, "000-0000", "");
720 assertEquals(
721 Array.from(tag.involvesTags(), ($) => $.identifier),
722 [involved2.identifier],
723 );
724 });
725
726 it("[[Call]] returns this", () => {
727 const tag = new Tag("ConceptualTag");
728 assertStrictEquals(tag.deleteInvolvesTag(), tag);
729 });
730 });
731
732 describe("::hasInCanonTags", () => {
733 it("[[Call]] yields the persisted tags which have this tag in canon", () => {
734 const canon = new Tag("CanonTag");
735 canon.persist();
736 const entity = new Tag("EntityTag");
737 entity.addInCanonTag(canon);
738 entity.persist();
739 const entity2 = new Tag("EntityTag");
740 entity2.addInCanonTag(canon);
741 entity2.persist();
742 const tag = Tag.fromIdentifier(canon.identifier); // reload
743 assertEquals(
744 Array.from(tag.hasInCanonTags(), ($) => $.identifier),
745 [entity.identifier, entity2.identifier],
746 );
747 });
748 });
749
750 // `::hiddenLabels` is tested by `::addHiddenLabel`.
751
752 // `::identifier` is tested by a `.fromIdentifier`.
753
754 // `::inCanonTags` is tested by `::addInCanonTag`.
755
756 describe("::involvedInTags", () => {
757 it("[[Call]] yields the persisted tags which involve this tag", () => {
758 const involved = new Tag();
759 involved.persist();
760 const conceptual = new Tag("ConceptualTag");
761 conceptual.addInvolvesTag(involved);
762 conceptual.persist();
763 const conceptual2 = new Tag("ConceptualTag");
764 conceptual2.addInvolvesTag(involved);
765 conceptual2.persist();
766 const tag = Tag.fromIdentifier(involved.identifier); // reload
767 assertEquals(
768 Array.from(tag.involvedInTags(), ($) => $.identifier),
769 [conceptual.identifier, conceptual2.identifier],
770 );
771 });
772 });
773
774 // `::involvesTags` is tested by `::addInvolvesTag`.
775
776 // `::iri` is tested by a `.fromIRI`.
777
778 // `::iriSpace` is tested by a `.fromIRI`.
779
780 // `::kind` is tested by the constructor.
781
782 describe("::narrowerTags", () => {
783 it("[[Call]] yields the persisted tags which are narrower than this tag", () => {
784 const broader = new Tag();
785 broader.persist();
786 const narrower = new Tag();
787 narrower.addBroaderTag(broader);
788 narrower.persist();
789 const narrower2 = new Tag();
790 narrower2.addBroaderTag(broader);
791 narrower2.persist();
792 const tag = Tag.fromIdentifier(broader.identifier); // reload
793 assertEquals(
794 Array.from(tag.narrowerTags(), ($) => $.identifier),
795 [narrower.identifier, narrower2.identifier],
796 );
797 });
798 });
799
800 describe("::narrowerTransitiveTags", () => {
801 it("[[Call]] returns narrower tags transitively", () => {
802 const broad = new Tag();
803 broad.persist();
804 const narrow = new Tag();
805 narrow.addBroaderTag(broad);
806 narrow.persist();
807 const superNarrow = new Tag();
808 superNarrow.addBroaderTag(narrow);
809 superNarrow.persist();
810 const tag = Tag.fromIdentifier(broad.identifier); // reload
811 assertEquals(
812 Array.from(
813 tag.narrowerTransitiveTags(),
814 ($) => $.identifier,
815 ),
816 [narrow.identifier, superNarrow.identifier],
817 );
818 });
819
820 it("[[Call]] cannot recurse infinitely", () => {
821 const tag = new Tag();
822 tag.persist();
823 const broad = new Tag();
824 broad.addBroaderTag(tag);
825 broad.persist();
826 tag.addBroaderTag(broad);
827 tag.persist();
828 assertEquals(
829 Array.from(tag.broaderTransitiveTags(), ($) => $.identifier),
830 [broad.identifier, tag.identifier],
831 );
832 });
833 });
834
835 describe("::persist", () => {
836 it("[[Call]] returns an object with expected properties if there were changes", () => {
837 const tag = new Tag();
838 const activity = tag.persist();
839 assertObjectMatch(
840 activity,
841 {
842 "@context":
843 "https://ns.1024.gdn/Tagging/discovery.context.jsonld",
844 context: system.iri,
845 object: tag.iri,
846 },
847 );
848 assertArrayIncludes(activity["@type"], ["TagActivity"]);
849 assert("endTime" in activity);
850 });
851
852 it("[[Call]] returns a Create activity with a type predicate for new objects", () => {
853 const activity = new Tag().persist();
854 assertEquals(activity["@type"], ["TagActivity", "Create"]);
855 assertArrayIncludes(activity.states, [{
856 predicate: "a",
857 object: "Tag",
858 }]);
859 });
860
861 it("[[Call]] returns an Update activity for old objects", () => {
862 const tag = new Tag();
863 tag.persist();
864 tag.prefLabel = "etaoin";
865 const activity = tag.persist();
866 assertEquals(activity["@type"], ["TagActivity", "Update"]);
867 });
868
869 it("[[Call]] states and unstates changes", () => {
870 const broader1 = new Tag();
871 broader1.persist();
872 const broader2 = new Tag();
873 broader2.persist();
874 const tag = new Tag();
875 tag.addBroaderTag(broader1);
876 tag.persist();
877 tag.prefLabel = "etaoin";
878 tag.deleteBroaderTag(broader1);
879 tag.addBroaderTag(broader2);
880 const activity = tag.persist();
881 assertObjectMatch(activity, {
882 unstates: [
883 { predicate: "prefLabel", object: { "@value": "" } },
884 { predicate: "broader", object: broader1.iri },
885 ],
886 states: [
887 { predicate: "prefLabel", object: { "@value": "etaoin" } },
888 { predicate: "broader", object: broader2.iri },
889 ],
890 });
891 });
892
893 it("[[Call]] doesn’t state if there are no additions", () => {
894 const tag = new Tag();
895 tag.addAltLabel("etaoin");
896 tag.persist();
897 tag.deleteAltLabel("etaoin");
898 const activity = tag.persist();
899 assertFalse("state" in activity);
900 });
901
902 it("[[Call]] doesn’t unstate if there are no removals", () => {
903 const tag = new Tag();
904 tag.persist();
905 tag.addAltLabel("etaoin");
906 const activity = tag.persist();
907 assertFalse("unstate" in activity);
908 });
909
910 it("[[Call]] returns null if no meaningful changes were made", () => {
911 const tag = new Tag();
912 tag.persist();
913 const activity = tag.persist();
914 assertStrictEquals(activity, null);
915 });
916
917 it("[[Call]] returns undefined for a silent persist", () => {
918 const broader = new Tag();
919 broader.persist();
920 const tag = new Tag();
921 tag.prefLabel = "etaoin";
922 tag.addBroaderTag(broader);
923 assertStrictEquals(tag.persist(true), undefined);
924 });
925 });
926
927 describe("::prefLabel", () => {
928 it("[[Set]] sets the preferred label", () => {
929 const tag = new Tag();
930 tag.prefLabel = "one";
931 assertEquals({ ...tag.prefLabel }, { "@value": "one" });
932 tag.prefLabel = { "@value": "two" };
933 assertEquals({ ...tag.prefLabel }, { "@value": "two" });
934 tag.prefLabel = { "@value": "three", "@language": "en" };
935 assertEquals(
936 { ...tag.prefLabel },
937 { "@value": "three", "@language": "en" },
938 );
939 });
940 });
941
942 // `::tagURI` is tested by a `.fromTagURI`.
943
944 describe("::taggingEntity", () => {
945 it("[[Get]] returns the tagging entity of the tag system", () => {
946 assertStrictEquals(
947 new Tag().taggingEntity,
948 system.taggingEntity,
949 );
950 });
951 });
952
953 describe("::toString", () => {
954 it("[[Get]] returns the string value of the preferred label", () => {
955 const tag = new Tag();
956 tag.prefLabel = { "@value": "etaoin", "@language": "zxx" };
957 assertStrictEquals(tag.toString(), "etaoin");
958 });
959 });
960
961 // `::[Storage.toObject]` is tested by `::persist`.
962 });
963
964 describe("::authorityName", () => {
965 it("[[Get]] returns the authority name", () => {
966 const system = new TagSystem("etaoin.example", "1972-12-31");
967 assertStrictEquals(system.authorityName, "etaoin.example");
968 });
969 });
970
971 describe("::authorityName", () => {
972 it("[[Get]] returns the date", () => {
973 const system = new TagSystem("etaoin.example", "1972-12-31");
974 assertStrictEquals(system.date, "1972-12-31");
975 });
976 });
977
978 describe("::identifiers", () => {
979 it("[[Get]] yields the extant entities", () => {
980 const system = new TagSystem("etaoin.example", "1972-12-31");
981 const tags = new Set(function* () {
982 let i = 0;
983 while (i++ < 5) {
984 // Generate 5 tags and remember their identifiers.
985 const tag = new system.Tag();
986 tag.persist();
987 yield tag.identifier;
988 }
989 }());
990 assertEquals(
991 new Set(Array.from(system.entities(), ($) => $.identifier)),
992 tags,
993 );
994 });
995 });
996
997 describe("::identifier", () => {
998 it("[[Get]] returns the identifier", () => {
999 const system = new TagSystem("etaoin.example", "1972-12-31");
1000 assertStrictEquals(system.identifier, "");
1001 const system2 = new TagSystem(
1002 "etaoin.example",
1003 "1972-12-31",
1004 "etaoin",
1005 );
1006 assertStrictEquals(system2.identifier, "etaoin");
1007 });
1008 });
1009
1010 describe("::identifiers", () => {
1011 it("[[Get]] yields the identifiers in use", () => {
1012 const system = new TagSystem("etaoin.example", "1972-12-31");
1013 const tags = new Set(function* () {
1014 let i = 0;
1015 while (i++ < 5) {
1016 // Generate 5 tags and remember their identifiers.
1017 const tag = new system.Tag();
1018 tag.persist();
1019 yield tag.identifier;
1020 }
1021 }());
1022 assertEquals(new Set(system.identifiers()), tags);
1023 });
1024 });
1025
1026 describe("::iri", () => {
1027 it("[[Get]] returns the I·R·I", () => {
1028 const system = new TagSystem("etaoin.example", "1972-12-31");
1029 assertStrictEquals(
1030 system.iri,
1031 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
1032 );
1033 const system2 = new TagSystem(
1034 "etaoin.example",
1035 "1972-12-31",
1036 "etaoin",
1037 );
1038 assertStrictEquals(
1039 system2.iri,
1040 "https://etaoin.example/tag:etaoin.example,1972-12-31:etaoin",
1041 );
1042 });
1043 });
1044
1045 describe("::iriSpace", () => {
1046 it("[[Get]] returns the I·R·I space", () => {
1047 const system = new TagSystem("etaoin.example", "1972-12-31");
1048 assertStrictEquals(
1049 system.iriSpace,
1050 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
1051 );
1052 const system2 = new TagSystem(
1053 "etaoin.example",
1054 "1972-12-31",
1055 "etaoin",
1056 );
1057 assertStrictEquals(
1058 system2.iriSpace,
1059 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
1060 );
1061 });
1062 });
1063
1064 describe("::tagURI", () => {
1065 it("[[Get]] returns the Tag U·R·I", () => {
1066 const system = new TagSystem("etaoin.example", "1972-12-31");
1067 assertStrictEquals(
1068 system.tagURI,
1069 "tag:etaoin.example,1972-12-31:",
1070 );
1071 const system2 = new TagSystem(
1072 "etaoin.example",
1073 "1972-12-31",
1074 "etaoin",
1075 );
1076 assertStrictEquals(
1077 system2.tagURI,
1078 "tag:etaoin.example,1972-12-31:etaoin",
1079 );
1080 });
1081 });
1082
1083 describe("::taggingEntity", () => {
1084 it("[[Get]] returns the tagging entity", () => {
1085 const system = new TagSystem("etaoin.example", "1972-12-31");
1086 assertStrictEquals(
1087 system.taggingEntity,
1088 "etaoin.example,1972-12-31",
1089 );
1090 });
1091 });
1092 });
This page took 0.16526 seconds and 3 git commands to generate.