@react-text-game/core 0.1.0
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.
- package/README.md +744 -0
- package/dist/baseGameObject.d.ts +90 -0
- package/dist/baseGameObject.d.ts.map +1 -0
- package/dist/baseGameObject.js +109 -0
- package/dist/baseGameObject.js.map +1 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +12 -0
- package/dist/constants.js.map +1 -0
- package/dist/game.d.ts +294 -0
- package/dist/game.d.ts.map +1 -0
- package/dist/game.js +489 -0
- package/dist/game.js.map +1 -0
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +6 -0
- package/dist/helpers.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useCurrentPassage.d.ts +10 -0
- package/dist/hooks/useCurrentPassage.d.ts.map +1 -0
- package/dist/hooks/useCurrentPassage.js +17 -0
- package/dist/hooks/useCurrentPassage.js.map +1 -0
- package/dist/hooks/useGameEntity.d.ts +21 -0
- package/dist/hooks/useGameEntity.d.ts.map +1 -0
- package/dist/hooks/useGameEntity.js +70 -0
- package/dist/hooks/useGameEntity.js.map +1 -0
- package/dist/hooks/useGameIsStarted.d.ts +12 -0
- package/dist/hooks/useGameIsStarted.d.ts.map +1 -0
- package/dist/hooks/useGameIsStarted.js +18 -0
- package/dist/hooks/useGameIsStarted.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +36 -0
- package/dist/logger.js.map +1 -0
- package/dist/options.d.ts +13 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +15 -0
- package/dist/options.js.map +1 -0
- package/dist/passages/interactiveMap/fabric.d.ts +4 -0
- package/dist/passages/interactiveMap/fabric.d.ts.map +1 -0
- package/dist/passages/interactiveMap/fabric.js +3 -0
- package/dist/passages/interactiveMap/fabric.js.map +1 -0
- package/dist/passages/interactiveMap/index.d.ts +4 -0
- package/dist/passages/interactiveMap/index.d.ts.map +1 -0
- package/dist/passages/interactiveMap/index.js +4 -0
- package/dist/passages/interactiveMap/index.js.map +1 -0
- package/dist/passages/interactiveMap/interactiveMap.d.ts +89 -0
- package/dist/passages/interactiveMap/interactiveMap.d.ts.map +1 -0
- package/dist/passages/interactiveMap/interactiveMap.js +103 -0
- package/dist/passages/interactiveMap/interactiveMap.js.map +1 -0
- package/dist/passages/interactiveMap/types.d.ts +822 -0
- package/dist/passages/interactiveMap/types.d.ts.map +1 -0
- package/dist/passages/interactiveMap/types.js +2 -0
- package/dist/passages/interactiveMap/types.js.map +1 -0
- package/dist/passages/passage.d.ts +57 -0
- package/dist/passages/passage.d.ts.map +1 -0
- package/dist/passages/passage.js +64 -0
- package/dist/passages/passage.js.map +1 -0
- package/dist/passages/story/fabric.d.ts +4 -0
- package/dist/passages/story/fabric.d.ts.map +1 -0
- package/dist/passages/story/fabric.js +3 -0
- package/dist/passages/story/fabric.js.map +1 -0
- package/dist/passages/story/index.d.ts +5 -0
- package/dist/passages/story/index.d.ts.map +1 -0
- package/dist/passages/story/index.js +5 -0
- package/dist/passages/story/index.js.map +1 -0
- package/dist/passages/story/start.d.ts +14 -0
- package/dist/passages/story/start.d.ts.map +1 -0
- package/dist/passages/story/start.js +22 -0
- package/dist/passages/story/start.js.map +1 -0
- package/dist/passages/story/story.d.ts +84 -0
- package/dist/passages/story/story.d.ts.map +1 -0
- package/dist/passages/story/story.js +88 -0
- package/dist/passages/story/story.js.map +1 -0
- package/dist/passages/story/types.d.ts +911 -0
- package/dist/passages/story/types.d.ts.map +1 -0
- package/dist/passages/story/types.js +2 -0
- package/dist/passages/story/types.js.map +1 -0
- package/dist/passages/types/index.d.ts +3 -0
- package/dist/passages/types/index.d.ts.map +1 -0
- package/dist/passages/types/index.js +2 -0
- package/dist/passages/types/index.js.map +1 -0
- package/dist/passages/widget.d.ts +62 -0
- package/dist/passages/widget.d.ts.map +1 -0
- package/dist/passages/widget.js +66 -0
- package/dist/passages/widget.js.map +1 -0
- package/dist/saves/constants.d.ts +17 -0
- package/dist/saves/constants.d.ts.map +1 -0
- package/dist/saves/constants.js +17 -0
- package/dist/saves/constants.js.map +1 -0
- package/dist/saves/db.d.ts +119 -0
- package/dist/saves/db.d.ts.map +1 -0
- package/dist/saves/db.js +231 -0
- package/dist/saves/db.js.map +1 -0
- package/dist/saves/helpers.d.ts +28 -0
- package/dist/saves/helpers.d.ts.map +1 -0
- package/dist/saves/helpers.js +84 -0
- package/dist/saves/helpers.js.map +1 -0
- package/dist/saves/hooks/index.d.ts +10 -0
- package/dist/saves/hooks/index.d.ts.map +1 -0
- package/dist/saves/hooks/index.js +10 -0
- package/dist/saves/hooks/index.js.map +1 -0
- package/dist/saves/hooks/useDeleteAllSlots.d.ts +18 -0
- package/dist/saves/hooks/useDeleteAllSlots.d.ts.map +1 -0
- package/dist/saves/hooks/useDeleteAllSlots.js +18 -0
- package/dist/saves/hooks/useDeleteAllSlots.js.map +1 -0
- package/dist/saves/hooks/useDeleteGame.d.ts +22 -0
- package/dist/saves/hooks/useDeleteGame.d.ts.map +1 -0
- package/dist/saves/hooks/useDeleteGame.js +33 -0
- package/dist/saves/hooks/useDeleteGame.js.map +1 -0
- package/dist/saves/hooks/useExportSaves.d.ts +27 -0
- package/dist/saves/hooks/useExportSaves.d.ts.map +1 -0
- package/dist/saves/hooks/useExportSaves.js +54 -0
- package/dist/saves/hooks/useExportSaves.js.map +1 -0
- package/dist/saves/hooks/useImportSaves.d.ts +29 -0
- package/dist/saves/hooks/useImportSaves.d.ts.map +1 -0
- package/dist/saves/hooks/useImportSaves.js +108 -0
- package/dist/saves/hooks/useImportSaves.js.map +1 -0
- package/dist/saves/hooks/useLastLoadGame.d.ts +39 -0
- package/dist/saves/hooks/useLastLoadGame.d.ts.map +1 -0
- package/dist/saves/hooks/useLastLoadGame.js +72 -0
- package/dist/saves/hooks/useLastLoadGame.js.map +1 -0
- package/dist/saves/hooks/useLoadGame.d.ts +22 -0
- package/dist/saves/hooks/useLoadGame.d.ts.map +1 -0
- package/dist/saves/hooks/useLoadGame.js +40 -0
- package/dist/saves/hooks/useLoadGame.js.map +1 -0
- package/dist/saves/hooks/useRestartGame.d.ts +20 -0
- package/dist/saves/hooks/useRestartGame.d.ts.map +1 -0
- package/dist/saves/hooks/useRestartGame.js +29 -0
- package/dist/saves/hooks/useRestartGame.js.map +1 -0
- package/dist/saves/hooks/useSaveGame.d.ts +22 -0
- package/dist/saves/hooks/useSaveGame.d.ts.map +1 -0
- package/dist/saves/hooks/useSaveGame.js +34 -0
- package/dist/saves/hooks/useSaveGame.js.map +1 -0
- package/dist/saves/hooks/useSaveSlots.d.ts +45 -0
- package/dist/saves/hooks/useSaveSlots.d.ts.map +1 -0
- package/dist/saves/hooks/useSaveSlots.js +42 -0
- package/dist/saves/hooks/useSaveSlots.js.map +1 -0
- package/dist/saves/index.d.ts +4 -0
- package/dist/saves/index.d.ts.map +1 -0
- package/dist/saves/index.js +3 -0
- package/dist/saves/index.js.map +1 -0
- package/dist/saves/types.d.ts +52 -0
- package/dist/saves/types.d.ts.map +1 -0
- package/dist/saves/types.js +2 -0
- package/dist/saves/types.js.map +1 -0
- package/dist/storage.d.ts +124 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +229 -0
- package/dist/storage.js.map +1 -0
- package/dist/tests/game.test.d.ts +2 -0
- package/dist/tests/game.test.d.ts.map +1 -0
- package/dist/tests/game.test.js +602 -0
- package/dist/tests/game.test.js.map +1 -0
- package/dist/tests/interactiveMap.test.d.ts +2 -0
- package/dist/tests/interactiveMap.test.d.ts.map +1 -0
- package/dist/tests/interactiveMap.test.js +1003 -0
- package/dist/tests/interactiveMap.test.js.map +1 -0
- package/dist/tests/storage.test.d.ts +2 -0
- package/dist/tests/storage.test.d.ts.map +1 -0
- package/dist/tests/storage.test.js +328 -0
- package/dist/tests/storage.test.js.map +1 -0
- package/dist/tests/story.test.d.ts +2 -0
- package/dist/tests/story.test.d.ts.map +1 -0
- package/dist/tests/story.test.js +698 -0
- package/dist/tests/story.test.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { Game } from "../game";
|
|
3
|
+
import { newStory } from "../passages/story/fabric";
|
|
4
|
+
import { Story } from "../passages/story/story";
|
|
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("Story", () => {
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
// Clear storage
|
|
14
|
+
Storage.setState({});
|
|
15
|
+
// Re-initialize the game for each test
|
|
16
|
+
await Game.init({ gameName: "Test Game", isDevMode: true });
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
// Reset game state after each test
|
|
20
|
+
Game._resetForTesting();
|
|
21
|
+
});
|
|
22
|
+
describe("Constructor", () => {
|
|
23
|
+
test("creates a story with id and content", () => {
|
|
24
|
+
const id = uniqueId("story");
|
|
25
|
+
const content = () => [
|
|
26
|
+
{ type: "text", content: "Hello" },
|
|
27
|
+
];
|
|
28
|
+
const story = new Story(id, content);
|
|
29
|
+
expect(story.id).toBe(id);
|
|
30
|
+
expect(story.type).toBe("story");
|
|
31
|
+
});
|
|
32
|
+
test("creates a story with options", () => {
|
|
33
|
+
const id = uniqueId("story");
|
|
34
|
+
const content = () => [
|
|
35
|
+
{ type: "text", content: "Hello" },
|
|
36
|
+
];
|
|
37
|
+
const options = {
|
|
38
|
+
background: { image: "/bg.jpg" },
|
|
39
|
+
classNames: { container: "custom-class" },
|
|
40
|
+
};
|
|
41
|
+
const story = new Story(id, content, options);
|
|
42
|
+
expect(story.id).toBe(id);
|
|
43
|
+
});
|
|
44
|
+
test("creates a story without options", () => {
|
|
45
|
+
const id = uniqueId("story");
|
|
46
|
+
const content = () => [
|
|
47
|
+
{ type: "text", content: "Hello" },
|
|
48
|
+
];
|
|
49
|
+
const story = new Story(id, content);
|
|
50
|
+
expect(story.id).toBe(id);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("Factory Function", () => {
|
|
54
|
+
test("newStory creates a Story instance", () => {
|
|
55
|
+
const id = uniqueId("story");
|
|
56
|
+
const content = () => [
|
|
57
|
+
{ type: "text", content: "Test" },
|
|
58
|
+
];
|
|
59
|
+
const story = newStory(id, content);
|
|
60
|
+
expect(story).toBeInstanceOf(Story);
|
|
61
|
+
expect(story.id).toBe(id);
|
|
62
|
+
});
|
|
63
|
+
test("newStory with options", () => {
|
|
64
|
+
const id = uniqueId("story");
|
|
65
|
+
const content = () => [
|
|
66
|
+
{ type: "text", content: "Test" },
|
|
67
|
+
];
|
|
68
|
+
const options = {
|
|
69
|
+
background: { image: "/test.jpg" },
|
|
70
|
+
};
|
|
71
|
+
const story = newStory(id, content, options);
|
|
72
|
+
expect(story).toBeInstanceOf(Story);
|
|
73
|
+
expect(story.id).toBe(id);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("Display Method", () => {
|
|
77
|
+
test("displays story with simple text component", () => {
|
|
78
|
+
const content = () => [
|
|
79
|
+
{ type: "text", content: "Hello World" },
|
|
80
|
+
];
|
|
81
|
+
const story = newStory(uniqueId("story"), content);
|
|
82
|
+
const result = story.display();
|
|
83
|
+
expect(result.components).toHaveLength(1);
|
|
84
|
+
expect(result.components[0]?.type).toBe("text");
|
|
85
|
+
expect(result.components[0]?.content).toBe("Hello World");
|
|
86
|
+
});
|
|
87
|
+
test("displays story with multiple components", () => {
|
|
88
|
+
const content = () => [
|
|
89
|
+
{
|
|
90
|
+
type: "header",
|
|
91
|
+
content: "Title",
|
|
92
|
+
props: { level: 1 },
|
|
93
|
+
},
|
|
94
|
+
{ type: "text", content: "Paragraph text" },
|
|
95
|
+
{ type: "image", content: "/image.jpg" },
|
|
96
|
+
];
|
|
97
|
+
const story = newStory(uniqueId("story"), content);
|
|
98
|
+
const result = story.display();
|
|
99
|
+
expect(result.components).toHaveLength(3);
|
|
100
|
+
expect(result.components[0]?.type).toBe("header");
|
|
101
|
+
expect(result.components[1]?.type).toBe("text");
|
|
102
|
+
expect(result.components[2]?.type).toBe("image");
|
|
103
|
+
});
|
|
104
|
+
test("displays story with header component", () => {
|
|
105
|
+
const content = () => [
|
|
106
|
+
{
|
|
107
|
+
type: "header",
|
|
108
|
+
content: "Chapter 1",
|
|
109
|
+
props: { level: 1, className: "custom" },
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
const story = newStory(uniqueId("story"), content);
|
|
113
|
+
const result = story.display();
|
|
114
|
+
expect(result.components[0]?.type).toBe("header");
|
|
115
|
+
expect(result.components[0]?.content).toBe("Chapter 1");
|
|
116
|
+
expect(result.components[0]?.props?.level).toBe(1);
|
|
117
|
+
expect(result.components[0]?.props?.className).toBe("custom");
|
|
118
|
+
});
|
|
119
|
+
test("displays story with image component", () => {
|
|
120
|
+
const content = () => [
|
|
121
|
+
{
|
|
122
|
+
type: "image",
|
|
123
|
+
content: "/path/to/image.jpg",
|
|
124
|
+
props: { alt: "Test Image", className: "rounded" },
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
const story = newStory(uniqueId("story"), content);
|
|
128
|
+
const result = story.display();
|
|
129
|
+
const imgComp = result.components[0];
|
|
130
|
+
expect(imgComp.type).toBe("image");
|
|
131
|
+
expect(imgComp.content).toBe("/path/to/image.jpg");
|
|
132
|
+
expect(imgComp.props?.alt).toBe("Test Image");
|
|
133
|
+
expect(imgComp.props?.className).toBe("rounded");
|
|
134
|
+
});
|
|
135
|
+
test("displays story with video component", () => {
|
|
136
|
+
const content = () => [
|
|
137
|
+
{
|
|
138
|
+
type: "video",
|
|
139
|
+
content: "/video.mp4",
|
|
140
|
+
props: {
|
|
141
|
+
controls: true,
|
|
142
|
+
autoPlay: false,
|
|
143
|
+
loop: true,
|
|
144
|
+
muted: false,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
const story = newStory(uniqueId("story"), content);
|
|
149
|
+
const result = story.display();
|
|
150
|
+
const vidComp = result.components[0];
|
|
151
|
+
expect(vidComp.type).toBe("video");
|
|
152
|
+
expect(vidComp.content).toBe("/video.mp4");
|
|
153
|
+
expect(vidComp.props?.controls).toBe(true);
|
|
154
|
+
expect(vidComp.props?.loop).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
test("displays story with actions component", () => {
|
|
157
|
+
const mockAction = () => { };
|
|
158
|
+
const content = () => [
|
|
159
|
+
{
|
|
160
|
+
type: "actions",
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
label: "Continue",
|
|
164
|
+
action: mockAction,
|
|
165
|
+
color: "primary",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: "Back",
|
|
169
|
+
action: mockAction,
|
|
170
|
+
color: "secondary",
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
props: { direction: "horizontal" },
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
const story = newStory(uniqueId("story"), content);
|
|
177
|
+
const result = story.display();
|
|
178
|
+
const actComp = result.components[0];
|
|
179
|
+
expect(actComp.type).toBe("actions");
|
|
180
|
+
expect(actComp.content).toHaveLength(2);
|
|
181
|
+
expect(actComp.content[0]?.label).toBe("Continue");
|
|
182
|
+
expect(actComp.props?.direction).toBe("horizontal");
|
|
183
|
+
});
|
|
184
|
+
test("displays story with conversation component", () => {
|
|
185
|
+
const content = () => [
|
|
186
|
+
{
|
|
187
|
+
type: "conversation",
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
who: { name: "NPC" },
|
|
191
|
+
content: "Hello!",
|
|
192
|
+
side: "left",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
who: { name: "Player" },
|
|
196
|
+
content: "Hi there!",
|
|
197
|
+
side: "right",
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
appearance: "atOnce",
|
|
201
|
+
props: { variant: "chat" },
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
const story = newStory(uniqueId("story"), content);
|
|
205
|
+
const result = story.display();
|
|
206
|
+
const convComp = result.components[0];
|
|
207
|
+
expect(convComp.type).toBe("conversation");
|
|
208
|
+
expect(convComp.content).toHaveLength(2);
|
|
209
|
+
expect(convComp.content[0]?.who?.name).toBe("NPC");
|
|
210
|
+
expect(convComp.appearance).toBe("atOnce");
|
|
211
|
+
});
|
|
212
|
+
test("displays story with anotherStory component", () => {
|
|
213
|
+
const content = () => [
|
|
214
|
+
{
|
|
215
|
+
type: "anotherStory",
|
|
216
|
+
storyId: "embedded-story",
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
const story = newStory(uniqueId("story"), content);
|
|
220
|
+
const result = story.display();
|
|
221
|
+
const anotherComp = result.components[0];
|
|
222
|
+
expect(anotherComp.type).toBe("anotherStory");
|
|
223
|
+
expect(anotherComp.storyId).toBe("embedded-story");
|
|
224
|
+
});
|
|
225
|
+
test("passes props to content function", () => {
|
|
226
|
+
const content = (props) => [
|
|
227
|
+
{
|
|
228
|
+
type: "text",
|
|
229
|
+
content: `Hello ${props.playerName}!`,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: "text",
|
|
233
|
+
content: `Level: ${props.level}`,
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
// @ts-expect-error TS2345
|
|
237
|
+
const story = newStory(uniqueId("story"), content);
|
|
238
|
+
const result = story.display({ playerName: "Hero", level: 5 });
|
|
239
|
+
expect(result.components[0]?.content).toBe("Hello Hero!");
|
|
240
|
+
expect(result.components[1]?.content).toBe("Level: 5");
|
|
241
|
+
});
|
|
242
|
+
test("returns story options", () => {
|
|
243
|
+
const content = () => [
|
|
244
|
+
{ type: "text", content: "Test" },
|
|
245
|
+
];
|
|
246
|
+
const options = {
|
|
247
|
+
background: { image: "/bg.jpg" },
|
|
248
|
+
classNames: {
|
|
249
|
+
base: "base-class",
|
|
250
|
+
container: "container-class",
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
const story = newStory(uniqueId("story"), content, options);
|
|
254
|
+
const result = story.display();
|
|
255
|
+
expect(result.options).toEqual(options);
|
|
256
|
+
});
|
|
257
|
+
test("returns undefined options when not provided", () => {
|
|
258
|
+
const content = () => [
|
|
259
|
+
{ type: "text", content: "Test" },
|
|
260
|
+
];
|
|
261
|
+
const story = newStory(uniqueId("story"), content);
|
|
262
|
+
const result = story.display();
|
|
263
|
+
expect(result.options).toEqual({});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
describe("Component Types", () => {
|
|
267
|
+
test("text component with all properties", () => {
|
|
268
|
+
const content = () => [
|
|
269
|
+
{
|
|
270
|
+
type: "text",
|
|
271
|
+
id: "text-1",
|
|
272
|
+
content: "Sample text",
|
|
273
|
+
initialVariant: "display",
|
|
274
|
+
props: { className: "custom-text" },
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
const story = newStory(uniqueId("story"), content);
|
|
278
|
+
const result = story.display();
|
|
279
|
+
const textComp = result.components[0];
|
|
280
|
+
expect(textComp?.type).toBe("text");
|
|
281
|
+
expect(textComp?.id).toBe("text-1");
|
|
282
|
+
expect(textComp?.initialVariant).toBe("display");
|
|
283
|
+
expect(textComp?.props?.className).toBe("custom-text");
|
|
284
|
+
});
|
|
285
|
+
test("header component with all levels", () => {
|
|
286
|
+
const content = () => [
|
|
287
|
+
{
|
|
288
|
+
type: "header",
|
|
289
|
+
content: "H1",
|
|
290
|
+
props: { level: 1 },
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
type: "header",
|
|
294
|
+
content: "H2",
|
|
295
|
+
props: { level: 2 },
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
type: "header",
|
|
299
|
+
content: "H3",
|
|
300
|
+
props: { level: 3 },
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
type: "header",
|
|
304
|
+
content: "H4",
|
|
305
|
+
props: { level: 4 },
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
type: "header",
|
|
309
|
+
content: "H5",
|
|
310
|
+
props: { level: 5 },
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
type: "header",
|
|
314
|
+
content: "H6",
|
|
315
|
+
props: { level: 6 },
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
const story = newStory(uniqueId("story"), content);
|
|
319
|
+
const result = story.display();
|
|
320
|
+
expect(result.components).toHaveLength(6);
|
|
321
|
+
for (let i = 0; i < 6; i++) {
|
|
322
|
+
expect(result.components[i]?.type).toBe("header");
|
|
323
|
+
expect(result.components[i]?.props?.level).toBe((i + 1));
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
test("image component with all properties", () => {
|
|
327
|
+
const mockClick = () => { };
|
|
328
|
+
const content = () => [
|
|
329
|
+
{
|
|
330
|
+
type: "image",
|
|
331
|
+
content: "/image.jpg",
|
|
332
|
+
props: {
|
|
333
|
+
alt: "Alt text",
|
|
334
|
+
className: "custom-image",
|
|
335
|
+
disableModal: true,
|
|
336
|
+
onClick: mockClick,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
];
|
|
340
|
+
const story = newStory(uniqueId("story"), content);
|
|
341
|
+
const result = story.display();
|
|
342
|
+
const imgComp = result.components[0];
|
|
343
|
+
expect(imgComp?.type).toBe("image");
|
|
344
|
+
expect(imgComp.props?.alt).toBe("Alt text");
|
|
345
|
+
expect(imgComp.props?.disableModal).toBe(true);
|
|
346
|
+
expect(imgComp.props?.onClick).toBe(mockClick);
|
|
347
|
+
});
|
|
348
|
+
test("actions component with disabled action", () => {
|
|
349
|
+
const content = () => [
|
|
350
|
+
{
|
|
351
|
+
type: "actions",
|
|
352
|
+
content: [
|
|
353
|
+
{
|
|
354
|
+
label: "Disabled",
|
|
355
|
+
action: () => { },
|
|
356
|
+
isDisabled: true,
|
|
357
|
+
tooltip: {
|
|
358
|
+
content: "Not available yet",
|
|
359
|
+
position: "top",
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
];
|
|
365
|
+
const story = newStory(uniqueId("story"), content);
|
|
366
|
+
const result = story.display();
|
|
367
|
+
const action = result.components[0]
|
|
368
|
+
.content[0];
|
|
369
|
+
expect(action?.isDisabled).toBe(true);
|
|
370
|
+
expect(action?.tooltip?.content).toBe("Not available yet");
|
|
371
|
+
expect(action?.tooltip?.position).toBe("top");
|
|
372
|
+
});
|
|
373
|
+
test("conversation component with avatars and colors", () => {
|
|
374
|
+
const content = () => [
|
|
375
|
+
{
|
|
376
|
+
type: "conversation",
|
|
377
|
+
content: [
|
|
378
|
+
{
|
|
379
|
+
who: { name: "Alice", avatar: "/alice.png" },
|
|
380
|
+
content: "Hello",
|
|
381
|
+
side: "left",
|
|
382
|
+
color: "#ff0000",
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
who: { name: "Bob", avatar: "/bob.png" },
|
|
386
|
+
content: "Hi",
|
|
387
|
+
side: "right",
|
|
388
|
+
color: "#00ff00",
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
appearance: "byClick",
|
|
392
|
+
props: { variant: "messenger" },
|
|
393
|
+
},
|
|
394
|
+
];
|
|
395
|
+
const story = newStory(uniqueId("story"), content);
|
|
396
|
+
const result = story.display();
|
|
397
|
+
const conversation = result.components[0];
|
|
398
|
+
expect(conversation.appearance).toBe("byClick");
|
|
399
|
+
expect(conversation.props?.variant).toBe("messenger");
|
|
400
|
+
expect(conversation.content[0]?.color).toBe("#ff0000");
|
|
401
|
+
expect(conversation.content[1]?.who?.avatar).toBe("/bob.png");
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
describe("Dynamic Content", () => {
|
|
405
|
+
test("content function receives empty object by default", () => {
|
|
406
|
+
let receivedProps = null;
|
|
407
|
+
const content = (props) => {
|
|
408
|
+
receivedProps = props;
|
|
409
|
+
return [{ type: "text", content: "Test" }];
|
|
410
|
+
};
|
|
411
|
+
const story = newStory(uniqueId("story"), content);
|
|
412
|
+
story.display();
|
|
413
|
+
expect(receivedProps).toEqual({});
|
|
414
|
+
});
|
|
415
|
+
test("content function receives provided props", () => {
|
|
416
|
+
let receivedProps = null;
|
|
417
|
+
const content = (props) => {
|
|
418
|
+
receivedProps = props;
|
|
419
|
+
return [{ type: "text", content: "Test" }];
|
|
420
|
+
};
|
|
421
|
+
const story = newStory(uniqueId("story"), content);
|
|
422
|
+
const testProps = { test: "value", number: 42 };
|
|
423
|
+
story.display(testProps);
|
|
424
|
+
expect(receivedProps).toEqual(testProps);
|
|
425
|
+
});
|
|
426
|
+
test("conditional rendering based on props", () => {
|
|
427
|
+
const content = (props) => {
|
|
428
|
+
const components = [
|
|
429
|
+
{
|
|
430
|
+
type: "text",
|
|
431
|
+
content: "You approach the door.",
|
|
432
|
+
},
|
|
433
|
+
];
|
|
434
|
+
if (props.hasKey) {
|
|
435
|
+
components.push({
|
|
436
|
+
type: "actions",
|
|
437
|
+
content: [{ label: "Open door", action: () => { } }],
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
components.push({
|
|
442
|
+
type: "text",
|
|
443
|
+
content: "The door is locked.",
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return components;
|
|
447
|
+
};
|
|
448
|
+
// @ts-expect-error TS2345
|
|
449
|
+
const story = newStory(uniqueId("story"), content);
|
|
450
|
+
const resultWithKey = story.display({ hasKey: true });
|
|
451
|
+
expect(resultWithKey.components).toHaveLength(2);
|
|
452
|
+
expect(resultWithKey.components[1]?.type).toBe("actions");
|
|
453
|
+
const resultWithoutKey = story.display({ hasKey: false });
|
|
454
|
+
expect(resultWithoutKey.components).toHaveLength(2);
|
|
455
|
+
expect(resultWithoutKey.components[1]?.type).toBe("text");
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
describe("Story Options", () => {
|
|
459
|
+
test("background with static image", () => {
|
|
460
|
+
const content = () => [
|
|
461
|
+
{ type: "text", content: "Test" },
|
|
462
|
+
];
|
|
463
|
+
const options = {
|
|
464
|
+
background: { image: "/background.jpg" },
|
|
465
|
+
};
|
|
466
|
+
const story = newStory(uniqueId("story"), content, options);
|
|
467
|
+
const result = story.display();
|
|
468
|
+
expect(result.options?.background?.image).toBe("/background.jpg");
|
|
469
|
+
});
|
|
470
|
+
test("background with function returning image", () => {
|
|
471
|
+
const content = () => [{ type: "text", content: "Test" }];
|
|
472
|
+
const options = {
|
|
473
|
+
background: { image: () => "/dynamic-bg.jpg" },
|
|
474
|
+
};
|
|
475
|
+
const story = newStory(uniqueId("story"), content, options);
|
|
476
|
+
const result = story.display();
|
|
477
|
+
expect(typeof result.options?.background?.image).toBe("function");
|
|
478
|
+
if (typeof result.options?.background?.image === "function") {
|
|
479
|
+
expect(result.options.background.image()).toBe("/dynamic-bg.jpg");
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
test("classNames for base and container", () => {
|
|
483
|
+
const content = () => [{ type: "text", content: "Test" }];
|
|
484
|
+
const options = {
|
|
485
|
+
classNames: {
|
|
486
|
+
base: "base-custom",
|
|
487
|
+
container: "container-custom",
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
const story = newStory(uniqueId("story"), content, options);
|
|
491
|
+
const result = story.display();
|
|
492
|
+
expect(result.options?.classNames?.base).toBe("base-custom");
|
|
493
|
+
expect(result.options?.classNames?.container).toBe("container-custom");
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
describe("Integration with Game", () => {
|
|
497
|
+
test("story is registered with Game on creation", () => {
|
|
498
|
+
const id = uniqueId("story");
|
|
499
|
+
const content = () => [{ type: "text", content: "Test" }];
|
|
500
|
+
newStory(id, content);
|
|
501
|
+
const retrieved = Game.getPassageById(id);
|
|
502
|
+
expect(retrieved).not.toBeNull();
|
|
503
|
+
expect(retrieved?.id).toBe(id);
|
|
504
|
+
expect(retrieved?.type).toBe("story");
|
|
505
|
+
});
|
|
506
|
+
test("can navigate to story using Game.jumpTo", () => {
|
|
507
|
+
const id = uniqueId("story");
|
|
508
|
+
const content = () => [{ type: "text", content: "Test" }];
|
|
509
|
+
newStory(id, content);
|
|
510
|
+
Game.jumpTo(id);
|
|
511
|
+
expect(Game.selfState.currentPassageId).toBe(id);
|
|
512
|
+
});
|
|
513
|
+
test("can retrieve and display current story", () => {
|
|
514
|
+
const id = uniqueId("story");
|
|
515
|
+
const content = () => [
|
|
516
|
+
{
|
|
517
|
+
type: "text",
|
|
518
|
+
content: "Current story content",
|
|
519
|
+
},
|
|
520
|
+
];
|
|
521
|
+
newStory(id, content);
|
|
522
|
+
Game.jumpTo(id);
|
|
523
|
+
const currentPassage = Game.currentPassage;
|
|
524
|
+
expect(currentPassage).not.toBeNull();
|
|
525
|
+
expect(currentPassage?.type).toBe("story");
|
|
526
|
+
const result = currentPassage.display();
|
|
527
|
+
expect(result.components[0].content).toBe("Current story content");
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
describe("Edge Cases", () => {
|
|
531
|
+
test("empty content array", () => {
|
|
532
|
+
const content = () => [];
|
|
533
|
+
const story = newStory(uniqueId("story"), content);
|
|
534
|
+
const result = story.display();
|
|
535
|
+
expect(result.components).toHaveLength(0);
|
|
536
|
+
});
|
|
537
|
+
test("content function returns different results on each call", () => {
|
|
538
|
+
let callCount = 0;
|
|
539
|
+
const content = () => {
|
|
540
|
+
callCount++;
|
|
541
|
+
return [
|
|
542
|
+
{
|
|
543
|
+
type: "text",
|
|
544
|
+
content: `Call ${callCount}`,
|
|
545
|
+
},
|
|
546
|
+
];
|
|
547
|
+
};
|
|
548
|
+
const story = newStory(uniqueId("story"), content);
|
|
549
|
+
const result1 = story.display();
|
|
550
|
+
expect(result1.components[0].content).toBe("Call 1");
|
|
551
|
+
const result2 = story.display();
|
|
552
|
+
expect(result2.components[0].content).toBe("Call 2");
|
|
553
|
+
});
|
|
554
|
+
test("text component with JSX-like content", () => {
|
|
555
|
+
const content = () => [{ type: "text", content: "Bold text" }];
|
|
556
|
+
const story = newStory(uniqueId("story"), content);
|
|
557
|
+
const result = story.display();
|
|
558
|
+
expect(result.components[0].content).toBe("Bold text");
|
|
559
|
+
});
|
|
560
|
+
test("actions with all color variants", () => {
|
|
561
|
+
const content = () => [
|
|
562
|
+
{
|
|
563
|
+
type: "actions",
|
|
564
|
+
content: [
|
|
565
|
+
{
|
|
566
|
+
label: "Default",
|
|
567
|
+
action: () => { },
|
|
568
|
+
color: "default",
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
label: "Primary",
|
|
572
|
+
action: () => { },
|
|
573
|
+
color: "primary",
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
label: "Secondary",
|
|
577
|
+
action: () => { },
|
|
578
|
+
color: "secondary",
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
label: "Success",
|
|
582
|
+
action: () => { },
|
|
583
|
+
color: "success",
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
label: "Warning",
|
|
587
|
+
action: () => { },
|
|
588
|
+
color: "warning",
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
label: "Danger",
|
|
592
|
+
action: () => { },
|
|
593
|
+
color: "danger",
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
},
|
|
597
|
+
];
|
|
598
|
+
const story = newStory(uniqueId("story"), content);
|
|
599
|
+
const result = story.display();
|
|
600
|
+
const actions = result.components[0];
|
|
601
|
+
expect(actions.content).toHaveLength(6);
|
|
602
|
+
expect(actions.content[0]?.color).toBe("default");
|
|
603
|
+
expect(actions.content[5]?.color).toBe("danger");
|
|
604
|
+
});
|
|
605
|
+
test("actions with all button variants", () => {
|
|
606
|
+
const content = () => [
|
|
607
|
+
{
|
|
608
|
+
type: "actions",
|
|
609
|
+
content: [
|
|
610
|
+
{
|
|
611
|
+
label: "Solid",
|
|
612
|
+
action: () => { },
|
|
613
|
+
variant: "solid",
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
label: "Bordered",
|
|
617
|
+
action: () => { },
|
|
618
|
+
variant: "bordered",
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
label: "Light",
|
|
622
|
+
action: () => { },
|
|
623
|
+
variant: "light",
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
label: "Flat",
|
|
627
|
+
action: () => { },
|
|
628
|
+
variant: "flat",
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
label: "Faded",
|
|
632
|
+
action: () => { },
|
|
633
|
+
variant: "faded",
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
label: "Shadow",
|
|
637
|
+
action: () => { },
|
|
638
|
+
variant: "shadow",
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
label: "Ghost",
|
|
642
|
+
action: () => { },
|
|
643
|
+
variant: "ghost",
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
},
|
|
647
|
+
];
|
|
648
|
+
const story = newStory(uniqueId("story"), content);
|
|
649
|
+
const result = story.display();
|
|
650
|
+
const actions = result.components[0];
|
|
651
|
+
expect(actions.content).toHaveLength(7);
|
|
652
|
+
expect(actions.content[0]?.variant).toBe("solid");
|
|
653
|
+
expect(actions.content[6]?.variant).toBe("ghost");
|
|
654
|
+
});
|
|
655
|
+
test("conversation bubble with custom classNames", () => {
|
|
656
|
+
const content = () => [
|
|
657
|
+
{
|
|
658
|
+
type: "conversation",
|
|
659
|
+
content: [
|
|
660
|
+
{
|
|
661
|
+
content: "Test",
|
|
662
|
+
props: {
|
|
663
|
+
classNames: {
|
|
664
|
+
base: "custom-base",
|
|
665
|
+
content: "custom-content",
|
|
666
|
+
avatar: "custom-avatar",
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
],
|
|
671
|
+
},
|
|
672
|
+
];
|
|
673
|
+
const story = newStory(uniqueId("story"), content);
|
|
674
|
+
const result = story.display();
|
|
675
|
+
const conversation = result.components[0];
|
|
676
|
+
expect(conversation.content[0]?.props?.classNames?.base).toBe("custom-base");
|
|
677
|
+
expect(conversation.content[0]?.props?.classNames?.content).toBe("custom-content");
|
|
678
|
+
expect(conversation.content[0]?.props?.classNames?.avatar).toBe("custom-avatar");
|
|
679
|
+
});
|
|
680
|
+
test("multiple nested anotherStory components", () => {
|
|
681
|
+
const content = () => [
|
|
682
|
+
{ type: "anotherStory", storyId: "story-1" },
|
|
683
|
+
{ type: "text", content: "Middle content" },
|
|
684
|
+
{ type: "anotherStory", storyId: "story-2" },
|
|
685
|
+
];
|
|
686
|
+
const story = newStory(uniqueId("story"), content);
|
|
687
|
+
const result = story.display();
|
|
688
|
+
const story1 = result.components[0];
|
|
689
|
+
const story2 = result.components[2];
|
|
690
|
+
expect(result.components).toHaveLength(3);
|
|
691
|
+
expect(story1.type).toBe("anotherStory");
|
|
692
|
+
expect(story1.storyId).toBe("story-1");
|
|
693
|
+
expect(story2.type).toBe("anotherStory");
|
|
694
|
+
expect(story2.storyId).toBe("story-2");
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
//# sourceMappingURL=story.test.js.map
|