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