extwee 2.3.10 → 2.3.12

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 (180) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/CONTRIBUTING.md +305 -0
  3. package/README.md +16 -0
  4. package/build/extwee.core.min.js +1 -1
  5. package/build/extwee.twine1html.min.js +1 -1
  6. package/build/extwee.twine2archive.min.js +1 -1
  7. package/build/extwee.tws.min.js +1 -1
  8. package/package.json +8 -5
  9. package/src/Passage.js +8 -9
  10. package/src/Story.js +2 -2
  11. package/types/src/Story.d.ts +1 -1
  12. package/.github/FUNDING.yml +0 -3
  13. package/.github/codeql-analysis.yml +0 -51
  14. package/.github/dependabot.yml +0 -11
  15. package/.github/workflows/dependabot-automerge.yml +0 -23
  16. package/.github/workflows/nodejs.yml +0 -28
  17. package/.travis.yml +0 -13
  18. package/babel.config.json +0 -18
  19. package/docs/.nojekyll +0 -0
  20. package/docs/README.md +0 -224
  21. package/docs/_sidebar.md +0 -19
  22. package/docs/build/extwee.core.min.js +0 -1
  23. package/docs/build/extwee.twine1html.min.js +0 -1
  24. package/docs/build/extwee.twine2archive.min.js +0 -1
  25. package/docs/build/extwee.tws.min.js +0 -1
  26. package/docs/demos/compiler/extwee.core.min.js +0 -1
  27. package/docs/demos/compiler/index.css +0 -105
  28. package/docs/demos/compiler/index.html +0 -359
  29. package/docs/demos/decompile/extwee.core.min.js +0 -1
  30. package/docs/demos/decompile/index.css +0 -584
  31. package/docs/demos/decompile/index.html +0 -468
  32. package/docs/examples/dynamicPassages.md +0 -28
  33. package/docs/examples/jsonToTwee.md +0 -23
  34. package/docs/examples/twsToTwee.md +0 -25
  35. package/docs/formats/json.md +0 -17
  36. package/docs/formats/twee.md +0 -13
  37. package/docs/formats/twine1HTML.md +0 -13
  38. package/docs/formats/twine2ArchiveHTML.md +0 -13
  39. package/docs/formats/twine2HTML.md +0 -13
  40. package/docs/formats/tws.md +0 -9
  41. package/docs/index.html +0 -26
  42. package/docs/install/npm.md +0 -16
  43. package/docs/install/npx.md +0 -79
  44. package/docs/objects/passage.md +0 -47
  45. package/docs/objects/story.md +0 -69
  46. package/docs/objects/storyformat.md +0 -27
  47. package/eslint.config.js +0 -28
  48. package/extwee.config.json +0 -6
  49. package/extwee.config.md +0 -67
  50. package/jest.config.json +0 -5
  51. package/test/CLI/CLI.test.js +0 -49
  52. package/test/CLI/files/example.json +0 -1
  53. package/test/CLI/files/example6.twee +0 -22
  54. package/test/CLI/files/harlowe.js +0 -3
  55. package/test/CLI/files/input.html +0 -47
  56. package/test/CLI/files/output/test.twee +0 -0
  57. package/test/CLI/files/test.twee +0 -18
  58. package/test/CLI/files/tweeExample.twee +0 -17
  59. package/test/CLI/files/twine1/LICENSE.txt +0 -32
  60. package/test/CLI/files/twine1/code.js +0 -5
  61. package/test/CLI/files/twine1/engine.js +0 -43
  62. package/test/CLI/files/twine1/header.html +0 -325
  63. package/test/CLI/files/twine1Test.html +0 -371
  64. package/test/CLI/files/twineExample.html +0 -16
  65. package/test/Config/Config.test.js +0 -76
  66. package/test/Config/files/empty.json +0 -3
  67. package/test/Config/files/full.json +0 -8
  68. package/test/Config/files/invalid.json +0 -1
  69. package/test/Config/files/valid.json +0 -6
  70. package/test/Config/isDirectory.test.js +0 -50
  71. package/test/Config/isFile.test.js +0 -53
  72. package/test/Config/loadStoryFormat.test.js +0 -117
  73. package/test/Config/readDirectories.test.js +0 -78
  74. package/test/IFID/IFID.Generate.test.js +0 -10
  75. package/test/JSON/JSON.Parse.test.js +0 -316
  76. package/test/Objects/Passage.test.js +0 -270
  77. package/test/Objects/SnowmanCompatibility.test.js +0 -125
  78. package/test/Objects/Story.test.js +0 -1075
  79. package/test/Objects/StoryFormat.test.js +0 -219
  80. package/test/Roundtrip/Files/Example1.html +0 -64
  81. package/test/Roundtrip/Files/LICENSE +0 -19
  82. package/test/Roundtrip/Files/example1.twee +0 -10
  83. package/test/Roundtrip/Files/example2.twee +0 -27
  84. package/test/Roundtrip/Files/example4.twee +0 -27
  85. package/test/Roundtrip/Files/harlowe.js +0 -3
  86. package/test/Roundtrip/Files/round.html +0 -49
  87. package/test/Roundtrip/Roundtrip.test.js +0 -54
  88. package/test/StoryFormat/StoryFormat.Parse.test.js +0 -479
  89. package/test/TWS/Parse.test.js +0 -56
  90. package/test/TWS/TWSParser/Example5.tws +0 -414
  91. package/test/TWS/TWSParser/noscale.tws +0 -0
  92. package/test/TWS/TWSParser/nostory.tws +0 -0
  93. package/test/Twee/Twee.Escaping.test.js +0 -200
  94. package/test/Twee/Twee.Parse.test.js +0 -108
  95. package/test/Twee/TweeParser/cursed.twee +0 -16
  96. package/test/Twee/TweeParser/cycling.twee +0 -75
  97. package/test/Twee/TweeParser/emptytags.twee +0 -2
  98. package/test/Twee/TweeParser/example.twee +0 -32
  99. package/test/Twee/TweeParser/malformed.twee +0 -2
  100. package/test/Twee/TweeParser/missing.twee +0 -19
  101. package/test/Twee/TweeParser/multipleScriptPassages.twee +0 -19
  102. package/test/Twee/TweeParser/multipleStyleTag.twee +0 -19
  103. package/test/Twee/TweeParser/multipletags.twee +0 -10
  104. package/test/Twee/TweeParser/noTitle.twee +0 -2
  105. package/test/Twee/TweeParser/notes.twee +0 -16
  106. package/test/Twee/TweeParser/pasagemetadataerror.twee +0 -2
  107. package/test/Twee/TweeParser/scriptPassage.twee +0 -16
  108. package/test/Twee/TweeParser/singletag.twee +0 -13
  109. package/test/Twee/TweeParser/start.twee +0 -2
  110. package/test/Twee/TweeParser/startMetadata.twee +0 -14
  111. package/test/Twee/TweeParser/storydataerror.twee +0 -25
  112. package/test/Twee/TweeParser/style.twee +0 -16
  113. package/test/Twee/TweeParser/stylePassage.twee +0 -16
  114. package/test/Twee/TweeParser/test.twee +0 -25
  115. package/test/Twine1HTML/Twine1HTML.Compile.test.js +0 -180
  116. package/test/Twine1HTML/Twine1HTML.Parse.Web.test.js +0 -484
  117. package/test/Twine1HTML/Twine1HTML.Parse.test.js +0 -183
  118. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +0 -674
  119. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +0 -43
  120. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +0 -4
  121. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +0 -4
  122. package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +0 -1
  123. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +0 -32
  124. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +0 -4
  125. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +0 -327
  126. package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
  127. package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +0 -6
  128. package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +0 -6
  129. package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +0 -43
  130. package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +0 -372
  131. package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +0 -372
  132. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +0 -35
  133. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.Web.test.js +0 -293
  134. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +0 -42
  135. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +0 -6
  136. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +0 -3
  137. package/test/Twine2HTML/Twine2HTML.Compile.test.js +0 -139
  138. package/test/Twine2HTML/Twine2HTML.Parse.Web.test.js +0 -329
  139. package/test/Twine2HTML/Twine2HTML.Parse.test.js +0 -192
  140. package/test/Twine2HTML/Twine2HTMLCompiler/TestTags.html +0 -42
  141. package/test/Twine2HTML/Twine2HTMLCompiler/creator.html +0 -50
  142. package/test/Twine2HTML/Twine2HTMLCompiler/example6.twee +0 -16
  143. package/test/Twine2HTML/Twine2HTMLCompiler/format.js +0 -9
  144. package/test/Twine2HTML/Twine2HTMLCompiler/missingStoryTitle.twee +0 -29
  145. package/test/Twine2HTML/Twine2HTMLCompiler/test11.html +0 -121
  146. package/test/Twine2HTML/Twine2HTMLCompiler/test2.html +0 -58
  147. package/test/Twine2HTML/Twine2HTMLCompiler/test3.html +0 -49
  148. package/test/Twine2HTML/Twine2HTMLCompiler/test4.html +0 -50
  149. package/test/Twine2HTML/Twine2HTMLCompiler/test6.html +0 -49
  150. package/test/Twine2HTML/Twine2HTMLParser/Example1.html +0 -53
  151. package/test/Twine2HTML/Twine2HTMLParser/Tags.html +0 -15
  152. package/test/Twine2HTML/Twine2HTMLParser/lyingStartnode.html +0 -15
  153. package/test/Twine2HTML/Twine2HTMLParser/lyingTagColors.html +0 -48
  154. package/test/Twine2HTML/Twine2HTMLParser/missingCreator.html +0 -11
  155. package/test/Twine2HTML/Twine2HTMLParser/missingCreatorVersion.html +0 -11
  156. package/test/Twine2HTML/Twine2HTMLParser/missingFormat.html +0 -11
  157. package/test/Twine2HTML/Twine2HTMLParser/missingFormatVersion.html +0 -11
  158. package/test/Twine2HTML/Twine2HTMLParser/missingIFID.html +0 -11
  159. package/test/Twine2HTML/Twine2HTMLParser/missingPassageTags.html +0 -15
  160. package/test/Twine2HTML/Twine2HTMLParser/missingPosition.html +0 -15
  161. package/test/Twine2HTML/Twine2HTMLParser/missingScript.html +0 -14
  162. package/test/Twine2HTML/Twine2HTMLParser/missingSize.html +0 -35
  163. package/test/Twine2HTML/Twine2HTMLParser/missingStartnode.html +0 -11
  164. package/test/Twine2HTML/Twine2HTMLParser/missingStyle.html +0 -14
  165. package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +0 -11
  166. package/test/Twine2HTML/Twine2HTMLParser/tagColors.html +0 -31
  167. package/test/Twine2HTML/Twine2HTMLParser/twineExample.html +0 -23
  168. package/test/Twine2HTML/Twine2HTMLParser/twineExample2.html +0 -15
  169. package/test/Twine2HTML/Twine2HTMLParser/twineExample3.html +0 -15
  170. package/test/Twine2HTML/Twine2HTMLParser/unescaping.html +0 -33
  171. package/test/Web/web-core-coverage.test.js +0 -175
  172. package/test/Web/web-core-global.test.js +0 -93
  173. package/test/Web/web-core.test.js +0 -156
  174. package/test/Web/web-exports.test.js +0 -136
  175. package/test/Web/web-twine1html.test.js +0 -105
  176. package/test/Web/web-twine2archive.test.js +0 -96
  177. package/test/Web/web-tws.test.js +0 -77
  178. package/test/Web/window.Extwee.test.js +0 -97
  179. package/tsconfig.json +0 -21
  180. package/webpack.config.js +0 -47
@@ -1,329 +0,0 @@
1
- /**
2
- * @jest-environment jsdom
3
- */
4
- import { parse } from '../../src/Twine2HTML/parse-web.js';
5
- import { Story } from '../../src/Story.js';
6
-
7
- /**
8
- * Mock environment to force fallback parsing since jsdom doesn't behave like browser DOMParser
9
- * @param {string} content Content to parse
10
- * @returns {Story} Story object
11
- */
12
- function parseTwine2HTMLWeb(content) {
13
- // Force fallback mode by temporarily hiding DOMParser
14
- const originalDOMParser = global.DOMParser;
15
- delete global.DOMParser;
16
-
17
- try {
18
- return parse(content);
19
- } finally {
20
- // Restore DOMParser
21
- if (originalDOMParser) {
22
- global.DOMParser = originalDOMParser;
23
- }
24
- }
25
- }
26
-
27
- describe('Twine2HTML', function () {
28
- describe('parse-web()', function () {
29
- describe('Error handling', function () {
30
- it('Should throw TypeError for non-string content', function () {
31
- expect(() => { parseTwine2HTMLWeb(null); }).toThrow('TypeError: Content is not a string!');
32
- expect(() => { parseTwine2HTMLWeb(undefined); }).toThrow('TypeError: Content is not a string!');
33
- expect(() => { parseTwine2HTMLWeb(123); }).toThrow('TypeError: Content is not a string!');
34
- expect(() => { parseTwine2HTMLWeb({}); }).toThrow('TypeError: Content is not a string!');
35
- });
36
-
37
- it('Should throw TypeError for non-Twine2 HTML content', function () {
38
- expect(() => { parseTwine2HTMLWeb('<div>not twine content</div>'); }).toThrow('TypeError: Not Twine 2 HTML content!');
39
- expect(() => { parseTwine2HTMLWeb(''); }).toThrow('TypeError: Not Twine 2 HTML content!');
40
- });
41
-
42
- it('Should throw Error for passages without PID', function () {
43
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata name="Test Passage">Content</tw-passagedata></tw-storydata>';
44
- expect(() => { parseTwine2HTMLWeb(content); }).toThrow('Error: Passages are required to have PID!');
45
- });
46
- });
47
-
48
- describe('Basic parsing functionality', function () {
49
- it('Should parse basic Twine2 HTML with required attributes', function () {
50
- const content = '<tw-storydata name="Test Story" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start">Hello World</tw-passagedata></tw-storydata>';
51
-
52
- const story = parseTwine2HTMLWeb(content);
53
-
54
- expect(story.name).toBe('Test Story');
55
- expect(story.IFID).toBe('12345678-1234-1234-1234-123456789012');
56
- expect(story.size()).toBe(1);
57
-
58
- const passage = story.getPassageByName('Start');
59
- expect(passage.name).toBe('Start');
60
- expect(passage.text).toBe('Hello World');
61
- });
62
-
63
- it('Should parse story with all optional attributes', function () {
64
- const content = '<tw-storydata name="Complex Story" ifid="12345678-1234-1234-1234-123456789012" creator="Twine" creator-version="2.3.9" format="Harlowe" format-version="3.1.0" startnode="2" zoom="1.5" options="key1:value1,key2:value2" hidden="passage1,passage2"><tw-passagedata pid="2" name="Start" position="100,200" size="150,100" tags="start important">Welcome to the story</tw-passagedata></tw-storydata>';
65
-
66
- const story = parseTwine2HTMLWeb(content);
67
-
68
- expect(story.name).toBe('Complex Story');
69
- expect(story.IFID).toBe('12345678-1234-1234-1234-123456789012');
70
- expect(story.creator).toBe('Twine');
71
- expect(story.creatorVersion).toBe('2.3.9');
72
- expect(story.format).toBe('Harlowe');
73
- expect(story.formatVersion).toBe('3.1.0');
74
- expect(story.start).toBe('Start');
75
- expect(story.zoom).toBe(1.5);
76
- expect(story.metadata.key1).toBe('value1');
77
- expect(story.metadata.key2).toBe('value2');
78
- expect(story.metadata.hidden).toBe('passage1,passage2');
79
-
80
- const passage = story.getPassageByName('Start');
81
- expect(passage.name).toBe('Start');
82
- expect(passage.text).toBe('Welcome to the story');
83
- expect(passage.tags).toEqual(['start', 'important']);
84
- expect(passage.metadata.position).toBe('100,200');
85
- expect(passage.metadata.size).toBe('150,100');
86
- });
87
-
88
- it('Should parse story with multiple passages', function () {
89
- const content = '<tw-storydata name="Multi Story" ifid="12345678-1234-1234-1234-123456789012" startnode="1"><tw-passagedata pid="1" name="Start">Start content</tw-passagedata><tw-passagedata pid="2" name="Second" tags="special">Second content</tw-passagedata></tw-storydata>';
90
-
91
- const story = parseTwine2HTMLWeb(content);
92
-
93
- expect(story.size()).toBe(2);
94
- expect(story.start).toBe('Start');
95
-
96
- const startPassage = story.getPassageByName('Start');
97
- expect(startPassage.text).toBe('Start content');
98
-
99
- const secondPassage = story.getPassageByName('Second');
100
- expect(secondPassage.text).toBe('Second content');
101
- expect(secondPassage.tags).toEqual(['special']);
102
- });
103
- });
104
-
105
- describe('Warning generation', function () {
106
- let originalConsoleWarn;
107
-
108
- beforeEach(() => {
109
- originalConsoleWarn = console.warn;
110
- });
111
-
112
- afterEach(() => {
113
- console.warn = originalConsoleWarn;
114
- });
115
-
116
- it('Should warn for missing name attribute', function () {
117
- let warningMessage = '';
118
- console.warn = (msg) => { warningMessage = msg; };
119
-
120
- const content = '<tw-storydata ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
121
- parseTwine2HTMLWeb(content);
122
- expect(warningMessage).toBe('Warning: The name attribute is missing from tw-storydata!');
123
- });
124
-
125
- it('Should warn for missing IFID attribute', function () {
126
- let warningMessage = '';
127
- console.warn = (msg) => { warningMessage = msg; };
128
-
129
- const content = '<tw-storydata name="Test Story"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
130
- parseTwine2HTMLWeb(content);
131
- expect(warningMessage).toBe('Warning: The ifid attribute is missing from tw-storydata!');
132
- });
133
-
134
- it('Should warn for malformed IFID', function () {
135
- let warningMessage = '';
136
- console.warn = (msg) => { warningMessage = msg; };
137
-
138
- const content = '<tw-storydata name="Test Story" ifid="invalid-ifid"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
139
- parseTwine2HTMLWeb(content);
140
- expect(warningMessage).toBe('Warning: The IFID is not in valid UUIDv4 formatting on tw-storydata!');
141
- });
142
-
143
- it('Should warn for passage without name', function () {
144
- let warningMessage = '';
145
- console.warn = (msg) => { warningMessage = msg; };
146
-
147
- const content = '<tw-storydata name="Test Story" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1">Content</tw-passagedata></tw-storydata>';
148
- parseTwine2HTMLWeb(content);
149
- expect(warningMessage).toBe('Warning: Cannot parse passage data without name!');
150
- });
151
- });
152
-
153
- describe('Tag color parsing from styles', function () {
154
- it('Should parse tag colors from style elements', function () {
155
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><style>tw-story-tag-important { color: red; } tw-story-tag-special { color: #00ff00; }</style><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
156
-
157
- const story = parseTwine2HTMLWeb(content);
158
-
159
- expect(story.tagColors.important).toBe('red;');
160
- expect(story.tagColors.special).toBe('#00ff00;');
161
- });
162
-
163
- it('Should handle multiple style elements', function () {
164
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><style>tw-story-tag-red { color: red; }</style><style>tw-story-tag-blue { color: blue; }</style><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
165
-
166
- const story = parseTwine2HTMLWeb(content);
167
-
168
- expect(story.tagColors.red).toBe('red;');
169
- expect(story.tagColors.blue).toBe('blue;');
170
- });
171
- });
172
-
173
- describe('HTML entity decoding', function () {
174
- it('Should decode HTML entities in passage text', function () {
175
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start">&lt;p&gt;Hello &amp; welcome&lt;/p&gt;</tw-passagedata></tw-storydata>';
176
-
177
- const story = parseTwine2HTMLWeb(content);
178
- const passage = story.getPassageByName('Start');
179
-
180
- expect(passage.text).toBe('<p>Hello & welcome</p>');
181
- });
182
-
183
- it('Should handle complex HTML entities', function () {
184
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start">&quot;Quote&quot; &apos;Apostrophe&apos; &copy; &amp; more</tw-passagedata></tw-storydata>';
185
-
186
- const story = parseTwine2HTMLWeb(content);
187
- const passage = story.getPassageByName('Start');
188
-
189
- expect(passage.text).toBe('"Quote" \'Apostrophe\' © & more');
190
- });
191
- });
192
-
193
- describe('Tag handling', function () {
194
- it('Should handle empty tags attribute', function () {
195
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start" tags="">Content</tw-passagedata></tw-storydata>';
196
-
197
- const story = parseTwine2HTMLWeb(content);
198
- const passage = story.getPassageByName('Start');
199
-
200
- expect(passage.tags.length).toBe(0);
201
- });
202
-
203
- it('Should handle quoted empty tags', function () {
204
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start" tags=\'""\'">Content</tw-passagedata></tw-storydata>';
205
-
206
- const story = parseTwine2HTMLWeb(content);
207
- const passage = story.getPassageByName('Start');
208
-
209
- expect(passage.tags.length).toBe(0);
210
- });
211
-
212
- it('Should handle multiple tags', function () {
213
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start" tags="tag1 tag2 tag3">Content</tw-passagedata></tw-storydata>';
214
-
215
- const story = parseTwine2HTMLWeb(content);
216
- const passage = story.getPassageByName('Start');
217
-
218
- expect(passage.tags).toEqual(['tag1', 'tag2', 'tag3']);
219
- });
220
-
221
- it('Should filter out empty tag strings', function () {
222
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start" tags="tag1 tag2 tag3">Content</tw-passagedata></tw-storydata>';
223
-
224
- const story = parseTwine2HTMLWeb(content);
225
- const passage = story.getPassageByName('Start');
226
-
227
- expect(passage.tags).toEqual(['tag1', 'tag2', 'tag3']);
228
- });
229
- });
230
-
231
- describe('Options parsing', function () {
232
- it('Should parse empty options', function () {
233
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012" options=""><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
234
-
235
- const story = parseTwine2HTMLWeb(content);
236
-
237
- expect(Object.keys(story.metadata).length).toBe(0);
238
- });
239
-
240
- it('Should parse single option', function () {
241
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012" options="debug:true"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
242
-
243
- const story = parseTwine2HTMLWeb(content);
244
-
245
- expect(story.metadata.debug).toBe('true');
246
- });
247
-
248
- it('Should parse multiple options', function () {
249
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012" options="debug:true,undo:false,jquery:disabled"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
250
-
251
- const story = parseTwine2HTMLWeb(content);
252
-
253
- expect(story.metadata.debug).toBe('true');
254
- expect(story.metadata.undo).toBe('false');
255
- expect(story.metadata.jquery).toBe('disabled');
256
- });
257
-
258
- it('Should handle malformed options gracefully', function () {
259
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012" options="debug:true,malformed,another:value"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
260
-
261
- const story = parseTwine2HTMLWeb(content);
262
-
263
- expect(story.metadata.debug).toBe('true');
264
- expect(story.metadata.another).toBe('value');
265
- expect(story.metadata.malformed).toBeUndefined();
266
- });
267
- });
268
-
269
- describe('Fallback DOM parsing', function () {
270
- it('Should work without DOMParser (fallback mode)', function () {
271
- const originalDOMParser = global.DOMParser;
272
- global.DOMParser = undefined;
273
-
274
- try {
275
- const content = '<tw-storydata name="Fallback Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Start" tags="test" position="100,200">Fallback content</tw-passagedata></tw-storydata>';
276
- const story = parseTwine2HTMLWeb(content);
277
-
278
- expect(story.name).toBe('Fallback Test');
279
- expect(story.size()).toBe(1);
280
-
281
- const passage = story.getPassageByName('Start');
282
- expect(passage.text).toBe('Fallback content');
283
- expect(passage.tags).toEqual(['test']);
284
- expect(passage.metadata.position).toBe('100,200');
285
- } finally {
286
- global.DOMParser = originalDOMParser;
287
- }
288
- });
289
- });
290
-
291
- describe('Edge cases', function () {
292
- it('Should handle empty passage content', function () {
293
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Empty"></tw-passagedata></tw-storydata>';
294
-
295
- const story = parseTwine2HTMLWeb(content);
296
- const passage = story.getPassageByName('Empty');
297
-
298
- expect(passage.text).toBe('');
299
- });
300
-
301
- it('Should handle special characters in passage names', function () {
302
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Passage with special: chars & symbols!">Content</tw-passagedata></tw-storydata>';
303
-
304
- const story = parseTwine2HTMLWeb(content);
305
- const passage = story.getPassageByName('Passage with special: chars & symbols!');
306
-
307
- expect(passage.text).toBe('Content');
308
- });
309
-
310
- it('Should handle whitespace in passage content', function () {
311
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012"><tw-passagedata pid="1" name="Whitespace"> \\n\\t Content with whitespace \\n\\t </tw-passagedata></tw-storydata>';
312
-
313
- const story = parseTwine2HTMLWeb(content);
314
- const passage = story.getPassageByName('Whitespace');
315
-
316
- expect(passage.text).toBe('\\n\\t Content with whitespace \\n\\t');
317
- });
318
-
319
- it('Should handle numeric zoom values', function () {
320
- const content = '<tw-storydata name="Test" ifid="12345678-1234-1234-1234-123456789012" zoom="2.5"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
321
-
322
- const story = parseTwine2HTMLWeb(content);
323
-
324
- expect(story.zoom).toBe(2.5);
325
- expect(typeof story.zoom).toBe('number');
326
- });
327
- });
328
- });
329
- });
@@ -1,192 +0,0 @@
1
- import {jest} from '@jest/globals';
2
- import { readFileSync } from 'node:fs';
3
- import { parse as parseTwine2HTML } from '../../src/Twine2HTML/parse.js';
4
-
5
- // Pull the name and version of this project from package.json.
6
- // These are used as the 'creator' and 'creator-version'.
7
- const { version } = JSON.parse(readFileSync('package.json', 'utf-8'));
8
-
9
- describe('Twine2HTMLParser', () => {
10
- describe('Errors', () => {
11
- it('Should throw error if content is not a string', () => {
12
- expect(() => { parseTwine2HTML({}); }).toThrow();
13
- });
14
-
15
- it('Should throw error if content is not Twine-2 style HTML', () => {
16
- expect(() => { parseTwine2HTML(''); }).toThrow();
17
- });
18
- });
19
-
20
- describe('#parse()', () => {
21
- it('Should be able to parse Twine 2 HTML for story name', () => {
22
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/twineExample.html', 'utf-8');
23
- const story = parseTwine2HTML(fr);
24
- expect(story.name).toBe('twineExample');
25
- });
26
-
27
- it('Should be able to parse Twine 2 HTML for correct number of passages', () => {
28
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/twineExample.html', 'utf-8');
29
- const tp = parseTwine2HTML(fr);
30
- expect(tp.size()).toBe(5);
31
- });
32
-
33
- it('Should be able to correctly parse passage tags', () => {
34
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/Tags.html', 'utf-8');
35
- const story = parseTwine2HTML(fr);
36
- const p = story.getPassageByName('Untitled Passage');
37
- expect(p.tags).toHaveLength(2);
38
- });
39
-
40
- it('Should have Extwee for creator when missing', () => {
41
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingCreator.html', 'utf-8');
42
- const tp = parseTwine2HTML(fr);
43
- expect(tp.creator).toBe('extwee');
44
- });
45
-
46
- it('Should have correct for creatorVersion when missing', () => {
47
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingCreatorVersion.html', 'utf-8');
48
- const tp = parseTwine2HTML(fr);
49
- expect(tp.creatorVersion).toBe(version);
50
- });
51
-
52
- it('Should have empty string as format when missing', () => {
53
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingFormat.html', 'utf-8');
54
- const tp = parseTwine2HTML(fr);
55
- expect(tp.format).toBe('');
56
- });
57
-
58
- it('Should have empty string as formatVersion when missing', () => {
59
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingFormatVersion.html', 'utf-8');
60
- const tp = parseTwine2HTML(fr);
61
- expect(tp.formatVersion).toBe('');
62
- });
63
-
64
- it('Should have empty string as zoom when missing', () => {
65
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingZoom.html', 'utf-8');
66
- const tp = parseTwine2HTML(fr);
67
- expect(tp.zoom).toBe(1);
68
- });
69
-
70
- it('Should not have position if passage does not', () => {
71
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingPosition.html', 'utf-8');
72
- const story = parseTwine2HTML(fr);
73
- const p = story.getPassageByName('Untitled Passage');
74
- expect(Object.prototype.hasOwnProperty.call(p.metadata, 'position')).toBe(false);
75
- });
76
-
77
- it('Should not have size if passage does not', () => {
78
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingSize.html', 'utf-8');
79
- const story = parseTwine2HTML(fr);
80
- const p = story.getPassageByName('Untitled Passage');
81
- expect(Object.prototype.hasOwnProperty.call(p.metadata, 'size')).toBe(false);
82
- });
83
-
84
- it('Should have empty array as tags if tags is missing', () => {
85
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingPassageTags.html', 'utf-8');
86
- const story = parseTwine2HTML(fr);
87
- const p = story.getPassageByName('Untitled Passage');
88
- expect(p.tags).toHaveLength(0);
89
- });
90
-
91
- it('Should not have stylesheet tag if no passages exist with it', () => {
92
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingStyle.html', 'utf-8');
93
- const story = parseTwine2HTML(fr);
94
- const passages = story.getPassagesByTag('stylesheet');
95
- expect(passages.length).toBe(0);
96
- });
97
-
98
- it('Should not have script tag if no passages exist with it', () => {
99
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingScript.html', 'utf-8');
100
- const story = parseTwine2HTML(fr);
101
- const passages = story.getPassagesByTag('script');
102
- expect(passages.length).toBe(0);
103
- });
104
-
105
- it('Should have script and style tags normally', () => {
106
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/Example1.html', 'utf-8');
107
- const story = parseTwine2HTML(fr);
108
- // Script-tagged passages are now stored in storyJavaScript, not passages array
109
- expect(story.storyJavaScript.length).toBeGreaterThan(0);
110
- expect(story.storyStylesheet.length).toBeGreaterThan(0);
111
- });
112
-
113
- it('Should parse HTML without passage start node', () => {
114
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingStartnode.html', 'utf-8');
115
- const story = parseTwine2HTML(fr);
116
- expect(story.start).toBe('');
117
- });
118
-
119
- it('Should parse tag colors', () => {
120
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/tagColors.html', 'utf-8');
121
- const story = parseTwine2HTML(fr);
122
- // Test for tag colors
123
- const tagColors = story.tagColors;
124
- expect(tagColors.a).toBe('red');
125
- });
126
-
127
- it('Do not update name and color if those attributes do not exist', () => {
128
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/lyingTagColors.html', 'utf-8');
129
- const story = parseTwine2HTML(fr);
130
- const tagColorProperties = Object.keys(story.tagColors).length;
131
- expect(tagColorProperties).toBe(0);
132
- });
133
- });
134
-
135
- describe('Unescaping', () => {
136
- it('Should unescape HTML metacharacters for passage searching', () => {
137
- const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/unescaping.html', 'utf-8');
138
- const story = parseTwine2HTML(fr);
139
- expect(story.getPassageByName('"Test"').text).toBe('Success');
140
- });
141
- });
142
-
143
- describe('Warnings', () => {
144
- beforeEach(() => {
145
- // Mock console.warn.
146
- jest.spyOn(console, 'warn').mockImplementation();
147
- });
148
-
149
- afterEach(() => {
150
- // Restore all mocks.
151
- jest.restoreAllMocks();
152
- });
153
-
154
- it('Should generate a warning if name attribute is missing from tw-storydata', () => {
155
- const s = '<tw-storydata ifid=\'E70FC479-01D9-4E44-AC6A-AFF9F5E1C475\'></tw-storydata>';
156
- parseTwine2HTML(s);
157
- expect(console.warn).toHaveBeenCalledWith('Warning: The name attribute is missing from tw-storydata!');
158
- });
159
-
160
- it('Should generate a warning if ifid attribute is missing from tw-storydata', () => {
161
- const s = '<tw-storydata name=\'Test\'></tw-storydata>';
162
- parseTwine2HTML(s);
163
- expect(console.warn).toHaveBeenCalledWith('Warning: The ifid attribute is missing from tw-storydata!');
164
- });
165
-
166
- it('Should generate a warning if ifid on tw-storydata is malformed', () => {
167
- const s = '<tw-storydata ifid=\'1234\'></tw-storydata>';
168
- parseTwine2HTML(s);
169
- expect(console.warn).toHaveBeenCalledWith('Warning: The IFID is not in valid UUIDv4 formatting on tw-storydata!');
170
- });
171
-
172
- it('Should generate warning if passage name is missing', () => {
173
- const fr = `<tw-storydata name="Tags" startnode="1" creator="Twine" creator-version="2.3.9" ifid="1A6023FC-F68A-4E55-BE9A-5EDFDB7879E6" zoom="1" format="Harlowe" format-version="3.1.0" options="" hidden>
174
- <style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css"></style>
175
- <script role="script" id="twine-user-script" type="text/twine-javascript"></script>
176
- <tw-passagedata pid="1" tags="this-one another-one-like-this" position="200,99" size="100,100">Double-click this passage to edit it.</tw-passagedata>
177
- </tw-storydata>`;
178
- parseTwine2HTML(fr);
179
- expect(console.warn).toHaveBeenCalledWith('Warning: name attribute is missing! Default passage name will be used.');
180
- });
181
-
182
- it('Should generate error if passage PID is missing', () => {
183
- const fr = `<tw-storydata name="Tags" startnode="1" creator="Twine" creator-version="2.3.9" ifid="1A6023FC-F68A-4E55-BE9A-5EDFDB7879E6" zoom="1" format="Harlowe" format-version="3.1.0" options="" hidden>
184
- <style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css"></style>
185
- <script role="script" id="twine-user-script" type="text/twine-javascript"></script>
186
- <tw-passagedata name="Untitled Passage" tags="this-one another-one-like-this" position="200,99" size="100,100">Double-click this passage to edit it.</tw-passagedata>
187
- </tw-storydata>`;
188
- parseTwine2HTML(fr);
189
- expect(console.warn).toHaveBeenCalledWith('Warning: pid attribute is missing! Default PID will be used.');
190
- });
191
- });
192
- });