extwee 2.3.4 → 2.3.6

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.
@@ -0,0 +1,200 @@
1
+ import { parse as parseTwee, escapeTweeMetacharacters, unescapeTweeMetacharacters } from '../../src/Twee/parse.js';
2
+ import Passage from '../../src/Passage.js';
3
+
4
+ describe('Twee Escaping', () => {
5
+ describe('unescapeTweeMetacharacters()', () => {
6
+ it('Should unescape square brackets', () => {
7
+ expect(unescapeTweeMetacharacters('Test\\[Name\\]')).toBe('Test[Name]');
8
+ });
9
+
10
+ it('Should unescape curly braces', () => {
11
+ expect(unescapeTweeMetacharacters('Test\\{Name\\}')).toBe('Test{Name}');
12
+ });
13
+
14
+ it('Should unescape backslashes', () => {
15
+ expect(unescapeTweeMetacharacters('Test\\\\Name')).toBe('Test\\Name');
16
+ });
17
+
18
+ it('Should unescape any character after backslash (robust decoding)', () => {
19
+ expect(unescapeTweeMetacharacters('\\q')).toBe('q');
20
+ expect(unescapeTweeMetacharacters('\\x')).toBe('x');
21
+ expect(unescapeTweeMetacharacters('\\5')).toBe('5');
22
+ });
23
+
24
+ it('Should handle complex combinations', () => {
25
+ expect(unescapeTweeMetacharacters('\\[Hello\\] \\{world\\} \\\\\\\\')).toBe('[Hello] {world} \\\\');
26
+ });
27
+
28
+ it('Should handle non-string input gracefully', () => {
29
+ expect(unescapeTweeMetacharacters(null)).toBe(null);
30
+ expect(unescapeTweeMetacharacters(undefined)).toBe(undefined);
31
+ expect(unescapeTweeMetacharacters(123)).toBe(123);
32
+ });
33
+
34
+ it('Should handle empty strings', () => {
35
+ expect(unescapeTweeMetacharacters('')).toBe('');
36
+ });
37
+
38
+ it('Should handle strings without escape sequences', () => {
39
+ expect(unescapeTweeMetacharacters('Normal Text')).toBe('Normal Text');
40
+ });
41
+ });
42
+
43
+ describe('escapeTweeMetacharacters()', () => {
44
+ it('Should escape square brackets', () => {
45
+ expect(escapeTweeMetacharacters('Test[Name]')).toBe('Test\\[Name\\]');
46
+ });
47
+
48
+ it('Should escape curly braces', () => {
49
+ expect(escapeTweeMetacharacters('Test{Name}')).toBe('Test\\{Name\\}');
50
+ });
51
+
52
+ it('Should escape backslashes', () => {
53
+ expect(escapeTweeMetacharacters('Test\\Name')).toBe('Test\\\\Name');
54
+ });
55
+
56
+ it('Should escape complex combinations', () => {
57
+ expect(escapeTweeMetacharacters('[Hello] {world} \\\\')).toBe('\\[Hello\\] \\{world\\} \\\\\\\\');
58
+ });
59
+
60
+ it('Should handle non-string input gracefully', () => {
61
+ expect(escapeTweeMetacharacters(null)).toBe(null);
62
+ expect(escapeTweeMetacharacters(undefined)).toBe(undefined);
63
+ expect(escapeTweeMetacharacters(123)).toBe(123);
64
+ });
65
+
66
+ it('Should handle empty strings', () => {
67
+ expect(escapeTweeMetacharacters('')).toBe('');
68
+ });
69
+
70
+ it('Should handle strings without metacharacters', () => {
71
+ expect(escapeTweeMetacharacters('Normal Text')).toBe('Normal Text');
72
+ });
73
+ });
74
+
75
+ describe('Round-trip escaping', () => {
76
+ it('Should preserve text through escape -> unescape cycle', () => {
77
+ const original = 'Test[Name]{Value}\\Path';
78
+ const escaped = escapeTweeMetacharacters(original);
79
+ const unescaped = unescapeTweeMetacharacters(escaped);
80
+ expect(unescaped).toBe(original);
81
+ });
82
+
83
+ it('Should handle complex nested scenarios', () => {
84
+ const original = '[{\\}]{[\\]}';
85
+ const escaped = escapeTweeMetacharacters(original);
86
+ const unescaped = unescapeTweeMetacharacters(escaped);
87
+ expect(unescaped).toBe(original);
88
+ });
89
+ });
90
+
91
+ describe('Twee parsing with escaping', () => {
92
+ it('Should correctly parse escaped passage names', () => {
93
+ const content = ':: Test\\[Name\\] \\{with\\} \\\\brackets\nContent here';
94
+ const story = parseTwee(content);
95
+ expect(story.passages[0].name).toBe('Test[Name] {with} \\brackets');
96
+ });
97
+
98
+ it('Should correctly parse escaped tags', () => {
99
+ const content = ':: TestPassage [tag\\[1\\] tag\\{2\\}]\nContent here';
100
+ const story = parseTwee(content);
101
+ expect(story.passages[0].tags).toEqual(['tag[1]', 'tag{2}']);
102
+ });
103
+
104
+ it('Should handle mixed escaped and non-escaped content', () => {
105
+ const content = ':: Test\\[Passage [normal escaped\\]tag]\nContent here';
106
+ const story = parseTwee(content);
107
+ expect(story.passages[0].name).toBe('Test[Passage');
108
+ expect(story.passages[0].tags).toEqual(['normal', 'escaped]tag']);
109
+ });
110
+
111
+ it('Should handle the cursed.twee test case correctly', () => {
112
+ const content = `:: StoryData
113
+ {
114
+ "ifid": "22F25A58-7062-4927-95B6-F424DDB2EC65",
115
+ "format": "Harlowe",
116
+ "format-version": "3.3.8",
117
+ "start": "[Hello] {world} \\\\\\\\",
118
+ "zoom": 1
119
+ }
120
+
121
+ :: \\[Hello\\] \\{world\\} \\\\\\\\
122
+ Content here`;
123
+
124
+ const story = parseTwee(content);
125
+ expect(story.start).toBe('[Hello] {world} \\\\');
126
+
127
+ // Find the passage by the unescaped name
128
+ const passage = story.getPassageByName('[Hello] {world} \\\\');
129
+ expect(passage).not.toBeNull();
130
+ expect(passage.name).toBe('[Hello] {world} \\\\');
131
+ });
132
+
133
+ it('Should handle complex metadata with escaped keys (if supported)', () => {
134
+ const content = ':: TestPassage {"position": "100,200", "size": "150,100"}\nContent here';
135
+ const story = parseTwee(content);
136
+ expect(story.passages[0].metadata.position).toBe('100,200');
137
+ expect(story.passages[0].metadata.size).toBe('150,100');
138
+ });
139
+ });
140
+
141
+ describe('Twee generation with escaping', () => {
142
+ it('Should escape passage names when generating Twee', () => {
143
+ const passage = new Passage('Test[Name]', 'Content', [], {});
144
+ const tweeOutput = passage.toTwee();
145
+ expect(tweeOutput).toContain(':: Test\\[Name\\]');
146
+ });
147
+
148
+ it('Should escape tag names when generating Twee', () => {
149
+ const passage = new Passage('TestPassage', 'Content', ['tag[1]', 'tag{2}'], {});
150
+ const tweeOutput = passage.toTwee();
151
+ expect(tweeOutput).toContain('[tag\\[1\\] tag\\{2\\}]');
152
+ });
153
+
154
+ it('Should handle complex escaping scenarios', () => {
155
+ const passage = new Passage('Test[Name]{Value}\\Path', 'Content', ['complex[tag]'], {});
156
+ const tweeOutput = passage.toTwee();
157
+ expect(tweeOutput).toContain(':: Test\\[Name\\]\\{Value\\}\\\\Path');
158
+ expect(tweeOutput).toContain('[complex\\[tag\\]]');
159
+ });
160
+
161
+ it('Should preserve normal names and tags without escaping', () => {
162
+ const passage = new Passage('NormalName', 'Content', ['normaltag'], {});
163
+ const tweeOutput = passage.toTwee();
164
+ expect(tweeOutput).toContain(':: NormalName');
165
+ expect(tweeOutput).toContain('[normaltag]');
166
+ });
167
+ });
168
+
169
+ describe('Edge cases', () => {
170
+ it('Should handle empty passage names gracefully', () => {
171
+ const content = ':: \nContent here';
172
+ expect(() => parseTwee(content)).toThrow('Malformed passage header!');
173
+ });
174
+
175
+ it('Should handle passages with only escaped characters as names', () => {
176
+ const content = ':: \\[\\]\nContent here';
177
+ const story = parseTwee(content);
178
+ expect(story.passages[0].name).toBe('[]');
179
+ });
180
+
181
+ it('Should handle multiple consecutive escapes', () => {
182
+ const content = ':: Test\\\\\\\\Name\nContent here';
183
+ const story = parseTwee(content);
184
+ expect(story.passages[0].name).toBe('Test\\\\Name');
185
+ });
186
+
187
+ it('Should handle tags with only metacharacters', () => {
188
+ const content = ':: TestPassage [\\[\\] \\{\\}]\nContent here';
189
+ const story = parseTwee(content);
190
+ expect(story.passages[0].tags).toEqual(['[]', '{}']);
191
+ });
192
+
193
+ it('Should handle mixed whitespace and escaping', () => {
194
+ const content = ':: Test\\[Name\\] [ tag\\[1\\] tag2 ]\nContent here';
195
+ const story = parseTwee(content);
196
+ expect(story.passages[0].name).toBe('Test[Name]');
197
+ expect(story.passages[0].tags).toEqual(['tag[1]', 'tag2']);
198
+ });
199
+ });
200
+ });
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
5
+ /**
6
+ * Tests for package.json web exports
7
+ * These tests verify that users can import the web builds directly
8
+ */
9
+
10
+ import { describe, expect, it } from '@jest/globals';
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+
14
+ describe('Web build exports', () => {
15
+ const buildDir = path.resolve(process.cwd(), 'build');
16
+
17
+ describe('Web build files exist', () => {
18
+ it('should have core web build file', () => {
19
+ const corePath = path.join(buildDir, 'extwee.core.min.js');
20
+ expect(fs.existsSync(corePath)).toBe(true);
21
+ });
22
+
23
+ it('should have twine1html web build file', () => {
24
+ const twine1Path = path.join(buildDir, 'extwee.twine1html.min.js');
25
+ expect(fs.existsSync(twine1Path)).toBe(true);
26
+ });
27
+
28
+ it('should have twine2archive web build file', () => {
29
+ const twine2ArchivePath = path.join(buildDir, 'extwee.twine2archive.min.js');
30
+ expect(fs.existsSync(twine2ArchivePath)).toBe(true);
31
+ });
32
+
33
+ it('should have tws web build file', () => {
34
+ const twsPath = path.join(buildDir, 'extwee.tws.min.js');
35
+ expect(fs.existsSync(twsPath)).toBe(true);
36
+ });
37
+ });
38
+
39
+ describe('Web build content validation', () => {
40
+ it('should have UMD wrapper in core build', () => {
41
+ const corePath = path.join(buildDir, 'extwee.core.min.js');
42
+ const content = fs.readFileSync(corePath, 'utf8');
43
+
44
+ // Should contain UMD pattern
45
+ expect(content).toMatch(/function.*webpackUniversalModuleDefinition|define.*function|module\.exports.*function/);
46
+
47
+ // Should be minified (no excessive whitespace)
48
+ expect(content.split('\n').length).toBeLessThan(10);
49
+ });
50
+
51
+ it('should have Extwee functionality in core build', () => {
52
+ const corePath = path.join(buildDir, 'extwee.core.min.js');
53
+ const content = fs.readFileSync(corePath, 'utf8');
54
+
55
+ // Should contain core Extwee functionality
56
+ expect(content).toMatch(/parseTwee|parseJSON|generateIFID|Story/);
57
+ });
58
+
59
+ it('should have appropriate size for minified builds', () => {
60
+ const corePath = path.join(buildDir, 'extwee.core.min.js');
61
+ const stats = fs.statSync(corePath);
62
+
63
+ // Core build should be substantial but not excessive (roughly 50-150KB)
64
+ expect(stats.size).toBeGreaterThan(50000); // > 50KB
65
+ expect(stats.size).toBeLessThan(150000); // < 150KB
66
+ });
67
+
68
+ it('should have smaller specialized builds', () => {
69
+ const corePath = path.join(buildDir, 'extwee.core.min.js');
70
+ const twine1Path = path.join(buildDir, 'extwee.twine1html.min.js');
71
+ const coreStats = fs.statSync(corePath);
72
+ const twine1Stats = fs.statSync(twine1Path);
73
+
74
+ // Specialized builds should be smaller than core
75
+ expect(twine1Stats.size).toBeLessThan(coreStats.size);
76
+ });
77
+ });
78
+
79
+ describe('Package.json exports configuration', () => {
80
+ it('should have exports field in package.json', () => {
81
+ const packagePath = path.resolve(process.cwd(), 'package.json');
82
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
83
+
84
+ expect(packageJson.exports).toBeDefined();
85
+ expect(typeof packageJson.exports).toBe('object');
86
+ });
87
+
88
+ it('should have correct web export paths', () => {
89
+ const packagePath = path.resolve(process.cwd(), 'package.json');
90
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
91
+
92
+ expect(packageJson.exports['.']).toBe('./index.js');
93
+ expect(packageJson.exports['./web']).toBe('./build/extwee.core.min.js');
94
+ expect(packageJson.exports['./web/core']).toBe('./build/extwee.core.min.js');
95
+ expect(packageJson.exports['./web/twine1html']).toBe('./build/extwee.twine1html.min.js');
96
+ expect(packageJson.exports['./web/twine2archive']).toBe('./build/extwee.twine2archive.min.js');
97
+ expect(packageJson.exports['./web/tws']).toBe('./build/extwee.tws.min.js');
98
+ });
99
+
100
+ it('should point to files that actually exist', () => {
101
+ const packagePath = path.resolve(process.cwd(), 'package.json');
102
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
103
+
104
+ // Check each web export path exists
105
+ const webExports = [
106
+ packageJson.exports['./web'],
107
+ packageJson.exports['./web/core'],
108
+ packageJson.exports['./web/twine1html'],
109
+ packageJson.exports['./web/twine2archive'],
110
+ packageJson.exports['./web/tws']
111
+ ];
112
+
113
+ for (const exportPath of webExports) {
114
+ const fullPath = path.resolve(process.cwd(), exportPath);
115
+ expect(fs.existsSync(fullPath)).toBe(true);
116
+ }
117
+ });
118
+ });
119
+
120
+ describe('Documentation for web exports', () => {
121
+ it('should provide clear usage examples', () => {
122
+ // This test documents how users can import the web builds
123
+ const usageExamples = {
124
+ 'import Extwee from "extwee/web"': 'Core web build with most functionality',
125
+ 'import Extwee from "extwee/web/core"': 'Same as above (explicit)',
126
+ 'import Extwee from "extwee/web/twine1html"': 'Twine 1 HTML parsing only',
127
+ 'import Extwee from "extwee/web/twine2archive"': 'Twine 2 Archive HTML parsing only',
128
+ 'import Extwee from "extwee/web/tws"': 'TWS (Twine 1 workspace) parsing only'
129
+ };
130
+
131
+ // Just verify we have documented the usage patterns
132
+ expect(Object.keys(usageExamples)).toHaveLength(5);
133
+ expect(usageExamples['import Extwee from "extwee/web"']).toContain('Core web build');
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
5
+ /**
6
+ * Tests for web-twine1html.js module
7
+ * Tests module exports and functionality
8
+ */
9
+
10
+ import { describe, expect, it } from '@jest/globals';
11
+
12
+ // Import to test basic functionality
13
+ import { parse, compile } from '../../src/Web/web-twine1html.js';
14
+ import Extwee from '../../src/Web/web-twine1html.js';
15
+
16
+ describe('web-twine1html.js module tests', () => {
17
+
18
+ describe('ES6 module exports', () => {
19
+ it('should export parse and compile functions', () => {
20
+ expect(parse).toBeDefined();
21
+ expect(compile).toBeDefined();
22
+ expect(typeof parse).toBe('function');
23
+ expect(typeof compile).toBe('function');
24
+ });
25
+
26
+ it('should export default object with parseTwine1HTML and compileTwine1HTML', () => {
27
+ expect(Extwee.parseTwine1HTML).toBeDefined();
28
+ expect(Extwee.compileTwine1HTML).toBeDefined();
29
+ expect(Extwee.parse).toBeDefined();
30
+ expect(Extwee.compile).toBeDefined();
31
+ expect(typeof Extwee.parseTwine1HTML).toBe('function');
32
+ expect(typeof Extwee.compileTwine1HTML).toBe('function');
33
+ });
34
+ });
35
+
36
+ describe('Global object assignment', () => {
37
+ it('should assign functions to global object when available', () => {
38
+ // In Node.js environment, should assign to globalThis
39
+ expect(globalThis.Extwee).toBeDefined();
40
+ expect(globalThis.Extwee.parseTwine1HTML).toBeDefined();
41
+ expect(globalThis.Extwee.compileTwine1HTML).toBeDefined();
42
+ expect(typeof globalThis.Extwee.parseTwine1HTML).toBe('function');
43
+ expect(typeof globalThis.Extwee.compileTwine1HTML).toBe('function');
44
+ });
45
+
46
+ it('should preserve existing Extwee properties', () => {
47
+ // Should not overwrite the entire object, just add properties
48
+ if (globalThis.Extwee && globalThis.Extwee.version) {
49
+ expect(globalThis.Extwee.version).toBeDefined();
50
+ }
51
+ expect(globalThis.Extwee.parseTwine1HTML).toBeDefined();
52
+ expect(globalThis.Extwee.compileTwine1HTML).toBeDefined();
53
+ });
54
+ });
55
+
56
+ describe('Functional integration tests', () => {
57
+ it('should have working parseTwine1HTML function', () => {
58
+ // Test with valid Twine 1 HTML
59
+ const sampleHtml = `
60
+ <html>
61
+ <head><title>Test</title></head>
62
+ <body>
63
+ <div id="storeArea" data-size="2">
64
+ <div tiddler="Start" tags="" twine-position="100,100">Start passage</div>
65
+ </div>
66
+ </body>
67
+ </html>
68
+ `;
69
+
70
+ expect(() => {
71
+ const result = parse(sampleHtml);
72
+ expect(result).toBeDefined();
73
+ expect(result.passages).toBeDefined();
74
+ }).not.toThrow();
75
+ });
76
+
77
+ it('should have working compileTwine1HTML function', async () => {
78
+ // Import required classes dynamically to avoid circular imports
79
+ const { Story } = await import('../../src/Story.js');
80
+ const { default: Passage } = await import('../../src/Passage.js');
81
+ const { default: StoryFormat } = await import('../../src/StoryFormat.js');
82
+
83
+ const story = new Story();
84
+ story.name = "Test Story";
85
+ story.addPassage(new Passage("Start", "This is the start", [], {}));
86
+
87
+ const storyFormat = new StoryFormat();
88
+ storyFormat.source = "window.story = STORY;";
89
+ storyFormat.version = "1.0.0";
90
+
91
+ expect(() => {
92
+ const result = compile(story, storyFormat, '', '', '');
93
+ expect(typeof result).toBe('string');
94
+ }).not.toThrow();
95
+ });
96
+
97
+ it('should have same functions in exports and global', () => {
98
+ // Test that parse and compile are the same functions
99
+ expect(parse).toBe(Extwee.parse);
100
+ expect(compile).toBe(Extwee.compile);
101
+ expect(parse).toBe(Extwee.parseTwine1HTML);
102
+ expect(compile).toBe(Extwee.compileTwine1HTML);
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
5
+ /**
6
+ * Tests for web-twine2archive.js module
7
+ * Tests module exports and functionality
8
+ */
9
+
10
+ import { describe, expect, it } from '@jest/globals';
11
+
12
+ // Import to test basic functionality
13
+ import { parse, compile } from '../../src/Web/web-twine2archive.js';
14
+ import Extwee from '../../src/Web/web-twine2archive.js';
15
+
16
+ describe('web-twine2archive.js module tests', () => {
17
+
18
+ describe('ES6 module exports', () => {
19
+ it('should export parse and compile functions', () => {
20
+ expect(parse).toBeDefined();
21
+ expect(compile).toBeDefined();
22
+ expect(typeof parse).toBe('function');
23
+ expect(typeof compile).toBe('function');
24
+ });
25
+
26
+ it('should export default object with parseTwine2ArchiveHTML and compileTwine2ArchiveHTML', () => {
27
+ expect(Extwee.parseTwine2ArchiveHTML).toBeDefined();
28
+ expect(Extwee.compileTwine2ArchiveHTML).toBeDefined();
29
+ expect(Extwee.parse).toBeDefined();
30
+ expect(Extwee.compile).toBeDefined();
31
+ expect(typeof Extwee.parseTwine2ArchiveHTML).toBe('function');
32
+ expect(typeof Extwee.compileTwine2ArchiveHTML).toBe('function');
33
+ });
34
+ });
35
+
36
+ describe('Global object assignment', () => {
37
+ it('should assign functions to global object when available', () => {
38
+ // In Node.js environment, should assign to globalThis
39
+ expect(globalThis.Extwee).toBeDefined();
40
+ expect(globalThis.Extwee.parseTwine2ArchiveHTML).toBeDefined();
41
+ expect(globalThis.Extwee.compileTwine2ArchiveHTML).toBeDefined();
42
+ expect(typeof globalThis.Extwee.parseTwine2ArchiveHTML).toBe('function');
43
+ expect(typeof globalThis.Extwee.compileTwine2ArchiveHTML).toBe('function');
44
+ });
45
+
46
+ it('should preserve existing Extwee properties', () => {
47
+ // Should not overwrite the entire object, just add properties
48
+ if (globalThis.Extwee && globalThis.Extwee.version) {
49
+ expect(globalThis.Extwee.version).toBeDefined();
50
+ }
51
+ expect(globalThis.Extwee.parseTwine2ArchiveHTML).toBeDefined();
52
+ expect(globalThis.Extwee.compileTwine2ArchiveHTML).toBeDefined();
53
+ });
54
+ });
55
+
56
+ describe('Functional integration tests', () => {
57
+ it('should have working parseTwine2ArchiveHTML function', () => {
58
+ // Test with valid Twine 2 Archive HTML
59
+ const sampleHtml = `
60
+ <tw-storydata name="Test" startnode="1" creator="Twine" creator-version="2.3.5">
61
+ <tw-passagedata pid="1" name="Start" tags="">Start passage</tw-passagedata>
62
+ </tw-storydata>
63
+ `;
64
+
65
+ expect(() => {
66
+ const result = parse(sampleHtml);
67
+ expect(result).toBeDefined();
68
+ expect(Array.isArray(result)).toBe(true);
69
+ }).not.toThrow();
70
+ });
71
+
72
+ it('should have working compileTwine2ArchiveHTML function', async () => {
73
+ // Import required classes
74
+ const { Story } = await import('../../src/Story.js');
75
+ const { default: Passage } = await import('../../src/Passage.js');
76
+
77
+ const story = new Story();
78
+ story.name = "Test Story";
79
+ story.IFID = "12345678-1234-5678-9012-123456789012";
80
+ story.addPassage(new Passage("Start", "This is the start", [], {}));
81
+
82
+ expect(() => {
83
+ const result = compile([story]);
84
+ expect(typeof result).toBe('string');
85
+ }).not.toThrow();
86
+ });
87
+
88
+ it('should have same functions in exports and global', () => {
89
+ // Test that parse and compile are the same functions
90
+ expect(parse).toBe(Extwee.parse);
91
+ expect(compile).toBe(Extwee.compile);
92
+ expect(parse).toBe(Extwee.parseTwine2ArchiveHTML);
93
+ expect(compile).toBe(Extwee.compileTwine2ArchiveHTML);
94
+ });
95
+ });
96
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
5
+ /**
6
+ * Tests for web-tws.js module
7
+ * Tests module exports and functionality
8
+ */
9
+
10
+ import { describe, expect, it } from '@jest/globals';
11
+
12
+ // Import to test basic functionality
13
+ import { parse } from '../../src/Web/web-tws.js';
14
+ import Extwee from '../../src/Web/web-tws.js';
15
+
16
+ describe('web-tws.js module tests', () => {
17
+
18
+ describe('ES6 module exports', () => {
19
+ it('should export parse function', () => {
20
+ expect(parse).toBeDefined();
21
+ expect(typeof parse).toBe('function');
22
+ });
23
+
24
+ it('should export default object with parseTWS', () => {
25
+ expect(Extwee.parseTWS).toBeDefined();
26
+ expect(Extwee.parse).toBeDefined();
27
+ expect(typeof Extwee.parseTWS).toBe('function');
28
+ expect(typeof Extwee.parse).toBe('function');
29
+ });
30
+ });
31
+
32
+ describe('Global object assignment', () => {
33
+ it('should assign functions to global object when available', () => {
34
+ // In Node.js environment, should assign to globalThis
35
+ expect(globalThis.Extwee).toBeDefined();
36
+ expect(globalThis.Extwee.parseTWS).toBeDefined();
37
+ expect(typeof globalThis.Extwee.parseTWS).toBe('function');
38
+ });
39
+
40
+ it('should preserve existing Extwee properties', () => {
41
+ // Should not overwrite the entire object, just add properties
42
+ if (globalThis.Extwee && globalThis.Extwee.version) {
43
+ expect(globalThis.Extwee.version).toBeDefined();
44
+ }
45
+ expect(globalThis.Extwee.parseTWS).toBeDefined();
46
+ });
47
+ });
48
+
49
+ describe('Functional integration tests', () => {
50
+ it('should have working parseTWS function', () => {
51
+ // Create a minimal valid TWS buffer (pickled data)
52
+ // This is a very basic test - TWS parsing is complex
53
+ const validBuffer = Buffer.from([
54
+ 0x80, 0x02, // Python pickle protocol version 2
55
+ 0x7d, 0x71, 0x00, // Empty dict
56
+ 0x2e // STOP
57
+ ]);
58
+
59
+ expect(() => {
60
+ const result = parse(validBuffer);
61
+ expect(result).toBeDefined();
62
+ }).not.toThrow();
63
+ });
64
+
65
+ it('should throw error for invalid input', () => {
66
+ expect(() => {
67
+ parse("not a buffer");
68
+ }).toThrow();
69
+ });
70
+
71
+ it('should have same functions in exports and global', () => {
72
+ // Test that parse is the same function
73
+ expect(parse).toBe(Extwee.parse);
74
+ expect(parse).toBe(Extwee.parseTWS);
75
+ });
76
+ });
77
+ });
@@ -251,5 +251,5 @@ export class Story {
251
251
  #private;
252
252
  }
253
253
  export const creatorName: "extwee";
254
- export const creatorVersion: "2.3.4";
254
+ export const creatorVersion: "2.3.6";
255
255
  import Passage from './Passage.js';
@@ -6,4 +6,25 @@
6
6
  * @returns {Story} story
7
7
  */
8
8
  export function parse(fileContents: string): Story;
9
+ /**
10
+ * Escapes Twee 3 metacharacters according to the specification.
11
+ * This is used when writing Twee files to ensure special characters are properly escaped.
12
+ * @function escapeTweeMetacharacters
13
+ * @param {string} text - Text to escape
14
+ * @returns {string} Escaped text
15
+ */
16
+ export function escapeTweeMetacharacters(text: string): string;
17
+ /**
18
+ * Unescapes Twee 3 metacharacters according to the specification.
19
+ *
20
+ * From the Twee 3 specification:
21
+ * - Encoding: To avoid ambiguity, non-escape backslashes must also be escaped via
22
+ * the same mechanism (i.e. `foo\bar` must become `foo\\bar`).
23
+ * - Decoding: To make decoding more robust, any escaped character within a chunk of
24
+ * encoded text must yield the character minus the backslash (i.e. `\q` must yield `q`).
25
+ * @function unescapeTweeMetacharacters
26
+ * @param {string} text - Text to unescape
27
+ * @returns {string} Unescaped text
28
+ */
29
+ export function unescapeTweeMetacharacters(text: string): string;
9
30
  import { Story } from '../Story.js';