extwee 2.3.11 → 2.3.13

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 (186) 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/logs/arrays.html +79 -0
  9. package/logs/arrays.twee +64 -0
  10. package/logs/example.html +47 -0
  11. package/logs/fixing.js +23 -0
  12. package/logs/format-fixed.js +8 -0
  13. package/logs/format.js +8 -0
  14. package/logs/test-entities.cjs +40 -0
  15. package/package.json +19 -16
  16. package/src/Story.js +2 -2
  17. package/types/src/Story.d.ts +1 -1
  18. package/.github/FUNDING.yml +0 -3
  19. package/.github/codeql-analysis.yml +0 -51
  20. package/.github/dependabot.yml +0 -11
  21. package/.github/workflows/dependabot-automerge.yml +0 -23
  22. package/.github/workflows/nodejs.yml +0 -28
  23. package/.travis.yml +0 -13
  24. package/babel.config.json +0 -18
  25. package/docs/.nojekyll +0 -0
  26. package/docs/README.md +0 -224
  27. package/docs/_sidebar.md +0 -19
  28. package/docs/build/extwee.core.min.js +0 -1
  29. package/docs/build/extwee.twine1html.min.js +0 -1
  30. package/docs/build/extwee.twine2archive.min.js +0 -1
  31. package/docs/build/extwee.tws.min.js +0 -1
  32. package/docs/demos/compiler/extwee.core.min.js +0 -1
  33. package/docs/demos/compiler/index.css +0 -105
  34. package/docs/demos/compiler/index.html +0 -359
  35. package/docs/demos/decompile/extwee.core.min.js +0 -1
  36. package/docs/demos/decompile/index.css +0 -584
  37. package/docs/demos/decompile/index.html +0 -468
  38. package/docs/examples/dynamicPassages.md +0 -28
  39. package/docs/examples/jsonToTwee.md +0 -23
  40. package/docs/examples/twsToTwee.md +0 -25
  41. package/docs/formats/json.md +0 -17
  42. package/docs/formats/twee.md +0 -13
  43. package/docs/formats/twine1HTML.md +0 -13
  44. package/docs/formats/twine2ArchiveHTML.md +0 -13
  45. package/docs/formats/twine2HTML.md +0 -13
  46. package/docs/formats/tws.md +0 -9
  47. package/docs/index.html +0 -26
  48. package/docs/install/npm.md +0 -16
  49. package/docs/install/npx.md +0 -79
  50. package/docs/objects/passage.md +0 -47
  51. package/docs/objects/story.md +0 -69
  52. package/docs/objects/storyformat.md +0 -27
  53. package/eslint.config.js +0 -28
  54. package/extwee.config.json +0 -6
  55. package/extwee.config.md +0 -67
  56. package/jest.config.json +0 -5
  57. package/test/CLI/CLI.test.js +0 -49
  58. package/test/CLI/files/example.json +0 -1
  59. package/test/CLI/files/example6.twee +0 -22
  60. package/test/CLI/files/harlowe.js +0 -3
  61. package/test/CLI/files/input.html +0 -47
  62. package/test/CLI/files/output/test.twee +0 -0
  63. package/test/CLI/files/test.twee +0 -18
  64. package/test/CLI/files/tweeExample.twee +0 -17
  65. package/test/CLI/files/twine1/LICENSE.txt +0 -32
  66. package/test/CLI/files/twine1/code.js +0 -5
  67. package/test/CLI/files/twine1/engine.js +0 -43
  68. package/test/CLI/files/twine1/header.html +0 -325
  69. package/test/CLI/files/twine1Test.html +0 -371
  70. package/test/CLI/files/twineExample.html +0 -16
  71. package/test/Config/Config.test.js +0 -76
  72. package/test/Config/files/empty.json +0 -3
  73. package/test/Config/files/full.json +0 -8
  74. package/test/Config/files/invalid.json +0 -1
  75. package/test/Config/files/valid.json +0 -6
  76. package/test/Config/isDirectory.test.js +0 -50
  77. package/test/Config/isFile.test.js +0 -53
  78. package/test/Config/loadStoryFormat.test.js +0 -117
  79. package/test/Config/readDirectories.test.js +0 -78
  80. package/test/IFID/IFID.Generate.test.js +0 -10
  81. package/test/JSON/JSON.Parse.test.js +0 -316
  82. package/test/Objects/Passage.test.js +0 -274
  83. package/test/Objects/SnowmanCompatibility.test.js +0 -111
  84. package/test/Objects/Story.test.js +0 -1075
  85. package/test/Objects/StoryFormat.test.js +0 -219
  86. package/test/Roundtrip/Files/Example1.html +0 -64
  87. package/test/Roundtrip/Files/LICENSE +0 -19
  88. package/test/Roundtrip/Files/example1.twee +0 -10
  89. package/test/Roundtrip/Files/example2.twee +0 -27
  90. package/test/Roundtrip/Files/example4.twee +0 -27
  91. package/test/Roundtrip/Files/harlowe.js +0 -3
  92. package/test/Roundtrip/Files/round.html +0 -49
  93. package/test/Roundtrip/Roundtrip.test.js +0 -54
  94. package/test/StoryFormat/StoryFormat.Parse.test.js +0 -479
  95. package/test/TWS/Parse.test.js +0 -56
  96. package/test/TWS/TWSParser/Example5.tws +0 -414
  97. package/test/TWS/TWSParser/noscale.tws +0 -0
  98. package/test/TWS/TWSParser/nostory.tws +0 -0
  99. package/test/Twee/Twee.Escaping.test.js +0 -200
  100. package/test/Twee/Twee.Parse.test.js +0 -108
  101. package/test/Twee/TweeParser/cursed.twee +0 -16
  102. package/test/Twee/TweeParser/cycling.twee +0 -75
  103. package/test/Twee/TweeParser/emptytags.twee +0 -2
  104. package/test/Twee/TweeParser/example.twee +0 -32
  105. package/test/Twee/TweeParser/malformed.twee +0 -2
  106. package/test/Twee/TweeParser/missing.twee +0 -19
  107. package/test/Twee/TweeParser/multipleScriptPassages.twee +0 -19
  108. package/test/Twee/TweeParser/multipleStyleTag.twee +0 -19
  109. package/test/Twee/TweeParser/multipletags.twee +0 -10
  110. package/test/Twee/TweeParser/noTitle.twee +0 -2
  111. package/test/Twee/TweeParser/notes.twee +0 -16
  112. package/test/Twee/TweeParser/pasagemetadataerror.twee +0 -2
  113. package/test/Twee/TweeParser/scriptPassage.twee +0 -16
  114. package/test/Twee/TweeParser/singletag.twee +0 -13
  115. package/test/Twee/TweeParser/start.twee +0 -2
  116. package/test/Twee/TweeParser/startMetadata.twee +0 -14
  117. package/test/Twee/TweeParser/storydataerror.twee +0 -25
  118. package/test/Twee/TweeParser/style.twee +0 -16
  119. package/test/Twee/TweeParser/stylePassage.twee +0 -16
  120. package/test/Twee/TweeParser/test.twee +0 -25
  121. package/test/Twine1HTML/Twine1HTML.Compile.test.js +0 -180
  122. package/test/Twine1HTML/Twine1HTML.Parse.Web.test.js +0 -484
  123. package/test/Twine1HTML/Twine1HTML.Parse.test.js +0 -183
  124. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +0 -674
  125. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +0 -43
  126. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +0 -4
  127. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +0 -4
  128. package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +0 -1
  129. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +0 -32
  130. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +0 -4
  131. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +0 -327
  132. package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
  133. package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +0 -6
  134. package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +0 -6
  135. package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +0 -43
  136. package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +0 -372
  137. package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +0 -372
  138. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +0 -35
  139. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.Web.test.js +0 -293
  140. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +0 -42
  141. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +0 -6
  142. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +0 -3
  143. package/test/Twine2HTML/Twine2HTML.Compile.test.js +0 -139
  144. package/test/Twine2HTML/Twine2HTML.Parse.Web.test.js +0 -329
  145. package/test/Twine2HTML/Twine2HTML.Parse.test.js +0 -192
  146. package/test/Twine2HTML/Twine2HTMLCompiler/TestTags.html +0 -42
  147. package/test/Twine2HTML/Twine2HTMLCompiler/creator.html +0 -50
  148. package/test/Twine2HTML/Twine2HTMLCompiler/example6.twee +0 -16
  149. package/test/Twine2HTML/Twine2HTMLCompiler/format.js +0 -9
  150. package/test/Twine2HTML/Twine2HTMLCompiler/missingStoryTitle.twee +0 -29
  151. package/test/Twine2HTML/Twine2HTMLCompiler/test11.html +0 -121
  152. package/test/Twine2HTML/Twine2HTMLCompiler/test2.html +0 -58
  153. package/test/Twine2HTML/Twine2HTMLCompiler/test3.html +0 -49
  154. package/test/Twine2HTML/Twine2HTMLCompiler/test4.html +0 -50
  155. package/test/Twine2HTML/Twine2HTMLCompiler/test6.html +0 -49
  156. package/test/Twine2HTML/Twine2HTMLParser/Example1.html +0 -53
  157. package/test/Twine2HTML/Twine2HTMLParser/Tags.html +0 -15
  158. package/test/Twine2HTML/Twine2HTMLParser/lyingStartnode.html +0 -15
  159. package/test/Twine2HTML/Twine2HTMLParser/lyingTagColors.html +0 -48
  160. package/test/Twine2HTML/Twine2HTMLParser/missingCreator.html +0 -11
  161. package/test/Twine2HTML/Twine2HTMLParser/missingCreatorVersion.html +0 -11
  162. package/test/Twine2HTML/Twine2HTMLParser/missingFormat.html +0 -11
  163. package/test/Twine2HTML/Twine2HTMLParser/missingFormatVersion.html +0 -11
  164. package/test/Twine2HTML/Twine2HTMLParser/missingIFID.html +0 -11
  165. package/test/Twine2HTML/Twine2HTMLParser/missingPassageTags.html +0 -15
  166. package/test/Twine2HTML/Twine2HTMLParser/missingPosition.html +0 -15
  167. package/test/Twine2HTML/Twine2HTMLParser/missingScript.html +0 -14
  168. package/test/Twine2HTML/Twine2HTMLParser/missingSize.html +0 -35
  169. package/test/Twine2HTML/Twine2HTMLParser/missingStartnode.html +0 -11
  170. package/test/Twine2HTML/Twine2HTMLParser/missingStyle.html +0 -14
  171. package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +0 -11
  172. package/test/Twine2HTML/Twine2HTMLParser/tagColors.html +0 -31
  173. package/test/Twine2HTML/Twine2HTMLParser/twineExample.html +0 -23
  174. package/test/Twine2HTML/Twine2HTMLParser/twineExample2.html +0 -15
  175. package/test/Twine2HTML/Twine2HTMLParser/twineExample3.html +0 -15
  176. package/test/Twine2HTML/Twine2HTMLParser/unescaping.html +0 -33
  177. package/test/Web/web-core-coverage.test.js +0 -175
  178. package/test/Web/web-core-global.test.js +0 -93
  179. package/test/Web/web-core.test.js +0 -156
  180. package/test/Web/web-exports.test.js +0 -136
  181. package/test/Web/web-twine1html.test.js +0 -105
  182. package/test/Web/web-twine2archive.test.js +0 -96
  183. package/test/Web/web-tws.test.js +0 -77
  184. package/test/Web/window.Extwee.test.js +0 -97
  185. package/tsconfig.json +0 -21
  186. 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
- });