@rpgjs/server 5.0.0-alpha.24 → 5.0.0-alpha.26
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/dist/Player/BattleManager.d.ts +1 -1
- package/dist/Player/ClassManager.d.ts +1 -1
- package/dist/Player/ComponentManager.d.ts +1 -1
- package/dist/Player/EffectManager.d.ts +1 -1
- package/dist/Player/ElementManager.d.ts +1 -1
- package/dist/Player/GoldManager.d.ts +1 -1
- package/dist/Player/GuiManager.d.ts +1 -1
- package/dist/Player/ItemFixture.d.ts +1 -1
- package/dist/Player/ItemManager.d.ts +1 -1
- package/dist/Player/MoveManager.d.ts +1 -1
- package/dist/Player/ParameterManager.d.ts +1 -1
- package/dist/Player/Player.d.ts +32 -23
- package/dist/Player/SkillManager.d.ts +1 -1
- package/dist/Player/StateManager.d.ts +1 -1
- package/dist/Player/VariableManager.d.ts +1 -1
- package/dist/RpgServer.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8267 -10267
- package/dist/index.js.map +1 -1
- package/dist/rooms/BaseRoom.d.ts +95 -0
- package/dist/rooms/lobby.d.ts +4 -1
- package/dist/rooms/map.d.ts +67 -81
- package/package.json +10 -8
- package/src/Player/ItemManager.ts +55 -16
- package/src/Player/ParameterManager.ts +6 -1
- package/src/Player/Player.ts +164 -148
- package/src/module.ts +13 -0
- package/src/rooms/BaseRoom.ts +120 -0
- package/src/rooms/lobby.ts +11 -1
- package/src/rooms/map.ts +148 -152
- package/tests/change-map.spec.ts +72 -0
- package/tests/item.spec.ts +591 -0
- package/tests/module.spec.ts +38 -0
- package/tests/player-param.spec.ts +28 -0
- package/tests/world-maps.spec.ts +814 -0
- package/vite.config.ts +16 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { beforeEach, test, expect, afterEach, describe, vi } from "vitest";
|
|
2
|
+
import { testing, waitForSyncComplete } from "@rpgjs/testing";
|
|
3
|
+
import { defineModule, createModule } from "@rpgjs/common";
|
|
4
|
+
import { RpgPlayer, RpgServer } from "../src";
|
|
5
|
+
import { RpgClient } from "../../client/src";
|
|
6
|
+
import { ItemLog } from "../src/logs";
|
|
7
|
+
import type { ItemObject } from "../src/Player/ItemManager";
|
|
8
|
+
|
|
9
|
+
// Define test items as objects for database
|
|
10
|
+
const TestPotion = {
|
|
11
|
+
id: "TestPotion",
|
|
12
|
+
name: "Test Potion",
|
|
13
|
+
description: "Restores 100 HP",
|
|
14
|
+
price: 200,
|
|
15
|
+
hpValue: 100,
|
|
16
|
+
consumable: true,
|
|
17
|
+
_type: "item" as const,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const TestSword = {
|
|
21
|
+
name: "Test Sword",
|
|
22
|
+
description: "A basic sword",
|
|
23
|
+
price: 500,
|
|
24
|
+
atk: 50,
|
|
25
|
+
_type: "weapon" as const,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const TestArmor = {
|
|
29
|
+
name: "Test Armor",
|
|
30
|
+
description: "Basic armor",
|
|
31
|
+
price: 300,
|
|
32
|
+
pdef: 30,
|
|
33
|
+
sdef: 20,
|
|
34
|
+
_type: "armor" as const,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const TestNonConsumable = {
|
|
38
|
+
name: "Test Non-Consumable",
|
|
39
|
+
description: "Cannot be used",
|
|
40
|
+
price: 100,
|
|
41
|
+
consumable: false,
|
|
42
|
+
_type: "item" as const,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const TestExpensiveItem = {
|
|
46
|
+
name: "Expensive Item",
|
|
47
|
+
description: "Very expensive",
|
|
48
|
+
price: 10000,
|
|
49
|
+
_type: "item" as const,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const TestNoPriceItem = {
|
|
53
|
+
name: "No Price Item",
|
|
54
|
+
description: "Item without price",
|
|
55
|
+
_type: "item" as const,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let player: RpgPlayer;
|
|
59
|
+
let clientTesting: any;
|
|
60
|
+
let fixture: any;
|
|
61
|
+
|
|
62
|
+
// Define server module with items in database
|
|
63
|
+
const serverModule = defineModule<RpgServer>({
|
|
64
|
+
maps: [
|
|
65
|
+
{
|
|
66
|
+
id: "test-map",
|
|
67
|
+
file: "",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
database: {
|
|
71
|
+
TestPotion: TestPotion,
|
|
72
|
+
TestSword: TestSword,
|
|
73
|
+
TestArmor: TestArmor,
|
|
74
|
+
TestNonConsumable: TestNonConsumable,
|
|
75
|
+
TestExpensiveItem: TestExpensiveItem,
|
|
76
|
+
TestNoPriceItem: TestNoPriceItem,
|
|
77
|
+
},
|
|
78
|
+
player: {
|
|
79
|
+
async onConnected(player) {
|
|
80
|
+
await player.changeMap("test-map", { x: 100, y: 100 });
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Define client module
|
|
86
|
+
const clientModule = defineModule<RpgClient>({
|
|
87
|
+
// Client-side logic
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
|
|
93
|
+
const myModule = createModule("TestModule", [
|
|
94
|
+
{
|
|
95
|
+
server: serverModule,
|
|
96
|
+
client: clientModule,
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
fixture = await testing(myModule);
|
|
101
|
+
clientTesting = await fixture.createClient();
|
|
102
|
+
player = await clientTesting.waitForMapChange("test-map");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(async () => {
|
|
106
|
+
await fixture.clear();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
describe("Item Management - Basic Operations", () => {
|
|
111
|
+
|
|
112
|
+
test("should add item ", async () => {
|
|
113
|
+
const item = player.addItem(TestPotion, 5);
|
|
114
|
+
expect(item).toBeDefined();
|
|
115
|
+
expect(item.id()).toBe("TestPotion");
|
|
116
|
+
expect(item.quantity()).toBe(5);
|
|
117
|
+
expect(item.name()).toBe("Test Potion");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("should add item using string ID", () => {
|
|
121
|
+
const item = player.addItem("TestPotion", 5);
|
|
122
|
+
expect(item).toBeDefined();
|
|
123
|
+
expect(item.id()).toBe("TestPotion");
|
|
124
|
+
expect(item.quantity()).toBe(5);
|
|
125
|
+
expect(item.name()).toBe("Test Potion");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
test("should add item using object", () => {
|
|
130
|
+
const customItem: ItemObject = {
|
|
131
|
+
id: "custom-item",
|
|
132
|
+
name: "Custom Item",
|
|
133
|
+
description: "A custom item",
|
|
134
|
+
price: 150,
|
|
135
|
+
onAdd(player) {
|
|
136
|
+
// Hook test
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const item = player.addItem(customItem, 3);
|
|
140
|
+
expect(item).toBeDefined();
|
|
141
|
+
expect(item.id()).toBe("custom-item");
|
|
142
|
+
expect(item.quantity()).toBe(3);
|
|
143
|
+
expect(item.name()).toBe("Custom Item");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("should add item without ID (auto-generated)", () => {
|
|
147
|
+
const customItem: ItemObject = {
|
|
148
|
+
name: "Auto ID Item",
|
|
149
|
+
price: 100,
|
|
150
|
+
};
|
|
151
|
+
const item = player.addItem(customItem, 1);
|
|
152
|
+
expect(item).toBeDefined();
|
|
153
|
+
expect(item.id()).toMatch(/^item-\d+$/);
|
|
154
|
+
expect(item.name()).toBe("Auto ID Item");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("should increment quantity when adding existing item", () => {
|
|
158
|
+
player.addItem("TestPotion", 3);
|
|
159
|
+
const item = player.addItem("TestPotion", 2);
|
|
160
|
+
expect(item.quantity()).toBe(5);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("should add item when player is in Lobby (no map)", async () => {
|
|
164
|
+
const newFixture = await testing();
|
|
165
|
+
const newClient = await newFixture.createClient();
|
|
166
|
+
const newPlayer = newClient.player;
|
|
167
|
+
|
|
168
|
+
newPlayer.getCurrentMap()?.addInDatabase("TestPotion", TestPotion);
|
|
169
|
+
const item = newPlayer.addItem("TestPotion", 1);
|
|
170
|
+
expect(item).toBeDefined();
|
|
171
|
+
expect(item.id()).toBe("TestPotion");
|
|
172
|
+
expect(item.quantity()).toBe(1);
|
|
173
|
+
await newFixture.clear();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("should throw error when adding item with invalid string ID", () => {
|
|
177
|
+
expect(() => {
|
|
178
|
+
player.addItem("NonExistentItem", 1);
|
|
179
|
+
}).toThrow("The ID=NonExistentItem data is not found in the database");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("should get item from inventory", () => {
|
|
183
|
+
player.addItem("TestPotion", 5);
|
|
184
|
+
const item = player.getItem("TestPotion");
|
|
185
|
+
expect(item).toBeDefined();
|
|
186
|
+
expect(item.id()).toBe("TestPotion");
|
|
187
|
+
expect(item.quantity()).toBe(5);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("should return undefined when getting non-existent item", () => {
|
|
191
|
+
const item = player.getItem("TestPotion");
|
|
192
|
+
expect(item).toBeUndefined();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("should check if player has item", () => {
|
|
196
|
+
expect(player.hasItem("TestPotion")).toBe(false);
|
|
197
|
+
player.addItem("TestPotion", 1);
|
|
198
|
+
expect(player.hasItem("TestPotion")).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("should remove item from inventory", () => {
|
|
202
|
+
player.addItem("TestPotion", 5);
|
|
203
|
+
const item = player.removeItem("TestPotion", 2);
|
|
204
|
+
expect(item).toBeDefined();
|
|
205
|
+
expect(item?.quantity()).toBe(3);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("should remove item completely when quantity reaches zero", () => {
|
|
209
|
+
player.addItem("TestPotion", 2);
|
|
210
|
+
player.removeItem("TestPotion", 2);
|
|
211
|
+
expect(player.hasItem("TestPotion")).toBe(false);
|
|
212
|
+
const item = player.getItem("TestPotion");
|
|
213
|
+
expect(item).toBeUndefined();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("should throw error when removing non-existent item", () => {
|
|
217
|
+
expect(() => {
|
|
218
|
+
player.removeItem("TestPotion", 1);
|
|
219
|
+
}).toThrow();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("Item Management - Buy and Sell", () => {
|
|
224
|
+
test("should buy item and reduce gold", () => {
|
|
225
|
+
player.gold = 1000;
|
|
226
|
+
const item = player.buyItem("TestPotion", 2);
|
|
227
|
+
expect(item).toBeDefined();
|
|
228
|
+
expect(item.quantity()).toBe(2);
|
|
229
|
+
expect(player.gold).toBe(600); // 1000 - (200 * 2)
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("should throw error when buying item without price", () => {
|
|
233
|
+
player.gold = 1000;
|
|
234
|
+
expect(() => {
|
|
235
|
+
player.buyItem("TestNoPriceItem", 1);
|
|
236
|
+
}).toThrow();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("should throw error when not enough gold", () => {
|
|
240
|
+
player.gold = 100;
|
|
241
|
+
expect(() => {
|
|
242
|
+
player.buyItem("TestExpensiveItem", 1);
|
|
243
|
+
}).toThrow();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("should sell item and increase gold", () => {
|
|
247
|
+
player.gold = 1000;
|
|
248
|
+
player.addItem("TestPotion", 3);
|
|
249
|
+
const item = player.sellItem("TestPotion", 2);
|
|
250
|
+
expect(item).toBeDefined();
|
|
251
|
+
expect(player.gold).toBe(1200); // 1000 + (200 / 2 * 2)
|
|
252
|
+
expect(player.getItem("TestPotion")?.quantity()).toBe(1);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("should throw error when selling non-existent item", () => {
|
|
256
|
+
expect(() => {
|
|
257
|
+
player.sellItem("TestPotion", 1);
|
|
258
|
+
}).toThrow();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("should throw error when selling more items than available", () => {
|
|
262
|
+
player.addItem("TestPotion", 2);
|
|
263
|
+
expect(() => {
|
|
264
|
+
player.sellItem("TestPotion", 5);
|
|
265
|
+
}).toThrow();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("should throw error when selling item without price", () => {
|
|
269
|
+
player.addItem("TestNoPriceItem", 1);
|
|
270
|
+
expect(() => {
|
|
271
|
+
player.sellItem("TestNoPriceItem", 1);
|
|
272
|
+
}).toThrow();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("Item Management - Use Item", () => {
|
|
277
|
+
test("should use consumable item", () => {
|
|
278
|
+
const initialHp = player.hp;
|
|
279
|
+
player.addItem("TestPotion", 1);
|
|
280
|
+
const item = player.useItem("TestPotion");
|
|
281
|
+
expect(item).toBeDefined();
|
|
282
|
+
expect(player.hasItem("TestPotion")).toBe(false);
|
|
283
|
+
// Note: applyEffect might not be implemented in test environment
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("should throw error when using non-existent item", () => {
|
|
287
|
+
expect(() => {
|
|
288
|
+
player.useItem("TestPotion");
|
|
289
|
+
}).toThrow();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("should throw error when using non-consumable item", () => {
|
|
293
|
+
player.addItem("TestNonConsumable", 1);
|
|
294
|
+
expect(() => {
|
|
295
|
+
player.useItem("TestNonConsumable");
|
|
296
|
+
}).toThrow();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("should handle hitRate chance (success)", () => {
|
|
300
|
+
// Mock Math.random to return a value that passes hitRate
|
|
301
|
+
const originalRandom = Math.random;
|
|
302
|
+
Math.random = vi.fn(() => 0.5); // 0.5 < 1.0 (default hitRate)
|
|
303
|
+
|
|
304
|
+
player.addItem("TestPotion", 1);
|
|
305
|
+
const item = player.useItem("TestPotion");
|
|
306
|
+
expect(item).toBeDefined();
|
|
307
|
+
expect(player.hasItem("TestPotion")).toBe(false);
|
|
308
|
+
|
|
309
|
+
Math.random = originalRandom;
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("should handle hitRate chance (failure)", () => {
|
|
313
|
+
// Create a potion with low hitRate (10% chance)
|
|
314
|
+
const lowHitRatePotion: ItemObject = {
|
|
315
|
+
id: "LowHitRatePotion",
|
|
316
|
+
name: "Low Hit Rate Potion",
|
|
317
|
+
price: 200,
|
|
318
|
+
consumable: true,
|
|
319
|
+
hitRate: 0.1, // 10% chance
|
|
320
|
+
};
|
|
321
|
+
player.getCurrentMap()?.addInDatabase("LowHitRatePotion", lowHitRatePotion);
|
|
322
|
+
|
|
323
|
+
// Mock Math.random to return a value that fails hitRate
|
|
324
|
+
const originalRandom = Math.random;
|
|
325
|
+
Math.random = vi.fn(() => 0.9); // 0.9 > 0.1 (hitRate)
|
|
326
|
+
|
|
327
|
+
player.addItem("LowHitRatePotion", 1);
|
|
328
|
+
expect(() => {
|
|
329
|
+
player.useItem("LowHitRatePotion");
|
|
330
|
+
}).toThrow();
|
|
331
|
+
|
|
332
|
+
// Item should still be removed even on failure
|
|
333
|
+
expect(player.hasItem("LowHitRatePotion")).toBe(false);
|
|
334
|
+
|
|
335
|
+
Math.random = originalRandom;
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("should use item with custom hitRate", () => {
|
|
339
|
+
const customItem: ItemObject = {
|
|
340
|
+
id: "chance-item",
|
|
341
|
+
name: "Chance Item",
|
|
342
|
+
price: 100,
|
|
343
|
+
consumable: true,
|
|
344
|
+
hitRate: 0.5, // 50% chance
|
|
345
|
+
};
|
|
346
|
+
player.addItem(customItem, 1);
|
|
347
|
+
|
|
348
|
+
// Mock Math.random to pass
|
|
349
|
+
const originalRandom = Math.random;
|
|
350
|
+
Math.random = vi.fn(() => 0.3); // 0.3 < 0.5
|
|
351
|
+
|
|
352
|
+
const item = player.useItem("chance-item");
|
|
353
|
+
expect(item).toBeDefined();
|
|
354
|
+
|
|
355
|
+
Math.random = originalRandom;
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe("Item Management - Equipment", () => {
|
|
360
|
+
test("should equip weapon", () => {
|
|
361
|
+
player.addItem("TestSword", 1);
|
|
362
|
+
player.equip("TestSword", true);
|
|
363
|
+
const item = player.getItem("TestSword");
|
|
364
|
+
expect((item as any).equipped).toBe(true);
|
|
365
|
+
expect(player.equipments().some((eq) => eq.id() === "TestSword")).toBe(
|
|
366
|
+
true
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("should unequip weapon", () => {
|
|
371
|
+
player.addItem("TestSword", 1);
|
|
372
|
+
player.equip("TestSword", true);
|
|
373
|
+
player.equip("TestSword", false);
|
|
374
|
+
const item = player.getItem("TestSword");
|
|
375
|
+
expect((item as any).equipped).toBe(false);
|
|
376
|
+
expect(player.equipments().some((eq) => eq.id() === "TestSword")).toBe(
|
|
377
|
+
false
|
|
378
|
+
);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("should equip armor", () => {
|
|
382
|
+
player.addItem("TestArmor", 1);
|
|
383
|
+
player.equip("TestArmor", true);
|
|
384
|
+
const item = player.getItem("TestArmor");
|
|
385
|
+
expect((item as any).equipped).toBe(true);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("should throw error when equipping non-existent item", () => {
|
|
389
|
+
expect(() => {
|
|
390
|
+
player.equip("TestSword", true);
|
|
391
|
+
}).toThrow();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("should throw error when equipping regular item (not weapon/armor)", () => {
|
|
395
|
+
player.addItem("TestPotion", 1);
|
|
396
|
+
expect(() => {
|
|
397
|
+
player.equip("TestPotion", true);
|
|
398
|
+
}).toThrow();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("should throw error when equipping already equipped item", () => {
|
|
402
|
+
player.addItem("TestSword", 1);
|
|
403
|
+
player.equip("TestSword", true);
|
|
404
|
+
expect(() => {
|
|
405
|
+
player.equip("TestSword", true);
|
|
406
|
+
}).toThrow();
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
describe("Item Management - Parameters (ATK, PDEF, SDEF)", () => {
|
|
411
|
+
test("should calculate attack from equipped weapon", () => {
|
|
412
|
+
player.addItem("TestSword", 1);
|
|
413
|
+
player.equip("TestSword", true);
|
|
414
|
+
expect(player.atk).toBe(50);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("should calculate physical defense from equipped armor", () => {
|
|
418
|
+
player.addItem("TestArmor", 1);
|
|
419
|
+
player.equip("TestArmor", true);
|
|
420
|
+
expect(player.pdef).toBe(30);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("should calculate skill defense from equipped armor", () => {
|
|
424
|
+
player.addItem("TestArmor", 1);
|
|
425
|
+
player.equip("TestArmor", true);
|
|
426
|
+
expect(player.sdef).toBe(20);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("should return 0 for parameters when no equipment", () => {
|
|
430
|
+
expect(player.atk).toBe(0);
|
|
431
|
+
expect(player.pdef).toBe(0);
|
|
432
|
+
expect(player.sdef).toBe(0);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("should sum parameters from multiple equipped items", () => {
|
|
436
|
+
const sword2: ItemObject & { atk: number } = {
|
|
437
|
+
id: "sword2",
|
|
438
|
+
name: "Sword 2",
|
|
439
|
+
price: 600,
|
|
440
|
+
atk: 75,
|
|
441
|
+
_type: "weapon" as const,
|
|
442
|
+
};
|
|
443
|
+
player.getCurrentMap()?.addInDatabase("sword2", sword2);
|
|
444
|
+
|
|
445
|
+
player.addItem("TestSword", 1);
|
|
446
|
+
player.addItem("sword2", 1);
|
|
447
|
+
player.equip("TestSword", true);
|
|
448
|
+
player.equip("sword2", true);
|
|
449
|
+
|
|
450
|
+
// Only one weapon should be equipped at a time typically, but test the sum
|
|
451
|
+
expect(player.atk).toBeGreaterThanOrEqual(50);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
describe("Item Management - Hooks", () => {
|
|
456
|
+
test("should call onAdd hook when adding item", () => {
|
|
457
|
+
const onAddSpy = vi.fn();
|
|
458
|
+
const customItem: ItemObject = {
|
|
459
|
+
id: "hook-item",
|
|
460
|
+
name: "Hook Item",
|
|
461
|
+
price: 100,
|
|
462
|
+
onAdd: onAddSpy,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
player.addItem(customItem, 1);
|
|
466
|
+
expect(onAddSpy).toHaveBeenCalledWith(player);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("should call onRemove hook when removing item", () => {
|
|
470
|
+
const onRemoveSpy = vi.fn();
|
|
471
|
+
const customItem: ItemObject = {
|
|
472
|
+
id: "remove-hook-item",
|
|
473
|
+
name: "Remove Hook Item",
|
|
474
|
+
price: 100,
|
|
475
|
+
onRemove: onRemoveSpy,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
player.addItem(customItem, 1);
|
|
479
|
+
player.removeItem("remove-hook-item", 1);
|
|
480
|
+
expect(onRemoveSpy).toHaveBeenCalledWith(player);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test("should call onUse hook when using item", () => {
|
|
484
|
+
const onUseSpy = vi.fn();
|
|
485
|
+
const customItem: ItemObject = {
|
|
486
|
+
id: "use-hook-item",
|
|
487
|
+
name: "Use Hook Item",
|
|
488
|
+
price: 100,
|
|
489
|
+
consumable: true,
|
|
490
|
+
onUse: onUseSpy,
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Mock Math.random to ensure success
|
|
494
|
+
const originalRandom = Math.random;
|
|
495
|
+
Math.random = vi.fn(() => 0.5);
|
|
496
|
+
|
|
497
|
+
player.addItem(customItem, 1);
|
|
498
|
+
player.useItem("use-hook-item");
|
|
499
|
+
expect(onUseSpy).toHaveBeenCalledWith(player);
|
|
500
|
+
|
|
501
|
+
Math.random = originalRandom;
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("should call onUseFailed hook when item usage fails", () => {
|
|
505
|
+
const onUseFailedSpy = vi.fn();
|
|
506
|
+
const customItem: ItemObject = {
|
|
507
|
+
id: "fail-hook-item",
|
|
508
|
+
name: "Fail Hook Item",
|
|
509
|
+
price: 100,
|
|
510
|
+
consumable: true,
|
|
511
|
+
hitRate: 0.1, // 10% chance
|
|
512
|
+
onUseFailed: onUseFailedSpy,
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Mock Math.random to fail
|
|
516
|
+
const originalRandom = Math.random;
|
|
517
|
+
Math.random = vi.fn(() => 0.9); // 0.9 > 0.1
|
|
518
|
+
|
|
519
|
+
player.addItem(customItem, 1);
|
|
520
|
+
try {
|
|
521
|
+
player.useItem("fail-hook-item");
|
|
522
|
+
} catch (e) {
|
|
523
|
+
// Expected to throw
|
|
524
|
+
}
|
|
525
|
+
expect(onUseFailedSpy).toHaveBeenCalledWith(player);
|
|
526
|
+
|
|
527
|
+
Math.random = originalRandom;
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("should call onEquip hook when equipping item", () => {
|
|
531
|
+
const onEquipSpy = vi.fn();
|
|
532
|
+
const customWeapon: ItemObject & { atk: number } = {
|
|
533
|
+
id: "equip-hook-weapon",
|
|
534
|
+
name: "Equip Hook Weapon",
|
|
535
|
+
price: 500,
|
|
536
|
+
atk: 40,
|
|
537
|
+
_type: "weapon" as const,
|
|
538
|
+
onEquip: onEquipSpy,
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
player.addItem(customWeapon, 1);
|
|
542
|
+
player.equip("equip-hook-weapon", true);
|
|
543
|
+
expect(onEquipSpy).toHaveBeenCalledWith(player, true);
|
|
544
|
+
|
|
545
|
+
player.equip("equip-hook-weapon", false);
|
|
546
|
+
expect(onEquipSpy).toHaveBeenCalledWith(player, false);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
describe("Item Management - Edge Cases", () => {
|
|
551
|
+
test("should handle adding item with class (if supported)", () => {
|
|
552
|
+
// This test would require an actual Item class
|
|
553
|
+
// For now, we test that object items work
|
|
554
|
+
const itemObj: ItemObject = {
|
|
555
|
+
id: "class-like-item",
|
|
556
|
+
name: "Class Like Item",
|
|
557
|
+
price: 100,
|
|
558
|
+
};
|
|
559
|
+
const item = player.addItem(itemObj, 1);
|
|
560
|
+
expect(item).toBeDefined();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("should handle merging existing item data with new object", () => {
|
|
564
|
+
// Add item first
|
|
565
|
+
player.addItem("TestPotion", 1);
|
|
566
|
+
|
|
567
|
+
// Add same item with different properties
|
|
568
|
+
const updatedItem: ItemObject = {
|
|
569
|
+
id: "TestPotion",
|
|
570
|
+
name: "Updated Potion",
|
|
571
|
+
price: 250,
|
|
572
|
+
};
|
|
573
|
+
const item = player.addItem(updatedItem, 1);
|
|
574
|
+
expect(item.name()).toBe("Updated Potion");
|
|
575
|
+
expect(item.quantity()).toBe(2);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("should handle multiple items in inventory", () => {
|
|
579
|
+
player.addItem("TestPotion", 3);
|
|
580
|
+
player.addItem("TestSword", 1);
|
|
581
|
+
player.addItem("TestArmor", 2);
|
|
582
|
+
|
|
583
|
+
expect(player.hasItem("TestPotion")).toBe(true);
|
|
584
|
+
expect(player.hasItem("TestSword")).toBe(true);
|
|
585
|
+
expect(player.hasItem("TestArmor")).toBe(true);
|
|
586
|
+
|
|
587
|
+
expect(player.getItem("TestPotion")?.quantity()).toBe(3);
|
|
588
|
+
expect(player.getItem("TestSword")?.quantity()).toBe(1);
|
|
589
|
+
expect(player.getItem("TestArmor")?.quantity()).toBe(2);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { beforeEach, test, expect } from 'vitest'
|
|
2
|
+
import { testing } from '@rpgjs/testing'
|
|
3
|
+
import { defineModule, createModule } from '@rpgjs/common'
|
|
4
|
+
import { RpgPlayer, RpgServer } from '../src'
|
|
5
|
+
import { RpgClient } from '../../client/src'
|
|
6
|
+
|
|
7
|
+
// Define your server module
|
|
8
|
+
const serverModule = defineModule<RpgServer>({
|
|
9
|
+
player: {
|
|
10
|
+
onConnected(player) {
|
|
11
|
+
player.setVariable('test', 'value')
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// Define your client module
|
|
17
|
+
const clientModule = defineModule<RpgClient>({
|
|
18
|
+
// Client-side logic
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
let player: RpgPlayer
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
// Create module using createModule
|
|
25
|
+
const myModule = createModule('MyModule', [{
|
|
26
|
+
server: serverModule,
|
|
27
|
+
client: clientModule
|
|
28
|
+
}])
|
|
29
|
+
|
|
30
|
+
// Pass the module created with createModule to testing
|
|
31
|
+
const fixture = await testing(myModule)
|
|
32
|
+
const client = await fixture.createClient()
|
|
33
|
+
player = client.player
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('Module hook was called', () => {
|
|
37
|
+
expect(player.getVariable('test')).toBe('value')
|
|
38
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import { testing } from '@rpgjs/testing'
|
|
3
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
|
+
import { ATK, MAXHP, MAXHP_CURVE, MAXSP, MAXSP_CURVE, RpgPlayer } from '../src'
|
|
5
|
+
|
|
6
|
+
let player: RpgPlayer
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const fixture = await testing();
|
|
10
|
+
const client = await fixture.createClient()
|
|
11
|
+
player = client.player
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('Test HP', () => {
|
|
15
|
+
expect(player.hp).toBe(MAXHP_CURVE.start)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('Test SP', () => {
|
|
19
|
+
expect(player.sp).toBe(MAXSP_CURVE.start)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('Test MaxHP', () => {
|
|
23
|
+
expect(player.param[MAXHP]).toBe(MAXHP_CURVE.start)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('Test MaxSP', () => {
|
|
27
|
+
expect(player.param[MAXSP]).toBe(MAXSP_CURVE.start)
|
|
28
|
+
})
|