@axiapps/gw2-data 0.1.0 → 0.1.2

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.
Files changed (43) hide show
  1. package/README.md +156 -0
  2. package/data/overrides.json +24 -0
  3. package/package.json +3 -1
  4. package/src/engine/attributes.js +102 -23
  5. package/src/engine/boons.js +19 -1
  6. package/src/engine/constants.js +27 -2
  7. package/src/engine/index.js +4 -4
  8. package/src/engine/modifiers.js +57 -21
  9. package/src/wiki/parser.js +17 -9
  10. package/scripts/generate-fixtures.js +0 -242
  11. package/tests/api-client.test.js +0 -138
  12. package/tests/cache.test.js +0 -108
  13. package/tests/engine/attributes.test.js +0 -252
  14. package/tests/engine/boons.test.js +0 -129
  15. package/tests/engine/combos.test.js +0 -76
  16. package/tests/engine/constants.test.js +0 -576
  17. package/tests/engine/fixtures/berserker-thief.json +0 -61
  18. package/tests/engine/fixtures/berserker-warrior.json +0 -113
  19. package/tests/engine/fixtures/celestial-firebrand-wvw.json +0 -94
  20. package/tests/engine/fixtures/harrier-druid.json +0 -119
  21. package/tests/engine/fixtures/viper-mirage.json +0 -104
  22. package/tests/engine/graph.test.js +0 -30
  23. package/tests/engine/integration.test.js +0 -111
  24. package/tests/engine/modifiers.test.js +0 -473
  25. package/tests/engine/overrides.test.js +0 -70
  26. package/tests/engine/snapshot.test.js +0 -53
  27. package/tests/engine/test-utils.js +0 -20
  28. package/tests/engine/tooltips.test.js +0 -62
  29. package/tests/fixtures/capture.js +0 -160
  30. package/tests/fixtures/fixtures.json +0 -839
  31. package/tests/integration.test.js +0 -100
  32. package/tests/match.test.js +0 -176
  33. package/tests/merge.test.js +0 -128
  34. package/tests/normalize.test.js +0 -78
  35. package/tests/parser.test.js +0 -506
  36. package/tests/real-data.test.js +0 -296
  37. package/tests/relations.test.js +0 -80
  38. package/tests/resolver.test.js +0 -721
  39. package/tests/validate-live.js +0 -191
  40. package/tests/wiki-client.test.js +0 -468
  41. package/tests/wiki-integration.test.js +0 -177
  42. package/tests/wiki-live-validation.test.js +0 -61
  43. package/tests/wiki-snapshots.test.js +0 -166
@@ -1,576 +0,0 @@
1
- "use strict";
2
-
3
- const {
4
- STAT_COMBOS,
5
- STAT_COMBOS_BY_LABEL,
6
- getStatCombo,
7
- getEffectiveStats,
8
- SLOT_WEIGHTS,
9
- TWO_HAND_WEIGHTS,
10
- LAND_ONLY_SLOTS,
11
- AQUATIC_SLOTS,
12
- PROFESSION_WEIGHT,
13
- ARMOR_DEFENSE_BY_WEIGHT,
14
- PROFESSION_BASE_HP,
15
- WEAPON_STRENGTH_MIDPOINT,
16
- BUFF_FACT_TYPES,
17
- MIGHT_POWER_PER_STACK,
18
- MIGHT_CONDI_PER_STACK,
19
- FURY_CRIT_CHANCE,
20
- FURY_CRIT_CHANCE_WVW,
21
- STACKING_SIGIL_DEFS,
22
- SIGNET_PASSIVE_BUFFS,
23
- BOON_NAMES,
24
- CONDITION_NAMES,
25
- CONDITION_NAME_NORMALIZE,
26
- BOON_DISPLAY_ORDER,
27
- ALL_STAT_KEYS,
28
- CONVERSION_TARGET_MAP,
29
- } = require("../../src/engine/constants");
30
-
31
- // ---------------------------------------------------------------------------
32
- // getStatCombo / STAT_COMBOS_BY_LABEL
33
- // ---------------------------------------------------------------------------
34
- describe("getStatCombo", () => {
35
- test("returns combo for Berserker's", () => {
36
- const combo = getStatCombo("Berserker's");
37
- expect(combo).toBeDefined();
38
- expect(combo.label).toBe("Berserker's");
39
- expect(combo.stats).toEqual(["Power", "Precision", "Ferocity"]);
40
- });
41
-
42
- test("alias without apostrophe-s resolves to same combo", () => {
43
- const full = getStatCombo("Berserker's");
44
- const alias = getStatCombo("Berserker");
45
- expect(alias).toBe(full);
46
- });
47
-
48
- test("Wanderer alias resolves correctly", () => {
49
- const full = getStatCombo("Wanderer's");
50
- const alias = getStatCombo("Wanderer");
51
- expect(alias).toBe(full);
52
- expect(full.stats).toContain("Concentration");
53
- });
54
-
55
- test("Celestial returns all 9 stats", () => {
56
- const combo = getStatCombo("Celestial");
57
- expect(combo).toBeDefined();
58
- expect(combo.stats).toHaveLength(9);
59
- expect(combo.stats).toContain("Expertise");
60
- expect(combo.stats).toContain("Concentration");
61
- });
62
-
63
- test("returns undefined for unknown label", () => {
64
- expect(getStatCombo("Nonexistent")).toBeUndefined();
65
- });
66
-
67
- test("Marauder's is a 4-stat combo", () => {
68
- const combo = getStatCombo("Marauder's");
69
- expect(combo.stats).toHaveLength(4);
70
- expect(combo.stats).toEqual(["Power", "Precision", "Vitality", "Ferocity"]);
71
- });
72
-
73
- test("Viper's stats are correct", () => {
74
- const combo = getStatCombo("Viper's");
75
- expect(combo.stats).toEqual(["Power", "ConditionDamage", "Precision", "Expertise"]);
76
- });
77
-
78
- test("Demolisher has no apostrophe-s and is not aliased", () => {
79
- const combo = getStatCombo("Demolisher");
80
- expect(combo).toBeDefined();
81
- expect(combo.label).toBe("Demolisher");
82
- });
83
- });
84
-
85
- // ---------------------------------------------------------------------------
86
- // getEffectiveStats
87
- // ---------------------------------------------------------------------------
88
- describe("getEffectiveStats", () => {
89
- test("PvE Celestial returns all 9 stats", () => {
90
- const combo = getStatCombo("Celestial");
91
- const stats = getEffectiveStats(combo, "pve");
92
- expect(stats).toHaveLength(9);
93
- expect(stats).toContain("Expertise");
94
- expect(stats).toContain("Concentration");
95
- });
96
-
97
- test("WvW Celestial excludes Expertise and Concentration", () => {
98
- const combo = getStatCombo("Celestial");
99
- const stats = getEffectiveStats(combo, "wvw");
100
- expect(stats).not.toContain("Expertise");
101
- expect(stats).not.toContain("Concentration");
102
- expect(stats).toHaveLength(7);
103
- });
104
-
105
- test("WvW non-Celestial combo returns all its stats unchanged", () => {
106
- const combo = getStatCombo("Berserker's");
107
- const stats = getEffectiveStats(combo, "wvw");
108
- expect(stats).toEqual(["Power", "Precision", "Ferocity"]);
109
- });
110
-
111
- test("PvE Trailblazer's (has Expertise) returns all stats", () => {
112
- const combo = getStatCombo("Trailblazer's");
113
- const stats = getEffectiveStats(combo, "pve");
114
- expect(stats).toContain("Expertise");
115
- expect(stats).toHaveLength(4);
116
- });
117
-
118
- test("returns empty array for null/undefined combo", () => {
119
- expect(getEffectiveStats(null, "pve")).toEqual([]);
120
- expect(getEffectiveStats(undefined, "wvw")).toEqual([]);
121
- });
122
- });
123
-
124
- // ---------------------------------------------------------------------------
125
- // SLOT_WEIGHTS
126
- // ---------------------------------------------------------------------------
127
- describe("SLOT_WEIGHTS", () => {
128
- const EXPECTED_SLOTS = [
129
- "head", "shoulders", "chest", "hands", "legs", "feet",
130
- "mainhand1", "offhand1", "mainhand2", "offhand2",
131
- "back", "amulet", "ring1", "ring2", "accessory1", "accessory2",
132
- "breather", "aquatic1", "aquatic2",
133
- ];
134
-
135
- test("has exactly 19 slot keys", () => {
136
- expect(Object.keys(SLOT_WEIGHTS)).toHaveLength(19);
137
- });
138
-
139
- test("contains all expected slots", () => {
140
- for (const slot of EXPECTED_SLOTS) {
141
- expect(SLOT_WEIGHTS).toHaveProperty(slot);
142
- }
143
- });
144
-
145
- test("each slot has p, s, p4, s4, c keys", () => {
146
- for (const slot of EXPECTED_SLOTS) {
147
- const w = SLOT_WEIGHTS[slot];
148
- expect(typeof w.p).toBe("number");
149
- expect(typeof w.s).toBe("number");
150
- expect(typeof w.p4).toBe("number");
151
- expect(typeof w.s4).toBe("number");
152
- expect(typeof w.c).toBe("number");
153
- }
154
- });
155
-
156
- test("chest has the highest armor weights", () => {
157
- expect(SLOT_WEIGHTS.chest.p).toBe(141);
158
- expect(SLOT_WEIGHTS.chest.s).toBe(101);
159
- });
160
-
161
- test("aquatic slots have same weights as TWO_HAND_WEIGHTS", () => {
162
- expect(SLOT_WEIGHTS.aquatic1).toEqual(TWO_HAND_WEIGHTS);
163
- expect(SLOT_WEIGHTS.aquatic2).toEqual(TWO_HAND_WEIGHTS);
164
- });
165
-
166
- test("back slot has correct values", () => {
167
- expect(SLOT_WEIGHTS.back).toEqual({ p: 63, s: 40, p4: 51, s4: 27, c: 28 });
168
- });
169
- });
170
-
171
- // ---------------------------------------------------------------------------
172
- // TWO_HAND_WEIGHTS
173
- // ---------------------------------------------------------------------------
174
- describe("TWO_HAND_WEIGHTS", () => {
175
- test("matches aquatic weapon weights", () => {
176
- expect(TWO_HAND_WEIGHTS).toEqual({ p: 251, s: 179, p4: 215, s4: 118, c: 118 });
177
- });
178
-
179
- test("has all five weight keys", () => {
180
- expect(Object.keys(TWO_HAND_WEIGHTS)).toEqual(["p", "s", "p4", "s4", "c"]);
181
- });
182
- });
183
-
184
- // ---------------------------------------------------------------------------
185
- // LAND_ONLY_SLOTS / AQUATIC_SLOTS
186
- // ---------------------------------------------------------------------------
187
- describe("LAND_ONLY_SLOTS and AQUATIC_SLOTS", () => {
188
- test("LAND_ONLY_SLOTS contains expected slots", () => {
189
- expect(LAND_ONLY_SLOTS.has("head")).toBe(true);
190
- expect(LAND_ONLY_SLOTS.has("mainhand1")).toBe(true);
191
- expect(LAND_ONLY_SLOTS.has("offhand1")).toBe(true);
192
- expect(LAND_ONLY_SLOTS.has("mainhand2")).toBe(true);
193
- expect(LAND_ONLY_SLOTS.has("offhand2")).toBe(true);
194
- expect(LAND_ONLY_SLOTS.size).toBe(5);
195
- });
196
-
197
- test("AQUATIC_SLOTS contains expected slots", () => {
198
- expect(AQUATIC_SLOTS.has("breather")).toBe(true);
199
- expect(AQUATIC_SLOTS.has("aquatic1")).toBe(true);
200
- expect(AQUATIC_SLOTS.has("aquatic2")).toBe(true);
201
- expect(AQUATIC_SLOTS.size).toBe(3);
202
- });
203
-
204
- test("LAND_ONLY_SLOTS and AQUATIC_SLOTS are disjoint", () => {
205
- for (const slot of LAND_ONLY_SLOTS) {
206
- expect(AQUATIC_SLOTS.has(slot)).toBe(false);
207
- }
208
- for (const slot of AQUATIC_SLOTS) {
209
- expect(LAND_ONLY_SLOTS.has(slot)).toBe(false);
210
- }
211
- });
212
- });
213
-
214
- // ---------------------------------------------------------------------------
215
- // PROFESSION_BASE_HP
216
- // ---------------------------------------------------------------------------
217
- describe("PROFESSION_BASE_HP", () => {
218
- test("Warrior has high HP (9212)", () => {
219
- expect(PROFESSION_BASE_HP.Warrior).toBe(9212);
220
- });
221
-
222
- test("Berserker (elite spec of Warrior) has same HP as Warrior", () => {
223
- expect(PROFESSION_BASE_HP.Berserker).toBe(9212);
224
- });
225
-
226
- test("Necromancer and Harbinger have high HP", () => {
227
- expect(PROFESSION_BASE_HP.Necromancer).toBe(9212);
228
- expect(PROFESSION_BASE_HP.Harbinger).toBe(9212);
229
- });
230
-
231
- test("Ranger and its elite specs have medium HP (5922)", () => {
232
- expect(PROFESSION_BASE_HP.Ranger).toBe(5922);
233
- expect(PROFESSION_BASE_HP.Druid).toBe(5922);
234
- expect(PROFESSION_BASE_HP.Soulbeast).toBe(5922);
235
- expect(PROFESSION_BASE_HP.Untamed).toBe(5922);
236
- });
237
-
238
- test("Mesmer and Chronomancer have medium HP", () => {
239
- expect(PROFESSION_BASE_HP.Mesmer).toBe(5922);
240
- expect(PROFESSION_BASE_HP.Chronomancer).toBe(5922);
241
- });
242
-
243
- test("Guardian and its elite specs have low HP (1645)", () => {
244
- expect(PROFESSION_BASE_HP.Guardian).toBe(1645);
245
- expect(PROFESSION_BASE_HP.Dragonhunter).toBe(1645);
246
- expect(PROFESSION_BASE_HP.Firebrand).toBe(1645);
247
- expect(PROFESSION_BASE_HP.Willbender).toBe(1645);
248
- });
249
-
250
- test("Elementalist and Tempest and Weaver have low HP", () => {
251
- expect(PROFESSION_BASE_HP.Elementalist).toBe(1645);
252
- expect(PROFESSION_BASE_HP.Tempest).toBe(1645);
253
- expect(PROFESSION_BASE_HP.Weaver).toBe(1645);
254
- });
255
-
256
- test("Thief and Antiquary have low HP", () => {
257
- expect(PROFESSION_BASE_HP.Thief).toBe(1645);
258
- expect(PROFESSION_BASE_HP.Antiquary).toBe(1645);
259
- });
260
- });
261
-
262
- // ---------------------------------------------------------------------------
263
- // PROFESSION_WEIGHT
264
- // ---------------------------------------------------------------------------
265
- describe("PROFESSION_WEIGHT", () => {
266
- test("light armor professions", () => {
267
- expect(PROFESSION_WEIGHT.Elementalist).toBe("light");
268
- expect(PROFESSION_WEIGHT.Mesmer).toBe("light");
269
- expect(PROFESSION_WEIGHT.Necromancer).toBe("light");
270
- });
271
-
272
- test("medium armor professions", () => {
273
- expect(PROFESSION_WEIGHT.Engineer).toBe("medium");
274
- expect(PROFESSION_WEIGHT.Ranger).toBe("medium");
275
- expect(PROFESSION_WEIGHT.Thief).toBe("medium");
276
- });
277
-
278
- test("heavy armor professions", () => {
279
- expect(PROFESSION_WEIGHT.Guardian).toBe("heavy");
280
- expect(PROFESSION_WEIGHT.Warrior).toBe("heavy");
281
- expect(PROFESSION_WEIGHT.Revenant).toBe("heavy");
282
- });
283
- });
284
-
285
- // ---------------------------------------------------------------------------
286
- // ARMOR_DEFENSE_BY_WEIGHT
287
- // ---------------------------------------------------------------------------
288
- describe("ARMOR_DEFENSE_BY_WEIGHT", () => {
289
- test("has correct defense values", () => {
290
- expect(ARMOR_DEFENSE_BY_WEIGHT).toEqual({ light: 967, medium: 1118, heavy: 1271 });
291
- });
292
- });
293
-
294
- // ---------------------------------------------------------------------------
295
- // WEAPON_STRENGTH_MIDPOINT
296
- // ---------------------------------------------------------------------------
297
- describe("WEAPON_STRENGTH_MIDPOINT", () => {
298
- const EXPECTED_WEAPONS = [
299
- "axe", "dagger", "mace", "pistol", "sword", "scepter",
300
- "focus", "shield", "torch", "warhorn",
301
- "greatsword", "hammer", "longbow", "rifle", "shortbow", "staff",
302
- "spear", "trident", "harpoon",
303
- ];
304
-
305
- test("has all 19 weapon types", () => {
306
- expect(Object.keys(WEAPON_STRENGTH_MIDPOINT)).toHaveLength(19);
307
- });
308
-
309
- test("contains all expected weapon keys", () => {
310
- for (const w of EXPECTED_WEAPONS) {
311
- expect(WEAPON_STRENGTH_MIDPOINT).toHaveProperty(w);
312
- }
313
- });
314
-
315
- test("axe and sword have same midpoint", () => {
316
- expect(WEAPON_STRENGTH_MIDPOINT.axe).toBe(952.5);
317
- expect(WEAPON_STRENGTH_MIDPOINT.sword).toBe(952.5);
318
- });
319
-
320
- test("rifle has the highest midpoint", () => {
321
- const max = Math.max(...Object.values(WEAPON_STRENGTH_MIDPOINT));
322
- expect(WEAPON_STRENGTH_MIDPOINT.rifle).toBe(max);
323
- expect(WEAPON_STRENGTH_MIDPOINT.rifle).toBe(1095.5);
324
- });
325
-
326
- test("aquatic weapons have midpoints", () => {
327
- expect(typeof WEAPON_STRENGTH_MIDPOINT.harpoon).toBe("number");
328
- expect(typeof WEAPON_STRENGTH_MIDPOINT.trident).toBe("number");
329
- });
330
- });
331
-
332
- // ---------------------------------------------------------------------------
333
- // BUFF_FACT_TYPES
334
- // ---------------------------------------------------------------------------
335
- describe("BUFF_FACT_TYPES", () => {
336
- test("contains Buff, ApplyBuffCondition, PrefixedBuff", () => {
337
- expect(BUFF_FACT_TYPES.has("Buff")).toBe(true);
338
- expect(BUFF_FACT_TYPES.has("ApplyBuffCondition")).toBe(true);
339
- expect(BUFF_FACT_TYPES.has("PrefixedBuff")).toBe(true);
340
- expect(BUFF_FACT_TYPES.size).toBe(3);
341
- });
342
- });
343
-
344
- // ---------------------------------------------------------------------------
345
- // Might and Fury constants
346
- // ---------------------------------------------------------------------------
347
- describe("Might and Fury constants", () => {
348
- test("MIGHT_POWER_PER_STACK is 30", () => {
349
- expect(MIGHT_POWER_PER_STACK).toBe(30);
350
- });
351
-
352
- test("MIGHT_CONDI_PER_STACK is 30", () => {
353
- expect(MIGHT_CONDI_PER_STACK).toBe(30);
354
- });
355
-
356
- test("FURY_CRIT_CHANCE is 25 (PvE)", () => {
357
- expect(FURY_CRIT_CHANCE).toBe(25);
358
- });
359
-
360
- test("FURY_CRIT_CHANCE_WVW is 20", () => {
361
- expect(FURY_CRIT_CHANCE_WVW).toBe(20);
362
- });
363
- });
364
-
365
- // ---------------------------------------------------------------------------
366
- // STACKING_SIGIL_DEFS
367
- // ---------------------------------------------------------------------------
368
- describe("STACKING_SIGIL_DEFS", () => {
369
- test("has 9 entries", () => {
370
- expect(STACKING_SIGIL_DEFS).toHaveLength(9);
371
- });
372
-
373
- test("each entry has id, key, label, perStack, maxStacks", () => {
374
- for (const def of STACKING_SIGIL_DEFS) {
375
- expect(typeof def.id).toBe("number");
376
- expect(typeof def.key).toBe("string");
377
- expect(typeof def.label).toBe("string");
378
- expect(typeof def.perStack).toBe("number");
379
- expect(def.maxStacks).toBe(25);
380
- }
381
- });
382
-
383
- test("Bloodlust targets Power", () => {
384
- const bloodlust = STACKING_SIGIL_DEFS.find((d) => d.label === "Bloodlust");
385
- expect(bloodlust).toBeDefined();
386
- expect(bloodlust.stat).toBe("Power");
387
- expect(bloodlust.perStack).toBe(10);
388
- expect(bloodlust.id).toBe(24575);
389
- });
390
-
391
- test("Stars targets all stats", () => {
392
- const stars = STACKING_SIGIL_DEFS.find((d) => d.label === "Stars");
393
- expect(stars).toBeDefined();
394
- expect(Array.isArray(stars.allStats)).toBe(true);
395
- expect(stars.allStats).toHaveLength(9);
396
- expect(stars.perStack).toBe(2);
397
- });
398
-
399
- test("Benevolence uses modifier instead of stat", () => {
400
- const bene = STACKING_SIGIL_DEFS.find((d) => d.label === "Benevolence");
401
- expect(bene).toBeDefined();
402
- expect(bene.modifier).toBe("Outgoing Healing");
403
- expect(bene.stat).toBeUndefined();
404
- });
405
- });
406
-
407
- // ---------------------------------------------------------------------------
408
- // SIGNET_PASSIVE_BUFFS
409
- // ---------------------------------------------------------------------------
410
- describe("SIGNET_PASSIVE_BUFFS", () => {
411
- test("is a Map", () => {
412
- expect(SIGNET_PASSIVE_BUFFS instanceof Map).toBe(true);
413
- });
414
-
415
- test("has 13 entries", () => {
416
- expect(SIGNET_PASSIVE_BUFFS.size).toBe(13);
417
- });
418
-
419
- test("Bane Signet (9093) grants Power 180", () => {
420
- const entry = SIGNET_PASSIVE_BUFFS.get(9093);
421
- expect(entry).toBeDefined();
422
- expect(entry.stat).toBe("Power");
423
- expect(entry.value).toBe(180);
424
- });
425
-
426
- test("Signet of Midnight (10234) grants Expertise 180", () => {
427
- const entry = SIGNET_PASSIVE_BUFFS.get(10234);
428
- expect(entry).toBeDefined();
429
- expect(entry.stat).toBe("Expertise");
430
- expect(entry.value).toBe(180);
431
- });
432
-
433
- test("Signet of the Wild (12491) grants Ferocity 180", () => {
434
- const entry = SIGNET_PASSIVE_BUFFS.get(12491);
435
- expect(entry).toBeDefined();
436
- expect(entry.stat).toBe("Ferocity");
437
- expect(entry.value).toBe(180);
438
- });
439
-
440
- test("all entries have stat string and value 180", () => {
441
- for (const [, entry] of SIGNET_PASSIVE_BUFFS) {
442
- expect(typeof entry.stat).toBe("string");
443
- expect(entry.value).toBe(180);
444
- }
445
- });
446
- });
447
-
448
- // ---------------------------------------------------------------------------
449
- // BOON_NAMES
450
- // ---------------------------------------------------------------------------
451
- describe("BOON_NAMES", () => {
452
- test("has exactly 12 boons", () => {
453
- expect(BOON_NAMES.size).toBe(12);
454
- });
455
-
456
- test("contains all standard boons", () => {
457
- const expected = [
458
- "Aegis", "Alacrity", "Fury", "Might", "Protection", "Quickness",
459
- "Regeneration", "Resistance", "Resolution", "Stability", "Swiftness", "Vigor",
460
- ];
461
- for (const boon of expected) {
462
- expect(BOON_NAMES.has(boon)).toBe(true);
463
- }
464
- });
465
- });
466
-
467
- // ---------------------------------------------------------------------------
468
- // CONDITION_NAMES
469
- // ---------------------------------------------------------------------------
470
- describe("CONDITION_NAMES", () => {
471
- test("contains canonical and variant names", () => {
472
- expect(CONDITION_NAMES.has("Blinded")).toBe(true);
473
- expect(CONDITION_NAMES.has("Blind")).toBe(true);
474
- expect(CONDITION_NAMES.has("Chilled")).toBe(true);
475
- expect(CONDITION_NAMES.has("Chill")).toBe(true);
476
- expect(CONDITION_NAMES.has("Immobilized")).toBe(true);
477
- expect(CONDITION_NAMES.has("Immobile")).toBe(true);
478
- });
479
- });
480
-
481
- // ---------------------------------------------------------------------------
482
- // CONDITION_NAME_NORMALIZE
483
- // ---------------------------------------------------------------------------
484
- describe("CONDITION_NAME_NORMALIZE", () => {
485
- test("Blind normalizes to Blinded", () => {
486
- expect(CONDITION_NAME_NORMALIZE.Blind).toBe("Blinded");
487
- });
488
-
489
- test("Chill normalizes to Chilled", () => {
490
- expect(CONDITION_NAME_NORMALIZE.Chill).toBe("Chilled");
491
- });
492
-
493
- test("Cripple normalizes to Crippled", () => {
494
- expect(CONDITION_NAME_NORMALIZE.Cripple).toBe("Crippled");
495
- });
496
-
497
- test("Immobilize and Immobilized both normalize to Immobile", () => {
498
- expect(CONDITION_NAME_NORMALIZE.Immobilize).toBe("Immobile");
499
- expect(CONDITION_NAME_NORMALIZE.Immobilized).toBe("Immobile");
500
- });
501
-
502
- test("Poison normalizes to Poisoned", () => {
503
- expect(CONDITION_NAME_NORMALIZE.Poison).toBe("Poisoned");
504
- });
505
- });
506
-
507
- // ---------------------------------------------------------------------------
508
- // BOON_DISPLAY_ORDER
509
- // ---------------------------------------------------------------------------
510
- describe("BOON_DISPLAY_ORDER", () => {
511
- test("is an array of 12 boons", () => {
512
- expect(Array.isArray(BOON_DISPLAY_ORDER)).toBe(true);
513
- expect(BOON_DISPLAY_ORDER).toHaveLength(12);
514
- });
515
-
516
- test("contains same boons as BOON_NAMES", () => {
517
- for (const boon of BOON_DISPLAY_ORDER) {
518
- expect(BOON_NAMES.has(boon)).toBe(true);
519
- }
520
- });
521
-
522
- test("starts with Aegis and ends with Vigor", () => {
523
- expect(BOON_DISPLAY_ORDER[0]).toBe("Aegis");
524
- expect(BOON_DISPLAY_ORDER[BOON_DISPLAY_ORDER.length - 1]).toBe("Vigor");
525
- });
526
- });
527
-
528
- // ---------------------------------------------------------------------------
529
- // ALL_STAT_KEYS
530
- // ---------------------------------------------------------------------------
531
- describe("ALL_STAT_KEYS", () => {
532
- test("has exactly 9 stat keys", () => {
533
- expect(ALL_STAT_KEYS).toHaveLength(9);
534
- });
535
-
536
- test("contains all expected stat keys", () => {
537
- const expected = [
538
- "Power", "Precision", "Toughness", "Vitality", "Ferocity",
539
- "ConditionDamage", "HealingPower", "Expertise", "Concentration",
540
- ];
541
- for (const key of expected) {
542
- expect(ALL_STAT_KEYS).toContain(key);
543
- }
544
- });
545
- });
546
-
547
- // ---------------------------------------------------------------------------
548
- // CONVERSION_TARGET_MAP
549
- // ---------------------------------------------------------------------------
550
- describe("CONVERSION_TARGET_MAP", () => {
551
- test("BoonDuration maps to Concentration", () => {
552
- expect(CONVERSION_TARGET_MAP.BoonDuration).toBe("Concentration");
553
- });
554
-
555
- test("ConditionDuration maps to Expertise", () => {
556
- expect(CONVERSION_TARGET_MAP.ConditionDuration).toBe("Expertise");
557
- });
558
-
559
- test("CritDamage maps to Ferocity", () => {
560
- expect(CONVERSION_TARGET_MAP.CritDamage).toBe("Ferocity");
561
- });
562
-
563
- test("Healing maps to HealingPower", () => {
564
- expect(CONVERSION_TARGET_MAP.Healing).toBe("HealingPower");
565
- });
566
-
567
- test("has exactly 4 entries", () => {
568
- expect(Object.keys(CONVERSION_TARGET_MAP)).toHaveLength(4);
569
- });
570
-
571
- test("all values are valid stat keys from ALL_STAT_KEYS", () => {
572
- for (const value of Object.values(CONVERSION_TARGET_MAP)) {
573
- expect(ALL_STAT_KEYS).toContain(value);
574
- }
575
- });
576
- });
@@ -1,61 +0,0 @@
1
- {
2
- "name": "Berserker Thief",
3
- "description": "Medium armor, sparse gear (testing empty slots gracefully)",
4
- "ctx": {
5
- "profession": "Thief",
6
- "specializations": [],
7
- "equipment": {
8
- "slots": {
9
- "chest": "Berserker's",
10
- "legs": "Berserker's"
11
- },
12
- "weapons": {},
13
- "runes": {},
14
- "infusions": {},
15
- "enrichment": null,
16
- "food": null,
17
- "utility": null
18
- },
19
- "gameMode": "pve",
20
- "underwaterMode": false,
21
- "activeWeaponSet": 1,
22
- "skills": {
23
- "healId": null,
24
- "utilityIds": [],
25
- "eliteId": null
26
- },
27
- "assumedBoons": null,
28
- "sigilStacks": null
29
- },
30
- "catalogs": {
31
- "traits": [],
32
- "specializations": [],
33
- "skills": [],
34
- "runes": [],
35
- "foods": [],
36
- "utilities": [],
37
- "infusions": [],
38
- "enrichments": []
39
- },
40
- "expected": {
41
- "total": {
42
- "Power": 1235,
43
- "Precision": 1168,
44
- "Toughness": 1000,
45
- "Vitality": 1000,
46
- "Ferocity": 168,
47
- "ConditionDamage": 0,
48
- "HealingPower": 0,
49
- "Expertise": 0,
50
- "Concentration": 0
51
- },
52
- "derived": {
53
- "health": 11645,
54
- "critChance": 18,
55
- "critDamage": 161.2,
56
- "conditionDuration": 0,
57
- "boonDuration": 0,
58
- "armor": 2118
59
- }
60
- }
61
- }