diff --git a/package-lock.json b/package-lock.json
index 48c6c15e..25362c07 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,7 +31,7 @@
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jest": "^27.4.0",
- "eslint-plugin-jsdoc": "^46.8.1",
+ "eslint-plugin-jsdoc": "^46.8.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^6.1.1",
"jest": "^29.7.0",
@@ -5152,9 +5152,9 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
- "version": "46.8.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.1.tgz",
- "integrity": "sha512-uTce7IBluPKXIQMWJkIwFsI1gv7sZRmLjctca2K5DIxPi8fSBj9f4iru42XmGwuiMyH2f3nfc60sFmnSGv4Z/A==",
+ "version": "46.8.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.2.tgz",
+ "integrity": "sha512-5TSnD018f3tUJNne4s4gDWQflbsgOycIKEUBoCLn6XtBMgNHxQFmV8vVxUtiPxAQq8lrX85OaSG/2gnctxw9uQ==",
"dev": true,
"dependencies": {
"@es-joy/jsdoccomment": "~0.40.1",
@@ -14811,9 +14811,9 @@
}
},
"eslint-plugin-jsdoc": {
- "version": "46.8.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.1.tgz",
- "integrity": "sha512-uTce7IBluPKXIQMWJkIwFsI1gv7sZRmLjctca2K5DIxPi8fSBj9f4iru42XmGwuiMyH2f3nfc60sFmnSGv4Z/A==",
+ "version": "46.8.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.2.tgz",
+ "integrity": "sha512-5TSnD018f3tUJNne4s4gDWQflbsgOycIKEUBoCLn6XtBMgNHxQFmV8vVxUtiPxAQq8lrX85OaSG/2gnctxw9uQ==",
"dev": true,
"requires": {
"@es-joy/jsdoccomment": "~0.40.1",
diff --git a/package.json b/package.json
index 08e2b4a3..433401bf 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jest": "^27.4.0",
- "eslint-plugin-jsdoc": "^46.8.1",
+ "eslint-plugin-jsdoc": "^46.8.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^6.1.1",
"jest": "^29.7.0",
diff --git a/src/Passage.js b/src/Passage.js
index 8a609a09..504b8a48 100644
--- a/src/Passage.js
+++ b/src/Passage.js
@@ -27,24 +27,16 @@ export default class Passage {
*/
#_text = '';
- /**
- * Internal PID of passage
- * @private
- */
- #_pid = -1;
-
/**
* A passage is the smallest unit of a story.
- * (The 'pid' property is only used in Twine 2 HTML.)
* @function Passage
* @class
* @param {string} name - Name
* @param {string} text - Content
* @param {Array} tags - Tags
* @param {object} metadata - Metadata
- * @param {number} pid - Passage ID (PID)
*/
- constructor (name = '', text = '', tags = [], metadata = {}, pid = -1) {
+ constructor (name = '', text = '', tags = [], metadata = {}) {
// Set name
this.name = name;
@@ -56,9 +48,6 @@ export default class Passage {
// Sets text
this.text = text;
-
- // Sets pid
- this.pid = pid;
}
/**
@@ -140,26 +129,6 @@ export default class Passage {
}
}
- /**
- * Passage ID (PID)
- * @public
- * @memberof Passage
- * @returns {number} Passage ID (PID)
- */
- get pid () { return this.#_pid; }
-
- /**
- * @param {number} p - Replacement PID
- */
- set pid (p) {
- // Test if PID is a number
- if (Number.isInteger(p)) {
- this.#_pid = p;
- } else {
- throw new Error('PID should be a number!');
- }
- }
-
/**
* Return a Twee representation.
* @public
@@ -170,7 +139,7 @@ export default class Passage {
toTwee () {
// Start empty string.
let content = '';
-
+
// Write the name.
content += `:: ${this.name}`;
@@ -200,7 +169,7 @@ export default class Passage {
* @memberof Passage
* @returns {string} JSON string.
*/
- toJSON() {
+ toJSON () {
// Create an initial object for later serialization.
const p = {
name: this.name,
@@ -215,32 +184,34 @@ export default class Passage {
/**
* Return Twine 2 HTML representation.
+ * (Default Passage ID is 1.)
* @public
* @function toTwine2HTML
+ * @param {number} pid - Passage ID (PID) to record in HTML.
* @returns {string} Twine 2 HTML string.
*/
- toTwine2HTML() {
- // Start the passage element
+ toTwine2HTML (pid = 1) {
+ // Start the passage element.
let passageData = '\t${escape(this.text)}\n`;
@@ -290,27 +260,27 @@ export default class Passage {
}
/**
- * Return Twine 1 HTML representation.
- * @public
- * @function toTwine2HTML
- * @returns {string} Twine 1 HTML string.
- */
- toTwine1HTML() {
+ * Return Twine 1 HTML representation.
+ * @public
+ * @function toTwine2HTML
+ * @returns {string} Twine 1 HTML string.
+ */
+ toTwine1HTML () {
/**
- *
[[One passage]]
*/
- // Start the passage element
- let passageData = '\t passage.pid === pid);
- // Return passages or null
- return results !== undefined ? results : null;
- }
-
/**
* Size (number of passages).
* @public
@@ -505,123 +475,122 @@ export default class Story {
fromJSON (jsonString) {
// Create future object.
let result = {};
-
+
// Try to parse the string.
try {
- result = JSON.parse(jsonString);
- } catch (error) {
- throw new Error('Invalid JSON!');
- }
-
- // Name
- if (Object.prototype.hasOwnProperty.call(result, 'name')) {
- // Convert to String (if not String).
- this.name = String(result.name);
- }
-
- // Start Passage
- if (Object.prototype.hasOwnProperty.call(result, 'start')) {
- // Convert to String (if not String).
- this.start = String(result.start);
- }
-
- // IFID
- if (Object.prototype.hasOwnProperty.call(result, 'ifid')) {
- // Convert to String (if not String).
- // Enforce the uppercase rule of Twine 2 and Twee 3.
- this.IFID = String(result.ifid).toUpperCase();
- }
-
- // Format
- if (Object.prototype.hasOwnProperty.call(result, 'format')) {
- // Convert to String (if not String).
- this.format = String(result.format);
- }
-
- // Format Version
- if (Object.prototype.hasOwnProperty.call(result, 'formatVersion')) {
- // Convert to String (if not String).
- this.formatVersion = String(result.formatVersion);
- }
-
- // Creator
- if (Object.prototype.hasOwnProperty.call(result, 'creator')) {
- // Convert to String (if not String).
- this.creator = String(result.creator);
- }
-
- // Creator Version
- if (Object.prototype.hasOwnProperty.call(result, 'creatorVersion')) {
- // Convert to String (if not String).
- this.creatorVersion = String(result.creatorVersion);
- }
-
- // Zoom
- if (Object.prototype.hasOwnProperty.call(result, 'zoom')) {
- // Set Zoom.
- this.zoom = result.zoom;
- }
-
- // Tag Colors
- if (Object.prototype.hasOwnProperty.call(result, 'tagColors')) {
- // Set tagColors.
- this.tagColors = result.tagColors;
- }
-
- // Metadata
- if (Object.prototype.hasOwnProperty.call(result, 'metadata')) {
- // Set metadata.
- this.metadata = result.metadata;
- }
-
- // Passages
- if (Object.prototype.hasOwnProperty.call(result, 'passages')) {
- // Reset internal passages.
- this.#_passages = [];
- // Is this an array?
- if (Array.isArray(result.passages)) {
- // For each passage, convert into Passage objects.
- result.passages.forEach((p) => {
- // Create default passage.
- const newP = new Passage();
-
- // Does this have a name?
- if (Object.prototype.hasOwnProperty.call(p, 'name')) {
- // Set name.
- newP.name = p.name;
- }
-
- // Does this have tags?
- if (Object.prototype.hasOwnProperty.call(p, 'tags')) {
- // Set tags.
- newP.tags = p.tags;
- }
-
- // Does this have metadata?
- if (Object.prototype.hasOwnProperty.call(p, 'metadata')) {
- // Set metadata.
- newP.metadata = p.metadata;
- }
-
- // Does this have text?
- if (Object.prototype.hasOwnProperty.call(p, 'text')) {
- // Set text.
- newP.text = p.text;
- }
-
- // Add the new passage.
- this.addPassage(newP);
- });
- }
+ result = JSON.parse(jsonString);
+ } catch (error) {
+ throw new Error('Invalid JSON!');
+ }
+
+ // Name
+ if (Object.prototype.hasOwnProperty.call(result, 'name')) {
+ // Convert to String (if not String).
+ this.name = String(result.name);
+ }
+
+ // Start Passage
+ if (Object.prototype.hasOwnProperty.call(result, 'start')) {
+ // Convert to String (if not String).
+ this.start = String(result.start);
+ }
+
+ // IFID
+ if (Object.prototype.hasOwnProperty.call(result, 'ifid')) {
+ // Convert to String (if not String).
+ // Enforce the uppercase rule of Twine 2 and Twee 3.
+ this.IFID = String(result.ifid).toUpperCase();
+ }
+
+ // Format
+ if (Object.prototype.hasOwnProperty.call(result, 'format')) {
+ // Convert to String (if not String).
+ this.format = String(result.format);
+ }
+
+ // Format Version
+ if (Object.prototype.hasOwnProperty.call(result, 'formatVersion')) {
+ // Convert to String (if not String).
+ this.formatVersion = String(result.formatVersion);
+ }
+
+ // Creator
+ if (Object.prototype.hasOwnProperty.call(result, 'creator')) {
+ // Convert to String (if not String).
+ this.creator = String(result.creator);
+ }
+
+ // Creator Version
+ if (Object.prototype.hasOwnProperty.call(result, 'creatorVersion')) {
+ // Convert to String (if not String).
+ this.creatorVersion = String(result.creatorVersion);
+ }
+
+ // Zoom
+ if (Object.prototype.hasOwnProperty.call(result, 'zoom')) {
+ // Set Zoom.
+ this.zoom = result.zoom;
+ }
+
+ // Tag Colors
+ if (Object.prototype.hasOwnProperty.call(result, 'tagColors')) {
+ // Set tagColors.
+ this.tagColors = result.tagColors;
+ }
+
+ // Metadata
+ if (Object.prototype.hasOwnProperty.call(result, 'metadata')) {
+ // Set metadata.
+ this.metadata = result.metadata;
+ }
+
+ // Passages
+ if (Object.prototype.hasOwnProperty.call(result, 'passages')) {
+ // Reset internal passages.
+ this.#_passages = [];
+ // Is this an array?
+ if (Array.isArray(result.passages)) {
+ // For each passage, convert into Passage objects.
+ result.passages.forEach((p) => {
+ // Create default passage.
+ const newP = new Passage();
+
+ // Does this have a name?
+ if (Object.prototype.hasOwnProperty.call(p, 'name')) {
+ // Set name.
+ newP.name = p.name;
+ }
+
+ // Does this have tags?
+ if (Object.prototype.hasOwnProperty.call(p, 'tags')) {
+ // Set tags.
+ newP.tags = p.tags;
+ }
+
+ // Does this have metadata?
+ if (Object.prototype.hasOwnProperty.call(p, 'metadata')) {
+ // Set metadata.
+ newP.metadata = p.metadata;
+ }
+
+ // Does this have text?
+ if (Object.prototype.hasOwnProperty.call(p, 'text')) {
+ // Set text.
+ newP.text = p.text;
+ }
+
+ // Add the new passage.
+ this.addPassage(newP);
+ });
}
}
+ }
/**
* Return Twee representation.
- *
+ *
* See: Twee 3 Specification
* (https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md)
- *
* @function toTwee
* @memberof Story
* @returns {string} Twee String
@@ -633,6 +602,9 @@ export default class Story {
// Create default object.
const metadata = {};
+ /**
+ * ifid: (string) Required. Maps to
.
+ */
// Is there an IFID?
if (this.IFID === '') {
// Generate a new IFID for this work.
@@ -643,32 +615,33 @@ export default class Story {
metadata.ifid = this.IFID;
}
- // Is there a format?
- if (this.format !== '') {
- // Using existing format
- metadata.format = this.format;
- }
-
- // Is there a formatVersion?
- if (this.formatVersion !== '') {
- // Using existing format version
- metadata['format-version'] = this.formatVersion;
- }
-
- // Is there a zoom?
- if (this.zoom !== 0) {
- // Using existing zoom.
- metadata.zoom = this.zoom;
- }
-
- // Is there a start?
- if (this.start !== '') {
- // Using existing start
- metadata.start = this.start;
- }
-
- // Get number of colors.
+ /**
+ * format: (string) Optional. Maps to .
+ */
+ metadata.format = this.format;
+
+ /**
+ * format-version: (string) Optional. Maps to .
+ */
+ metadata['format-version'] = this.formatVersion;
+
+ /**
+ * zoom: (decimal) Optional. Maps to .
+ */
+ metadata.zoom = this.zoom;
+
+ /**
+ * start: (string) Optional.
+ * Maps to of the node whose pid matches .
+ */
+ metadata.start = this.start;
+
+ /**
+ * tag-colors: (object of tag(string):color(string) pairs) Optional.
+ * Pairs map to nodes as :.
+ */
const numberOfColors = Object.keys(this.tagColors).length;
+
// Are there any colors?
if (numberOfColors > 0) {
// Add a tag-colors property
@@ -681,70 +654,69 @@ export default class Story {
// Add two newlines.
outputContents += '\n\n';
- // Look for StoryTitle
- const storyTitlePassage = this.getPassageByName('StoryTitle');
-
- // Does it exist?
- if (storyTitlePassage !== null) {
- // Append StoryTitle content
- outputContents += storyTitlePassage.toTwee();
+ // Is there an explicit StoryTitle passage?
+ // (If it does exist, do nothing.)
+ if (this.getPassageByName('StoryTitle') === null) {
+ // We do not have an explicit StoryTitle passage.
+ // Generate one.
+ const p = new Passage('StoryTitle', this.name);
+ // Add to story.
+ this.addPassage(p);
}
// For each passage, append it to the output.
this.forEachPassage((passage) => {
- // For each passage, append it to the output.
outputContents += passage.toTwee();
});
- // Return the Twee string.
- return outputContents;
+ // Return the Twee string.
+ return outputContents;
}
/**
- * Combine StoryFormat to create Twine 2 HTML.
+ * Return Twine 2 HTML.
+ *
+ * See: Twine 2 HTML Output
+ * (https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md)
* @public
* @function toTwine2HTML
- * @param {StoryFormat} storyFormat - StoryFormat to combine
* @returns {string} Twine 2 HTML string
*/
- toTwine2HTML (storyFormat) {
- if (!(storyFormat instanceof StoryFormat)) {
- throw new Error('storyFormat must be a StoryFormat object!');
- }
+ toTwine2HTML () {
+ // Prepare HTML content.
+ let storyData = ` {
+ // Have we found the starting passage?
+ if (p.name === this.start) {
+ // If so, set the PID based on index.
+ startPID = PIDcounter;
}
- } else {
- // Throw error if no starting passage exists
- throw new Error('No starting passage found!');
- }
+ // Increase and keep looking.
+ PIDcounter++;
+ });
+
+ // Set starting passage PID.
+ storyData += ` startnode="${startPID}"`;
// Defaults to 'extwee' if missing.
storyData += ` creator="${this.creator}"`;
@@ -766,12 +738,12 @@ export default class Story {
storyData += ` zoom="${this.zoom}"`;
// Write existing or default value.
- storyData += ` format="${storyFormat.name}"`;
+ storyData += ` format="${this.#_format}"`;
// Write existing or default value.
- storyData += ` format-version="${storyFormat.version}"`;
+ storyData += ` format-version="${this.#_formatVersion}"`;
- // Add the default.
+ // Add the default attributes.
storyData += ' options hidden>\n';
// Start the STYLE.
@@ -801,27 +773,50 @@ export default class Story {
storyData += passage.text;
});
- // Close SCRIPT
+ // Close SCRIPT.
storyData += '\n';
- // Build the passages.
+ // Reset the PID counter.
+ PIDcounter = 1;
+
+ // Build the passages HTML.
this.forEachPassage((passage) => {
- // Append each passage element.
- storyData += passage.toTwine2HTML();
+ // Append each passage element using the PID counter.
+ storyData += passage.toTwine2HTML(PIDcounter);
+ // Increase counter inside loop.
+ PIDcounter++;
});
+ // Close the HTML element.
storyData += '';
- // Replace the story name in the source file.
- storyFormat.source = storyFormat.source.replaceAll(/{{STORY_NAME}}/gm, this.name);
+ // Return HTML contents.
+ return storyData;
+ }
+
+ /**
+ * Return Twine 1 HTML.
+ *
+ * See: Twine 1 HTML Output
+ * (https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md)
+ * @public
+ * @function toTwine1HTML
+ * @returns {string} Twine 1 HTML string.
+ */
+ toTwine1HTML () {
+ // Begin HTML output.
+ let outputContents = ``;
- // Replace the story data.
- storyFormat.source = storyFormat.source.replaceAll(/{{STORY_DATA}}/gm, storyData);
+ // Process passages (if any).
+ this.forEachPassage((p) => {
+ // Output HTML output per passage.
+ outputContents += `\t${p.toTwine1HTML()}`;
+ });
- // Combine everything together.
- outputContents += storyFormat.source;
+ // Close HTML element.
+ outputContents += '
';
- // Return HTML contents.
+ // Return Twine 1 HTML content.
return outputContents;
}
}
diff --git a/src/TWSParser.js b/src/TWSParser.js
index be77cc67..526e6bfb 100644
--- a/src/TWSParser.js
+++ b/src/TWSParser.js
@@ -5,9 +5,9 @@ import { Parser } from 'pickleparser';
/**
* @class TWSParser
* @module TWSParser
- *
+ *
* Parse TWS (Python Pickle) into Story object.
- *
+ *
* See: Twine 1 TWS Documentation [Approval Pending]
*/
export default class TWSParser {
diff --git a/src/TweeParser.js b/src/TweeParser.js
index cb3e92fd..13ab412f 100644
--- a/src/TweeParser.js
+++ b/src/TweeParser.js
@@ -3,9 +3,9 @@ import Story from './Story.js';
/**
* @class TweeParser
* @module TweeParser
- *
+ *
* Parses Twee 3 text into a Story object.
- *
+ *
* See: Twee 3 Specification
* (https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md)
*/
@@ -152,7 +152,7 @@ export default class TweeParser {
throw new Error('Malformed passage header!');
}
- // addPassage() method does all the work
+ // addPassage() method does all the work.
story.addPassage(new Passage(name, text, tags, metadata, pid));
// Increase pid
diff --git a/src/Twine1HTMLParser.js b/src/Twine1HTMLParser.js
index 7e85fb70..b92066a7 100644
--- a/src/Twine1HTMLParser.js
+++ b/src/Twine1HTMLParser.js
@@ -9,9 +9,9 @@ import Story from './Story.js';
/**
* @class Twine1HTMLParser
* @module Twine1HTMLParser
- *
+ *
* Parses Twine 1 HTML into a Story object.
- *
+ *
* See: Twine 1 HTML Output Documentation
* (https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md)
*/
diff --git a/src/Twine2HTMLParser.js b/src/Twine2HTMLParser.js
index 2cdc8e2f..1ae13aa4 100644
--- a/src/Twine2HTMLParser.js
+++ b/src/Twine2HTMLParser.js
@@ -9,9 +9,9 @@ import Passage from './Passage.js';
/**
* @class Twine2HTMLParser
* @module Twine2HTMLParser
- *
+ *
* Parses Twine 2 HTML into a Story object.
- *
+ *
* See: Twine 2 HTML Output Specification
* (https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md)
*/
diff --git a/test/Passage.test.js b/test/Passage.test.js
index 91a82e32..b77f2b76 100644
--- a/test/Passage.test.js
+++ b/test/Passage.test.js
@@ -8,7 +8,6 @@ describe('Passage', () => {
expect(p.tags).toHaveLength(0);
expect(p.text).toBe('');
expect(typeof p.metadata).toBe('object');
- expect(p.pid).toBe(-1);
});
});
@@ -72,21 +71,6 @@ describe('Passage', () => {
});
});
- describe('pid', () => {
- it('Set PID', () => {
- const p = new Passage();
- p.pid = 12;
- expect(p.pid).toBe(12);
- });
-
- it('Throw error if pid is not a Number', () => {
- const p = new Passage();
- expect(() => {
- p.pid = [];
- }).toThrow();
- });
- });
-
describe('toTwee()', () => {
it('Create name string', () => {
const p = new Passage('Name', 'Test');
@@ -113,77 +97,76 @@ describe('Passage', () => {
});
});
- describe('toTwine2HTML()', function() {
-
+ describe('toTwine2HTML()', function () {
let p = null;
let result = null;
beforeEach(() => {
- p = new Passage("Test", "Word", ['tag1'], {'some': 'thing'}, 10);
+ p = new Passage('Test', 'Word', ['tag1'], { some: 'thing' });
result = p.toTwine2HTML();
});
- it('Should contain PID', function() {
- expect(result.includes('pid="10"')).toBe(true);
+ it('Should contain default PID', function () {
+ expect(result.includes('pid="1"')).toBe(true);
});
- it('Should include name', function() {
+ it('Should include name', function () {
expect(result.includes('name="Test"')).toBe(true);
});
- it('Should include single tag', function() {
+ it('Should include single tag', function () {
expect(result.includes('tags="tag1"')).toBe(true);
});
- it('Should include multiple tags', function() {
+ it('Should include multiple tags', function () {
p.tags = ['tag1', 'tag2'];
result = p.toTwine2HTML();
expect(result.includes('tags="tag1 tag2"')).toBe(true);
});
- it('Should include position, if it exists', function() {
- p.metadata = {'position': "102,99"};
+ it('Should include position, if it exists', function () {
+ p.metadata = { position: '102,99' };
result = p.toTwine2HTML();
expect(result.includes('position="102,99"')).toBe(true);
});
- it('Should include size, if it exists', function() {
- p.metadata = {'size': "100,100"};
+ it('Should include size, if it exists', function () {
+ p.metadata = { size: '100,100' };
result = p.toTwine2HTML();
expect(result.includes('size="100,100"')).toBe(true);
});
});
- describe('toTwine1HTML()', function() {
+ describe('toTwine1HTML()', function () {
let p = null;
let result = null;
beforeEach(() => {
- p = new Passage("Test", "Word", ['tag1'], {'position': '12, 12'});
+ p = new Passage('Test', 'Word', ['tag1'], { position: '12, 12' });
result = p.toTwine1HTML();
});
- it('Should include tiddler', function() {
+ it('Should include tiddler', function () {
expect(result.includes('tiddler="Test"')).toBe(true);
});
- it('Should include single tag', function() {
+ it('Should include single tag', function () {
expect(result.includes('tags="tag1"')).toBe(true);
});
- it('Should include multiple tags', function() {
+ it('Should include multiple tags', function () {
p.tags = ['tag1', 'tag2'];
result = p.toTwine1HTML();
expect(result.includes('tags="tag1 tag2"')).toBe(true);
});
- it('Should include position, if it exists', function() {
- p.metadata = {'position': "102,99"};
+ it('Should include position, if it exists', function () {
+ p.metadata = { position: '102,99' };
result = p.toTwine1HTML();
expect(result.includes('position="102,99"')).toBe(true);
});
- it('Should use default position', function() {
+ it('Should use default position', function () {
p.metadata = {};
result = p.toTwine1HTML();
expect(result.includes('position="10,10"')).toBe(true);
diff --git a/test/Story.test.js b/test/Story.test.js
index e3000629..d25f6a36 100644
--- a/test/Story.test.js
+++ b/test/Story.test.js
@@ -1,13 +1,14 @@
import Story from '../src/Story.js';
import Passage from '../src/Passage';
-import FileReader from '../src/FileReader.js';
+import TweeParser from '../src/TweeParser.js';
+import { readFileSync } from 'node:fs';
// Pull the name and version of this project from package.json.
// These are used as the 'creator' and 'creator-version'.
-const { name, version } = JSON.parse(FileReader.read('package.json'));
+const { name, version } = JSON.parse(readFileSync('package.json'));
describe('Story', () => {
- describe('#constructor()', () => {
+ describe('constructor()', () => {
let s = null;
beforeEach(() => {
@@ -107,8 +108,8 @@ describe('Story', () => {
});
it('Set using String', () => {
- s.formatVersion = 'New';
- expect(s.formatVersion).toBe('New');
+ s.formatVersion = '1.1.1';
+ expect(s.formatVersion).toBe('1.1.1');
});
it('Should throw error if not String', () => {
@@ -242,6 +243,45 @@ describe('Story', () => {
s.addPassage(p2);
expect(s.size()).toBe(1);
});
+
+ it('addPassage() - should override StoryData: ifid', function () {
+ // Generate object.
+ const o = {
+ ifid: 'D674C58C-DEFA-4F70-B7A2-27742230C0FC'
+ };
+
+ // Add the passage.
+ s.addPassage(new Passage('StoryData', JSON.stringify(o)));
+
+ // Test for IFID.
+ expect(s.IFID).toBe('D674C58C-DEFA-4F70-B7A2-27742230C0FC');
+ });
+
+ it('addPassage() - should override StoryData: format', function () {
+ // Generate object.
+ const o = {
+ format: 'SugarCube'
+ };
+
+ // Add the passage.
+ s.addPassage(new Passage('StoryData', JSON.stringify(o)));
+
+ // Test for format.
+ expect(s.format).toBe('SugarCube');
+ });
+
+ it('addPassage() - should override StoryData: formatVersion', function () {
+ // Generate object.
+ const o = {
+ 'format-version': '2.28.2'
+ };
+
+ // Add the passage.
+ s.addPassage(new Passage('StoryData', JSON.stringify(o)));
+
+ // Test for format.
+ expect(s.formatVersion).toBe('2.28.2');
+ });
});
describe('removePassageByName()', () => {
@@ -305,25 +345,6 @@ describe('Story', () => {
});
});
- describe('getPassageByPID()', () => {
- let s = null;
-
- beforeEach(() => {
- s = new Story();
- });
-
- it('getPassageByPID() - should get passage by PID', () => {
- const p = new Passage('Find', '', [], {}, 12);
- s.addPassage(p);
- const passage = s.getPassageByPID(12);
- expect(passage.name).toBe('Find');
- });
-
- it('getPassageByPID() - should return null if not found', () => {
- expect(s.getPassageByPID(12)).toBe(null);
- });
- });
-
describe('forEachPassage()', () => {
let s = null;
@@ -396,4 +417,543 @@ describe('Story', () => {
expect(result.passages.length).toBe(1);
});
});
+
+ describe('fromJSON()', function () {
+ it('Should throw error if JSON is invalid', function () {
+ const s = new Story();
+ expect(() => { s.fromJSON('{'); }).toThrow();
+ });
+
+ it('Should roundtrip default Story values using toJSON() and fromJSON()', function () {
+ // Create Story.
+ const s = new Story();
+ // Convert to JSON and back.
+ s.fromJSON(s.toJSON());
+ // Check all properties.
+ expect(s.name).toBe('');
+ expect(Object.keys(s.tagColors).length).toBe(0);
+ expect(s.IFID).toBe('');
+ expect(s.start).toBe('');
+ expect(s.formatVersion).toBe('');
+ expect(s.format).toBe('');
+ expect(s.creator).toBe('extwee');
+ expect(s.creatorVersion).toBe('2.2.0');
+ expect(s.zoom).toBe(0);
+ expect(Object.keys(s.metadata).length).toBe(0);
+ });
+
+ it('Should parse passage data', function () {
+ // Create passage.
+ const p = new Passage('Test', 'Default');
+ // Create Story.
+ const s = new Story();
+ // Add a passage.
+ s.addPassage(p);
+ // Convert to JSON.
+ const js = s.toJSON(s);
+ // Convert back to Story.
+ const result = new Story();
+ // Convert back.
+ result.fromJSON(js);
+ // Should have a single passage.
+ expect(result.size()).toBe(1);
+ });
+
+ describe('Partial Story Processing', function () {
+ it('Should parse everything but name', function () {
+ const s = '{"tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but tagColors', function () {
+ const s = '{"name":"Test","ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(0);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but ifid', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but start', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Star","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but formatVersion', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but format', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but creator', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but creator version', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but zoom', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(0);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but metadata', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(0);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ });
+
+ it('Should parse everything but passages', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(0);
+ });
+
+ it('Should ignore non-arrays for passages', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":{}}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(0);
+ });
+
+ it('Should parse everything but passage name', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"tags":["tag1"],"metadata":{},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ expect(r.getPassageByName('').name).toBe('');
+ });
+
+ it('Should parse everything but passage tags', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","metadata":{"s":"e"},"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ expect(r.getPassageByName('Start').metadata.s).toBe('e');
+ expect(r.getPassageByName('Start').tags.length).toBe(0);
+ });
+
+ it('Should parse everything but passage text', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"metadata":{"s":"e"}}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ expect(r.getPassageByName('Start').metadata.s).toBe('e');
+ expect(r.getPassageByName('Start').text).toBe('');
+ });
+
+ it('Parse everything but passage metadata', function () {
+ const s = '{"name":"Test","tagColors":{"r":"red"},"ifid":"dd","start":"Start","formatVersion":"1.0","metadata":{"some":"thing"},"format":"Snowman","creator":"extwee","creatorVersion":"2.2.0","zoom":1,"passages":[{"name":"Start","tags":["tag1"],"text":"Word"}]}';
+ const r = new Story();
+ r.fromJSON(s);
+ expect(r.name).toBe('Test');
+ expect(Object.keys(r.tagColors).length).toBe(1);
+ expect(r.IFID).toBe('DD');
+ expect(r.start).toBe('Start');
+ expect(r.formatVersion).toBe('1.0');
+ expect(Object.keys(r.metadata).length).toBe(1);
+ expect(r.format).toBe('Snowman');
+ expect(r.creator).toBe('extwee');
+ expect(r.creatorVersion).toBe('2.2.0');
+ expect(r.zoom).toBe(1);
+ expect(r.size()).toBe(1);
+ expect(Object.prototype.hasOwnProperty.call(r.getPassageByName('Start').metadata, 's')).toBe(false);
+ expect(r.getPassageByName('Start').text).toBe('Word');
+ });
+ });
+ });
+
+ describe('toTwee()', function () {
+ let s = null;
+
+ beforeEach(() => {
+ s = new Story();
+ });
+
+ it('Should detect StoryTitle text', function () {
+ // Add one passage.
+ s.addPassage(new Passage('Start', 'Content'));
+
+ // Change Story name.
+ s.name = 'Title';
+
+ // Convert to Twee.
+ const t = s.toTwee();
+
+ // Parse into a new story.
+ const story = TweeParser.parse(t);
+
+ // Test for name.
+ expect(story.name).toBe('Title');
+ });
+
+ it('Should encode IFID', () => {
+ // Add passages.
+ s.addPassage(new Passage('Start'));
+ s.addPassage(new Passage('StoryTitle', 'Title'));
+
+ // Set an ifid property.
+ s.IFID = 'DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A';
+
+ // Convert to Twee.
+ const t = s.toTwee();
+
+ // Parse file.
+ const tp = TweeParser.parse(t);
+
+ // Verify IFID.
+ expect(tp.IFID).toBe('DE7DF8AD-E4CD-499E-A4E7-C5B98B73449A');
+ });
+
+ it('Should encode format, formatVersion, zoom, and start', () => {
+ // Add passages.
+ s.addPassage(new Passage('Start', 'Content'));
+ s.addPassage(new Passage('Untitled', 'Some stuff'));
+
+ s.name = 'Title';
+ s.format = 'Test';
+ s.formatVersion = '1.2.3';
+ s.zoom = 1;
+ s.start = 'Untitled';
+
+ // Convert to Twee.
+ const t = s.toTwee();
+
+ // Parse Twee.
+ const story2 = TweeParser.parse(t);
+
+ // Test for format, formatVersion, zoom, and start.
+ expect(story2.formatVersion).toBe('1.2.3');
+ expect(story2.format).toBe('Test');
+ expect(story2.zoom).toBe(1);
+ expect(story2.start).toBe('Untitled');
+ });
+
+ it('Should write tag colors', () => {
+ // Add some passages.
+ s.addPassage(new Passage('Start', 'Content'));
+ s.addPassage(new Passage('Untitled', 'Some stuff'));
+
+ // Add tag colors.
+ s.tagColors = {
+ bar: 'green',
+ foo: 'red',
+ qaz: 'blue'
+ };
+
+ // Convert to Twee.
+ const t = s.toTwee();
+
+ // Convert back into Story.
+ const story2 = TweeParser.parse(t);
+
+ // Test for tag colors
+ expect(story2.tagColors.bar).toBe('green');
+ expect(story2.tagColors.foo).toBe('red');
+ expect(story2.tagColors.qaz).toBe('blue');
+ });
+
+ it('Should encode "script" tag', () => {
+ // Add passages.
+ s.addPassage(new Passage('Test', 'Test', ['script']));
+ s.addPassage(new Passage('Start', 'Content'));
+
+ // Convert into Twee.
+ const t = s.toTwee();
+
+ // Convert back into Story.
+ const story = TweeParser.parse(t);
+
+ // Search for 'script'.
+ const p = story.getPassagesByTag('script');
+
+ // Test for passage text.
+ expect(p[0].text).toBe('Test');
+ });
+
+ it('Should encode "stylesheet" tag', () => {
+ // Add passages.
+ s.addPassage(new Passage('Test', 'Test', ['stylesheet']));
+ s.addPassage(new Passage('Start', 'Content'));
+
+ // Convert into Twee.
+ const t = s.toTwee();
+
+ // Convert back into Story.
+ const story = TweeParser.parse(t);
+
+ // Search for 'stylesheet'.
+ const p = story.getPassagesByTag('stylesheet');
+
+ // Test for passage text.
+ expect(p[0].text).toBe('Test');
+ });
+ });
+
+ describe('toTwine2HTML()', () => {
+ let s = null;
+
+ beforeEach(() => {
+ s = new Story();
+ });
+
+ it('Should throw error if no starting passage', function () {
+ // No start set.
+ expect(() => { s.toTwine2HTML(); }).toThrow();
+ });
+
+ it('Should throw error if starting passage cannot be found', function () {
+ // Set start.
+ s.start = 'Unknown';
+ // Has a start, but not part of collection.
+ expect(() => { s.toTwine2HTML(); }).toThrow();
+ });
+
+ it('Should encode name', () => {
+ // Add passage.
+ s.addPassage(new Passage('Start', 'Word'));
+ // Set start.
+ s.start = 'Start';
+ // Set name.
+ s.name = 'Test';
+ // Create HTML.
+ const result = s.toTwine2HTML();
+ // Expect the name to be encoded.
+ expect(result.includes(' {
+ // Add passage.
+ s.addPassage(new Passage('Start', 'Word'));
+ // Set start.
+ s.start = 'Start';
+ // Set IFID.
+ s.IFID = 'B94AC8AD-03E3-4496-96C8-FE958645FE61';
+ // Create HTML.
+ const result = s.toTwine2HTML();
+ // Expect the IFID to be encoded.
+ expect(result.includes('ifid="B94AC8AD-03E3-4496-96C8-FE958645FE61"')).toBe(true);
+ });
+
+ it('Should encode stylesheet passages', () => {
+ // Add passage.
+ s.addPassage(new Passage('Start', 'Word'));
+ // Set start.
+ s.start = 'Start';
+ // Add a stylesheet passage.
+ s.addPassage(new Passage('Test', 'Word', ['stylesheet']));
+ // Create HTML.
+ const result = s.toTwine2HTML();
+ // Expect the stylesheet passage text to be encoded.
+ expect(result.includes('