@react-text-game/core 0.5.15 → 0.5.17

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.
@@ -0,0 +1,330 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { Game } from "../game";
3
+ import { newInteractiveMap } from "../passages/interactiveMap/fabric";
4
+ import { newStory } from "../passages/story/fabric";
5
+ import { newWidget } from "../passages/widget";
6
+ import { Storage } from "../storage";
7
+ // Counter for unique IDs
8
+ let testCounter = 0;
9
+ function uniqueId(prefix) {
10
+ return `${prefix}-${testCounter++}`;
11
+ }
12
+ describe("Passage Display Cache - No Double Side Effects", () => {
13
+ beforeEach(async () => {
14
+ Storage.setState({});
15
+ await Game.init({ gameName: "Test Game", isDevMode: true });
16
+ });
17
+ afterEach(() => {
18
+ Game._resetForTesting();
19
+ });
20
+ describe("Story Cache", () => {
21
+ test("getLastDisplayResult returns null before first display", () => {
22
+ const story = newStory(uniqueId("story"), () => [
23
+ { type: "text", content: "Hello" },
24
+ ]);
25
+ expect(story.getLastDisplayResult()).toBeNull();
26
+ expect(story.hasDisplayCache()).toBe(false);
27
+ });
28
+ test("getLastDisplayResult returns cached result after display", () => {
29
+ const story = newStory(uniqueId("story"), () => [
30
+ { type: "text", content: "Hello" },
31
+ ]);
32
+ const displayResult = story.display();
33
+ const cachedResult = story.getLastDisplayResult();
34
+ expect(cachedResult).toEqual(displayResult);
35
+ expect(story.hasDisplayCache()).toBe(true);
36
+ });
37
+ test("getLastDisplayResult does NOT trigger content function (no side effects)", () => {
38
+ let callCount = 0;
39
+ const story = newStory(uniqueId("story"), () => {
40
+ callCount++;
41
+ return [{ type: "text", content: `Call ${callCount}` }];
42
+ });
43
+ // First display() call - triggers content function
44
+ story.display();
45
+ expect(callCount).toBe(1);
46
+ // Multiple getLastDisplayResult() calls - should NOT trigger content function
47
+ story.getLastDisplayResult();
48
+ story.getLastDisplayResult();
49
+ story.getLastDisplayResult();
50
+ expect(callCount).toBe(1); // Still 1, no additional calls
51
+ // Another display() call - SHOULD trigger content function
52
+ story.display();
53
+ expect(callCount).toBe(2);
54
+ });
55
+ test("cache updates correctly after each display call", () => {
56
+ let callCount = 0;
57
+ const story = newStory(uniqueId("story"), () => {
58
+ callCount++;
59
+ return [
60
+ {
61
+ type: "text",
62
+ content: `Call ${callCount}`,
63
+ },
64
+ ];
65
+ });
66
+ story.display();
67
+ const cache1 = story.getLastDisplayResult();
68
+ expect((cache1?.components[0]).content).toBe("Call 1");
69
+ story.display();
70
+ const cache2 = story.getLastDisplayResult();
71
+ expect((cache2?.components[0]).content).toBe("Call 2");
72
+ });
73
+ test("simulates DevModeDrawer scenario - no side effects when reading passage data", () => {
74
+ let sideEffectCounter = 0;
75
+ const story = newStory(uniqueId("story"), () => {
76
+ sideEffectCounter++; // Simulates a side effect like incrementing a game counter
77
+ return [
78
+ {
79
+ type: "text",
80
+ content: `Viewed ${sideEffectCounter} times`,
81
+ },
82
+ ];
83
+ });
84
+ // User navigates to passage (display is called by UI)
85
+ Game.jumpTo(story.id);
86
+ story.display();
87
+ expect(sideEffectCounter).toBe(1);
88
+ // User opens DevModeDrawer - should NOT cause side effect
89
+ const cachedData = story.getLastDisplayResult();
90
+ expect(sideEffectCounter).toBe(1); // Still 1
91
+ expect(cachedData).not.toBeNull();
92
+ // User refreshes DevModeDrawer multiple times - still no side effects
93
+ story.getLastDisplayResult();
94
+ story.getLastDisplayResult();
95
+ expect(sideEffectCounter).toBe(1);
96
+ });
97
+ test("cached result has correct structure with options and components", () => {
98
+ const story = newStory(uniqueId("story"), () => [
99
+ { type: "text", content: "Test" },
100
+ { type: "header", content: "Header", props: { level: 1 } },
101
+ ], { background: { image: "/bg.jpg" } });
102
+ story.display();
103
+ const cached = story.getLastDisplayResult();
104
+ expect(cached).not.toBeNull();
105
+ expect(cached?.options?.background?.image).toBe("/bg.jpg");
106
+ expect(cached?.components).toHaveLength(2);
107
+ expect(cached?.components[0]?.type).toBe("text");
108
+ expect(cached?.components[1]?.type).toBe("header");
109
+ });
110
+ });
111
+ describe("InteractiveMap Cache", () => {
112
+ test("getLastDisplayResult returns null before first display", () => {
113
+ const map = newInteractiveMap(uniqueId("map"), {
114
+ image: "/map.jpg",
115
+ bgImage: "/bg.jpg",
116
+ hotspots: [],
117
+ });
118
+ expect(map.getLastDisplayResult()).toBeNull();
119
+ expect(map.hasDisplayCache()).toBe(false);
120
+ });
121
+ test("getLastDisplayResult returns cached result after display", () => {
122
+ const map = newInteractiveMap(uniqueId("map"), {
123
+ image: "/map.jpg",
124
+ bgImage: "/bg.jpg",
125
+ hotspots: [
126
+ {
127
+ type: "label",
128
+ content: "Test",
129
+ position: { x: 50, y: 50 },
130
+ action: () => { },
131
+ },
132
+ ],
133
+ });
134
+ const displayResult = map.display();
135
+ const cachedResult = map.getLastDisplayResult();
136
+ expect(cachedResult?.image).toBe(displayResult.image);
137
+ expect(cachedResult?.bgImage).toBe(displayResult.bgImage);
138
+ expect(cachedResult?.hotspots.length).toBe(displayResult.hotspots.length);
139
+ expect(map.hasDisplayCache()).toBe(true);
140
+ });
141
+ test("getLastDisplayResult does NOT trigger hotspot functions (no side effects)", () => {
142
+ let imageCallCount = 0;
143
+ let hotspotCallCount = 0;
144
+ const map = newInteractiveMap(uniqueId("map"), {
145
+ image: () => {
146
+ imageCallCount++;
147
+ return `/map-${imageCallCount}.jpg`;
148
+ },
149
+ bgImage: "/bg.jpg",
150
+ hotspots: [
151
+ () => {
152
+ hotspotCallCount++;
153
+ return {
154
+ type: "label",
155
+ content: `Hotspot ${hotspotCallCount}`,
156
+ position: { x: 50, y: 50 },
157
+ action: () => { },
158
+ };
159
+ },
160
+ ],
161
+ });
162
+ // First display() - triggers all functions
163
+ map.display();
164
+ expect(imageCallCount).toBe(1);
165
+ expect(hotspotCallCount).toBe(1);
166
+ // getLastDisplayResult() should NOT trigger functions
167
+ map.getLastDisplayResult();
168
+ map.getLastDisplayResult();
169
+ expect(imageCallCount).toBe(1);
170
+ expect(hotspotCallCount).toBe(1);
171
+ });
172
+ test("cached result has resolved images and hotspots", () => {
173
+ const map = newInteractiveMap(uniqueId("map"), {
174
+ image: () => "/dynamic-map.jpg",
175
+ bgImage: () => "/dynamic-bg.jpg",
176
+ hotspots: [
177
+ () => ({
178
+ type: "label",
179
+ content: "Dynamic Hotspot",
180
+ position: { x: 25, y: 75 },
181
+ action: () => { },
182
+ }),
183
+ ],
184
+ });
185
+ map.display();
186
+ const cached = map.getLastDisplayResult();
187
+ expect(cached).not.toBeNull();
188
+ expect(cached?.image).toBe("/dynamic-map.jpg");
189
+ expect(cached?.bgImage).toBe("/dynamic-bg.jpg");
190
+ expect(cached?.hotspots).toHaveLength(1);
191
+ const hotspot = cached?.hotspots[0];
192
+ if (hotspot && hotspot.type === "label") {
193
+ expect(hotspot.content).toBe("Dynamic Hotspot");
194
+ }
195
+ });
196
+ test("cache updates on each display call", () => {
197
+ let displayCount = 0;
198
+ const map = newInteractiveMap(uniqueId("map"), {
199
+ image: () => {
200
+ displayCount++;
201
+ return `/map-${displayCount}.jpg`;
202
+ },
203
+ bgImage: "/bg.jpg",
204
+ hotspots: [
205
+ {
206
+ type: "label",
207
+ content: "Test",
208
+ position: { x: 50, y: 50 },
209
+ action: () => { },
210
+ },
211
+ ],
212
+ });
213
+ map.display();
214
+ expect(displayCount).toBe(1);
215
+ expect(map.getLastDisplayResult()?.image).toBe("/map-1.jpg");
216
+ map.display();
217
+ expect(displayCount).toBe(2);
218
+ expect(map.getLastDisplayResult()?.image).toBe("/map-2.jpg");
219
+ });
220
+ });
221
+ describe("Widget Cache", () => {
222
+ test("getLastDisplayResult returns null before first display", () => {
223
+ const widget = newWidget(uniqueId("widget"), "Static Content");
224
+ expect(widget.getLastDisplayResult()).toBeNull();
225
+ expect(widget.hasDisplayCache()).toBe(false);
226
+ });
227
+ test("getLastDisplayResult returns cached result after display for static content", () => {
228
+ const staticContent = "Static Widget Content";
229
+ const widget = newWidget(uniqueId("widget"), staticContent);
230
+ const displayResult = widget.display();
231
+ const cachedResult = widget.getLastDisplayResult();
232
+ expect(cachedResult).toBe(staticContent);
233
+ expect(cachedResult).toBe(displayResult);
234
+ expect(widget.hasDisplayCache()).toBe(true);
235
+ });
236
+ test("function content is treated as React component (createElement)", () => {
237
+ // With the new behavior, functions are always treated as React components
238
+ // and wrapped in createElement, not called directly.
239
+ // This ensures hooks work correctly in minified production builds.
240
+ const widget = newWidget(uniqueId("widget"), () => "Content");
241
+ const displayResult = widget.display();
242
+ // The result is a React element, not the string "Content"
243
+ expect(displayResult).toBeDefined();
244
+ expect(typeof displayResult).toBe("object");
245
+ expect(widget.hasDisplayCache()).toBe(true);
246
+ });
247
+ });
248
+ describe("Base Passage Methods", () => {
249
+ test("hasDisplayCache returns false before display", () => {
250
+ const story = newStory(uniqueId("story"), () => [
251
+ { type: "text", content: "Test" },
252
+ ]);
253
+ expect(story.hasDisplayCache()).toBe(false);
254
+ });
255
+ test("hasDisplayCache returns true after display", () => {
256
+ const story = newStory(uniqueId("story"), () => [
257
+ { type: "text", content: "Test" },
258
+ ]);
259
+ story.display();
260
+ expect(story.hasDisplayCache()).toBe(true);
261
+ });
262
+ test("getLastDisplayResult returns properly typed result", () => {
263
+ const story = newStory(uniqueId("story"), () => [
264
+ { type: "text", content: "Test" },
265
+ ]);
266
+ story.display();
267
+ // Using the Story's typed getLastDisplayResult method
268
+ const result = story.getLastDisplayResult();
269
+ expect(result?.components[0]?.type).toBe("text");
270
+ });
271
+ });
272
+ describe("Practical Scenarios", () => {
273
+ test("simulates load game scenario - cache populated by first render", () => {
274
+ let stateChangeCount = 0;
275
+ const story = newStory(uniqueId("story"), () => {
276
+ stateChangeCount++; // Simulates state change during render
277
+ return [
278
+ {
279
+ type: "text",
280
+ content: `State changed ${stateChangeCount} times`,
281
+ },
282
+ ];
283
+ });
284
+ // Simulate initial render after loading game
285
+ story.display();
286
+ expect(stateChangeCount).toBe(1);
287
+ // Simulate DevModeDrawer opening (should use cache)
288
+ const cachedData = story.getLastDisplayResult();
289
+ expect(stateChangeCount).toBe(1); // No additional state change
290
+ expect(cachedData).not.toBeNull();
291
+ // Simulate re-render (intentional)
292
+ story.display();
293
+ expect(stateChangeCount).toBe(2);
294
+ });
295
+ test("multiple passages maintain independent caches", () => {
296
+ let story1CallCount = 0;
297
+ let story2CallCount = 0;
298
+ const story1 = newStory(uniqueId("story1"), () => {
299
+ story1CallCount++;
300
+ return [
301
+ {
302
+ type: "text",
303
+ content: `Story 1 call ${story1CallCount}`,
304
+ },
305
+ ];
306
+ });
307
+ const story2 = newStory(uniqueId("story2"), () => {
308
+ story2CallCount++;
309
+ return [
310
+ {
311
+ type: "text",
312
+ content: `Story 2 call ${story2CallCount}`,
313
+ },
314
+ ];
315
+ });
316
+ story1.display();
317
+ expect(story1CallCount).toBe(1);
318
+ expect(story2CallCount).toBe(0);
319
+ story2.display();
320
+ expect(story1CallCount).toBe(1);
321
+ expect(story2CallCount).toBe(1);
322
+ // Getting cached results doesn't affect call counts
323
+ story1.getLastDisplayResult();
324
+ story2.getLastDisplayResult();
325
+ expect(story1CallCount).toBe(1);
326
+ expect(story2CallCount).toBe(1);
327
+ });
328
+ });
329
+ });
330
+ //# sourceMappingURL=passageDisplayCache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passageDisplayCache.test.js","sourceRoot":"","sources":["../../src/tests/passageDisplayCache.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=widget.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.test.d.ts","sourceRoot":"","sources":["../../src/tests/widget.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,281 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { createElement } from "react";
3
+ import { Game } from "../game";
4
+ import { newWidget, Widget } from "../passages/widget";
5
+ import { Storage } from "../storage";
6
+ // Counter for unique IDs
7
+ let testCounter = 0;
8
+ function uniqueId(prefix) {
9
+ return `${prefix}-${testCounter++}`;
10
+ }
11
+ describe("Widget Passage", () => {
12
+ beforeEach(async () => {
13
+ Storage.setState({});
14
+ await Game.init({ gameName: "Test Game", isDevMode: true });
15
+ });
16
+ afterEach(() => {
17
+ Game._resetForTesting();
18
+ });
19
+ describe("Creation", () => {
20
+ test("creates widget with static ReactNode content", () => {
21
+ const content = createElement("div", null, "Hello World");
22
+ const widget = newWidget(uniqueId("widget"), content);
23
+ expect(widget).toBeInstanceOf(Widget);
24
+ expect(widget.type).toBe("widget");
25
+ });
26
+ test("creates widget with string content", () => {
27
+ const widget = newWidget(uniqueId("widget"), "Simple String");
28
+ expect(widget).toBeInstanceOf(Widget);
29
+ expect(widget.type).toBe("widget");
30
+ });
31
+ test("creates widget with number content", () => {
32
+ const widget = newWidget(uniqueId("widget"), 42);
33
+ expect(widget).toBeInstanceOf(Widget);
34
+ });
35
+ test("creates widget with null content", () => {
36
+ const widget = newWidget(uniqueId("widget"), null);
37
+ expect(widget).toBeInstanceOf(Widget);
38
+ });
39
+ test("creates widget with function component", () => {
40
+ const MyComponent = () => createElement("div", null, "Component");
41
+ const widget = newWidget(uniqueId("widget"), MyComponent);
42
+ expect(widget).toBeInstanceOf(Widget);
43
+ });
44
+ test("registers widget with Game on creation", () => {
45
+ const id = uniqueId("widget");
46
+ const widget = newWidget(id, "Content");
47
+ expect(Game.getPassageById(id)).toBe(widget);
48
+ });
49
+ });
50
+ describe("Display - Static Content", () => {
51
+ test("display returns static string content directly", () => {
52
+ const widget = newWidget(uniqueId("widget"), "Static String");
53
+ const result = widget.display();
54
+ expect(result).toBe("Static String");
55
+ });
56
+ test("display returns static number content directly", () => {
57
+ const widget = newWidget(uniqueId("widget"), 123);
58
+ const result = widget.display();
59
+ expect(result).toBe(123);
60
+ });
61
+ test("display returns null content directly", () => {
62
+ const widget = newWidget(uniqueId("widget"), null);
63
+ const result = widget.display();
64
+ expect(result).toBeNull();
65
+ });
66
+ test("display returns ReactNode content directly", () => {
67
+ const content = createElement("span", { className: "test" }, "Text");
68
+ const widget = newWidget(uniqueId("widget"), content);
69
+ const result = widget.display();
70
+ expect(result).toBe(content);
71
+ });
72
+ test("display returns pre-evaluated JSX directly", () => {
73
+ // Simulating: newWidget('id', (() => <div>...</div>)())
74
+ const preEvaluated = createElement("div", null, Date.now());
75
+ const widget = newWidget(uniqueId("widget"), preEvaluated);
76
+ const result = widget.display();
77
+ expect(result).toBe(preEvaluated);
78
+ });
79
+ });
80
+ describe("Display - Function Content (React Components)", () => {
81
+ test("display wraps function in createElement", () => {
82
+ const MyComponent = () => createElement("div", null, "Hello");
83
+ const widget = newWidget(uniqueId("widget"), MyComponent);
84
+ const result = widget.display();
85
+ // Result should be a React element, not the string "Hello"
86
+ expect(result).toBeDefined();
87
+ expect(typeof result).toBe("object");
88
+ expect(result).not.toBe("Hello");
89
+ });
90
+ test("display creates React element with correct type", () => {
91
+ const MyComponent = () => createElement("div", null, "Content");
92
+ const widget = newWidget(uniqueId("widget"), MyComponent);
93
+ const result = widget.display();
94
+ // The element's type should be the component function
95
+ expect(result.type).toBe(MyComponent);
96
+ });
97
+ test("function is NOT called directly during display", () => {
98
+ let wasCalled = false;
99
+ const MyComponent = () => {
100
+ wasCalled = true;
101
+ return createElement("div", null, "Content");
102
+ };
103
+ const widget = newWidget(uniqueId("widget"), MyComponent);
104
+ // display() should NOT call the function - it wraps it in createElement
105
+ widget.display();
106
+ // The function is not called until React renders the element
107
+ expect(wasCalled).toBe(false);
108
+ });
109
+ test("arrow function component is wrapped in createElement", () => {
110
+ const ArrowComponent = () => createElement("span", null, "Arrow");
111
+ const widget = newWidget(uniqueId("widget"), ArrowComponent);
112
+ const result = widget.display();
113
+ expect(result.type).toBe(ArrowComponent);
114
+ });
115
+ test("named function component is wrapped in createElement", () => {
116
+ function NamedComponent() {
117
+ return createElement("div", null, "Named");
118
+ }
119
+ const widget = newWidget(uniqueId("widget"), NamedComponent);
120
+ const result = widget.display();
121
+ expect(result.type).toBe(NamedComponent);
122
+ });
123
+ test("lowercase named function is still wrapped in createElement", () => {
124
+ // This is the key fix - even with lowercase names (like minified code),
125
+ // functions should be wrapped in createElement
126
+ function lowercaseComponent() {
127
+ return createElement("div", null, "lowercase");
128
+ }
129
+ const widget = newWidget(uniqueId("widget"), lowercaseComponent);
130
+ const result = widget.display();
131
+ expect(result.type).toBe(lowercaseComponent);
132
+ expect(typeof result).toBe("object");
133
+ });
134
+ test("minified-style single letter function is wrapped in createElement", () => {
135
+ // Simulating minified component names like 'e', 't', 'n'
136
+ const e = () => createElement("div", null, "minified");
137
+ const widget = newWidget(uniqueId("widget"), e);
138
+ const result = widget.display();
139
+ expect(result.type).toBe(e);
140
+ expect(typeof result).toBe("object");
141
+ });
142
+ });
143
+ describe("Display Caching", () => {
144
+ test("caches static content after display", () => {
145
+ const widget = newWidget(uniqueId("widget"), "Cached Content");
146
+ expect(widget.hasDisplayCache()).toBe(false);
147
+ widget.display();
148
+ expect(widget.hasDisplayCache()).toBe(true);
149
+ expect(widget.getLastDisplayResult()).toBe("Cached Content");
150
+ });
151
+ test("caches React element after display", () => {
152
+ const MyComponent = () => createElement("div", null, "Component");
153
+ const widget = newWidget(uniqueId("widget"), MyComponent);
154
+ expect(widget.hasDisplayCache()).toBe(false);
155
+ const displayResult = widget.display();
156
+ expect(widget.hasDisplayCache()).toBe(true);
157
+ const cachedResult = widget.getLastDisplayResult();
158
+ expect(cachedResult).toEqual(displayResult);
159
+ });
160
+ test("getLastDisplayResult returns null before first display", () => {
161
+ const widget = newWidget(uniqueId("widget"), "Content");
162
+ expect(widget.getLastDisplayResult()).toBeNull();
163
+ });
164
+ test("cache updates on subsequent display calls", () => {
165
+ const content1 = createElement("div", null, "First");
166
+ const widget = newWidget(uniqueId("widget"), content1);
167
+ widget.display();
168
+ const cache1 = widget.getLastDisplayResult();
169
+ // For static content, cache remains the same
170
+ widget.display();
171
+ const cache2 = widget.getLastDisplayResult();
172
+ expect(cache1).toBe(cache2);
173
+ });
174
+ });
175
+ describe("Integration with Game", () => {
176
+ test("can navigate to widget passage", () => {
177
+ const widget = newWidget(uniqueId("widget"), "Navigate Test");
178
+ Game.jumpTo(widget.id);
179
+ expect(Game.currentPassage).toBe(widget);
180
+ });
181
+ test("widget is accessible via Game.getPassageById", () => {
182
+ const id = uniqueId("widget");
183
+ const widget = newWidget(id, "Accessible Content");
184
+ const retrieved = Game.getPassageById(id);
185
+ expect(retrieved).toBe(widget);
186
+ });
187
+ test("multiple widgets can coexist", () => {
188
+ const widget1 = newWidget(uniqueId("widget1"), "Content 1");
189
+ const widget2 = newWidget(uniqueId("widget2"), "Content 2");
190
+ const widget3 = newWidget(uniqueId("widget3"), "Content 3");
191
+ expect(Game.getPassageById(widget1.id)).toBe(widget1);
192
+ expect(Game.getPassageById(widget2.id)).toBe(widget2);
193
+ expect(Game.getPassageById(widget3.id)).toBe(widget3);
194
+ });
195
+ });
196
+ describe("Edge Cases", () => {
197
+ test("handles undefined content gracefully", () => {
198
+ const widget = newWidget(uniqueId("widget"), undefined);
199
+ const result = widget.display();
200
+ expect(result).toBeUndefined();
201
+ });
202
+ test("handles boolean content", () => {
203
+ const widgetTrue = newWidget(uniqueId("widget"), true);
204
+ const widgetFalse = newWidget(uniqueId("widget"), false);
205
+ expect(widgetTrue.display()).toBe(true);
206
+ expect(widgetFalse.display()).toBe(false);
207
+ });
208
+ test("handles array content (fragment-like)", () => {
209
+ const content = [
210
+ createElement("div", { key: "1" }, "First"),
211
+ createElement("div", { key: "2" }, "Second"),
212
+ ];
213
+ const widget = newWidget(uniqueId("widget"), content);
214
+ const result = widget.display();
215
+ expect(result).toBe(content);
216
+ expect(Array.isArray(result)).toBe(true);
217
+ });
218
+ test("handles nested createElement", () => {
219
+ const content = createElement("div", null, createElement("span", null, "Nested"), createElement("p", null, "Content"));
220
+ const widget = newWidget(uniqueId("widget"), content);
221
+ const result = widget.display();
222
+ expect(result).toBe(content);
223
+ });
224
+ test("function returning null is wrapped in createElement", () => {
225
+ const NullComponent = () => null;
226
+ const widget = newWidget(uniqueId("widget"), NullComponent);
227
+ const result = widget.display();
228
+ expect(result.type).toBe(NullComponent);
229
+ });
230
+ test("function returning undefined is wrapped in createElement", () => {
231
+ const UndefinedComponent = () => undefined;
232
+ const widget = newWidget(uniqueId("widget"), UndefinedComponent);
233
+ const result = widget.display();
234
+ expect(result.type).toBe(UndefinedComponent);
235
+ });
236
+ });
237
+ describe("Minification Safety", () => {
238
+ // These tests verify the fix for the minification issue where
239
+ // function names get mangled to lowercase identifiers
240
+ test("component with mangled name 'a' works correctly", () => {
241
+ const a = () => createElement("div", null, "Mangled A");
242
+ const widget = newWidget(uniqueId("widget"), a);
243
+ const result = widget.display();
244
+ expect(typeof result).toBe("object");
245
+ expect(result.type).toBe(a);
246
+ });
247
+ test("component with mangled name 't' works correctly", () => {
248
+ const t = () => createElement("div", null, "Mangled T");
249
+ const widget = newWidget(uniqueId("widget"), t);
250
+ const result = widget.display();
251
+ expect(typeof result).toBe("object");
252
+ expect(result.type).toBe(t);
253
+ });
254
+ test("component with numeric-like name works correctly", () => {
255
+ const _0 = () => createElement("div", null, "Numeric");
256
+ const widget = newWidget(uniqueId("widget"), _0);
257
+ const result = widget.display();
258
+ expect(typeof result).toBe("object");
259
+ expect(result.type).toBe(_0);
260
+ });
261
+ test("anonymous function works correctly", () => {
262
+ const widget = newWidget(uniqueId("widget"), function () {
263
+ return createElement("div", null, "Anonymous");
264
+ });
265
+ const result = widget.display();
266
+ expect(typeof result).toBe("object");
267
+ });
268
+ test("component assigned to variable with different name works", () => {
269
+ function OriginalName() {
270
+ return createElement("div", null, "Original");
271
+ }
272
+ // Simulating: const x = OriginalName; (name property becomes 'OriginalName')
273
+ const x = OriginalName;
274
+ const widget = newWidget(uniqueId("widget"), x);
275
+ const result = widget.display();
276
+ expect(result.type).toBe(x);
277
+ expect(result.type).toBe(OriginalName);
278
+ });
279
+ });
280
+ });
281
+ //# sourceMappingURL=widget.test.js.map