@axiapps/gw2-data 0.1.1 → 0.1.3
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/data/overrides.json +24 -0
- package/package.json +4 -1
- package/src/engine/attributes.js +102 -23
- package/src/engine/boons.js +19 -1
- package/src/engine/constants.js +27 -2
- package/src/engine/index.js +4 -4
- package/src/engine/modifiers.js +57 -21
- package/src/index.js +5 -0
- package/src/wiki/parser.js +17 -9
- package/scripts/generate-fixtures.js +0 -242
- package/tests/api-client.test.js +0 -138
- package/tests/cache.test.js +0 -108
- package/tests/engine/attributes.test.js +0 -252
- package/tests/engine/boons.test.js +0 -129
- package/tests/engine/combos.test.js +0 -76
- package/tests/engine/constants.test.js +0 -576
- package/tests/engine/fixtures/berserker-thief.json +0 -61
- package/tests/engine/fixtures/berserker-warrior.json +0 -113
- package/tests/engine/fixtures/celestial-firebrand-wvw.json +0 -94
- package/tests/engine/fixtures/harrier-druid.json +0 -119
- package/tests/engine/fixtures/viper-mirage.json +0 -104
- package/tests/engine/graph.test.js +0 -30
- package/tests/engine/integration.test.js +0 -111
- package/tests/engine/modifiers.test.js +0 -473
- package/tests/engine/overrides.test.js +0 -70
- package/tests/engine/snapshot.test.js +0 -53
- package/tests/engine/test-utils.js +0 -20
- package/tests/engine/tooltips.test.js +0 -62
- package/tests/fixtures/capture.js +0 -160
- package/tests/fixtures/fixtures.json +0 -839
- package/tests/integration.test.js +0 -100
- package/tests/match.test.js +0 -176
- package/tests/merge.test.js +0 -128
- package/tests/normalize.test.js +0 -78
- package/tests/parser.test.js +0 -506
- package/tests/real-data.test.js +0 -296
- package/tests/relations.test.js +0 -80
- package/tests/resolver.test.js +0 -721
- package/tests/validate-live.js +0 -191
- package/tests/wiki-client.test.js +0 -468
- package/tests/wiki-integration.test.js +0 -177
- package/tests/wiki-live-validation.test.js +0 -61
- 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
|
-
}
|