yarn-spinner-runner-ts 0.1.4-a → 0.1.4-c

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.
@@ -1,47 +1,102 @@
1
- import { useState, useCallback, useRef } from "react";
2
- import { YarnRunner, type RunnerOptions } from "../runtime/runner.js";
3
- import type { IRProgram } from "../compile/ir.js";
4
- import type { RuntimeResult } from "../runtime/results.js";
5
-
6
- export function useYarnRunner(
7
- program: IRProgram,
8
- options: RunnerOptions
9
- ): {
10
- result: RuntimeResult | null;
11
- advance: (optionIndex?: number) => void;
12
- runner: YarnRunner;
13
- } {
14
- const runnerRef = useRef<YarnRunner | null>(null);
15
- const [result, setResult] = useState<RuntimeResult | null>(null);
16
- const optionsRef = useRef(options);
17
-
18
- // Update runner if functions change
19
- if (
20
- runnerRef.current &&
21
- JSON.stringify(optionsRef.current.functions) !== JSON.stringify(options.functions)
22
- ) {
23
- runnerRef.current = new YarnRunner(program, options);
24
- setResult(runnerRef.current.currentResult);
25
- optionsRef.current = options;
26
- }
27
-
28
- // Initialize runner if not exists
29
- if (!runnerRef.current) {
30
- runnerRef.current = new YarnRunner(program, options);
31
- setResult(runnerRef.current.currentResult);
32
- optionsRef.current = options;
33
- }
34
-
35
- const runner = runnerRef.current;
36
-
37
- const advance = useCallback(
38
- (optionIndex?: number) => {
39
- runner.advance(optionIndex);
40
- setResult(runner.currentResult);
41
- },
42
- [runner]
43
- );
44
-
45
- return { result, advance, runner };
46
- }
1
+ import { useState, useCallback, useRef, useEffect } from "react";
2
+ import { YarnRunner, type RunnerOptions } from "../runtime/runner.js";
3
+ import type { IRProgram } from "../compile/ir.js";
4
+ import type { RuntimeResult } from "../runtime/results.js";
5
+
6
+ function haveFunctionsChanged(
7
+ prev: RunnerOptions["functions"],
8
+ next: RunnerOptions["functions"]
9
+ ): boolean {
10
+ const prevFns = prev ?? {};
11
+ const nextFns = next ?? {};
12
+
13
+ const prevKeys = Object.keys(prevFns);
14
+ const nextKeys = Object.keys(nextFns);
15
+
16
+ if (prevKeys.length !== nextKeys.length) {
17
+ return true;
18
+ }
19
+
20
+ for (const key of prevKeys) {
21
+ if (!Object.prototype.hasOwnProperty.call(nextFns, key) || prevFns[key] !== nextFns[key]) {
22
+ return true;
23
+ }
24
+ }
25
+
26
+ return false;
27
+ }
28
+
29
+ function haveVariablesChanged(
30
+ prev: RunnerOptions["variables"],
31
+ next: RunnerOptions["variables"]
32
+ ): boolean {
33
+ const prevVars = prev ?? {};
34
+ const nextVars = next ?? {};
35
+ return JSON.stringify(prevVars) !== JSON.stringify(nextVars);
36
+ }
37
+
38
+ export function useYarnRunner(
39
+ program: IRProgram,
40
+ options: RunnerOptions
41
+ ): {
42
+ result: RuntimeResult | null;
43
+ advance: (optionIndex?: number) => void;
44
+ runner: YarnRunner;
45
+ } {
46
+ const runnerRef = useRef<YarnRunner | null>(null);
47
+ const optionsRef = useRef(options);
48
+ const programRef = useRef(program);
49
+ const [result, setResult] = useState<RuntimeResult | null>(() => {
50
+ const runner = new YarnRunner(program, options);
51
+ runnerRef.current = runner;
52
+ optionsRef.current = options;
53
+ programRef.current = program;
54
+ return runner.currentResult;
55
+ });
56
+
57
+ useEffect(() => {
58
+ const prevProgram = programRef.current;
59
+ const prevOptions = optionsRef.current;
60
+
61
+ const programChanged = prevProgram !== program;
62
+ const functionsChanged = haveFunctionsChanged(prevOptions?.functions, options.functions);
63
+ const startNodeChanged = prevOptions?.startAt !== options.startAt;
64
+ const variablesChanged = haveVariablesChanged(prevOptions?.variables, options.variables);
65
+ const handlersChanged =
66
+ prevOptions?.handleCommand !== options.handleCommand ||
67
+ prevOptions?.commandHandler !== options.commandHandler ||
68
+ prevOptions?.onStoryEnd !== options.onStoryEnd;
69
+
70
+ if (
71
+ !runnerRef.current ||
72
+ programChanged ||
73
+ functionsChanged ||
74
+ startNodeChanged ||
75
+ variablesChanged ||
76
+ handlersChanged
77
+ ) {
78
+ const runner = new YarnRunner(program, options);
79
+ runnerRef.current = runner;
80
+ setResult(runner.currentResult);
81
+ }
82
+
83
+ programRef.current = program;
84
+ optionsRef.current = options;
85
+ }, [program, options]);
86
+
87
+ const advance = useCallback((optionIndex?: number) => {
88
+ const runner = runnerRef.current;
89
+ if (!runner) {
90
+ return;
91
+ }
92
+ runner.advance(optionIndex);
93
+ setResult(runner.currentResult);
94
+ }, []);
95
+
96
+ return {
97
+ result,
98
+ advance,
99
+ runner: runnerRef.current as YarnRunner,
100
+ };
101
+ }
47
102
 
@@ -0,0 +1,26 @@
1
+ import { test } from "node:test";
2
+ import { ok } from "node:assert";
3
+ import React from "react";
4
+ import { renderToStaticMarkup } from "react-dom/server";
5
+ import { parseYarn } from "../parse/parser.js";
6
+ import { compile } from "../compile/compiler.js";
7
+ import { DialogueView } from "../react/DialogueView.js";
8
+
9
+ test("DialogueView renders initial variables provided via props", () => {
10
+ const yarn = `
11
+ title: Start
12
+ ---
13
+ Narrator: Hello {$playerName}!
14
+ ===`;
15
+
16
+ const program = compile(parseYarn(yarn));
17
+
18
+ const html = renderToStaticMarkup(
19
+ <DialogueView program={program} startNode="Start" variables={{ playerName: "V" }} />
20
+ );
21
+
22
+ ok(
23
+ html.includes("Hello V"),
24
+ "Expected rendered dialogue to include the interpolated variable value from props"
25
+ );
26
+ });
@@ -43,6 +43,23 @@ test("parseMarkup handles self-closing tags", () => {
43
43
  );
44
44
  });
45
45
 
46
+ test("parseMarkup handles br line breaks", () => {
47
+ const result = parseMarkup("Line one[br]Line two[/br][br/]Line three");
48
+ strictEqual(result.text, "Line one\nLine two\nLine three");
49
+
50
+ const brSegments = result.segments.filter(
51
+ (segment) => segment.selfClosing && segment.wrappers.some((wrapper) => wrapper.name === "br")
52
+ );
53
+
54
+ strictEqual(brSegments.length, 2);
55
+ ok(
56
+ brSegments.every((segment) =>
57
+ segment.wrappers.some((wrapper) => wrapper.name === "br" && wrapper.type === "default")
58
+ ),
59
+ "Expected br wrappers to use default HTML type"
60
+ );
61
+ });
62
+
46
63
  test("parseMarkup respects nomarkup blocks and escaping", () => {
47
64
  const result = parseMarkup(`[nomarkup][b] raw [/b][/nomarkup] and \\[escaped\\]`);
48
65
  strictEqual(result.text, "[b] raw [/b] and [escaped]");
@@ -59,4 +76,3 @@ function findSegment(result: MarkupParseResult, target: string) {
59
76
  return text === target;
60
77
  });
61
78
  }
62
-