@axiapps/gw2-data 0.1.1 → 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 (42) hide show
  1. package/data/overrides.json +24 -0
  2. package/package.json +3 -1
  3. package/src/engine/attributes.js +102 -23
  4. package/src/engine/boons.js +19 -1
  5. package/src/engine/constants.js +27 -2
  6. package/src/engine/index.js +4 -4
  7. package/src/engine/modifiers.js +57 -21
  8. package/src/wiki/parser.js +17 -9
  9. package/scripts/generate-fixtures.js +0 -242
  10. package/tests/api-client.test.js +0 -138
  11. package/tests/cache.test.js +0 -108
  12. package/tests/engine/attributes.test.js +0 -252
  13. package/tests/engine/boons.test.js +0 -129
  14. package/tests/engine/combos.test.js +0 -76
  15. package/tests/engine/constants.test.js +0 -576
  16. package/tests/engine/fixtures/berserker-thief.json +0 -61
  17. package/tests/engine/fixtures/berserker-warrior.json +0 -113
  18. package/tests/engine/fixtures/celestial-firebrand-wvw.json +0 -94
  19. package/tests/engine/fixtures/harrier-druid.json +0 -119
  20. package/tests/engine/fixtures/viper-mirage.json +0 -104
  21. package/tests/engine/graph.test.js +0 -30
  22. package/tests/engine/integration.test.js +0 -111
  23. package/tests/engine/modifiers.test.js +0 -473
  24. package/tests/engine/overrides.test.js +0 -70
  25. package/tests/engine/snapshot.test.js +0 -53
  26. package/tests/engine/test-utils.js +0 -20
  27. package/tests/engine/tooltips.test.js +0 -62
  28. package/tests/fixtures/capture.js +0 -160
  29. package/tests/fixtures/fixtures.json +0 -839
  30. package/tests/integration.test.js +0 -100
  31. package/tests/match.test.js +0 -176
  32. package/tests/merge.test.js +0 -128
  33. package/tests/normalize.test.js +0 -78
  34. package/tests/parser.test.js +0 -506
  35. package/tests/real-data.test.js +0 -296
  36. package/tests/relations.test.js +0 -80
  37. package/tests/resolver.test.js +0 -721
  38. package/tests/validate-live.js +0 -191
  39. package/tests/wiki-client.test.js +0 -468
  40. package/tests/wiki-integration.test.js +0 -177
  41. package/tests/wiki-live-validation.test.js +0 -61
  42. package/tests/wiki-snapshots.test.js +0 -166
@@ -1,296 +0,0 @@
1
- "use strict";
2
-
3
- /**
4
- * Regression tests against real GW2 wiki + API data.
5
- *
6
- * These tests use captured fixtures (fixtures/fixtures.json) containing actual
7
- * wikitext and API responses. They verify that our parser and merger produce
8
- * correct results against production data, not just synthetic test strings.
9
- *
10
- * Re-capture fixtures: node packages/gw2-data/tests/fixtures/capture.js
11
- */
12
-
13
- const fs = require("fs");
14
- const path = require("path");
15
- const { WikiClient } = require("../src/wiki/client");
16
- const { MemoryCache } = require("../src/wiki/cache");
17
- const { mergeFacts } = require("../src/facts/merge");
18
- const { parseSplitGrouping, parseWikitextFacts, mapWikiFactToApiFact } = require("../src/wiki/parser");
19
- const { normalizeFactType } = require("../src/facts/normalize");
20
-
21
- const fixturesPath = path.join(__dirname, "fixtures", "fixtures.json");
22
- if (!fs.existsSync(fixturesPath)) {
23
- // eslint-disable-next-line no-console
24
- console.warn("Fixtures not found — run: node packages/gw2-data/tests/fixtures/capture.js");
25
- }
26
- const fixtures = fs.existsSync(fixturesPath)
27
- ? JSON.parse(fs.readFileSync(fixturesPath, "utf-8"))
28
- : { skills: [], traits: [] };
29
-
30
- const describeIf = fixtures.skills.length > 0 ? describe : describe.skip;
31
-
32
- // Helper: create a WikiClient with a mock fetch and pre-populated cache
33
- function createClient() {
34
- const client = new WikiClient({
35
- cache: new MemoryCache(),
36
- fetch: jest.fn(),
37
- });
38
- return client;
39
- }
40
-
41
- // ── Skill Parser Tests ──────────────────────────────────────────────────────
42
-
43
- describeIf("real data — skill parsing", () => {
44
- const client = createClient();
45
-
46
- test.each(fixtures.skills.map((s) => [s.wikiTitle, s]))(
47
- "%s: parseFacts produces non-empty result for split skills",
48
- (_name, fixture) => {
49
- const hasSplit = /\|\s*split\s*=/i.test(fixture.wikitext);
50
- const result = client.parseFacts(fixture.wikitext);
51
-
52
- if (hasSplit) {
53
- expect(result.splitGrouping).not.toBeNull();
54
- expect(result.splitGrouping.wvwHasSplit).toBe(true);
55
- }
56
-
57
- // Every split skill should produce at least one WvW fact
58
- if (hasSplit) {
59
- expect(result.facts.length).toBeGreaterThan(0);
60
- }
61
- }
62
- );
63
-
64
- test("Fireball (5489): parses WvW damage coefficient correctly", () => {
65
- const fixture = fixtures.skills.find((s) => s.id === 5489);
66
- const result = client.parseFacts(fixture.wikitext);
67
-
68
- const damageFact = result.facts.find((f) => f.type === "Damage");
69
- expect(damageFact).toBeTruthy();
70
- // Wiki says coefficient=0.666 for pvp wvw
71
- expect(damageFact.dmg_multiplier).toBeCloseTo(0.666, 2);
72
- });
73
-
74
- test("Fireball (5489): split grouping is pve, wvw pvp", () => {
75
- const fixture = fixtures.skills.find((s) => s.id === 5489);
76
- const result = client.parseFacts(fixture.wikitext);
77
-
78
- expect(result.splitGrouping.wvwHasSplit).toBe(true);
79
- expect(result.splitGrouping.wvwGroupedWithPvp).toBe(true);
80
- });
81
-
82
- test("Fireball (5489): has range, radius, and targets facts", () => {
83
- const fixture = fixtures.skills.find((s) => s.id === 5489);
84
- const result = client.parseFacts(fixture.wikitext);
85
-
86
- expect(result.facts.find((f) => f.type === "Range")).toBeTruthy();
87
- expect(result.facts.find((f) => f.type === "Radius")).toBeTruthy();
88
- expect(result.facts.find((f) => f.type === "Number" && f.text === "Number of Targets")).toBeTruthy();
89
- });
90
-
91
- test("Berserk (30185): has duration split (pve=20, wvw=15)", () => {
92
- const fixture = fixtures.skills.find((s) => s.id === 30185);
93
- const result = client.parseFacts(fixture.wikitext);
94
-
95
- // WvW duration should be 15 (pvp wvw group)
96
- const durationFact = result.facts.find((f) => f.type === "Time");
97
- expect(durationFact).toBeTruthy();
98
- expect(durationFact.duration).toBe(15);
99
- });
100
-
101
- test("Eruption (5548): has combo blast finisher", () => {
102
- const fixture = fixtures.skills.find((s) => s.id === 5548);
103
- const result = client.parseFacts(fixture.wikitext);
104
-
105
- const comboFact = result.facts.find((f) => f.type === "ComboFinisher");
106
- expect(comboFact).toBeTruthy();
107
- expect(comboFact.finisher_type).toBe("Blast");
108
- });
109
-
110
- test("Eruption (5548): has bleeding and crippled conditions", () => {
111
- const fixture = fixtures.skills.find((s) => s.id === 5548);
112
- const result = client.parseFacts(fixture.wikitext);
113
-
114
- const bleedFact = result.facts.find((f) => f.status === "Bleeding");
115
- const cripFact = result.facts.find((f) => f.status === "Crippled");
116
- expect(bleedFact).toBeTruthy();
117
- expect(bleedFact.duration).toBe(12);
118
- expect(cripFact).toBeTruthy();
119
- expect(cripFact.duration).toBe(3);
120
- });
121
-
122
- test("Meteor Shower (5507): separate WvW split (not grouped with PvP)", () => {
123
- const fixture = fixtures.skills.find((s) => s.id === 5507);
124
- const result = client.parseFacts(fixture.wikitext);
125
-
126
- // split = pve, wvw, pvp — each has its own group
127
- expect(result.splitGrouping.wvwHasSplit).toBe(true);
128
- expect(result.splitGrouping.wvwGroupedWithPvp).toBe(false);
129
- });
130
-
131
- test("Meteor Shower (5507): WvW damage coefficient is 0.88 (not PvE 1.6 or PvP 1.1)", () => {
132
- const fixture = fixtures.skills.find((s) => s.id === 5507);
133
- const result = client.parseFacts(fixture.wikitext);
134
-
135
- const damageFacts = result.facts.filter((f) => f.type === "Damage");
136
- // Should have the WvW coefficient, not PvE or PvP
137
- const mainDamage = damageFacts.find((f) => f.dmg_multiplier === 0.88);
138
- expect(mainDamage).toBeTruthy();
139
- });
140
-
141
- test("Water Blast (5569): has healing fact with base and coefficient", () => {
142
- const fixture = fixtures.skills.find((s) => s.id === 5569);
143
- const result = client.parseFacts(fixture.wikitext);
144
-
145
- const healingFact = result.facts.find((f) => f.target === "Healing");
146
- expect(healingFact).toBeTruthy();
147
- // WvW healing: base=223, coefficient=0.15
148
- expect(healingFact.value).toBe(223);
149
- expect(healingFact.coefficient).toBeCloseTo(0.15, 2);
150
- });
151
-
152
- test("Glyph of Storms (5503): no split, parses universal facts", () => {
153
- const fixture = fixtures.skills.find((s) => s.id === 5503);
154
- const result = client.parseFacts(fixture.wikitext);
155
-
156
- expect(result.splitGrouping).toBeNull();
157
- expect(result.facts.length).toBeGreaterThan(0);
158
- });
159
- });
160
-
161
- // ── Trait Parser Tests ──────────────────────────────────────────────────────
162
-
163
- describeIf("real data — trait parsing", () => {
164
- const client = createClient();
165
-
166
- test("Flow like Water (1510): has WvW split facts", () => {
167
- const fixture = fixtures.traits.find((t) => t.id === 1510);
168
- const result = client.parseFacts(fixture.wikitext);
169
-
170
- expect(result.splitGrouping).not.toBeNull();
171
- expect(result.splitGrouping.wvwHasSplit).toBe(true);
172
- expect(result.facts.length).toBeGreaterThan(0);
173
- });
174
-
175
- test("Burning Precision (264): has burning condition with split", () => {
176
- const fixture = fixtures.traits.find((t) => t.id === 264);
177
- const result = client.parseFacts(fixture.wikitext);
178
-
179
- expect(result.splitGrouping.wvwHasSplit).toBe(true);
180
- const burnFact = result.facts.find((f) => f.status === "Burning");
181
- expect(burnFact).toBeTruthy();
182
- // WvW burning duration is 1s (pvp wvw), not 3s (pve)
183
- expect(burnFact.duration).toBe(1);
184
- });
185
-
186
- test("Swift Revenge (1502): no split, has universal facts", () => {
187
- const fixture = fixtures.traits.find((t) => t.id === 1502);
188
- const result = client.parseFacts(fixture.wikitext);
189
-
190
- expect(result.splitGrouping).toBeNull();
191
- expect(result.facts.length).toBeGreaterThan(0);
192
- });
193
- });
194
-
195
- // ── Fact Merge Tests (wiki → API) ───────────────────────────────────────────
196
-
197
- describeIf("real data — fact merging", () => {
198
- const client = createClient();
199
-
200
- test("Fireball (5489): wiki WvW facts merge onto API base facts", () => {
201
- const fixture = fixtures.skills.find((s) => s.id === 5489);
202
- const wikiResult = client.parseFacts(fixture.wikitext);
203
- const apiFacts = fixture.api.facts || [];
204
-
205
- const merged = mergeFacts(apiFacts, wikiResult.facts, { complete: false });
206
-
207
- // Merged result should retain API structure but with WvW values
208
- expect(merged.length).toBeGreaterThan(0);
209
-
210
- // Damage fact should have wiki's WvW coefficient
211
- const dmg = merged.find((f) => normalizeFactType(f.type) === "Damage" || f.type === "Damage");
212
- if (dmg && dmg._splitFact) {
213
- expect(dmg.dmg_multiplier).toBeCloseTo(0.666, 2);
214
- }
215
- });
216
-
217
- test("Berserk (30185): merged facts include attack speed and duration", () => {
218
- const fixture = fixtures.skills.find((s) => s.id === 30185);
219
- const wikiResult = client.parseFacts(fixture.wikitext);
220
- const apiFacts = fixture.api.facts || [];
221
-
222
- const merged = mergeFacts(apiFacts, wikiResult.facts, { complete: false });
223
- expect(merged.length).toBeGreaterThan(0);
224
-
225
- // Duration should reflect WvW (15, not PvE 20)
226
- const timeFact = merged.find((f) => f.type === "Time" || (f.type === "Duration" && f.duration));
227
- if (timeFact) {
228
- expect(timeFact.duration).toBe(15);
229
- }
230
- });
231
-
232
- test("Water Blast (5569): wiki healing merges with API AttributeAdjust", () => {
233
- const fixture = fixtures.skills.find((s) => s.id === 5569);
234
- const wikiResult = client.parseFacts(fixture.wikitext);
235
- const apiFacts = fixture.api.facts || [];
236
-
237
- const merged = mergeFacts(apiFacts, wikiResult.facts, { complete: false });
238
- expect(merged.length).toBeGreaterThan(0);
239
- });
240
-
241
- test("Eruption (5548): merged facts preserve combo finisher from wiki", () => {
242
- const fixture = fixtures.skills.find((s) => s.id === 5548);
243
- const wikiResult = client.parseFacts(fixture.wikitext);
244
- const apiFacts = fixture.api.facts || [];
245
-
246
- const merged = mergeFacts(apiFacts, wikiResult.facts, { complete: false });
247
-
248
- // Should have combo finisher either from API or wiki
249
- const combo = merged.find(
250
- (f) => f.type === "ComboFinisher" || f.finisher_type
251
- );
252
- expect(combo).toBeTruthy();
253
- });
254
- });
255
-
256
- // ── API Fact Type Coverage ──────────────────────────────────────────────────
257
-
258
- describeIf("real data — API fact type normalization", () => {
259
- test("all API fact types are recognized by normalizeFactType", () => {
260
- const allApiTypes = new Set();
261
- for (const fixture of [...fixtures.skills, ...fixtures.traits]) {
262
- for (const fact of fixture.api.facts || []) {
263
- allApiTypes.add(fact.type);
264
- }
265
- for (const fact of fixture.api.traited_facts || []) {
266
- allApiTypes.add(fact.type);
267
- }
268
- }
269
-
270
- // These are the types we expect to encounter in real API data
271
- const recognized = new Set([
272
- "Buff", "PrefixedBuff", "Damage", "Recharge", "Time", "Number",
273
- "Range", "Radius", "Distance", "AttributeAdjust", "Percent",
274
- "ComboField", "ComboFinisher", "StunBreak", "Unblockable",
275
- "NoData", "Duration",
276
- ]);
277
-
278
- for (const type of allApiTypes) {
279
- const normalized = normalizeFactType(type);
280
- // After normalization, it should be a known type (or pass through as-is)
281
- expect(typeof normalized).toBe("string");
282
- expect(normalized.length).toBeGreaterThan(0);
283
- }
284
- });
285
-
286
- test("PrefixedBuff normalizes to Buff", () => {
287
- // Water Blast (5569) has PrefixedBuff facts in the API
288
- const fixture = fixtures.skills.find((s) => s.id === 5569);
289
- const prefixed = (fixture.api.facts || []).filter((f) => f.type === "PrefixedBuff");
290
- expect(prefixed.length).toBeGreaterThan(0);
291
-
292
- for (const fact of prefixed) {
293
- expect(normalizeFactType(fact.type)).toBe("Buff");
294
- }
295
- });
296
- });
@@ -1,80 +0,0 @@
1
- "use strict";
2
-
3
- const { parseRelatedItems, parseRelatedGroups } = require("../src/wiki/relations");
4
-
5
- describe("parseRelatedItems", () => {
6
- test("extracts skill name from list item with link", () => {
7
- const html = '<li><span class="skill-icon"><a href="/wiki/Fireball" title="Fireball">Fireball</a></span></li>';
8
- const items = parseRelatedItems(html);
9
- expect(items).toHaveLength(1);
10
- expect(items[0].name).toBe("Fireball");
11
- });
12
-
13
- test("extracts context from em-dash separated text", () => {
14
- const html = '<li><a href="/wiki/Fireball" title="Fireball">Fireball</a> \u2014 deals damage to foes</li>';
15
- const items = parseRelatedItems(html);
16
- expect(items).toHaveLength(1);
17
- expect(items[0].name).toBe("Fireball");
18
- expect(items[0].context).toBe("deals damage to foes");
19
- });
20
-
21
- test("returns empty array for empty HTML", () => {
22
- expect(parseRelatedItems("")).toEqual([]);
23
- });
24
-
25
- test("skips list items with no link title", () => {
26
- const html = '<li>Some text with no link</li><li><a href="/wiki/Fireball" title="Fireball">Fireball</a></li>';
27
- const items = parseRelatedItems(html);
28
- expect(items).toHaveLength(1);
29
- expect(items[0].name).toBe("Fireball");
30
- });
31
-
32
- test("extracts icon from img src", () => {
33
- const html = '<li><img src="https://wiki.guildwars2.com/images/fireball.png"/><a title="Fireball">Fireball</a></li>';
34
- const items = parseRelatedItems(html);
35
- expect(items).toHaveLength(1);
36
- expect(items[0].icon).toBe("https://wiki.guildwars2.com/images/fireball.png");
37
- });
38
-
39
- test("converts protocol-relative icon URLs to https", () => {
40
- const html = '<li><img src="//wiki.guildwars2.com/images/fireball.png"/><a title="Fireball">Fireball</a></li>';
41
- const items = parseRelatedItems(html);
42
- expect(items).toHaveLength(1);
43
- expect(items[0].icon).toBe("https://wiki.guildwars2.com/images/fireball.png");
44
- });
45
-
46
- test("omits icon and context properties when absent", () => {
47
- const html = '<li><a title="Fireball">Fireball</a></li>';
48
- const items = parseRelatedItems(html);
49
- expect(items).toHaveLength(1);
50
- expect(items[0]).toEqual({ name: "Fireball" });
51
- expect(items[0]).not.toHaveProperty("icon");
52
- expect(items[0]).not.toHaveProperty("context");
53
- });
54
- });
55
-
56
- describe("parseRelatedGroups", () => {
57
- test("groups traits by h4 headings", () => {
58
- const html = [
59
- '<h4>Strength</h4>',
60
- '<li><a title="Peak Performance">Peak Performance</a> \u2014 +20% strike damage</li>',
61
- '<h4>Arms</h4>',
62
- '<li><a title="Rending Strikes">Rending Strikes</a> \u2014 critical hits apply vulnerability</li>',
63
- ].join("");
64
- const groups = parseRelatedGroups(html);
65
- expect(groups).toHaveLength(2);
66
- expect(groups[0].groupName).toBe("Strength");
67
- expect(groups[0].items).toHaveLength(1);
68
- expect(groups[0].items[0].name).toBe("Peak Performance");
69
- expect(groups[1].groupName).toBe("Arms");
70
- expect(groups[1].items).toHaveLength(1);
71
- });
72
-
73
- test("returns single unnamed group if no headings", () => {
74
- const html = '<li><a title="Some Trait">Some Trait</a></li>';
75
- const groups = parseRelatedGroups(html);
76
- expect(groups).toHaveLength(1);
77
- expect(groups[0].groupName).toBe("");
78
- expect(groups[0].items).toHaveLength(1);
79
- });
80
- });