From 06e7e66117d0e70126c9458cc10967f73997785d Mon Sep 17 00:00:00 2001 From: Dan Cox Date: Thu, 11 Jan 2024 21:06:46 -0500 Subject: [PATCH 1/2] Looser export but harder importing Twine 2 HTML --- index.js | 2 + package-lock.json | 7 + package.json | 1 + src/IFID/generate.js | 14 ++ src/Passage.js | 4 + src/Story.js | 163 +++++++++++++-------- src/Twine2HTML/compile.js | 36 ++++- src/Twine2HTML/parse.js | 36 ++--- test/IFID/IFID.Generate.test.js | 10 ++ test/Story.test.js | 12 -- test/Twine2HTML/Twine2HTML.Compile.test.js | 122 ++++----------- test/Twine2HTML/Twine2HTML.Parse.test.js | 25 +--- web-index.js | 2 + 13 files changed, 222 insertions(+), 212 deletions(-) create mode 100644 src/IFID/generate.js create mode 100644 test/IFID/IFID.Generate.test.js diff --git a/index.js b/index.js index 8103fdc1..ea316954 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ import { parse as parseTWS } from './src/TWS/parse.js'; import { compile as compileTwine1HTML } from './src/Twine1HTML/compile.js'; import { compile as compileTwine2HTML } from './src/Twine2HTML/compile.js'; import { compile as compileTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/compile.js'; +import { generate as generateIFID } from './src/IFID/generate.js'; import { Story } from './src/Story.js'; import Passage from './src/Passage.js'; import StoryFormat from './src/StoryFormat.js'; @@ -23,6 +24,7 @@ export { compileTwine1HTML, compileTwine2HTML, compileTwine2ArchiveHTML, + generateIFID, Story, Passage, StoryFormat diff --git a/package-lock.json b/package-lock.json index 36576bf3..f47038e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@babel/eslint-parser": "^7.23.3", "@babel/eslint-plugin": "^7.23.5", "@babel/preset-env": "^7.23.8", + "@types/uuid": "^9.0.7", "babel-loader": "^9.1.3", "clean-jsdoc-theme": "^4.2.17", "core-js": "^3.35.0", @@ -3520,6 +3521,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", diff --git a/package.json b/package.json index 41917ecd..d30ce1fb 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@babel/eslint-parser": "^7.23.3", "@babel/eslint-plugin": "^7.23.5", "@babel/preset-env": "^7.23.8", + "@types/uuid": "^9.0.7", "babel-loader": "^9.1.3", "clean-jsdoc-theme": "^4.2.17", "core-js": "^3.35.0", diff --git a/src/IFID/generate.js b/src/IFID/generate.js new file mode 100644 index 00000000..4dadca65 --- /dev/null +++ b/src/IFID/generate.js @@ -0,0 +1,14 @@ +import { v4 } from 'uuid'; + +/** + * Generates an IFID based the Treaty of Babel. + * @see {@link https://babel.ifarchive.org/babel_rev11.html#the-ifid-for-an-html-story-file} + * @function generate + * @description Generates a new IFID. + * @returns {string} IFID + */ +function generate () { + return v4().toUpperCase(); +} + +export { generate }; diff --git a/src/Passage.js b/src/Passage.js index a88091e4..4d9d26d7 100644 --- a/src/Passage.js +++ b/src/Passage.js @@ -57,6 +57,7 @@ export default class Passage { /** * @param {string} s - Name to replace + * @throws {Error} Name must be a String! */ set name (s) { if (typeof s === 'string') { @@ -74,6 +75,7 @@ export default class Passage { /** * @param {Array} t - Replacement array + * @throws {Error} Tags must be an array! */ set tags (t) { // Test if tags is an array @@ -93,6 +95,7 @@ export default class Passage { /** * @param {object} m - Replacement object + * @throws {Error} Metadata must be an object literal! */ set metadata (m) { // Test if metadata was an object @@ -111,6 +114,7 @@ export default class Passage { /** * @param {string} t - Replacement text + * @throws {Error} Text should be a String! */ set text (t) { // Test if text is a String diff --git a/src/Story.js b/src/Story.js index 30ccedb1..f1d8185e 100644 --- a/src/Story.js +++ b/src/Story.js @@ -1,5 +1,5 @@ import Passage from './Passage.js'; -import { v4 as uuidv4 } from 'uuid'; +import { generate as generateIFID } from './IFID/generate.js'; import { encode } from 'html-entities'; const creatorName = 'extwee'; @@ -119,13 +119,13 @@ class Story { } /** - * Interactive Fiction ID (IFID) of Story + * Interactive Fiction ID (IFID) of Story. * @returns {string} IFID */ get IFID () { return this.#_IFID; } /** - * @param {string} i - Replacement IFID + * @param {string} i - Replacement IFID. */ set IFID (i) { if (typeof i === 'string') { @@ -456,7 +456,7 @@ class Story { if (this.IFID === '') { // Generate a new IFID for this work. // Twine 2 uses v4 (random) UUIDs, using only capital letters. - metadata.ifid = uuidv4().toUpperCase(); + metadata.ifid = generateIFID(); } else { // Use existing (non-default) value. metadata.ifid = this.IFID; @@ -521,27 +521,41 @@ class Story { * * See: Twine 2 HTML Output * (https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md) + * + * The only required attributes are `name` and `ifid`. All others are optional. + * * @returns {string} Twine 2 HTML string */ toTwine2HTML () { - // Prepare HTML content. + // Twine 2 HTML starts with a element. + // See: Twine 2 HTML Output + + // name: (string) Required. The name of the story. + // + // Maps to . + // let storyData = `. + // + // Check if IFID exists. + if (this.IFID !== '') { + // Write the existing IFID. + storyData += ` ifid="${ this.IFID }"`; + } else { + // Generate a new IFID. + // Twine 2 uses v4 (random) UUIDs, using only capital letters. + storyData += ` ifid="${ generateIFID() }"`; } - // Try to find starting passage. - // If it doesn't exist, we throw an error. - if (this.getPassageByName(this.start) === null) { - // We can't create a Twine 2 HTML file without a starting passage. - throw new Error('Starting passage not found'); - } + // Passage Identification (PID) counter. + // (Twine 2 starts with 1, so we mirror that.) + let PIDcounter = 1; // Set initial PID value. let startPID = 1; @@ -557,66 +571,89 @@ class Story { PIDcounter++; }); - // Set starting passage PID. - storyData += ` startnode="${startPID}"`; - - // Defaults to 'extwee' if missing. - storyData += ` creator="${ encode( this.creator ) }"`; - - // Default to extwee version. - storyData += ` creator-version="${this.creatorVersion}"`; - - // Check if IFID exists. - if (this.IFID !== '') { - // Write the existing IFID. - storyData += ` ifid="${this.IFID}"`; - } else { - // Generate a new IFID. - // Twine 2 uses v4 (random) UUIDs, using only capital letters. - storyData += ` ifid="${uuidv4().toUpperCase()}"`; + // startnode: (integer) Optional. + // + // Maps to . + // + // Check if startnode exists. + if(this.start !== '') { + // Set starting passage PID. + storyData += ` startnode="${startPID}"`; + } + + // creator: (string) Optional. The name of the program that created the story. + // Maps to . + if(this.creator !== '') { + // Write existing creator. + storyData += ` creator="${ encode( this.creator ) }"`; } - // Write existing or default value. - storyData += ` zoom="${this.zoom}"`; + // creator-version: (string) Optional. The version of the program that created the story. + // Maps to . + if(this.creatorVersion !== '') { + // Default to extwee version. + storyData += ` creator-version="${this.creatorVersion}"`; + } - // Write existing or default value. - storyData += ` format="${ encode(this.#_format) }"`; + // zoom: (decimal) Optional. The zoom level of the story. + // Maps to . + if(this.zoom !== 0) { + // Write existing or default value. + storyData += ` zoom="${this.zoom}"`; + } - // Write existing or default value. - storyData += ` format-version="${this.#_formatVersion}"`; + // format: (string) Optional. The format of the story. + // Maps to . + if(this.format !== '') { + // Write existing or default value. + storyData += ` format="${this.format}"`; + } + + // format-version: (string) Optional. The version of the format of the story. + // Maps to . + if(this.formatVersion !== '') { + // Write existing or default value. + storyData += ` format-version="${this.formatVersion}"`; + } // Add the default attributes. storyData += ' options hidden>\n'; - // Start the STYLE. - storyData += '\t\n'; + // Concatenate passages. + stylesheetPassages.forEach((passage) => { + // Add text of passages. + storyData += passage.text; + }); - // Start the SCRIPT. - storyData += '\t\n'; + // Concatenate passages. + scriptPassages.forEach((passage) => { + // Add text of passages. + storyData += passage.text; + }); + + // Close SCRIPT. + storyData += '\n'; + } // Reset the PID counter. PIDcounter = 1; @@ -656,6 +693,8 @@ class Story { // Return Twine 1 HTML content. return outputContents; } + + } export { Story, creatorName, creatorVersion }; diff --git a/src/Twine2HTML/compile.js b/src/Twine2HTML/compile.js index 221ce4ce..78ade3d3 100644 --- a/src/Twine2HTML/compile.js +++ b/src/Twine2HTML/compile.js @@ -3,20 +3,47 @@ import StoryFormat from '../StoryFormat.js'; /** * Write a combination of Story + StoryFormat into Twine 2 HTML file. + * @function compile * @param {Story} story - Story object to write. * @param {StoryFormat} storyFormat - StoryFormat to write. - * @returns {string} Twine 2 HTML. + * @returns {string} Twine 2 HTML based on StoryFormat and Story. + * @throws {Error} If story is not instanceof Story. + * @throws {Error} If storyFormat is not instanceof StoryFormat. + * @throws {Error} If storyFormat.source is empty string. */ function compile (story, storyFormat) { + // Check if story is instanceof Story. if (!(story instanceof Story)) { throw new Error('Error: story must be a Story object!'); } + // Check if storyFormat is instanceof StoryFormat. if (!(storyFormat instanceof StoryFormat)) { throw new Error('storyFormat must be a StoryFormat object!'); } - let outputContents = ''; + // Check if storyFormat.source is empty string. + if (storyFormat.source === '') { + throw new Error('StoryFormat source empty string!'); + } + + /** + * There are two required attributes: + * - story.IFID: UUIDv4 + * - story.name: string (non-empty) + */ + + // Check if story.IFID is UUIDv4 formatted. + if (story.IFID.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[89ABab][0-9A-F]{3}-[0-9A-F]{12}$/) === null) { + throw new Error('Story IFID is invalid!'); + } + + // Check if story.name is empty string. + if (story.name === '') { + throw new Error('Story name empty string!'); + } + + // Translate story to Twine 2 HTML. const storyData = story.toTwine2HTML(); // Replace the story name in the source file. @@ -25,11 +52,8 @@ function compile (story, storyFormat) { // Replace the story data. storyFormat.source = storyFormat.source.replaceAll(/{{STORY_DATA}}/gm, storyData); - // Combine everything together. - outputContents += storyFormat.source; - // Return content. - return outputContents; + return storyFormat.source; } export { compile }; diff --git a/src/Twine2HTML/parse.js b/src/Twine2HTML/parse.js index 41dd161e..0536d132 100644 --- a/src/Twine2HTML/parse.js +++ b/src/Twine2HTML/parse.js @@ -10,6 +10,10 @@ import { decode } from 'html-entities'; * (https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md) * @param {string} content - Twine 2 HTML content to parse. * @returns {Story} Story + * @throws {TypeError} If content is not a string. + * @throws {Error} If content is not Twine 2 HTML. + * @throws {Error} If passage is missing name. + * @throws {Error} If passage is missing PID. */ function parse (content) { // Create new story. @@ -125,9 +129,6 @@ function parse (content) { if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'startnode')) { // Take string value and convert to Int startNode = Number.parseInt(storyData.attributes.startnode, 10); - } else { - // Throw error without start node. - throw new Error('Missing startnode in !'); } // Pull out the `` element. @@ -229,7 +230,7 @@ function parse (content) { // Update PID pid = Number.parseInt(attr.pid, 10); } else { - console.warn('Passages are required to have PID. Will not add!'); + throw new Error('Passages are required to have PID!'); } // Check the current PID against startNode number. @@ -239,24 +240,15 @@ function parse (content) { story.start = name; } - // If passage is missing name and PID (required attributes), - // they are not added. - if (name !== null && pid !== -1) { - // Add a new Passage into an array - story.addPassage( - new Passage( - decode(name), - decode(text), - tags.map(tag => decode(tag)), - metadata - ) - ); - } - } - - // There was an invalid startNode. - if (story.start === '') { - throw new Error('startNode does not exist within passages!'); + // Add a new Passage into an array + story.addPassage( + new Passage( + decode(name), + decode(text), + tags.map(tag => decode(tag)), + metadata + ) + ); } // Look for the style element diff --git a/test/IFID/IFID.Generate.test.js b/test/IFID/IFID.Generate.test.js new file mode 100644 index 00000000..bda16da1 --- /dev/null +++ b/test/IFID/IFID.Generate.test.js @@ -0,0 +1,10 @@ +import { generate } from '../../src/IFID/generate.js'; + +describe('src/IFID/generate.js', () => { + describe('generate()', () => { + it('should generate a valid IFID', () => { + const ifid = generate(); + expect(ifid).toMatch(/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/); + }); + }); +}); diff --git a/test/Story.test.js b/test/Story.test.js index 25fc6789..9da17d96 100644 --- a/test/Story.test.js +++ b/test/Story.test.js @@ -556,18 +556,6 @@ describe('Story', () => { 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')); diff --git a/test/Twine2HTML/Twine2HTML.Compile.test.js b/test/Twine2HTML/Twine2HTML.Compile.test.js index 9643e6fd..c785617f 100644 --- a/test/Twine2HTML/Twine2HTML.Compile.test.js +++ b/test/Twine2HTML/Twine2HTML.Compile.test.js @@ -3,6 +3,8 @@ import { parse as parseTwine2HTML } from '../../src/Twine2HTML/parse.js'; import { compile as compileTwine2HTML } from '../../src/Twine2HTML/compile.js'; import { Story } from '../../src/Story.js'; import Passage from '../../src/Passage.js'; +import StoryFormat from '../../src/StoryFormat.js'; +import { generate as generateIFID } from '../../src/IFID/generate.js'; import { readFileSync } from 'node:fs'; describe('Twine2HTMLCompiler', () => { @@ -76,148 +78,84 @@ describe('Twine2HTMLCompiler', () => { expect(tags).toBe(tags2); }); - it('Should not add optional position to passages', () => { - // Create Story. - const story = new Story(); - // Add passage. - story.addPassage(new Passage('A')); - // Add passage. - story.addPassage(new Passage('B')); - // Add StoryTitle - story.addPassage(new Passage('StoryTitle', 'Title')); - // Add Start - story.addPassage(new Passage('Start', 'Content')); - - // Read StoryFormat. - const fr2 = readFileSync('test/StoryFormat/StoryFormatParser/format.js', 'utf-8'); - - // Parse StoryFormat. - const storyFormat = parseStoryFormat(fr2); - - // Set start - story.start = 'Start'; - - // Write out HTML with story and storyFormat. - // (Will add position to passages without them.) - const fr3 = compileTwine2HTML(story, storyFormat); - - // Parse new HTML file. - const story2 = parseTwine2HTML(fr3); - - // Verify none of the directly created passages have position. - story.passages.forEach((passage) => { - expect(Object.prototype.hasOwnProperty.call(passage.metadata, 'position')).toBe(false); - }); - - // Verify none parsed passages have position. - story2.passages.forEach((passage) => { - expect(Object.prototype.hasOwnProperty.call(passage.metadata, 'position')).toBe(false); - }); - }); - - it("Don't write creator if missing originally", () => { + it('Throw error if IFID does not exist', () => { // Create a new story. const story = new Story(); // Create a passage. story.addPassage(new Passage('A')); - // Create another passage. - story.addPassage(new Passage('B')); - - // Create another passage. - story.addPassage(new Passage('StoryTitle', 'Title')); - - // Add Start - story.addPassage(new Passage('Start', 'Content')); - - // Set start - story.start = 'Start'; - // Read StoryFormat. const fr2 = readFileSync('test/StoryFormat/StoryFormatParser/format.js', 'utf-8'); // Parse StoryFormat. const storyFormat = parseStoryFormat(fr2); - // Write the HTML. - const fr3 = compileTwine2HTML(story, storyFormat); - - // Parse HTML. - const story2 = parseTwine2HTML(fr3); - - // Test creator (should be default) - expect(story.creator).toBe('extwee'); - - // Test parsed creator (should be default) - expect(story2.creator).toBe('extwee'); - - // Creator should be the same - expect(story.creator).toBe(story2.creator); + expect(() => { + compileTwine2HTML(story, storyFormat); + }).toThrow(); }); - it('Throw error if StoryTitle does not exist', () => { + it('Throw error if starting passage property does not exist', () => { // Create a new story. const story = new Story(); // Create a passage. story.addPassage(new Passage('A')); + // Create StoryTitle + story.addPassage(new Passage('StoryTitle', 'Name')); + + // Set a passage that doesn't exist + story.start = 'Nope'; + // Read StoryFormat. const fr2 = readFileSync('test/StoryFormat/StoryFormatParser/format.js', 'utf-8'); // Parse StoryFormat. const storyFormat = parseStoryFormat(fr2); + // Throws error. expect(() => { compileTwine2HTML(story, storyFormat); }).toThrow(); }); - it('Throw error if no start or Start exists', () => { + it('Throw error if source is empty string in StoryFormat', () => { // Create a new story. const story = new Story(); - // Create a passage. - story.addPassage(new Passage('A')); - - // Create StoryTitle - story.addPassage(new Passage('StoryTitle', 'Name')); - - // Read StoryFormat. - const fr2 = readFileSync('test/StoryFormat/StoryFormatParser/format.js', 'utf-8'); + // Create StoryFormat. + const sf = new StoryFormat(); - // Parse StoryFormat. - const storyFormat = parseStoryFormat(fr2); + // Set source to empty string. + sf.source = ''; // Throws error. expect(() => { - compileTwine2HTML(story, storyFormat); + compileTwine2HTML(story, sf); }).toThrow(); }); - it('Throw error if starting passage property does not exist', () => { + it('Throw error if story name is empty string', () => { // Create a new story. const story = new Story(); - // Create a passage. - story.addPassage(new Passage('A')); - - // Create StoryTitle - story.addPassage(new Passage('StoryTitle', 'Name')); + // Create StoryFormat. + const sf = new StoryFormat(); - // Set a passage that doesn't exist - story.start = 'Nope'; + // Set source to non-empty string. + sf.source = 'Test'; - // Read StoryFormat. - const fr2 = readFileSync('test/StoryFormat/StoryFormatParser/format.js', 'utf-8'); + // Generate IFID (to avoid throwing error). + story.IFID = generateIFID(); - // Parse StoryFormat. - const storyFormat = parseStoryFormat(fr2); + // Set story name to empty string. + story.name = ''; // Throws error. expect(() => { - compileTwine2HTML(story, storyFormat); + compileTwine2HTML(story, sf); }).toThrow(); }); }); diff --git a/test/Twine2HTML/Twine2HTML.Parse.test.js b/test/Twine2HTML/Twine2HTML.Parse.test.js index 8caf78f2..93e9beaa 100644 --- a/test/Twine2HTML/Twine2HTML.Parse.test.js +++ b/test/Twine2HTML/Twine2HTML.Parse.test.js @@ -120,21 +120,22 @@ describe('Twine2HTMLParser', () => { expect(stylesheetPassages.length).toBe(1); }); - it('Should throw error if startNode is missing', () => { - const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingStartnode.html', 'utf-8'); - expect(() => { parseTwine2HTML(fr); }).toThrow(); - }); - it('Should throw error if passage name is missing', () => { const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingPassageName.html', 'utf-8'); expect(() => { parseTwine2HTML(fr); }).toThrow(); }); - it('Should throw error without PID', () => { + it('Should throw error if passage PID is missing', () => { const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingPID.html', 'utf-8'); expect(() => { parseTwine2HTML(fr); }).toThrow(); }); + it('Should parse HTML without passage start node', () => { + const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingStartNode.html', 'utf-8'); + const story = parseTwine2HTML(fr); + expect(story.start).toBe(''); + }); + it('Should parse tag colors', () => { const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/tagColors.html', 'utf-8'); const story = parseTwine2HTML(fr); @@ -143,18 +144,6 @@ describe('Twine2HTMLParser', () => { expect(tagColors.a).toBe('red'); }); - it('Should throw error if startnode is missing', () => { - const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingStartnode.html', 'utf-8'); - expect(() => { parseTwine2HTML(fr); }).toThrow(); - }); - - it('Should throw error when startnode has PID that does not exist', () => { - const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/lyingStartnode.html', 'utf-8'); - expect(() => { - parseTwine2HTML(fr); - }).toThrow(); - }); - it('Do not update name and color if those attributes do not exist', () => { const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/lyingTagColors.html', 'utf-8'); const story = parseTwine2HTML(fr); diff --git a/web-index.js b/web-index.js index 64663aa5..712e1d5e 100644 --- a/web-index.js +++ b/web-index.js @@ -8,6 +8,7 @@ import { parse as parseTWS } from './src/TWS/parse.js'; import { compile as compileTwine1HTML } from './src/Twine1HTML/compile.js'; import { compile as compileTwine2HTML } from './src/Twine2HTML/compile.js'; import { compile as compileTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/compile.js'; +import { generate as generateIFID } from './src/IFID/generate.js'; import { Story } from './src/Story.js'; import Passage from './src/Passage.js'; import StoryFormat from './src/StoryFormat.js'; @@ -23,6 +24,7 @@ window.Extwee = { compileTwine1HTML, compileTwine2HTML, compileTwine2ArchiveHTML, + generateIFID, Story, Passage, StoryFormat From fbdbc629b0c9ae2ae411da89a26f04db0e609685 Mon Sep 17 00:00:00 2001 From: Dan Cox Date: Thu, 11 Jan 2024 21:53:18 -0500 Subject: [PATCH 2/2] Using 'Untitled Story' as default story name --- src/Story.js | 4 ++-- test/JSON/JSON.Parse.test.js | 4 ++-- test/Story.test.js | 7 ++++++- test/Twine2HTML/Twine2HTML.Parse.test.js | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Story.js b/src/Story.js index f1d8185e..e214766b 100644 --- a/src/Story.js +++ b/src/Story.js @@ -74,7 +74,7 @@ class Story { * Creates a story. * @param {string} name - Name of the story. */ - constructor (name = '') { + constructor (name = 'Untitled Story') { // Every story has a name. this.name = name; // Store the creator. @@ -297,7 +297,7 @@ class Story { // If it does, we ignore it and return. if (this.getPassageByName(p.name) !== null) { // Warn user - console.warn('Ignored passage with same name as existing one!'); + console.warn('Warning: Ignored passage with same name as existing one!'); // return; } diff --git a/test/JSON/JSON.Parse.test.js b/test/JSON/JSON.Parse.test.js index 7eeef5f3..cb2307c3 100644 --- a/test/JSON/JSON.Parse.test.js +++ b/test/JSON/JSON.Parse.test.js @@ -16,7 +16,7 @@ describe('JSON', () => { const s = parseJSON(r.toJSON()); // Check all properties. - expect(s.name).toBe(''); + expect(s.name).toBe('Untitled Story'); expect(Object.keys(s.tagColors).length).toBe(0); expect(s.IFID).toBe(''); expect(s.start).toBe(''); @@ -52,7 +52,7 @@ describe('JSON', () => { 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 = parseJSON(s); - expect(r.name).toBe(''); + expect(r.name).toBe('Untitled Story'); expect(Object.keys(r.tagColors).length).toBe(1); expect(r.IFID).toBe('DD'); expect(r.start).toBe('Start'); diff --git a/test/Story.test.js b/test/Story.test.js index 9da17d96..dab50d55 100644 --- a/test/Story.test.js +++ b/test/Story.test.js @@ -28,6 +28,11 @@ describe('Story', () => { s = new Story('Test'); expect(s.name).toBe('Test'); }); + + it('Should have default name', () => { + s = new Story(); + expect(s.name).toBe('Untitled Story'); + }); }); describe('creator', () => { @@ -401,7 +406,7 @@ describe('Story', () => { const s = new Story(); // Convert to string and then back to object. const result = JSON.parse(s.toJSON()); - expect(result.name).toBe(''); + expect(result.name).toBe('Untitled Story'); expect(Object.keys(result.tagColors).length).toBe(0); expect(result.ifid).toBe(''); expect(result.start).toBe(''); diff --git a/test/Twine2HTML/Twine2HTML.Parse.test.js b/test/Twine2HTML/Twine2HTML.Parse.test.js index 93e9beaa..6dfb22cc 100644 --- a/test/Twine2HTML/Twine2HTML.Parse.test.js +++ b/test/Twine2HTML/Twine2HTML.Parse.test.js @@ -37,7 +37,7 @@ describe('Twine2HTMLParser', () => { it('Should have default name', () => { const fr = readFileSync('test/Twine2HTML/Twine2HTMLParser/missingName.html', 'utf-8'); const story = parseTwine2HTML(fr); - expect(story.name).toBe(''); + expect(story.name).toBe('Untitled Story'); }); it('Should set a missing IFID to an empty string', () => {