From 51a65ad370b7e497e8aebd14c5117be59270b1fb Mon Sep 17 00:00:00 2001 From: Dan Cox Date: Wed, 10 Jan 2024 23:08:53 -0500 Subject: [PATCH] Adding access to Story.passages --- docs/objects/story.md | 3 +- package-lock.json | 4 +- src/Story.js | 54 +++++++++++++--------- test/Story.test.js | 49 ++++++++++---------- test/Twine2HTML/Twine2HTML.Compile.test.js | 8 ++-- 5 files changed, 63 insertions(+), 55 deletions(-) diff --git a/docs/objects/story.md b/docs/objects/story.md index 2bcf9828..4030ab9e 100644 --- a/docs/objects/story.md +++ b/docs/objects/story.md @@ -12,7 +12,7 @@ Depending on the incoming format or creation method, many possible properties ca - format ( string ) Name of the story format for Twine 2 HTML. - formatVersion ( string ) Semantic version of the named story format for Twine 2 HTML or Twee 3. - zoom ( float ) Zoom level for Twine 2 HTML or Twee 3. -- passages ( array(Passage) ) **[Read-only]** Collection of internal passages. +- passages ( array(Passage) ) Collection of internal passages. - creator ( string ) Name of story creation tool. (Defaults to "Extwee"). - creatorVersion ( string ) Semantic version of named creation tool. - metadata ( object ) Key-value pairs of metadata values. @@ -41,7 +41,6 @@ As collections of passages, each **Story** has multiple methods for accessing an - `getPassagesByTags(string)`: Returns an array of any passages containing a particular tag value. - `getPassageByName(string)`: Returns either `null`` or the named passage. - `size()`: Returns the number of passages in the collection. -- `forEachPassage(callback)`: Allows for iterating over the passage collection. ## Passage Creation Example diff --git a/package-lock.json b/package-lock.json index a76016fb..36576bf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "extwee", - "version": "2.2.0", + "version": "2.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "extwee", - "version": "2.2.0", + "version": "2.2.1", "license": "MIT", "dependencies": { "commander": "^11.1.0", diff --git a/src/Story.js b/src/Story.js index 344ae6e5..30ccedb1 100644 --- a/src/Story.js +++ b/src/Story.js @@ -255,6 +255,32 @@ class Story { } } + /** + * Passages in Story. + * @returns {Array} Passages + * @property {Array} passages - Passages + */ + get passages () { return this.#_passages; } + + /** + * Set passages in Story. + * @param {Array} p - Replacement passages + * @property {Array} passages - Passages + * @throws {Error} Passages must be an Array! + * @throws {Error} Passages must be an Array of Passage objects! + */ + set passages (p) { + if (Array.isArray(p)) { + if (p.every((passage) => passage instanceof Passage)) { + this.#_passages = p; + } else { + throw new Error('Passages must be an Array of Passage objects!'); + } + } else { + throw new Error('Passages must be an Array!'); + } + } + /** * Add a passage to the story. * `StoryData` will override story metadata and `StoryTitle` will override story name. @@ -375,24 +401,6 @@ class Story { return this.#_passages.length; } - /** - * forEach-style iterator of passages in Story. - * @param {Function} callback - Callback function - */ - forEachPassage (callback) { - // Check if argument is a function. - if (typeof callback !== 'function') { - // Throw error - throw new Error('Callback must be a function!'); - } - - // Use internal forEach. - this.#_passages.forEach((element, index) => { - // Call callback function with element and index. - callback(element, index); - }); - } - /** * Export Story as JSON representation. * @returns {string} JSON string. @@ -414,7 +422,7 @@ class Story { }; // For each passage, convert into simple object. - this.forEachPassage((p) => { + this.passages.forEach((p) => { s.passages.push({ name: p.name, tags: p.tags, @@ -500,7 +508,7 @@ class Story { outputContents += '\n\n'; // For each passage, append it to the output. - this.forEachPassage((passage) => { + this.passages.forEach((passage) => { outputContents += passage.toTwee(); }); @@ -539,7 +547,7 @@ class Story { let startPID = 1; // We have to do a bit of nonsense here. // Twine 2 HTML cares about PID values. - this.forEachPassage((p) => { + this.passages.forEach((p) => { // Have we found the starting passage? if (p.name === this.start) { // If so, set the PID based on index. @@ -614,7 +622,7 @@ class Story { PIDcounter = 1; // Build the passages HTML. - this.forEachPassage((passage) => { + this.passages.forEach((passage) => { // Append each passage element using the PID counter. storyData += passage.toTwine2HTML(PIDcounter); // Increase counter inside loop. @@ -640,7 +648,7 @@ class Story { let outputContents = ''; // Process passages (if any). - this.forEachPassage((p) => { + this.passages.forEach((p) => { // Output HTML output per passage. outputContents += `\t${p.toTwine1HTML()}`; }); diff --git a/test/Story.test.js b/test/Story.test.js index 55fca64a..25fc6789 100644 --- a/test/Story.test.js +++ b/test/Story.test.js @@ -223,6 +223,31 @@ describe('Story', () => { }); }); + describe('passages', () => { + let s = null; + + beforeEach(() => { + s = new Story(); + }); + + it('Set passages to a new array containing at least one Passage', () => { + s.passages = [new Passage()]; + expect(s.passages.length).toBe(1); + }); + + it('Should throw error if trying to set to a non-Array type', () => { + expect(() => { + s.passages = null; + }).toThrow(); + }); + + it('Should throw error if trying to set to an array containing non-Passage types', () => { + expect(() => { + s.passages = [null]; + }).toThrow(); + }); + }); + describe('addPassage()', () => { let s = null; @@ -351,30 +376,6 @@ describe('Story', () => { }); }); - describe('forEachPassage()', () => { - let s = null; - - beforeEach(() => { - s = new Story(); - }); - - it('forEachPassage() - should return if non-function', () => { - s.addPassage(new Passage('A')); - s.addPassage(new Passage('B')); - let passageNames = ''; - s.forEachPassage((p) => { - passageNames += p.name; - }); - expect(passageNames).toBe('AB'); - }); - - it('forEachPassage() - should throw error if non-function', () => { - expect(() => { - s.forEachPassage(null); - }).toThrow(); - }); - }); - describe('size()', () => { let s = null; diff --git a/test/Twine2HTML/Twine2HTML.Compile.test.js b/test/Twine2HTML/Twine2HTML.Compile.test.js index 76552188..9643e6fd 100644 --- a/test/Twine2HTML/Twine2HTML.Compile.test.js +++ b/test/Twine2HTML/Twine2HTML.Compile.test.js @@ -62,12 +62,12 @@ describe('Twine2HTMLCompiler', () => { let tags2 = ''; // Combine contents of tags. - story.forEachPassage((p) => { + story.passages.forEach((p) => { tags += p.tags.join(''); }); // Combine contents of tags. - story2.forEachPassage((p) => { + story2.passages.forEach((p) => { tags2 += p.tags.join(''); }); @@ -105,12 +105,12 @@ describe('Twine2HTMLCompiler', () => { const story2 = parseTwine2HTML(fr3); // Verify none of the directly created passages have position. - story.forEachPassage((passage) => { + story.passages.forEach((passage) => { expect(Object.prototype.hasOwnProperty.call(passage.metadata, 'position')).toBe(false); }); // Verify none parsed passages have position. - story2.forEachPassage((passage) => { + story2.passages.forEach((passage) => { expect(Object.prototype.hasOwnProperty.call(passage.metadata, 'position')).toBe(false); }); });