Snowman Dev Update (2.2: 26 June 2022)

Snowman 2.2 Development Updates:


Passage tags and styling

In previous versions of Snowman, any use of tags would enable the use of the same CSS class. In the current Twine Cookbook version, the following was possible:

:: StoryTitle
CSS and Passage Tags in Snowman

:: UserStylesheet[stylesheet]
.grey {
  background: grey;
  color: white;
}
.yellow {
  background: yellow;
  color: black;
}

:: Start[grey]
<% $("body").toggleClass("grey") %>
This passage has a grey background and white text.

[[Second]]

:: Second[yellow]
<% $("body").toggleClass("yellow") %>
This passage has a yellow background and black text.

In Snowman 2.2, this is changed slightly into the “Harlowe Standard” where the <tw-passage> element has the tags attribute set to whatever the showing passage is also using. The new functionality is the following:

:: StoryTitle
CSS and Passage Tags in Snowman 2.2

:: UserStylesheet[stylesheet]
[tags="grey"] {
    background: grey;
}

[tags="yellow"] {
    background: yellow;
    color: black;
}

:: Start[grey]
This passage has a grey background and (default) white text.
[[Second]]

:: Second[yellow]
This passage has a yellow background and black text.

This change came about as part of the switch over to embracing Twine 2 HTML elements rather than using selectors. Because the tw-passage element is a translation of the currently showing Passage entry, it makes the most sense for it to also have the element’s metadata as its attributes.

delay() Testing

In the Twine Cookbook, there is an example using the _.delay() Underscore.js method. When run, it shows text to a reader after five seconds. Normally, this is not much of a problem. However, trying to automate tests where some content might not appear after the initial rendering of the page has been quite annoying.

In Jest, an individual test should not exceed 5 seconds. By design, the _.delay() method example automatically exceeds the 5 seconds default value for the test. Accounting for Puppeteer, which introduces some slight latency because of the time needed to run the code in a headless browser, the average time to run the test became between 7-10 seconds.

Finding a solution required reading the documentation on how timers work with Jest and Puppeteer. It is possible to set an extended timeout for tests within a single file using its setTimeout() method. Next, in Puppeteer, the waitForSelector() method can be used to wait for one or more elements to appear within the DOM of the page. A new, working solution appears as the following:

/**
 * @jest-environment puppeteer
 */
const ShellJS = require('shelljs');
const path = require('path');
require('expect-puppeteer');

jest.setTimeout(15000);

ShellJS.exec(`extwee -c -s dist/format.js -i ${path.join(__dirname, 'index.twee')} -o ${path.join(__dirname, 'index.html')}`);
  
describe('Cookbook - Delayed Text', () => {
  beforeAll(async () => {
    await page.goto(`file://${path.join(__dirname, 'index.html')}`);
  });

  afterAll(async () => {
    ShellJS.rm(`${path.join(__dirname, 'index.html')}`);
  });
 
  it('Should display "5 seconds" after at least five seconds', async () => {
    await page.waitForSelector('#timer');
    await expect(page).toMatch('It has been 5 seconds. Show the text!');
   });
 });

In the updated code, Jest is set to wait for an extended timeout of up to 15 seconds. In practice, even accounting for the built-in delay and headless browser rendering latency, the test has been finishing in under 10 seconds across Windows and macOS computers I was able to run the same test on to compare their timing.

Listening for events

In Snowman 2.0, the list of all possible events were the following:

  • sm.story.started
  • sm.passage.hidden
  • sm.passage.showing
  • sm.checkpoint.added
  • sm.checkpoint.failed
  • sm.passage.shown
  • sm.story.error

In Snowman 2.2, this has changed significantly. First, instead of triggering on the window global, they are part of the State.events internal event emitter. This is designed to help authors better understand how to listen for story events, as there is only one source of them. Second, as the HTML5 History API is no longer being used, the checkpoint events are no longer applicable and have been removed. Third, instead of trying to capture and report every error across the story format, Snowman 2.2 allows them to happen. This was removed to help browsers more accurately report errors when and where they might occur, as the reporting functionality could sometimes confuse authors when trying to determine where an error had occurred originally.

Currently, the list of possible events in Snowman 2.2 is the following:

  • undo
  • redo
  • start
  • show

The reduced set is purposeful. In Snowman 2.0, my originally thinking was more events were a good thing, as it would give authors more options. However, in practice, and as shown in the code examples appearing in the Twine Cookbook, most authors did not use most of the events. For example, most authors preferred showing to hidden when reacting to story events. In that case, one event, show, could serve in the place of the two and more closely match the existing usage patterns.

Undo and Redo

As the last section eluded to, there are now undo and redo events. These are emitted when a reader clicks on the new Undo and Repo icons, triggering the event and passing along window.story.state to any listening functions

This past week, I spent some time working out how best to implement their effect on Story.history and possibly other values. I have not completely figured out exactly how I want them to work as I type this, and have added more work on their functionality as a TODO item for this coming week.

Cookbook Testing

Of the 43 Twine Cookbook examples I started with two weeks ago, Snowman 2.2 now tests against 31 of them. In most cases, writing a test has been something I created in less than five minutes. However, a number of examples, especially those referencing more legacy functionality, have produced much longer delays in development. I have decided, in most cases, to match the legacy functionality as an alias to newer methods and properties. In these cases, I have noted in the code itself where this is happening and will be updating the documentation when I work on it last.

As noted above, where Snowman 2.2 departs the most from Snowman 2.0 is in event handling and names. Any examples using the older events will need new entries for the Twine Cookbook. I have a running list of currently eight new entries where this happens, but the list may increase I hopefully finish out creating tests for the remaining examples across this week.

Deadline of end-of-June 2022

These development posts started almost a month ago. As I write this, I am very close to releasing a preview build for the public to try and provide feedback. Once I finish out the Twine Cookbook testing, which will probably happen closer to the end of this coming week, my hope is to post about the work on the Twine Discord and IntFiction forum. I will then, assuming no major issues are found, submit a pull request for the inclusion of Snowman 2.2 whenever the next build of Twine happens.