extwee 2.3.5 → 2.3.7
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.
- package/README.md +31 -0
- package/build/extwee.core.min.js +1 -1
- package/build/extwee.twine1html.min.js +1 -1
- package/build/extwee.twine2archive.min.js +1 -1
- package/build/extwee.tws.min.js +1 -1
- package/docs/README.md +33 -0
- package/docs/build/extwee.core.min.js +1 -1
- package/docs/build/extwee.twine1html.min.js +1 -1
- package/docs/build/extwee.twine2archive.min.js +1 -1
- package/docs/build/extwee.tws.min.js +1 -1
- package/docs/demos/decompile/extwee.core.min.js +1 -0
- package/docs/demos/decompile/index.css +584 -0
- package/docs/demos/decompile/index.html +462 -0
- package/package.json +17 -10
- package/src/Story.js +1 -1
- package/src/Twine1HTML/parse-web.js +47 -8
- package/src/Twine2ArchiveHTML/parse-web.js +33 -7
- package/src/Twine2HTML/parse-web.js +105 -17
- package/test/Twine1HTML/Twine1HTML.Parse.Web.test.js +1 -1
- package/test/Twine2HTML/Twine2HTML.Parse.Web.test.js +2 -2
- package/test/Web/web-exports.test.js +136 -0
- package/types/src/Story.d.ts +1 -1
|
@@ -8,23 +8,48 @@ class LightweightTwine2ArchiveParser {
|
|
|
8
8
|
constructor(html) {
|
|
9
9
|
this.html = html;
|
|
10
10
|
this.doc = null;
|
|
11
|
+
this.usingDOMParser = false;
|
|
11
12
|
|
|
12
13
|
// Parse HTML using browser's native DOMParser if available, otherwise fallback
|
|
13
14
|
if (typeof DOMParser !== 'undefined') {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
const parser = new DOMParser();
|
|
17
|
+
this.doc = parser.parseFromString(html, 'text/html');
|
|
18
|
+
this.usingDOMParser = true;
|
|
19
|
+
|
|
20
|
+
// Check if parsing was successful (DOMParser doesn't throw errors, but creates error documents)
|
|
21
|
+
const parserError = this.doc.querySelector('parsererror');
|
|
22
|
+
if (parserError) {
|
|
23
|
+
console.warn('DOMParser encountered an error, falling back to regex parsing:', parserError.textContent);
|
|
24
|
+
this.doc = this.createSimpleDOM(html);
|
|
25
|
+
this.usingDOMParser = false;
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.warn('DOMParser failed, falling back to regex parsing:', error.message);
|
|
29
|
+
this.doc = this.createSimpleDOM(html);
|
|
30
|
+
this.usingDOMParser = false;
|
|
31
|
+
}
|
|
16
32
|
} else {
|
|
17
33
|
// Fallback for environments without DOMParser
|
|
18
34
|
this.doc = this.createSimpleDOM(html);
|
|
35
|
+
this.usingDOMParser = false;
|
|
19
36
|
}
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
getElementsByTagName(tagName) {
|
|
23
|
-
if (this.doc && this.doc.getElementsByTagName) {
|
|
24
|
-
|
|
40
|
+
if (this.usingDOMParser && this.doc && this.doc.getElementsByTagName) {
|
|
41
|
+
// Use native DOM methods when DOMParser is available and working
|
|
42
|
+
const elements = Array.from(this.doc.getElementsByTagName(tagName));
|
|
43
|
+
|
|
44
|
+
// Convert DOM elements to expected format for compatibility
|
|
45
|
+
return elements.map(element => ({
|
|
46
|
+
outerHTML: element.outerHTML,
|
|
47
|
+
// For compatibility with the original parser interface
|
|
48
|
+
toString: () => element.outerHTML
|
|
49
|
+
}));
|
|
25
50
|
}
|
|
26
51
|
|
|
27
|
-
// Fallback implementation for
|
|
52
|
+
// Fallback implementation for environments without DOMParser
|
|
28
53
|
if (tagName === 'tw-storydata') {
|
|
29
54
|
return this.extractStoryDataElements();
|
|
30
55
|
}
|
|
@@ -52,11 +77,12 @@ class LightweightTwine2ArchiveParser {
|
|
|
52
77
|
|
|
53
78
|
// eslint-disable-next-line no-unused-vars
|
|
54
79
|
createSimpleDOM(_htmlContent) {
|
|
55
|
-
// Minimal DOM-like object for fallback
|
|
80
|
+
// Minimal DOM-like object for fallback when DOMParser is not available
|
|
81
|
+
// This should only be used in very limited environments
|
|
56
82
|
return {
|
|
57
83
|
getElementsByTagName: (tagName) => {
|
|
58
84
|
if (tagName === 'tw-storydata') {
|
|
59
|
-
return this.extractStoryDataElements(
|
|
85
|
+
return this.extractStoryDataElements();
|
|
60
86
|
}
|
|
61
87
|
return [];
|
|
62
88
|
}
|
|
@@ -10,23 +10,61 @@ class LightweightTwine2Parser {
|
|
|
10
10
|
constructor(html) {
|
|
11
11
|
this.html = html;
|
|
12
12
|
this.doc = null;
|
|
13
|
+
this.usingDOMParser = false;
|
|
13
14
|
|
|
14
15
|
// Parse HTML using browser's native DOMParser if available, otherwise fallback
|
|
15
16
|
if (typeof DOMParser !== 'undefined') {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
const parser = new DOMParser();
|
|
19
|
+
this.doc = parser.parseFromString(html, 'text/html');
|
|
20
|
+
this.usingDOMParser = true;
|
|
21
|
+
|
|
22
|
+
// Check if parsing was successful (DOMParser doesn't throw errors, but creates error documents)
|
|
23
|
+
const parserError = this.doc.querySelector('parsererror');
|
|
24
|
+
if (parserError) {
|
|
25
|
+
console.warn('DOMParser encountered an error, falling back to regex parsing:', parserError.textContent);
|
|
26
|
+
this.doc = this.createSimpleDOM(html);
|
|
27
|
+
this.usingDOMParser = false;
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.warn('DOMParser failed, falling back to regex parsing:', error.message);
|
|
31
|
+
this.doc = this.createSimpleDOM(html);
|
|
32
|
+
this.usingDOMParser = false;
|
|
33
|
+
}
|
|
18
34
|
} else {
|
|
19
35
|
// Fallback for environments without DOMParser
|
|
20
36
|
this.doc = this.createSimpleDOM(html);
|
|
37
|
+
this.usingDOMParser = false;
|
|
21
38
|
}
|
|
22
39
|
}
|
|
23
40
|
|
|
24
41
|
getElementsByTagName(tagName) {
|
|
25
|
-
if (this.doc && this.doc.getElementsByTagName) {
|
|
26
|
-
|
|
42
|
+
if (this.usingDOMParser && this.doc && this.doc.getElementsByTagName) {
|
|
43
|
+
// Use native DOM methods when DOMParser is available and working
|
|
44
|
+
const elements = Array.from(this.doc.getElementsByTagName(tagName));
|
|
45
|
+
|
|
46
|
+
// Convert DOM elements to our expected format
|
|
47
|
+
return elements.map(element => {
|
|
48
|
+
const attributes = {};
|
|
49
|
+
|
|
50
|
+
// Extract attributes using DOM methods - much more reliable than regex
|
|
51
|
+
if (element.attributes) {
|
|
52
|
+
for (let i = 0; i < element.attributes.length; i++) {
|
|
53
|
+
const attr = element.attributes[i];
|
|
54
|
+
// DOM automatically handles HTML entity decoding
|
|
55
|
+
attributes[attr.name] = attr.value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
attributes,
|
|
61
|
+
innerHTML: element.innerHTML || '',
|
|
62
|
+
rawText: element.textContent || element.innerText || ''
|
|
63
|
+
};
|
|
64
|
+
});
|
|
27
65
|
}
|
|
28
66
|
|
|
29
|
-
// Fallback implementation
|
|
67
|
+
// Fallback implementation for environments without DOMParser or when DOM parsing fails
|
|
30
68
|
if (tagName === 'tw-storydata') {
|
|
31
69
|
return this.extractStoryDataElements();
|
|
32
70
|
}
|
|
@@ -107,12 +145,49 @@ class LightweightTwine2Parser {
|
|
|
107
145
|
|
|
108
146
|
const openingTag = openingTagMatch[0];
|
|
109
147
|
|
|
110
|
-
//
|
|
111
|
-
|
|
148
|
+
// Enhanced attribute parsing to handle multiple formats:
|
|
149
|
+
// 1. Quoted attributes: name="value" or name='value'
|
|
150
|
+
// 2. Unquoted attributes: name=value
|
|
151
|
+
// 3. Boolean attributes: hidden, selected, etc.
|
|
152
|
+
|
|
153
|
+
// First, handle quoted attributes (including those with escaped quotes)
|
|
154
|
+
const quotedAttributeRegex = /(\w+(?:-\w+)*)=["']([^"']*)["']/g;
|
|
112
155
|
let match;
|
|
113
156
|
|
|
114
|
-
while ((match =
|
|
115
|
-
|
|
157
|
+
while ((match = quotedAttributeRegex.exec(openingTag)) !== null) {
|
|
158
|
+
// Decode basic HTML entities in attribute values
|
|
159
|
+
const value = match[2]
|
|
160
|
+
.replace(/"/g, '"')
|
|
161
|
+
.replace(/'/g, "'")
|
|
162
|
+
.replace(/</g, '<')
|
|
163
|
+
.replace(/>/g, '>')
|
|
164
|
+
.replace(/&/g, '&'); // This should be last
|
|
165
|
+
|
|
166
|
+
attributes[match[1]] = value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle unquoted attributes (but avoid matching already processed quoted ones)
|
|
170
|
+
let tagWithoutQuoted = openingTag;
|
|
171
|
+
const quotedMatches = [...openingTag.matchAll(quotedAttributeRegex)];
|
|
172
|
+
quotedMatches.forEach(quotedMatch => {
|
|
173
|
+
tagWithoutQuoted = tagWithoutQuoted.replace(quotedMatch[0], '');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const unquotedAttributeRegex = /(\w+(?:-\w+)*)=([^\s>]+)/g;
|
|
177
|
+
while ((match = unquotedAttributeRegex.exec(tagWithoutQuoted)) !== null) {
|
|
178
|
+
if (!attributes[match[1]]) { // Don't overwrite quoted attributes
|
|
179
|
+
attributes[match[1]] = match[2];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle boolean attributes (attributes without values)
|
|
184
|
+
const booleanAttributeRegex = /\s(\w+(?:-\w+)*)(?=\s|>|$)/g;
|
|
185
|
+
while ((match = booleanAttributeRegex.exec(openingTag)) !== null) {
|
|
186
|
+
const attrName = match[1];
|
|
187
|
+
// Only add if it's not already parsed as a key=value attribute and not the tag name
|
|
188
|
+
if (!attributes[attrName] && !openingTag.includes(`${attrName}=`) && attrName !== openingTag.match(/<(\w+)/)?.[1]) {
|
|
189
|
+
attributes[attrName] = true;
|
|
190
|
+
}
|
|
116
191
|
}
|
|
117
192
|
|
|
118
193
|
return attributes;
|
|
@@ -132,9 +207,11 @@ class LightweightTwine2Parser {
|
|
|
132
207
|
|
|
133
208
|
// eslint-disable-next-line no-unused-vars
|
|
134
209
|
createSimpleDOM(_html) {
|
|
135
|
-
// Minimal DOM-like object for fallback
|
|
210
|
+
// Minimal DOM-like object for fallback when DOMParser is not available
|
|
211
|
+
// This should only be used in very limited environments (like some older Node.js versions)
|
|
136
212
|
return {
|
|
137
213
|
getElementsByTagName: (tagName) => {
|
|
214
|
+
// Use regex-based extraction as fallback
|
|
138
215
|
if (tagName === 'tw-storydata') {
|
|
139
216
|
return this.extractStoryDataElements();
|
|
140
217
|
}
|
|
@@ -201,8 +278,14 @@ function parse(content) {
|
|
|
201
278
|
* The name of the story.
|
|
202
279
|
*/
|
|
203
280
|
if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'name')) {
|
|
204
|
-
//
|
|
205
|
-
|
|
281
|
+
// Validate that the name is a non-empty string before setting
|
|
282
|
+
const nameValue = storyData.attributes.name;
|
|
283
|
+
if (typeof nameValue === 'string' && nameValue.trim().length > 0) {
|
|
284
|
+
story.name = nameValue.trim();
|
|
285
|
+
} else {
|
|
286
|
+
console.warn('Warning: The name attribute is empty or invalid on tw-storydata!');
|
|
287
|
+
// Keep the default name from Story constructor
|
|
288
|
+
}
|
|
206
289
|
} else {
|
|
207
290
|
// Name is a required field. Warn user.
|
|
208
291
|
console.warn('Warning: The name attribute is missing from tw-storydata!');
|
|
@@ -215,15 +298,20 @@ function parse(content) {
|
|
|
215
298
|
* hyphen that uniquely identify a story (see Treaty of Babel).
|
|
216
299
|
*/
|
|
217
300
|
if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'ifid')) {
|
|
218
|
-
//
|
|
219
|
-
|
|
301
|
+
// Validate that the IFID is a non-empty string before setting
|
|
302
|
+
const ifidValue = storyData.attributes.ifid;
|
|
303
|
+
if (typeof ifidValue === 'string' && ifidValue.trim().length > 0) {
|
|
304
|
+
story.IFID = ifidValue.trim();
|
|
305
|
+
} else {
|
|
306
|
+
console.warn('Warning: The ifid attribute is empty or invalid on tw-storydata!');
|
|
307
|
+
}
|
|
220
308
|
} else {
|
|
221
|
-
//
|
|
309
|
+
// IFID is a required field. Warn user.
|
|
222
310
|
console.warn('Warning: The ifid attribute is missing from tw-storydata!');
|
|
223
311
|
}
|
|
224
312
|
|
|
225
|
-
// Check if the IFID has valid formatting.
|
|
226
|
-
if (story.IFID.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/) === null) {
|
|
313
|
+
// Check if the IFID has valid formatting (only if IFID was set).
|
|
314
|
+
if (story.IFID && story.IFID.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/) === null) {
|
|
227
315
|
// IFID is not valid.
|
|
228
316
|
console.warn('Warning: The IFID is not in valid UUIDv4 formatting on tw-storydata!');
|
|
229
317
|
}
|
|
@@ -312,7 +312,7 @@ describe('Twine1HTML', function () {
|
|
|
312
312
|
const el = '<div id="storeArea"><div tiddler="Test" modifier="twee">Content</div></div>';
|
|
313
313
|
const s = parseTwine1HTMLWeb(el);
|
|
314
314
|
expect(s.size()).toBe(1);
|
|
315
|
-
expect(s.creator).toBe('
|
|
315
|
+
expect(s.creator).toBe('extwee');
|
|
316
316
|
} finally {
|
|
317
317
|
global.DOMParser = originalDOMParser;
|
|
318
318
|
}
|
|
@@ -128,7 +128,7 @@ describe('Twine2HTML', function () {
|
|
|
128
128
|
|
|
129
129
|
const content = '<tw-storydata name="Test Story"><tw-passagedata pid="1" name="Start">Content</tw-passagedata></tw-storydata>';
|
|
130
130
|
parseTwine2HTMLWeb(content);
|
|
131
|
-
expect(warningMessage).toBe('Warning: The
|
|
131
|
+
expect(warningMessage).toBe('Warning: The ifid attribute is missing from tw-storydata!');
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
it('Should warn for malformed IFID', function () {
|
|
@@ -201,7 +201,7 @@ describe('Twine2HTML', function () {
|
|
|
201
201
|
});
|
|
202
202
|
|
|
203
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
|
|
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
205
|
|
|
206
206
|
const story = parseTwine2HTMLWeb(content);
|
|
207
207
|
const passage = story.getPassageByName('Start');
|
|
@@ -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
|
+
});
|
package/types/src/Story.d.ts
CHANGED