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