]> Lady’s Gitweb - Etiquette/blob - model.test.js
7190830c3a043dd200bfecf4ed7d68d924e6b32c
[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]] returns null if passed an invalid I·R·I", () => {
190 assertStrictEquals(Tag.fromIRI(`bad iri`), null);
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.fromIRI(`tag:${system.taggingEntity}:`),
237 null,
238 );
239 assertStrictEquals(
240 Tag.fromIRI(`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 // `::kind` is tested by the constructor.
767
768 describe("::narrowerTags", () => {
769 it("[[Call]] yields the persisted tags which are narrower than this tag", () => {
770 const broader = new Tag();
771 broader.persist();
772 const narrower = new Tag();
773 narrower.addBroaderTag(broader);
774 narrower.persist();
775 const narrower2 = new Tag();
776 narrower2.addBroaderTag(broader);
777 narrower2.persist();
778 const tag = Tag.fromIdentifier(broader.identifier); // reload
779 assertEquals(
780 Array.from(tag.narrowerTags(), ($) => $.identifier),
781 [narrower.identifier, narrower2.identifier],
782 );
783 });
784 });
785
786 describe("::narrowerTransitiveTags", () => {
787 it("[[Call]] returns narrower tags transitively", () => {
788 const broad = new Tag();
789 broad.persist();
790 const narrow = new Tag();
791 narrow.addBroaderTag(broad);
792 narrow.persist();
793 const superNarrow = new Tag();
794 superNarrow.addBroaderTag(narrow);
795 superNarrow.persist();
796 const tag = Tag.fromIdentifier(broad.identifier); // reload
797 assertEquals(
798 Array.from(
799 tag.narrowerTransitiveTags(),
800 ($) => $.identifier,
801 ),
802 [narrow.identifier, superNarrow.identifier],
803 );
804 });
805
806 it("[[Call]] cannot recurse infinitely", () => {
807 const tag = new Tag();
808 tag.persist();
809 const broad = new Tag();
810 broad.addBroaderTag(tag);
811 broad.persist();
812 tag.addBroaderTag(broad);
813 tag.persist();
814 assertEquals(
815 Array.from(tag.broaderTransitiveTags(), ($) => $.identifier),
816 [broad.identifier, tag.identifier],
817 );
818 });
819 });
820
821 describe("::persist", () => {
822 it("[[Call]] returns an object with expected properties if there were changes", () => {
823 const tag = new Tag();
824 const activity = tag.persist();
825 assertObjectMatch(
826 activity,
827 {
828 "@context":
829 "https://ns.1024.gdn/Tagging/discovery.context.jsonld",
830 context: system.iri,
831 object: tag.iri,
832 },
833 );
834 assertArrayIncludes(activity["@type"], ["TagActivity"]);
835 assert("endTime" in activity);
836 });
837
838 it("[[Call]] returns a Create activity with a type predicate for new objects", () => {
839 const activity = new Tag().persist();
840 assertEquals(activity["@type"], ["TagActivity", "Create"]);
841 assertArrayIncludes(activity.states, [{
842 predicate: "a",
843 object: "Tag",
844 }]);
845 });
846
847 it("[[Call]] returns an Update activity for old objects", () => {
848 const tag = new Tag();
849 tag.persist();
850 tag.prefLabel = "etaoin";
851 const activity = tag.persist();
852 assertEquals(activity["@type"], ["TagActivity", "Update"]);
853 });
854
855 it("[[Call]] states and unstates changes", () => {
856 const broader1 = new Tag();
857 broader1.persist();
858 const broader2 = new Tag();
859 broader2.persist();
860 const tag = new Tag();
861 tag.addBroaderTag(broader1);
862 tag.persist();
863 tag.prefLabel = "etaoin";
864 tag.deleteBroaderTag(broader1);
865 tag.addBroaderTag(broader2);
866 const activity = tag.persist();
867 assertObjectMatch(activity, {
868 unstates: [
869 { predicate: "prefLabel", object: { "@value": "" } },
870 { predicate: "broader", object: broader1.iri },
871 ],
872 states: [
873 { predicate: "prefLabel", object: { "@value": "etaoin" } },
874 { predicate: "broader", object: broader2.iri },
875 ],
876 });
877 });
878
879 it("[[Call]] doesn’t state if there are no additions", () => {
880 const tag = new Tag();
881 tag.addAltLabel("etaoin");
882 tag.persist();
883 tag.deleteAltLabel("etaoin");
884 const activity = tag.persist();
885 assertFalse("state" in activity);
886 });
887
888 it("[[Call]] doesn’t unstate if there are no removals", () => {
889 const tag = new Tag();
890 tag.persist();
891 tag.addAltLabel("etaoin");
892 const activity = tag.persist();
893 assertFalse("unstate" in activity);
894 });
895
896 it("[[Call]] returns null if no meaningful changes were made", () => {
897 const tag = new Tag();
898 tag.persist();
899 const activity = tag.persist();
900 assertStrictEquals(activity, null);
901 });
902
903 it("[[Call]] returns undefined for a silent persist", () => {
904 const broader = new Tag();
905 broader.persist();
906 const tag = new Tag();
907 tag.prefLabel = "etaoin";
908 tag.addBroaderTag(broader);
909 assertStrictEquals(tag.persist(true), undefined);
910 });
911 });
912
913 describe("::prefLabel", () => {
914 it("[[Set]] sets the preferred label", () => {
915 const tag = new Tag();
916 tag.prefLabel = "one";
917 assertStrictEquals(tag.prefLabel, "one");
918 tag.prefLabel = { "@value": "two" };
919 assertStrictEquals(tag.prefLabel, "two");
920 tag.prefLabel = { "@value": "three", "@language": "en" };
921 assertEquals(
922 { ...tag.prefLabel },
923 { "@value": "three", "@language": "en" },
924 );
925 });
926 });
927
928 // `::tagURI` is tested by a `.fromTagURI`.
929
930 describe("::taggingEntity", () => {
931 it("[[Get]] returns the tagging entity of the tag system", () => {
932 assertStrictEquals(
933 new Tag().taggingEntity,
934 system.taggingEntity,
935 );
936 });
937 });
938
939 describe("::toString", () => {
940 it("[[Get]] returns the string value of the preferred label", () => {
941 const tag = new Tag();
942 tag.prefLabel = { "@value": "etaoin", "@language": "zxx" };
943 assertStrictEquals(tag.toString(), "etaoin");
944 });
945 });
946
947 // `::[Storage.toObject]` is tested by `::persist`.
948 });
949
950 describe("::authorityName", () => {
951 it("[[Get]] returns the authority name", () => {
952 const system = new TagSystem("etaoin.example", "1972-12-31");
953 assertStrictEquals(system.authorityName, "etaoin.example");
954 });
955 });
956
957 describe("::authorityName", () => {
958 it("[[Get]] returns the date", () => {
959 const system = new TagSystem("etaoin.example", "1972-12-31");
960 assertStrictEquals(system.date, "1972-12-31");
961 });
962 });
963
964 describe("::identifiers", () => {
965 it("[[Get]] yields the extant entities", () => {
966 const system = new TagSystem("etaoin.example", "1972-12-31");
967 const tags = new Set(function* () {
968 let i = 0;
969 while (i++ < 5) {
970 // Generate 5 tags and remember their identifiers.
971 const tag = new system.Tag();
972 tag.persist();
973 yield tag.identifier;
974 }
975 }());
976 assertEquals(
977 new Set(Array.from(system.entities(), ($) => $.identifier)),
978 tags,
979 );
980 });
981 });
982
983 describe("::identifier", () => {
984 it("[[Get]] returns the identifier", () => {
985 const system = new TagSystem("etaoin.example", "1972-12-31");
986 assertStrictEquals(system.identifier, "");
987 const system2 = new TagSystem(
988 "etaoin.example",
989 "1972-12-31",
990 "etaoin",
991 );
992 assertStrictEquals(system2.identifier, "etaoin");
993 });
994 });
995
996 describe("::identifiers", () => {
997 it("[[Get]] yields the identifiers in use", () => {
998 const system = new TagSystem("etaoin.example", "1972-12-31");
999 const tags = new Set(function* () {
1000 let i = 0;
1001 while (i++ < 5) {
1002 // Generate 5 tags and remember their identifiers.
1003 const tag = new system.Tag();
1004 tag.persist();
1005 yield tag.identifier;
1006 }
1007 }());
1008 assertEquals(new Set(system.identifiers()), tags);
1009 });
1010 });
1011
1012 describe("::iri", () => {
1013 it("[[Get]] returns the I·R·I", () => {
1014 const system = new TagSystem("etaoin.example", "1972-12-31");
1015 assertStrictEquals(
1016 system.iri,
1017 "https://etaoin.example/tag:etaoin.example,1972-12-31:",
1018 );
1019 const system2 = new TagSystem(
1020 "etaoin.example",
1021 "1972-12-31",
1022 "etaoin",
1023 );
1024 assertStrictEquals(
1025 system2.iri,
1026 "https://etaoin.example/tag:etaoin.example,1972-12-31:etaoin",
1027 );
1028 });
1029 });
1030
1031 describe("::tagURI", () => {
1032 it("[[Get]] returns the Tag U·R·I", () => {
1033 const system = new TagSystem("etaoin.example", "1972-12-31");
1034 assertStrictEquals(
1035 system.tagURI,
1036 "tag:etaoin.example,1972-12-31:",
1037 );
1038 const system2 = new TagSystem(
1039 "etaoin.example",
1040 "1972-12-31",
1041 "etaoin",
1042 );
1043 assertStrictEquals(
1044 system2.tagURI,
1045 "tag:etaoin.example,1972-12-31:etaoin",
1046 );
1047 });
1048 });
1049
1050 describe("::taggingEntity", () => {
1051 it("[[Get]] returns the tagging entity", () => {
1052 const system = new TagSystem("etaoin.example", "1972-12-31");
1053 assertStrictEquals(
1054 system.taggingEntity,
1055 "etaoin.example,1972-12-31",
1056 );
1057 });
1058 });
1059 });
This page took 0.217209 seconds and 3 git commands to generate.