squiffy-runtime 6.0.0-alpha.15 → 6.0.0-alpha.18

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.
Files changed (45) hide show
  1. package/dist/animation.d.ts +11 -0
  2. package/dist/events.d.ts +8 -3
  3. package/dist/import.d.ts +4 -0
  4. package/dist/linkHandler.d.ts +8 -0
  5. package/dist/pluginManager.d.ts +23 -0
  6. package/dist/squiffy.runtime.d.ts +2 -2
  7. package/dist/squiffy.runtime.global.js +126 -0
  8. package/dist/squiffy.runtime.global.js.map +1 -0
  9. package/dist/squiffy.runtime.js +8785 -487
  10. package/dist/squiffy.runtime.js.map +1 -0
  11. package/dist/state.d.ts +19 -0
  12. package/dist/textProcessor.d.ts +8 -13
  13. package/dist/types.d.ts +5 -2
  14. package/dist/types.plugins.d.ts +27 -0
  15. package/dist/updater.d.ts +2 -0
  16. package/dist/utils.d.ts +1 -2
  17. package/package.json +12 -5
  18. package/src/__snapshots__/squiffy.runtime.test.ts.snap +53 -19
  19. package/src/animation.ts +68 -0
  20. package/src/events.ts +9 -10
  21. package/src/import.ts +5 -0
  22. package/src/linkHandler.ts +18 -0
  23. package/src/pluginManager.ts +74 -0
  24. package/src/plugins/animate.ts +97 -0
  25. package/src/plugins/index.ts +13 -0
  26. package/src/plugins/live.ts +83 -0
  27. package/src/plugins/random.ts +22 -0
  28. package/src/plugins/replaceLabel.ts +22 -0
  29. package/src/plugins/rotateSequence.ts +61 -0
  30. package/src/squiffy.runtime.test.ts +306 -134
  31. package/src/squiffy.runtime.ts +460 -332
  32. package/src/state.ts +106 -0
  33. package/src/textProcessor.ts +61 -164
  34. package/src/types.plugins.ts +41 -0
  35. package/src/types.ts +5 -2
  36. package/src/updater.ts +77 -0
  37. package/src/utils.ts +15 -12
  38. package/vite.config.ts +36 -0
  39. package/vitest.config.ts +9 -0
  40. package/vitest.setup.ts +16 -0
  41. package/dist/events.js +0 -35
  42. package/dist/squiffy.runtime.test.js +0 -394
  43. package/dist/textProcessor.js +0 -166
  44. package/dist/types.js +0 -1
  45. package/dist/utils.js +0 -14
@@ -1,8 +1,8 @@
1
- import { expect, test, beforeEach, afterEach, vi } from 'vitest';
2
- import fs from 'fs/promises';
3
- import globalJsdom from 'global-jsdom';
4
- import { init } from './squiffy.runtime.js';
5
- import { compile as squiffyCompile } from 'squiffy-compiler';
1
+ import { expect, test, beforeEach, afterEach, vi } from "vitest";
2
+ import fs from "fs/promises";
3
+ import globalJsdom from "global-jsdom";
4
+ import { init } from "./squiffy.runtime.js";
5
+ import { compile as squiffyCompile } from "squiffy-compiler";
6
6
 
7
7
  const html = `
8
8
  <!DOCTYPE html>
@@ -24,11 +24,11 @@ const compile = async (script: string) => {
24
24
  });
25
25
 
26
26
  if (!compileResult.success) {
27
- throw new Error('Compile failed');
27
+ throw new Error("Compile failed");
28
28
  }
29
29
 
30
30
  const story = compileResult.output.story;
31
- const js = compileResult.output.js.map(jsLines => new Function('squiffy', 'get', 'set', jsLines.join('\n')));
31
+ const js = compileResult.output.js.map(jsLines => new Function("squiffy", "get", "set", jsLines.join("\n")));
32
32
 
33
33
  return {
34
34
  story: {
@@ -36,26 +36,28 @@ const compile = async (script: string) => {
36
36
  ...story,
37
37
  },
38
38
  };
39
- }
39
+ };
40
40
 
41
41
  const initScript = async (script: string) => {
42
- const element = document.getElementById('squiffy');
42
+ const element = document.getElementById("squiffy");
43
43
 
44
44
  if (!element) {
45
- throw new Error('Element not found');
45
+ throw new Error("Element not found");
46
46
  }
47
47
 
48
48
  const compileResult = await compile(script);
49
49
 
50
- const squiffyApi = init({
50
+ const squiffyApi = await init({
51
51
  element: element,
52
52
  story: compileResult.story,
53
53
  });
54
54
 
55
+ await squiffyApi.begin();
56
+
55
57
  return {
56
58
  squiffyApi,
57
59
  element
58
- }
60
+ };
59
61
  };
60
62
 
61
63
  const findLink = (element: HTMLElement, linkType: string, linkText: string, onlyEnabled: boolean = false) => {
@@ -66,12 +68,12 @@ const findLink = (element: HTMLElement, linkType: string, linkText: string, only
66
68
  };
67
69
 
68
70
  const getTestOutput = () => {
69
- const testElement = document.getElementById('test');
71
+ const testElement = document.getElementById("test");
70
72
  if (!testElement) {
71
- throw new Error('Test element not found');
73
+ throw new Error("Test element not found");
72
74
  }
73
75
  return testElement.innerText;
74
- }
76
+ };
75
77
 
76
78
  let cleanup: { (): void };
77
79
 
@@ -88,76 +90,118 @@ test('"Hello world" script should run', async () => {
88
90
  expect(element.innerHTML).toMatchSnapshot();
89
91
  });
90
92
 
91
- test('Click a section link', async () => {
92
- const script = await fs.readFile('../examples/test/example.squiffy', 'utf-8');
93
+ test("Click a section link", async () => {
94
+ const script = await fs.readFile("../examples/test/example.squiffy", "utf-8");
93
95
  const { squiffyApi, element } = await initScript(script);
94
96
  expect(element.innerHTML).toMatchSnapshot();
95
97
 
96
- expect(element.querySelectorAll('a.squiffy-link').length).toBe(10);
97
- const linkToPassage = findLink(element, 'passage', 'a link to a passage');
98
- const section3Link = findLink(element, 'section', 'section 3');
98
+ expect(element.querySelectorAll("a.squiffy-link").length).toBe(10);
99
+ const linkToPassage = findLink(element, "passage", "a link to a passage");
100
+ const section3Link = findLink(element, "section", "section 3");
99
101
 
100
102
  expect(linkToPassage).not.toBeNull();
101
103
  expect(section3Link).not.toBeNull();
102
- expect(linkToPassage.classList).not.toContain('disabled');
104
+ expect(linkToPassage.classList).not.toContain("disabled");
103
105
 
104
106
  const handler = vi.fn();
105
- const off = squiffyApi.on('linkClick', handler);
107
+ const off = squiffyApi.on("linkClick", handler);
106
108
 
107
- const handled = squiffyApi.clickLink(section3Link);
109
+ const handled = await squiffyApi.clickLink(section3Link);
108
110
  expect(handled).toBe(true);
109
111
 
110
112
  expect(element.innerHTML).toMatchSnapshot();
111
113
 
112
114
  // passage link is from the previous section, so should be unclickable
113
- expect(squiffyApi.clickLink(linkToPassage)).toBe(false);
115
+ expect(await squiffyApi.clickLink(linkToPassage)).toBe(false);
114
116
 
115
117
  // await for the event to be processed
116
118
  await Promise.resolve();
117
119
 
118
120
  expect(handler).toHaveBeenCalledTimes(1);
119
121
  expect(handler).toHaveBeenCalledWith(
120
- expect.objectContaining({ linkType: 'section' })
122
+ expect.objectContaining({ linkType: "section" })
121
123
  );
122
124
  off();
123
125
  });
124
126
 
125
- test('Click a passage link', async () => {
126
- const script = await fs.readFile('../examples/test/example.squiffy', 'utf-8');
127
+ test("Click a section link and go back", async () => {
128
+ const script = await fs.readFile("../examples/test/example.squiffy", "utf-8");
129
+ const { squiffyApi, element } = await initScript(script);
130
+
131
+ const linkToPassage = findLink(element, "passage", "a link to a passage");
132
+ const section3Link = findLink(element, "section", "section 3");
133
+ expect(section3Link).not.toBeNull();
134
+
135
+ await squiffyApi.clickLink(section3Link);
136
+
137
+ // passage link is from the previous section, so should be unclickable
138
+ expect(await squiffyApi.clickLink(linkToPassage)).toBe(false);
139
+
140
+ // now go back
141
+ squiffyApi.goBack();
142
+
143
+ expect(element.innerHTML).toMatchSnapshot();
144
+
145
+ // passage link should be clickable now
146
+ expect(await squiffyApi.clickLink(linkToPassage)).toBe(true);
147
+ });
148
+
149
+ test("Click a passage link", async () => {
150
+ const script = await fs.readFile("../examples/test/example.squiffy", "utf-8");
127
151
  const { squiffyApi, element } = await initScript(script);
128
152
  expect(element.innerHTML).toMatchSnapshot();
129
153
 
130
- expect(element.querySelectorAll('a.squiffy-link').length).toBe(10);
131
- const linkToPassage = findLink(element, 'passage', 'a link to a passage');
132
- const section3Link = findLink(element, 'section', 'section 3');
154
+ expect(element.querySelectorAll("a.squiffy-link").length).toBe(10);
155
+ const linkToPassage = findLink(element, "passage", "a link to a passage");
156
+ const section3Link = findLink(element, "section", "section 3");
133
157
 
134
158
  expect(linkToPassage).not.toBeNull();
135
159
  expect(section3Link).not.toBeNull();
136
- expect(linkToPassage.classList).not.toContain('disabled');
160
+ expect(linkToPassage.classList).not.toContain("disabled");
137
161
 
138
162
  const handler = vi.fn();
139
- const off = squiffyApi.on('linkClick', handler);
163
+ const off = squiffyApi.on("linkClick", handler);
140
164
 
141
- const handled = squiffyApi.clickLink(linkToPassage);
165
+ const handled = await squiffyApi.clickLink(linkToPassage);
142
166
  expect(handled).toBe(true);
143
167
 
144
- expect(linkToPassage.classList).toContain('disabled');
168
+ expect(linkToPassage.classList).toContain("disabled");
145
169
  expect(element.innerHTML).toMatchSnapshot();
146
170
 
147
171
  // shouldn't be able to click it again
148
- expect(squiffyApi.clickLink(linkToPassage)).toBe(false);
172
+ expect(await squiffyApi.clickLink(linkToPassage)).toBe(false);
149
173
 
150
174
  // await for the event to be processed
151
175
  await Promise.resolve();
152
176
 
153
177
  expect(handler).toHaveBeenCalledTimes(1);
154
178
  expect(handler).toHaveBeenCalledWith(
155
- expect.objectContaining({ linkType: 'passage' })
179
+ expect.objectContaining({ linkType: "passage" })
156
180
  );
157
181
  off();
158
182
  });
159
183
 
160
- test('Run JavaScript functions', async () => {
184
+ test("Click a passage link and go back", async () => {
185
+ const script = await fs.readFile("../examples/test/example.squiffy", "utf-8");
186
+ const { squiffyApi, element } = await initScript(script);
187
+
188
+ const linkToPassage = findLink(element, "passage", "a link to a passage");
189
+
190
+ await squiffyApi.clickLink(linkToPassage);
191
+
192
+ // the passage link was clicked, so should be disabled
193
+ expect(linkToPassage.classList).toContain("disabled");
194
+
195
+ // now go back
196
+ squiffyApi.goBack();
197
+
198
+ expect(element.innerHTML).toMatchSnapshot();
199
+
200
+ // passage link should be clickable now
201
+ expect(await squiffyApi.clickLink(linkToPassage)).toBe(true);
202
+ });
203
+
204
+ test("Run JavaScript functions", async () => {
161
205
  const script = `
162
206
  document.getElementById('test').innerText = 'Initial JavaScript';
163
207
  @set some_string = some_value
@@ -180,32 +224,33 @@ test('Run JavaScript functions', async () => {
180
224
  document.getElementById('test').innerText = "In other section";
181
225
  `;
182
226
 
183
- const clickContinue = () => {
184
- const continueLink = findLink(element, 'section', 'Continue...', true);
227
+ const clickContinue = async () => {
228
+ const continueLink = findLink(element, "section", "Continue...", true);
185
229
  expect(continueLink).not.toBeNull();
186
- const handled = squiffyApi.clickLink(continueLink);
230
+ expect(continueLink).not.toBeUndefined();
231
+ const handled = await squiffyApi.clickLink(continueLink);
187
232
  expect(handled).toBe(true);
188
233
  };
189
234
 
190
235
  const { squiffyApi, element } = await initScript(script);
191
236
 
192
- expect(getTestOutput()).toBe('Initial JavaScript');
193
- clickContinue();
237
+ expect(getTestOutput()).toBe("Initial JavaScript");
238
+ await clickContinue();
194
239
 
195
- expect(getTestOutput()).toBe('Value: 5');
196
- clickContinue();
240
+ expect(getTestOutput()).toBe("Value: 5");
241
+ await clickContinue();
197
242
 
198
- expect(getTestOutput()).toBe('Value: some_value');
199
- clickContinue();
243
+ expect(getTestOutput()).toBe("Value: some_value");
244
+ await clickContinue();
200
245
 
201
- expect(getTestOutput()).toBe('Value: 10');
246
+ expect(getTestOutput()).toBe("Value: 10");
202
247
 
203
- clickContinue();
204
- clickContinue();
205
- expect(getTestOutput()).toBe('Value: 11');
248
+ await clickContinue();
249
+ await clickContinue();
250
+ expect(getTestOutput()).toBe("Value: 11");
206
251
 
207
- clickContinue();
208
- expect(getTestOutput()).toBe('In other section');
252
+ await clickContinue();
253
+ expect(getTestOutput()).toBe("In other section");
209
254
  });
210
255
 
211
256
  function safeQuerySelector(name: string) {
@@ -220,33 +265,33 @@ function getPassageContent(element: HTMLElement, section: string, passage: strin
220
265
  return element.querySelector(`[data-source='[[${safeQuerySelector(section)}]][${safeQuerySelector(passage)}]']`)?.textContent || null;
221
266
  }
222
267
 
223
- test('Update default section output', async () => {
268
+ test("Update default section output", async () => {
224
269
  const { squiffyApi, element } = await initScript("Hello world");
225
- let defaultOutput = getSectionContent(element, '_default');
226
- expect(defaultOutput).toBe('Hello world');
270
+ let defaultOutput = getSectionContent(element, "_default");
271
+ expect(defaultOutput).toBe("Hello world");
227
272
  expect(element.innerHTML).toMatchSnapshot();
228
273
 
229
274
  const updated = await compile("Updated content");
230
275
  squiffyApi.update(updated.story);
231
- defaultOutput = getSectionContent(element, '_default');
232
- expect(defaultOutput).toBe('Updated content');
276
+ defaultOutput = getSectionContent(element, "_default");
277
+ expect(defaultOutput).toBe("Updated content");
233
278
  expect(element.innerHTML).toMatchSnapshot();
234
279
  });
235
280
 
236
- test.each(['a', 'a\'1'])('Update passage output - passage name "%s"', async (name) => {
281
+ test.each(["a", "a'1"])('Update passage output - passage name "%s"', async (name) => {
237
282
  const { squiffyApi, element } = await initScript(`Click this: [${name}]
238
283
 
239
284
  [${name}]:
240
285
  Passage a content`);
241
286
 
242
- const link = findLink(element, 'passage', name);
243
- const handled = squiffyApi.clickLink(link);
287
+ const link = findLink(element, "passage", name);
288
+ const handled = await squiffyApi.clickLink(link);
244
289
  expect(handled).toBe(true);
245
290
 
246
- let defaultOutput = getSectionContent(element, '_default');
291
+ let defaultOutput = getSectionContent(element, "_default");
247
292
  expect(defaultOutput).toBe(`Click this: ${name}`);
248
- let passageOutput = getPassageContent(element, '_default', name);
249
- expect(passageOutput).toBe('Passage a content');
293
+ let passageOutput = getPassageContent(element, "_default", name);
294
+ expect(passageOutput).toBe("Passage a content");
250
295
  expect(element.innerHTML).toMatchSnapshot();
251
296
 
252
297
  const updated = await compile(`Click this: [${name}]
@@ -256,71 +301,71 @@ Updated passage content`);
256
301
 
257
302
  squiffyApi.update(updated.story);
258
303
 
259
- defaultOutput = getSectionContent(element, '_default');
304
+ defaultOutput = getSectionContent(element, "_default");
260
305
  expect(defaultOutput).toBe(`Click this: ${name}`);
261
306
 
262
- passageOutput = getPassageContent(element, '_default', name);
263
- expect(passageOutput).toBe('Updated passage content');
307
+ passageOutput = getPassageContent(element, "_default", name);
308
+ expect(passageOutput).toBe("Updated passage content");
264
309
  expect(element.innerHTML).toMatchSnapshot();
265
310
  });
266
311
 
267
- test('Delete section', async () => {
312
+ test("Delete section", async () => {
268
313
  const { squiffyApi, element } = await initScript(`Click this: [[a]]
269
314
 
270
315
  [[a]]:
271
316
  New section`);
272
317
 
273
- const link = findLink(element, 'section', 'a');
274
- const handled = squiffyApi.clickLink(link);
318
+ const link = findLink(element, "section", "a");
319
+ const handled = await squiffyApi.clickLink(link);
275
320
  expect(handled).toBe(true);
276
321
 
277
- let defaultOutput = getSectionContent(element, '_default');
278
- expect(defaultOutput).toBe('Click this: a');
279
- let sectionOutput = getSectionContent(element, 'a');
280
- expect(sectionOutput).toBe('New section');
322
+ let defaultOutput = getSectionContent(element, "_default");
323
+ expect(defaultOutput).toBe("Click this: a");
324
+ let sectionOutput = getSectionContent(element, "a");
325
+ expect(sectionOutput).toBe("New section");
281
326
  expect(element.innerHTML).toMatchSnapshot();
282
327
 
283
- const updated = await compile(`Click this: [[a]]`);
328
+ const updated = await compile("Click this: [[a]]");
284
329
 
285
330
  squiffyApi.update(updated.story);
286
331
 
287
- defaultOutput = getSectionContent(element, '_default');
288
- expect(defaultOutput).toBe('Click this: a');
289
- sectionOutput = getSectionContent(element, 'a');
332
+ defaultOutput = getSectionContent(element, "_default");
333
+ expect(defaultOutput).toBe("Click this: a");
334
+ sectionOutput = getSectionContent(element, "a");
290
335
  expect(sectionOutput).toBeNull();
291
336
 
292
337
  expect(element.innerHTML).toMatchSnapshot();
293
338
  });
294
339
 
295
- test('Delete passage', async () => {
340
+ test("Delete passage", async () => {
296
341
  const { squiffyApi, element } = await initScript(`Click this: [a]
297
342
 
298
343
  [a]:
299
344
  New passage`);
300
345
 
301
- const link = findLink(element, 'passage', 'a');
302
- const handled = squiffyApi.clickLink(link);
346
+ const link = findLink(element, "passage", "a");
347
+ const handled = await squiffyApi.clickLink(link);
303
348
  expect(handled).toBe(true);
304
349
 
305
- let defaultOutput = getSectionContent(element, '_default');
306
- expect(defaultOutput).toBe('Click this: a');
307
- let passageOutput = getPassageContent(element, '_default', 'a');
308
- expect(passageOutput).toBe('New passage');
350
+ let defaultOutput = getSectionContent(element, "_default");
351
+ expect(defaultOutput).toBe("Click this: a");
352
+ let passageOutput = getPassageContent(element, "_default", "a");
353
+ expect(passageOutput).toBe("New passage");
309
354
  expect(element.innerHTML).toMatchSnapshot();
310
355
 
311
- const updated = await compile(`Click this: [a]`);
356
+ const updated = await compile("Click this: [a]");
312
357
 
313
358
  squiffyApi.update(updated.story);
314
359
 
315
- defaultOutput = getSectionContent(element, '_default');
316
- expect(defaultOutput).toBe('Click this: a');
317
- passageOutput = getPassageContent(element, '_default', 'a');
360
+ defaultOutput = getSectionContent(element, "_default");
361
+ expect(defaultOutput).toBe("Click this: a");
362
+ passageOutput = getPassageContent(element, "_default", "a");
318
363
  expect(passageOutput).toBeNull();
319
364
 
320
365
  expect(element.innerHTML).toMatchSnapshot();
321
366
  });
322
367
 
323
- test('Clicked passage links remain disabled after an update', async () => {
368
+ test("Clicked passage links remain disabled after an update", async () => {
324
369
  const { squiffyApi, element } = await initScript(`Click one of these: [a] [b]
325
370
 
326
371
  [a]:
@@ -331,9 +376,9 @@ Output for passage B.`);
331
376
 
332
377
  // click linkA
333
378
 
334
- let linkA = findLink(element, 'passage', 'a');
335
- expect(linkA.classList).not.toContain('disabled');
336
- expect(squiffyApi.clickLink(linkA)).toBe(true);
379
+ let linkA = findLink(element, "passage", "a");
380
+ expect(linkA.classList).not.toContain("disabled");
381
+ expect(await squiffyApi.clickLink(linkA)).toBe(true);
337
382
 
338
383
  const updated = await compile(`Click one of these (updated): [a] [b]
339
384
 
@@ -347,18 +392,18 @@ Output for passage B.`);
347
392
 
348
393
  // linkA should still be disabled
349
394
 
350
- linkA = findLink(element, 'passage', 'a');
351
- expect(linkA.classList).toContain('disabled');
352
- expect(squiffyApi.clickLink(linkA)).toBe(false);
395
+ linkA = findLink(element, "passage", "a");
396
+ expect(linkA.classList).toContain("disabled");
397
+ expect(await squiffyApi.clickLink(linkA)).toBe(false);
353
398
 
354
399
  // linkB should still be enabled
355
400
 
356
- let linkB = findLink(element, 'passage', 'b');
357
- expect(linkB.classList).not.toContain('disabled');
358
- expect(squiffyApi.clickLink(linkB)).toBe(true);
401
+ const linkB = findLink(element, "passage", "b");
402
+ expect(linkB.classList).not.toContain("disabled");
403
+ expect(await squiffyApi.clickLink(linkB)).toBe(true);
359
404
  });
360
405
 
361
- test('Deleting the current section activates the previous section', async () => {
406
+ test("Deleting the current section activates the previous section", async () => {
362
407
  const { squiffyApi, element } = await initScript(`Choose a section: [[a]] [[b]], or passage [start1].
363
408
 
364
409
  [start1]:
@@ -372,17 +417,17 @@ Output for section B.`);
372
417
 
373
418
  // click linkA
374
419
 
375
- let linkA = findLink(element, 'section', 'a');
376
- let linkB = findLink(element, 'section', 'b');
377
- expect(linkA.classList).not.toContain('disabled');
378
- expect(squiffyApi.clickLink(linkA)).toBe(true);
420
+ const linkA = findLink(element, "section", "a");
421
+ let linkB = findLink(element, "section", "b");
422
+ expect(linkA.classList).not.toContain("disabled");
423
+ expect(await squiffyApi.clickLink(linkA)).toBe(true);
379
424
 
380
425
  // can't click start1 passage as we're in section [[a]] now
381
- let linkStart1 = findLink(element, 'passage', 'start1');
382
- expect(squiffyApi.clickLink(linkStart1)).toBe(false);
426
+ let linkStart1 = findLink(element, "passage", "start1");
427
+ expect(await squiffyApi.clickLink(linkStart1)).toBe(false);
383
428
 
384
429
  // can't click linkB as we're in section [[a]] now
385
- expect(squiffyApi.clickLink(linkB)).toBe(false);
430
+ expect(await squiffyApi.clickLink(linkB)).toBe(false);
386
431
 
387
432
  // now we delete section [[a]]
388
433
 
@@ -400,22 +445,22 @@ Passage in section B.`);
400
445
  squiffyApi.update(updated.story);
401
446
 
402
447
  // We're in the first section, so the start1 passage should be clickable now
403
- linkStart1 = findLink(element, 'passage', 'start1');
404
- expect(squiffyApi.clickLink(linkStart1)).toBe(true);
448
+ linkStart1 = findLink(element, "passage", "start1");
449
+ expect(await squiffyApi.clickLink(linkStart1)).toBe(true);
405
450
 
406
451
  // We're in the first section, so linkB should be clickable now
407
- linkB = findLink(element, 'section', 'b');
408
- expect(squiffyApi.clickLink(linkB)).toBe(true);
452
+ linkB = findLink(element, "section", "b");
453
+ expect(await squiffyApi.clickLink(linkB)).toBe(true);
409
454
 
410
455
  // and the passage [b1] within it should be clickable
411
- const linkB1 = findLink(element, 'passage', 'b1');
412
- expect(squiffyApi.clickLink(linkB1)).toBe(true);
456
+ const linkB1 = findLink(element, "passage", "b1");
457
+ expect(await squiffyApi.clickLink(linkB1)).toBe(true);
413
458
  });
414
459
 
415
- test('Embed text from a section', async () => {
460
+ test("Embed text from a section", async () => {
416
461
  const script = `
417
462
  [[section1]]:
418
- Here is some text from the next section: {section2}
463
+ Here is some text from the next section: {{embed "section2"}}
419
464
 
420
465
  [[section2]]:
421
466
  Text from next section.
@@ -423,14 +468,14 @@ Text from next section.
423
468
 
424
469
  const { element } = await initScript(script);
425
470
 
426
- let output = getSectionContent(element, 'section1');
427
- expect(output).toBe('Here is some text from the next section: Text from next section.');
471
+ const output = getSectionContent(element, "section1");
472
+ expect(output).toBe("Here is some text from the next section: Text from next section.");
428
473
  });
429
474
 
430
- test('Embed text from a passage', async () => {
475
+ test("Embed text from a passage", async () => {
431
476
  const script = `
432
477
  [[section1]]:
433
- Here is some text from a passage: {passage}
478
+ Here is some text from a passage: {{embed "passage"}}
434
479
 
435
480
  [passage]:
436
481
  Text from a passage.
@@ -438,14 +483,14 @@ Text from a passage.
438
483
 
439
484
  const { element } = await initScript(script);
440
485
 
441
- let output = getSectionContent(element, 'section1');
442
- expect(output).toBe('Here is some text from a passage: Text from a passage.');
486
+ const output = getSectionContent(element, "section1");
487
+ expect(output).toBe("Here is some text from a passage: Text from a passage.");
443
488
  });
444
489
 
445
- test('Update section with embedded text', async () => {
490
+ test("Update section with embedded text", async () => {
446
491
  const script = `
447
492
  [[section1]]:
448
- Here is some text from the next section: {section2}
493
+ Here is some text from the next section: {{embed "section2"}}
449
494
 
450
495
  [[section2]]:
451
496
  Text from next section.
@@ -453,12 +498,12 @@ Text from next section.
453
498
 
454
499
  const { squiffyApi, element } = await initScript(script);
455
500
 
456
- let output = getSectionContent(element, 'section1');
457
- expect(output).toBe('Here is some text from the next section: Text from next section.');
501
+ let output = getSectionContent(element, "section1");
502
+ expect(output).toBe("Here is some text from the next section: Text from next section.");
458
503
 
459
504
  const script2 = `
460
505
  [[section1]]:
461
- Here is an updated script with text from the next section: {section2}
506
+ Here is an updated script with text from the next section: {{embed "section2"}}
462
507
 
463
508
  [[section2]]:
464
509
  Updated text from next section.
@@ -466,17 +511,17 @@ Updated text from next section.
466
511
 
467
512
  const updated = await compile(script2);
468
513
  squiffyApi.update(updated.story);
469
- output = getSectionContent(element, 'section1');
514
+ output = getSectionContent(element, "section1");
470
515
 
471
516
  // NOTE: The embedded text does not currently get updated. We would need to track where the embedded
472
517
  // text has been written.
473
- expect(output).toBe('Here is an updated script with text from the next section: Text from next section.');
518
+ expect(output).toBe("Here is an updated script with text from the next section: Text from next section.");
474
519
  });
475
520
 
476
- test('Update passage with embedded text', async () => {
521
+ test("Update passage with embedded text", async () => {
477
522
  const script = `
478
523
  [[section1]]:
479
- Here is some text from a passage: {passage}
524
+ Here is some text from a passage: {{embed "passage"}}
480
525
 
481
526
  [passage]:
482
527
  Text from a passage.
@@ -484,12 +529,12 @@ Text from a passage.
484
529
 
485
530
  const { squiffyApi, element } = await initScript(script);
486
531
 
487
- let output = getSectionContent(element, 'section1');
488
- expect(output).toBe('Here is some text from a passage: Text from a passage.');
532
+ let output = getSectionContent(element, "section1");
533
+ expect(output).toBe("Here is some text from a passage: Text from a passage.");
489
534
 
490
535
  const script2 = `
491
536
  [[section1]]:
492
- Here is an updated script with text from a passage: {passage}
537
+ Here is an updated script with text from a passage: {{embed "passage"}}
493
538
 
494
539
  [passage]:
495
540
  Updated text from a passage.
@@ -497,9 +542,136 @@ Updated text from a passage.
497
542
 
498
543
  const updated = await compile(script2);
499
544
  squiffyApi.update(updated.story);
500
- output = getSectionContent(element, 'section1');
545
+ output = getSectionContent(element, "section1");
501
546
 
502
547
  // NOTE: The embedded text does not currently get updated. We would need to track where the embedded
503
548
  // text has been written.
504
- expect(output).toBe('Here is an updated script with text from a passage: Text from a passage.');
549
+ expect(output).toBe("Here is an updated script with text from a passage: Text from a passage.");
550
+ });
551
+
552
+ test("Clear entire script, then update", async () => {
553
+ const script = "Original content";
554
+
555
+ const { squiffyApi, element } = await initScript(script);
556
+
557
+ let output = getSectionContent(element, "_default");
558
+ expect(output).toBe("Original content");
559
+
560
+ const script2 = "";
561
+ const update2 = await compile(script2);
562
+ squiffyApi.update(update2.story);
563
+ output = getSectionContent(element, "_default");
564
+ expect(output).toBeNull();
565
+
566
+ const script3 = "New content";
567
+ const update3 = await compile(script3);
568
+ squiffyApi.update(update3.story);
569
+ output = getSectionContent(element, "_default");
570
+ expect(output).toBe("New content");
571
+ });
572
+
573
+ test("Changing the start section", async () => {
574
+ const script = "Original content in default section";
575
+
576
+ const { squiffyApi, element } = await initScript(script);
577
+
578
+ let output = getSectionContent(element, "_default");
579
+ expect(output).toBe("Original content in default section");
580
+
581
+ const script2 = `[[new]]:
582
+ This is the new start section`;
583
+ const update2 = await compile(script2);
584
+ squiffyApi.update(update2.story);
585
+ output = getSectionContent(element, "new");
586
+ expect(output).toBe("This is the new start section");
587
+ });
588
+
589
+ test("Going back handling @clear and attribute changes", async () => {
590
+ const script = `
591
+ Choose: [a], [b]
592
+
593
+ [a]:
594
+ @set test = 123
595
+ You chose a. Now [[continue]]
596
+
597
+ [b]:
598
+ @set test = 456
599
+ You chose b. Now [[continue]]
600
+
601
+ [[continue]]:
602
+ @set test = 789
603
+ Now choose: [c], [d]
604
+
605
+ [c]:
606
+ @clear
607
+ @set test = 321
608
+ You chose c. Now [[finish]]
609
+
610
+ [d]:
611
+ You chose d. Now [[finish]]
612
+
613
+ [[finish]]:
614
+ Done.
615
+ `;
616
+
617
+ const { squiffyApi, element } = await initScript(script);
618
+
619
+ const linkA = findLink(element, "passage", "a");
620
+
621
+ // Click link to "a"
622
+ await squiffyApi.clickLink(linkA);
623
+
624
+ // "a" should be marked as seen
625
+ expect(squiffyApi.get("_seen_sections") as []).toContain("a");
626
+ expect(squiffyApi.get("test")).toBe(123);
627
+
628
+ // Go back
629
+ squiffyApi.goBack();
630
+
631
+ // "a" should not be marked as seen
632
+ expect(squiffyApi.get("_seen_sections") as []).not.toContain("a");
633
+ expect(squiffyApi.get("test")).toBe(null);
634
+
635
+ // Click link to "b", then click link to "continue"
636
+ let linkB = findLink(element, "passage", "b");
637
+ await squiffyApi.clickLink(linkB);
638
+ expect(squiffyApi.get("test")).toBe(456);
639
+ let continueLink = findLink(element, "section", "continue");
640
+ await squiffyApi.clickLink(continueLink);
641
+
642
+ expect(squiffyApi.get("_seen_sections") as []).toContain("b");
643
+ expect(squiffyApi.get("test")).toBe(789);
644
+
645
+ // Go back
646
+ squiffyApi.goBack();
647
+
648
+ // "b" should still be marked as seen, because we didn't go back that far yet
649
+ expect(squiffyApi.get("_seen_sections") as []).toContain("b");
650
+ expect(squiffyApi.get("test")).toBe(456);
651
+
652
+ // Go back
653
+ squiffyApi.goBack();
654
+
655
+ // Now "b" should not be marked as seen
656
+ expect(squiffyApi.get("_seen_sections") as []).not.toContain("b");
657
+ expect(squiffyApi.get("test")).toBe(null);
658
+
659
+ // Click "b" again, then "continue", and "c", which clears the screen
660
+ linkB = findLink(element, "passage", "b");
661
+ await squiffyApi.clickLink(linkB);
662
+ continueLink = findLink(element, "section", "continue");
663
+ await squiffyApi.clickLink(continueLink);
664
+ const linkC = findLink(element, "passage", "c");
665
+ await squiffyApi.clickLink(linkC);
666
+
667
+ // Should match snapshot, where passage "c" is the only thing visible
668
+ expect(element.innerHTML).toMatchSnapshot();
669
+ expect(squiffyApi.get("test")).toBe(321);
670
+
671
+ // Go back
672
+ squiffyApi.goBack();
673
+
674
+ // Should match snapshot, where the previous text is visible again
675
+ expect(element.innerHTML).toMatchSnapshot();
676
+ expect(squiffyApi.get("test")).toBe(789);
505
677
  });