squiffy-runtime 6.0.0-alpha.2 → 6.0.0-alpha.20

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 (43) hide show
  1. package/LICENSE +22 -0
  2. package/dist/animation.d.ts +11 -0
  3. package/dist/events.d.ts +20 -0
  4. package/dist/import.d.ts +4 -0
  5. package/dist/linkHandler.d.ts +8 -0
  6. package/dist/pluginManager.d.ts +23 -0
  7. package/dist/squiffy.runtime.d.ts +3 -34
  8. package/dist/squiffy.runtime.global.js +126 -0
  9. package/dist/squiffy.runtime.global.js.map +1 -0
  10. package/dist/squiffy.runtime.js +8779 -547
  11. package/dist/squiffy.runtime.js.map +1 -0
  12. package/dist/squiffy.runtime.test.d.ts +1 -0
  13. package/dist/state.d.ts +19 -0
  14. package/dist/textProcessor.d.ts +11 -0
  15. package/dist/types.d.ts +57 -0
  16. package/dist/types.plugins.d.ts +27 -0
  17. package/dist/updater.d.ts +2 -0
  18. package/dist/utils.d.ts +1 -0
  19. package/package.json +26 -5
  20. package/src/__snapshots__/squiffy.runtime.test.ts.snap +138 -0
  21. package/src/animation.ts +68 -0
  22. package/src/events.ts +41 -0
  23. package/src/import.ts +5 -0
  24. package/src/linkHandler.ts +18 -0
  25. package/src/pluginManager.ts +74 -0
  26. package/src/plugins/animate.ts +97 -0
  27. package/src/plugins/index.ts +13 -0
  28. package/src/plugins/live.ts +83 -0
  29. package/src/plugins/random.ts +22 -0
  30. package/src/plugins/replaceLabel.ts +22 -0
  31. package/src/plugins/rotateSequence.ts +61 -0
  32. package/src/squiffy.runtime.test.ts +677 -0
  33. package/src/squiffy.runtime.ts +528 -499
  34. package/src/state.ts +106 -0
  35. package/src/textProcessor.ts +76 -0
  36. package/src/types.plugins.ts +41 -0
  37. package/src/types.ts +81 -0
  38. package/src/updater.ts +77 -0
  39. package/src/utils.ts +17 -0
  40. package/tsconfig.json +4 -1
  41. package/vite.config.ts +36 -0
  42. package/vitest.config.ts +9 -0
  43. package/vitest.setup.ts +16 -0
@@ -0,0 +1,677 @@
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
+
7
+ const html = `
8
+ <!DOCTYPE html>
9
+ <html>
10
+ <head>
11
+ </head>
12
+ <body>
13
+ <div id="squiffy">
14
+ </div>
15
+ <div id="test">
16
+ </div>
17
+ </body>
18
+ </html>
19
+ `;
20
+
21
+ const compile = async (script: string) => {
22
+ const compileResult = await squiffyCompile({
23
+ script: script,
24
+ });
25
+
26
+ if (!compileResult.success) {
27
+ throw new Error("Compile failed");
28
+ }
29
+
30
+ const story = compileResult.output.story;
31
+ const js = compileResult.output.js.map(jsLines => new Function("squiffy", "get", "set", jsLines.join("\n")));
32
+
33
+ return {
34
+ story: {
35
+ js: js as any,
36
+ ...story,
37
+ },
38
+ };
39
+ };
40
+
41
+ const initScript = async (script: string) => {
42
+ const element = document.getElementById("squiffy");
43
+
44
+ if (!element) {
45
+ throw new Error("Element not found");
46
+ }
47
+
48
+ const compileResult = await compile(script);
49
+
50
+ const squiffyApi = await init({
51
+ element: element,
52
+ story: compileResult.story,
53
+ });
54
+
55
+ await squiffyApi.begin();
56
+
57
+ return {
58
+ squiffyApi,
59
+ element
60
+ };
61
+ };
62
+
63
+ const findLink = (element: HTMLElement, linkType: string, linkText: string, onlyEnabled: boolean = false) => {
64
+ const links = onlyEnabled
65
+ ? element.querySelectorAll(`.squiffy-output-section:last-child a.squiffy-link.link-${linkType}`)
66
+ : element.querySelectorAll(`a.squiffy-link.link-${linkType}`);
67
+ return Array.from(links).find(link => link.textContent === linkText && (onlyEnabled ? !link.classList.contains("disabled") : true)) as HTMLElement;
68
+ };
69
+
70
+ const getTestOutput = () => {
71
+ const testElement = document.getElementById("test");
72
+ if (!testElement) {
73
+ throw new Error("Test element not found");
74
+ }
75
+ return testElement.innerText;
76
+ };
77
+
78
+ let cleanup: { (): void };
79
+
80
+ beforeEach(() => {
81
+ cleanup = globalJsdom(html);
82
+ });
83
+
84
+ afterEach(() => {
85
+ cleanup();
86
+ });
87
+
88
+ test('"Hello world" script should run', async () => {
89
+ const { element } = await initScript("Hello world");
90
+ expect(element.innerHTML).toMatchSnapshot();
91
+ });
92
+
93
+ test("Click a section link", async () => {
94
+ const script = await fs.readFile("../examples/test/example.squiffy", "utf-8");
95
+ const { squiffyApi, element } = await initScript(script);
96
+ expect(element.innerHTML).toMatchSnapshot();
97
+
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");
101
+
102
+ expect(linkToPassage).not.toBeNull();
103
+ expect(section3Link).not.toBeNull();
104
+ expect(linkToPassage.classList).not.toContain("disabled");
105
+
106
+ const handler = vi.fn();
107
+ const off = squiffyApi.on("linkClick", handler);
108
+
109
+ const handled = await squiffyApi.clickLink(section3Link);
110
+ expect(handled).toBe(true);
111
+
112
+ expect(element.innerHTML).toMatchSnapshot();
113
+
114
+ // passage link is from the previous section, so should be unclickable
115
+ expect(await squiffyApi.clickLink(linkToPassage)).toBe(false);
116
+
117
+ // await for the event to be processed
118
+ await Promise.resolve();
119
+
120
+ expect(handler).toHaveBeenCalledTimes(1);
121
+ expect(handler).toHaveBeenCalledWith(
122
+ expect.objectContaining({ linkType: "section" })
123
+ );
124
+ off();
125
+ });
126
+
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");
151
+ const { squiffyApi, element } = await initScript(script);
152
+ expect(element.innerHTML).toMatchSnapshot();
153
+
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");
157
+
158
+ expect(linkToPassage).not.toBeNull();
159
+ expect(section3Link).not.toBeNull();
160
+ expect(linkToPassage.classList).not.toContain("disabled");
161
+
162
+ const handler = vi.fn();
163
+ const off = squiffyApi.on("linkClick", handler);
164
+
165
+ const handled = await squiffyApi.clickLink(linkToPassage);
166
+ expect(handled).toBe(true);
167
+
168
+ expect(linkToPassage.classList).toContain("disabled");
169
+ expect(element.innerHTML).toMatchSnapshot();
170
+
171
+ // shouldn't be able to click it again
172
+ expect(await squiffyApi.clickLink(linkToPassage)).toBe(false);
173
+
174
+ // await for the event to be processed
175
+ await Promise.resolve();
176
+
177
+ expect(handler).toHaveBeenCalledTimes(1);
178
+ expect(handler).toHaveBeenCalledWith(
179
+ expect.objectContaining({ linkType: "passage" })
180
+ );
181
+ off();
182
+ });
183
+
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 () => {
205
+ const script = `
206
+ document.getElementById('test').innerText = 'Initial JavaScript';
207
+ @set some_string = some_value
208
+ @set some_number = 5
209
+
210
+ +++Continue...
211
+ document.getElementById('test').innerText = "Value: " + get("some_number");
212
+ +++Continue...
213
+ document.getElementById('test').innerText = "Value: " + get("some_string");
214
+ set("some_number", 10);
215
+ +++Continue...
216
+ document.getElementById('test').innerText = "Value: " + get("some_number");
217
+ +++Continue...
218
+ @inc some_number
219
+ +++Continue...
220
+ document.getElementById('test').innerText = "Value: " + get("some_number");
221
+ +++Continue...
222
+ squiffy.story.go("other section");
223
+ [[other section]]:
224
+ document.getElementById('test').innerText = "In other section";
225
+ `;
226
+
227
+ const clickContinue = async () => {
228
+ const continueLink = findLink(element, "section", "Continue...", true);
229
+ expect(continueLink).not.toBeNull();
230
+ expect(continueLink).not.toBeUndefined();
231
+ const handled = await squiffyApi.clickLink(continueLink);
232
+ expect(handled).toBe(true);
233
+ };
234
+
235
+ const { squiffyApi, element } = await initScript(script);
236
+
237
+ expect(getTestOutput()).toBe("Initial JavaScript");
238
+ await clickContinue();
239
+
240
+ expect(getTestOutput()).toBe("Value: 5");
241
+ await clickContinue();
242
+
243
+ expect(getTestOutput()).toBe("Value: some_value");
244
+ await clickContinue();
245
+
246
+ expect(getTestOutput()).toBe("Value: 10");
247
+
248
+ await clickContinue();
249
+ await clickContinue();
250
+ expect(getTestOutput()).toBe("Value: 11");
251
+
252
+ await clickContinue();
253
+ expect(getTestOutput()).toBe("In other section");
254
+ });
255
+
256
+ function safeQuerySelector(name: string) {
257
+ return name.replace(/'/g, "\\'");
258
+ }
259
+
260
+ function getSectionContent(element: HTMLElement, section: string) {
261
+ return element.querySelector(`[data-source='[[${safeQuerySelector(section)}]]']`)?.textContent || null;
262
+ }
263
+
264
+ function getPassageContent(element: HTMLElement, section: string, passage: string) {
265
+ return element.querySelector(`[data-source='[[${safeQuerySelector(section)}]][${safeQuerySelector(passage)}]']`)?.textContent || null;
266
+ }
267
+
268
+ test("Update default section output", async () => {
269
+ const { squiffyApi, element } = await initScript("Hello world");
270
+ let defaultOutput = getSectionContent(element, "_default");
271
+ expect(defaultOutput).toBe("Hello world");
272
+ expect(element.innerHTML).toMatchSnapshot();
273
+
274
+ const updated = await compile("Updated content");
275
+ squiffyApi.update(updated.story);
276
+ defaultOutput = getSectionContent(element, "_default");
277
+ expect(defaultOutput).toBe("Updated content");
278
+ expect(element.innerHTML).toMatchSnapshot();
279
+ });
280
+
281
+ test.each(["a", "a'1"])('Update passage output - passage name "%s"', async (name) => {
282
+ const { squiffyApi, element } = await initScript(`Click this: [${name}]
283
+
284
+ [${name}]:
285
+ Passage a content`);
286
+
287
+ const link = findLink(element, "passage", name);
288
+ const handled = await squiffyApi.clickLink(link);
289
+ expect(handled).toBe(true);
290
+
291
+ let defaultOutput = getSectionContent(element, "_default");
292
+ expect(defaultOutput).toBe(`Click this: ${name}`);
293
+ let passageOutput = getPassageContent(element, "_default", name);
294
+ expect(passageOutput).toBe("Passage a content");
295
+ expect(element.innerHTML).toMatchSnapshot();
296
+
297
+ const updated = await compile(`Click this: [${name}]
298
+
299
+ [${name}]:
300
+ Updated passage content`);
301
+
302
+ squiffyApi.update(updated.story);
303
+
304
+ defaultOutput = getSectionContent(element, "_default");
305
+ expect(defaultOutput).toBe(`Click this: ${name}`);
306
+
307
+ passageOutput = getPassageContent(element, "_default", name);
308
+ expect(passageOutput).toBe("Updated passage content");
309
+ expect(element.innerHTML).toMatchSnapshot();
310
+ });
311
+
312
+ test("Delete section", async () => {
313
+ const { squiffyApi, element } = await initScript(`Click this: [[a]]
314
+
315
+ [[a]]:
316
+ New section`);
317
+
318
+ const link = findLink(element, "section", "a");
319
+ const handled = await squiffyApi.clickLink(link);
320
+ expect(handled).toBe(true);
321
+
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");
326
+ expect(element.innerHTML).toMatchSnapshot();
327
+
328
+ const updated = await compile("Click this: [[a]]");
329
+
330
+ squiffyApi.update(updated.story);
331
+
332
+ defaultOutput = getSectionContent(element, "_default");
333
+ expect(defaultOutput).toBe("Click this: a");
334
+ sectionOutput = getSectionContent(element, "a");
335
+ expect(sectionOutput).toBeNull();
336
+
337
+ expect(element.innerHTML).toMatchSnapshot();
338
+ });
339
+
340
+ test("Delete passage", async () => {
341
+ const { squiffyApi, element } = await initScript(`Click this: [a]
342
+
343
+ [a]:
344
+ New passage`);
345
+
346
+ const link = findLink(element, "passage", "a");
347
+ const handled = await squiffyApi.clickLink(link);
348
+ expect(handled).toBe(true);
349
+
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");
354
+ expect(element.innerHTML).toMatchSnapshot();
355
+
356
+ const updated = await compile("Click this: [a]");
357
+
358
+ squiffyApi.update(updated.story);
359
+
360
+ defaultOutput = getSectionContent(element, "_default");
361
+ expect(defaultOutput).toBe("Click this: a");
362
+ passageOutput = getPassageContent(element, "_default", "a");
363
+ expect(passageOutput).toBeNull();
364
+
365
+ expect(element.innerHTML).toMatchSnapshot();
366
+ });
367
+
368
+ test("Clicked passage links remain disabled after an update", async () => {
369
+ const { squiffyApi, element } = await initScript(`Click one of these: [a] [b]
370
+
371
+ [a]:
372
+ Output for passage A.
373
+
374
+ [b]:
375
+ Output for passage B.`);
376
+
377
+ // click linkA
378
+
379
+ let linkA = findLink(element, "passage", "a");
380
+ expect(linkA.classList).not.toContain("disabled");
381
+ expect(await squiffyApi.clickLink(linkA)).toBe(true);
382
+
383
+ const updated = await compile(`Click one of these (updated): [a] [b]
384
+
385
+ [a]:
386
+ Output for passage A.
387
+
388
+ [b]:
389
+ Output for passage B.`);
390
+
391
+ squiffyApi.update(updated.story);
392
+
393
+ // linkA should still be disabled
394
+
395
+ linkA = findLink(element, "passage", "a");
396
+ expect(linkA.classList).toContain("disabled");
397
+ expect(await squiffyApi.clickLink(linkA)).toBe(false);
398
+
399
+ // linkB should still be enabled
400
+
401
+ const linkB = findLink(element, "passage", "b");
402
+ expect(linkB.classList).not.toContain("disabled");
403
+ expect(await squiffyApi.clickLink(linkB)).toBe(true);
404
+ });
405
+
406
+ test("Deleting the current section activates the previous section", async () => {
407
+ const { squiffyApi, element } = await initScript(`Choose a section: [[a]] [[b]], or passage [start1].
408
+
409
+ [start1]:
410
+ Output for passage start1.
411
+
412
+ [[a]]:
413
+ Output for section A.
414
+
415
+ [[b]]:
416
+ Output for section B.`);
417
+
418
+ // click linkA
419
+
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);
424
+
425
+ // can't click start1 passage as we're in section [[a]] now
426
+ let linkStart1 = findLink(element, "passage", "start1");
427
+ expect(await squiffyApi.clickLink(linkStart1)).toBe(false);
428
+
429
+ // can't click linkB as we're in section [[a]] now
430
+ expect(await squiffyApi.clickLink(linkB)).toBe(false);
431
+
432
+ // now we delete section [[a]]
433
+
434
+ const updated = await compile(`Choose a section: [[a]] [[b]], or passage [start1].
435
+
436
+ [start1]:
437
+ Output for passage start1.
438
+
439
+ [[b]]:
440
+ Output for section B. Here's a passage: [b1].
441
+
442
+ [b1]:
443
+ Passage in section B.`);
444
+
445
+ squiffyApi.update(updated.story);
446
+
447
+ // We're in the first section, so the start1 passage should be clickable now
448
+ linkStart1 = findLink(element, "passage", "start1");
449
+ expect(await squiffyApi.clickLink(linkStart1)).toBe(true);
450
+
451
+ // We're in the first section, so linkB should be clickable now
452
+ linkB = findLink(element, "section", "b");
453
+ expect(await squiffyApi.clickLink(linkB)).toBe(true);
454
+
455
+ // and the passage [b1] within it should be clickable
456
+ const linkB1 = findLink(element, "passage", "b1");
457
+ expect(await squiffyApi.clickLink(linkB1)).toBe(true);
458
+ });
459
+
460
+ test("Embed text from a section", async () => {
461
+ const script = `
462
+ [[section1]]:
463
+ Here is some text from the next section: {{embed "section2"}}
464
+
465
+ [[section2]]:
466
+ Text from next section.
467
+ `;
468
+
469
+ const { element } = await initScript(script);
470
+
471
+ const output = getSectionContent(element, "section1");
472
+ expect(output).toBe("Here is some text from the next section: Text from next section.");
473
+ });
474
+
475
+ test("Embed text from a passage", async () => {
476
+ const script = `
477
+ [[section1]]:
478
+ Here is some text from a passage: {{embed "passage"}}
479
+
480
+ [passage]:
481
+ Text from a passage.
482
+ `;
483
+
484
+ const { element } = await initScript(script);
485
+
486
+ const output = getSectionContent(element, "section1");
487
+ expect(output).toBe("Here is some text from a passage: Text from a passage.");
488
+ });
489
+
490
+ test("Update section with embedded text", async () => {
491
+ const script = `
492
+ [[section1]]:
493
+ Here is some text from the next section: {{embed "section2"}}
494
+
495
+ [[section2]]:
496
+ Text from next section.
497
+ `;
498
+
499
+ const { squiffyApi, element } = await initScript(script);
500
+
501
+ let output = getSectionContent(element, "section1");
502
+ expect(output).toBe("Here is some text from the next section: Text from next section.");
503
+
504
+ const script2 = `
505
+ [[section1]]:
506
+ Here is an updated script with text from the next section: {{embed "section2"}}
507
+
508
+ [[section2]]:
509
+ Updated text from next section.
510
+ `;
511
+
512
+ const updated = await compile(script2);
513
+ squiffyApi.update(updated.story);
514
+ output = getSectionContent(element, "section1");
515
+
516
+ // NOTE: The embedded text does not currently get updated. We would need to track where the embedded
517
+ // text has been written.
518
+ expect(output).toBe("Here is an updated script with text from the next section: Text from next section.");
519
+ });
520
+
521
+ test("Update passage with embedded text", async () => {
522
+ const script = `
523
+ [[section1]]:
524
+ Here is some text from a passage: {{embed "passage"}}
525
+
526
+ [passage]:
527
+ Text from a passage.
528
+ `;
529
+
530
+ const { squiffyApi, element } = await initScript(script);
531
+
532
+ let output = getSectionContent(element, "section1");
533
+ expect(output).toBe("Here is some text from a passage: Text from a passage.");
534
+
535
+ const script2 = `
536
+ [[section1]]:
537
+ Here is an updated script with text from a passage: {{embed "passage"}}
538
+
539
+ [passage]:
540
+ Updated text from a passage.
541
+ `;
542
+
543
+ const updated = await compile(script2);
544
+ squiffyApi.update(updated.story);
545
+ output = getSectionContent(element, "section1");
546
+
547
+ // NOTE: The embedded text does not currently get updated. We would need to track where the embedded
548
+ // text has been written.
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);
677
+ });