extwee 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.github/codeql-analysis.yml +51 -0
  2. package/README.md +9 -3
  3. package/docs/objects/story.md +1 -2
  4. package/index.js +2 -0
  5. package/package.json +17 -17
  6. package/src/IFID/generate.js +20 -0
  7. package/src/JSON/parse.js +43 -0
  8. package/src/Passage.js +52 -3
  9. package/src/Story.js +266 -107
  10. package/src/StoryFormat/parse.js +190 -80
  11. package/src/StoryFormat.js +78 -88
  12. package/src/TWS/parse.js +2 -2
  13. package/src/Twee/parse.js +2 -3
  14. package/src/Twine1HTML/compile.js +2 -0
  15. package/src/Twine1HTML/parse.js +2 -3
  16. package/src/Twine2ArchiveHTML/compile.js +8 -0
  17. package/src/Twine2ArchiveHTML/parse.js +33 -3
  18. package/src/Twine2HTML/compile.js +31 -6
  19. package/src/Twine2HTML/parse.js +49 -54
  20. package/test/IFID/IFID.Generate.test.js +10 -0
  21. package/test/JSON/JSON.Parse.test.js +4 -4
  22. package/test/Story.test.js +256 -46
  23. package/test/StoryFormat/StoryFormat.Parse.test.js +442 -55
  24. package/test/StoryFormat.test.js +9 -2
  25. package/test/TWS/Parse.test.js +1 -1
  26. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +20 -4
  27. package/test/Twine2HTML/Twine2HTML.Compile.test.js +35 -120
  28. package/test/Twine2HTML/Twine2HTML.Parse.test.js +57 -38
  29. package/test/Twine2HTML/Twine2HTMLCompiler/format.js +9 -0
  30. package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +1 -1
  31. package/web-index.js +2 -0
  32. package/test/StoryFormat/StoryFormatParser/example.js +0 -3
  33. package/test/StoryFormat/StoryFormatParser/example2.js +0 -3
  34. package/test/StoryFormat/StoryFormatParser/format.js +0 -1
  35. package/test/StoryFormat/StoryFormatParser/format_doublename.js +0 -1
  36. package/test/StoryFormat/StoryFormatParser/harlowe.js +0 -3
  37. package/test/StoryFormat/StoryFormatParser/missingAuthor.js +0 -1
  38. package/test/StoryFormat/StoryFormatParser/missingDescription.js +0 -1
  39. package/test/StoryFormat/StoryFormatParser/missingImage.js +0 -1
  40. package/test/StoryFormat/StoryFormatParser/missingLicense.js +0 -1
  41. package/test/StoryFormat/StoryFormatParser/missingName.js +0 -1
  42. package/test/StoryFormat/StoryFormatParser/missingProofing.js +0 -1
  43. package/test/StoryFormat/StoryFormatParser/missingSource.js +0 -1
  44. package/test/StoryFormat/StoryFormatParser/missingURL.js +0 -1
  45. package/test/StoryFormat/StoryFormatParser/missingVersion.js +0 -1
  46. package/test/StoryFormat/StoryFormatParser/versionWrong.js +0 -1
  47. package/test/Twine2HTML/Twine2HTMLParser/missingName.html +0 -33
  48. package/test/Twine2HTML/Twine2HTMLParser/missingPID.html +0 -15
  49. package/test/Twine2HTML/Twine2HTMLParser/missingPassageName.html +0 -15
@@ -3,6 +3,7 @@ import Passage from '../src/Passage';
3
3
  import { parse as parseTwee } from '../src/Twee/parse.js';
4
4
  import { readFileSync } from 'node:fs';
5
5
  import { parse as HTMLParser } from 'node-html-parser';
6
+ import { generate as generateIFID } from '../src/IFID/generate.js';
6
7
 
7
8
  // Pull the name and version of this project from package.json.
8
9
  // These are used as the 'creator' and 'creator-version'.
@@ -28,6 +29,11 @@ describe('Story', () => {
28
29
  s = new Story('Test');
29
30
  expect(s.name).toBe('Test');
30
31
  });
32
+
33
+ it('Should have default name', () => {
34
+ s = new Story();
35
+ expect(s.name).toBe('Untitled Story');
36
+ });
31
37
  });
32
38
 
33
39
  describe('creator', () => {
@@ -223,6 +229,31 @@ describe('Story', () => {
223
229
  });
224
230
  });
225
231
 
232
+ describe('passages', () => {
233
+ let s = null;
234
+
235
+ beforeEach(() => {
236
+ s = new Story();
237
+ });
238
+
239
+ it('Set passages to a new array containing at least one Passage', () => {
240
+ s.passages = [new Passage()];
241
+ expect(s.passages.length).toBe(1);
242
+ });
243
+
244
+ it('Should throw error if trying to set to a non-Array type', () => {
245
+ expect(() => {
246
+ s.passages = null;
247
+ }).toThrow();
248
+ });
249
+
250
+ it('Should throw error if trying to set to an array containing non-Passage types', () => {
251
+ expect(() => {
252
+ s.passages = [null];
253
+ }).toThrow();
254
+ });
255
+ });
256
+
226
257
  describe('addPassage()', () => {
227
258
  let s = null;
228
259
 
@@ -242,14 +273,6 @@ describe('Story', () => {
242
273
  }).toThrow();
243
274
  });
244
275
 
245
- it('addPassage() - should prevent passages with same name being added', () => {
246
- const p = new Passage('A');
247
- const p2 = new Passage('A');
248
- s.addPassage(p);
249
- s.addPassage(p2);
250
- expect(s.size()).toBe(1);
251
- });
252
-
253
276
  it('addPassage() - should override StoryData: ifid', function () {
254
277
  // Generate object.
255
278
  const o = {
@@ -351,30 +374,6 @@ describe('Story', () => {
351
374
  });
352
375
  });
353
376
 
354
- describe('forEachPassage()', () => {
355
- let s = null;
356
-
357
- beforeEach(() => {
358
- s = new Story();
359
- });
360
-
361
- it('forEachPassage() - should return if non-function', () => {
362
- s.addPassage(new Passage('A'));
363
- s.addPassage(new Passage('B'));
364
- let passageNames = '';
365
- s.forEachPassage((p) => {
366
- passageNames += p.name;
367
- });
368
- expect(passageNames).toBe('AB');
369
- });
370
-
371
- it('forEachPassage() - should throw error if non-function', () => {
372
- expect(() => {
373
- s.forEachPassage(null);
374
- }).toThrow();
375
- });
376
- });
377
-
378
377
  describe('size()', () => {
379
378
  let s = null;
380
379
 
@@ -400,7 +399,7 @@ describe('Story', () => {
400
399
  const s = new Story();
401
400
  // Convert to string and then back to object.
402
401
  const result = JSON.parse(s.toJSON());
403
- expect(result.name).toBe('');
402
+ expect(result.name).toBe('Untitled Story');
404
403
  expect(Object.keys(result.tagColors).length).toBe(0);
405
404
  expect(result.ifid).toBe('');
406
405
  expect(result.start).toBe('');
@@ -408,7 +407,7 @@ describe('Story', () => {
408
407
  expect(result.format).toBe('');
409
408
  expect(result.creator).toBe(creatorName);
410
409
  expect(result.creatorVersion).toBe(creatorVersion);
411
- expect(result.zoom).toBe(0);
410
+ expect(result.zoom).toBe(1);
412
411
  expect(Object.keys(result.metadata).length).toBe(0);
413
412
  });
414
413
 
@@ -431,10 +430,81 @@ describe('Story', () => {
431
430
  s = new Story();
432
431
  });
433
432
 
433
+ it('Should not generate format if empty', function () {
434
+ // Add one passage.
435
+ s.addPassage(new Passage('Start', 'Content'));
436
+
437
+ // Add an IFID (to prevent warning)
438
+ s.IFID = generateIFID();
439
+
440
+ // Set format to empty string.
441
+ s.format = '';
442
+
443
+ // Convert to Twee.
444
+ const t = s.toTwee();
445
+
446
+ // Test for format in metadata, should not exist.
447
+ expect(t.includes(`"'format":`)).not.toBe(true);
448
+ });
449
+
450
+ it('Should not generate formatVersion if empty', function () {
451
+ // Add one passage.
452
+ s.addPassage(new Passage('Start', 'Content'));
453
+
454
+ // Add an IFID (to prevent warning)
455
+ s.IFID = generateIFID();
456
+
457
+ // Set formatVersion to empty string.
458
+ s.formatVersion = '';
459
+
460
+ // Convert to Twee.
461
+ const t = s.toTwee();
462
+
463
+ // Test for formatVersion in metadata, should not exist.
464
+ expect(t.includes(`"'format-version":`)).not.toBe(true);
465
+ });
466
+
467
+ it('Should not generate zoom if zero', function () {
468
+ // Add one passage.
469
+ s.addPassage(new Passage('Start', 'Content'));
470
+
471
+ // Add an IFID (to prevent warning)
472
+ s.IFID = generateIFID();
473
+
474
+ // Set zoom to 0.
475
+ s.zoom = 0;
476
+
477
+ // Convert to Twee.
478
+ const t = s.toTwee();
479
+
480
+ // Test for zoom in metadata, should not exist.
481
+ expect(t.includes(`"'zoom":`)).not.toBe(true);
482
+ });
483
+
484
+ it('Should not generate start if empty', function () {
485
+ // Add one passage.
486
+ s.addPassage(new Passage('Start', 'Content'));
487
+
488
+ // Add an IFID (to prevent warning)
489
+ s.IFID = generateIFID();
490
+
491
+ // Set start to empty string.
492
+ s.start = '';
493
+
494
+ // Convert to Twee.
495
+ const t = s.toTwee();
496
+
497
+ // Test for start in metadata, should not exist.
498
+ expect(t.includes(`"'start":`)).not.toBe(true);
499
+ });
500
+
434
501
  it('Should detect StoryTitle text', function () {
435
502
  // Add one passage.
436
503
  s.addPassage(new Passage('StoryTitle', 'Content'));
437
504
 
505
+ // Add an IFID (to prevent warning)
506
+ s.IFID = generateIFID();
507
+
438
508
  // Convert to Twee.
439
509
  const t = s.toTwee();
440
510
 
@@ -473,6 +543,7 @@ describe('Story', () => {
473
543
  s.formatVersion = '1.2.3';
474
544
  s.zoom = 1;
475
545
  s.start = 'Untitled';
546
+ s.IFID = 'DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A';
476
547
 
477
548
  // Convert to Twee.
478
549
  const t = s.toTwee();
@@ -485,6 +556,7 @@ describe('Story', () => {
485
556
  expect(story2.format).toBe('Test');
486
557
  expect(story2.zoom).toBe(1);
487
558
  expect(story2.start).toBe('Untitled');
559
+ expect(story2.IFID).toBe('DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A');
488
560
  });
489
561
 
490
562
  it('Should write tag colors', () => {
@@ -492,6 +564,8 @@ describe('Story', () => {
492
564
  s.addPassage(new Passage('Start', 'Content'));
493
565
  s.addPassage(new Passage('Untitled', 'Some stuff'));
494
566
 
567
+ s.IFID = 'DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A';
568
+
495
569
  // Add tag colors.
496
570
  s.tagColors = {
497
571
  bar: 'green',
@@ -516,6 +590,9 @@ describe('Story', () => {
516
590
  s.addPassage(new Passage('Test', 'Test', ['script']));
517
591
  s.addPassage(new Passage('Start', 'Content'));
518
592
 
593
+ // Set IFID.
594
+ s.IFID = 'DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A';
595
+
519
596
  // Convert into Twee.
520
597
  const t = s.toTwee();
521
598
 
@@ -534,6 +611,9 @@ describe('Story', () => {
534
611
  s.addPassage(new Passage('Test', 'Test', ['stylesheet']));
535
612
  s.addPassage(new Passage('Start', 'Content'));
536
613
 
614
+ // Set IFID.
615
+ s.IFID = 'DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A';
616
+
537
617
  // Convert into Twee.
538
618
  const t = s.toTwee();
539
619
 
@@ -555,18 +635,6 @@ describe('Story', () => {
555
635
  s = new Story();
556
636
  });
557
637
 
558
- it('Should throw error if no starting passage', function () {
559
- // No start set.
560
- expect(() => { s.toTwine2HTML(); }).toThrow();
561
- });
562
-
563
- it('Should throw error if starting passage cannot be found', function () {
564
- // Set start.
565
- s.start = 'Unknown';
566
- // Has a start, but not part of collection.
567
- expect(() => { s.toTwine2HTML(); }).toThrow();
568
- });
569
-
570
638
  it('Should encode name', () => {
571
639
  // Add passage.
572
640
  s.addPassage(new Passage('Start', 'Word'));
@@ -618,6 +686,114 @@ describe('Story', () => {
618
686
  // Expect the script passage text to be encoded.
619
687
  expect(result.includes('<script role="script" id="twine-user-script" type="text/twine-javascript">Word')).toBe(true);
620
688
  });
689
+
690
+ it('Should encode format', () => {
691
+ // Add passage.
692
+ s.addPassage(new Passage('Start', 'Word'));
693
+ // Set start.
694
+ s.start = 'Start';
695
+ // Set format.
696
+ s.format = 'Harlowe';
697
+ // Create HTML.
698
+ const result = s.toTwine2HTML();
699
+ // Expect the format to be encoded.
700
+ expect(result.includes('format="Harlowe"')).toBe(true);
701
+ });
702
+
703
+ it('Should encode formatVersion', () => {
704
+ // Add passage.
705
+ s.addPassage(new Passage('Start', 'Word'));
706
+ // Set start.
707
+ s.start = 'Start';
708
+ // Set formatVersion.
709
+ s.formatVersion = '3.2.0';
710
+ // Create HTML.
711
+ const result = s.toTwine2HTML();
712
+ // Expect the formatVersion to be encoded.
713
+ expect(result.includes('format-version="3.2.0"')).toBe(true);
714
+ });
715
+
716
+ it('Should encode zoom', () => {
717
+ // Add passage.
718
+ s.addPassage(new Passage('Start', 'Word'));
719
+ // Set start.
720
+ s.start = 'Start';
721
+ // Set zoom.
722
+ s.zoom = 2;
723
+ // Create HTML.
724
+ const result = s.toTwine2HTML();
725
+ // Expect the zoom to be encoded.
726
+ expect(result.includes('zoom="2"')).toBe(true);
727
+ });
728
+
729
+ it('Should encode start', () => {
730
+ // Add passage.
731
+ s.addPassage(new Passage('Start', 'Word'));
732
+ // Set start.
733
+ s.start = 'Start';
734
+ // Create HTML.
735
+ const result = s.toTwine2HTML();
736
+ // Expect the start to be encoded.
737
+ expect(result.includes('startnode="1"')).toBe(true);
738
+ });
739
+
740
+ it('Should encode start if property is not set but Start passage is', () => {
741
+ // Add passage.
742
+ s.addPassage(new Passage('Start', 'Word'));
743
+ // Create HTML.
744
+ const result = s.toTwine2HTML();
745
+ // Expect the start to be encoded.
746
+ expect(result.includes('startnode="1"')).toBe(true);
747
+ });
748
+
749
+ it('Should encode creator', () => {
750
+ // Add passage.
751
+ s.addPassage(new Passage('Start', 'Word'));
752
+ // Set start.
753
+ s.start = 'Start';
754
+ // Create HTML.
755
+ const result = s.toTwine2HTML();
756
+ // Expect the creator to be encoded.
757
+ expect(result.includes(`creator="${creatorName}"`)).toBe(true);
758
+ });
759
+
760
+ it('Should encode creatorVersion', () => {
761
+ // Add passage.
762
+ s.addPassage(new Passage('Start', 'Word'));
763
+ // Set start.
764
+ s.start = 'Start';
765
+ // Create HTML.
766
+ const result = s.toTwine2HTML();
767
+ // Expect the creatorVersion to be encoded.
768
+ expect(result.includes(`creator-version="${creatorVersion}"`)).toBe(true);
769
+ });
770
+
771
+ it('Should not encode creatorVersion if not set', () => {
772
+ // Add passage.
773
+ s.addPassage(new Passage('Start', 'Word'));
774
+ // Set start.
775
+ s.start = 'Start';
776
+ // Set creatorVersion to empty string.
777
+ s.creatorVersion = '';
778
+ // Create HTML.
779
+ const result = s.toTwine2HTML();
780
+ // Expect the creatorVersion to be encoded.
781
+ expect(result.includes(`creator-version="${creatorVersion}"`)).not.toBe(true);
782
+ });
783
+
784
+ it('Should not encode creator if not set', () => {
785
+ // Add passage.
786
+ s.addPassage(new Passage('Start', 'Word'));
787
+ // Set start.
788
+ s.start = 'Start';
789
+ // Set creator to empty string.
790
+ s.creator = '';
791
+ // Create HTML.
792
+ const result = s.toTwine2HTML();
793
+ // Expect the creator to be encoded.
794
+ expect(result.includes(`creator="${creatorName}"`)).not.toBe(true);
795
+ });
796
+
621
797
  });
622
798
 
623
799
  describe('toTwine1HTML()', function () {
@@ -674,4 +850,38 @@ describe('Story', () => {
674
850
  expect(root.querySelector('div').getAttribute('tiddler')).toBe('"Test"');
675
851
  });
676
852
  });
853
+
854
+ describe('Warnings', function () {
855
+ beforeEach(() => {
856
+ // Mock console.warn.
857
+ jest.spyOn(console, 'warn').mockImplementation();
858
+ });
859
+
860
+ afterEach(() => {
861
+ // Restore all mocks.
862
+ jest.restoreAllMocks();
863
+ });
864
+
865
+ it('Should generate warning if a passage with the same name already exists', function () {
866
+ // Create a new Story.
867
+ const s = new Story();
868
+ // Add a passage.
869
+ s.addPassage(new Passage('Test'));
870
+ // Add a passage with the same name.
871
+ s.addPassage(new Passage('Test'));
872
+ // Expect warning.
873
+ expect(console.warn).toHaveBeenCalledWith('Warning: A passage with the name "Test" already exists!');
874
+ });
875
+
876
+ it('Should generate a warning if story IFID is not in the correct format', function () {
877
+ // Create a new Story.
878
+ const s = new Story();
879
+ // Set IFID.
880
+ s.IFID = 'Test';
881
+ // Create Twee
882
+ s.toTwee();
883
+ // Expect warning.
884
+ expect(console.warn).toHaveBeenCalledWith('Warning: IFID is not in UUIDv4 format! A new IFID was generated.');
885
+ });
886
+ });
677
887
  });