|
12 | 12 | import test from 'node:test'; |
13 | 13 | import assert from 'node:assert/strict'; |
14 | 14 |
|
15 | | -const { getNestedValue, extractCollectionField, buildChronicleFields } = |
| 15 | +const { getNestedValue, extractCollectionField, buildChronicleFields, normalizeFoundryValue } = |
16 | 16 | await import('../scripts/adapters/generic-adapter.mjs'); |
17 | 17 |
|
| 18 | +// ── Foundry-ish data structures for the normalizer tests ────────────────── |
| 19 | +// A Foundry Collection is a Map subclass exposing members via `.contents`. |
| 20 | +class FakeCollection extends Map { |
| 21 | + constructor(members) { |
| 22 | + super(members.map((m, i) => [m._id ?? String(i), m])); |
| 23 | + } |
| 24 | + get contents() { return Array.from(this.values()); } |
| 25 | +} |
| 26 | +// A pseudo-document / DataModel exposes data via toObject(), not own props — |
| 27 | +// the schema fields here are GETTERS so a naive key-copy would miss them. |
| 28 | +class FakeModel { |
| 29 | + constructor(source) { this._source = source; } |
| 30 | + get tier1() { return this._source.tier1; } |
| 31 | + toObject() { return JSON.parse(JSON.stringify(this._source)); } |
| 32 | +} |
| 33 | + |
18 | 34 | // A Foundry-ish actor with system data + an items collection (Map-like w/ .contents). |
19 | 35 | function makeActor() { |
20 | 36 | return { |
@@ -134,3 +150,69 @@ test('buildChronicleFields sets null for a missing scalar path', () => { |
134 | 150 | const out = buildChronicleFields(makeActor(), [{ key: 'speed', foundry_path: 'system.movement.speed', type: 'number' }]); |
135 | 151 | assert.equal(out.speed, null); |
136 | 152 | }); |
| 153 | + |
| 154 | +// ── normalizeFoundryValue ───────────────────────────────────────────────── |
| 155 | + |
| 156 | +test('normalizeFoundryValue passes primitives and plain values through', () => { |
| 157 | + assert.equal(normalizeFoundryValue(7), 7); |
| 158 | + assert.equal(normalizeFoundryValue('hi'), 'hi'); |
| 159 | + assert.equal(normalizeFoundryValue(null), null); |
| 160 | + assert.equal(normalizeFoundryValue(undefined), undefined); |
| 161 | + assert.deepEqual(normalizeFoundryValue([1, 2, 3]), [1, 2, 3]); |
| 162 | + assert.deepEqual(normalizeFoundryValue({ a: 1, b: 'x' }), { a: 1, b: 'x' }); |
| 163 | +}); |
| 164 | + |
| 165 | +test('normalizeFoundryValue turns a Set into an array (keywords/skills/statuses)', () => { |
| 166 | + assert.deepEqual(normalizeFoundryValue(new Set(['magic', 'ranged'])), ['magic', 'ranged']); |
| 167 | + assert.deepEqual(normalizeFoundryValue(new Set()), []); |
| 168 | +}); |
| 169 | + |
| 170 | +test('normalizeFoundryValue normalizes nested Sets inside a plain object', () => { |
| 171 | + const v = normalizeFoundryValue({ type: 'damage', types: new Set(['fire']), n: 2 }); |
| 172 | + assert.deepEqual(v, { type: 'damage', types: ['fire'], n: 2 }); |
| 173 | +}); |
| 174 | + |
| 175 | +test('normalizeFoundryValue unrolls a Foundry Collection via .contents + toObject', () => { |
| 176 | + const effects = new FakeCollection([ |
| 177 | + new FakeModel({ _id: 'a', type: 'damage', tier1: { value: '2 + @chr', types: ['fire'] } }), |
| 178 | + new FakeModel({ _id: 'b', type: 'damage', tier1: { value: '3', types: [] } }), |
| 179 | + ]); |
| 180 | + const out = normalizeFoundryValue(effects); |
| 181 | + assert.ok(Array.isArray(out)); |
| 182 | + assert.equal(out.length, 2); |
| 183 | + assert.deepEqual(out[0], { _id: 'a', type: 'damage', tier1: { value: '2 + @chr', types: ['fire'] } }); |
| 184 | + assert.equal(out[1].tier1.value, '3'); |
| 185 | +}); |
| 186 | + |
| 187 | +test('extractCollectionField projects a Set sub-field to a JSON array (ability keywords)', () => { |
| 188 | + const actor = { |
| 189 | + items: { contents: [ |
| 190 | + { id: 'i1', name: 'Ray of Wrath', type: 'ability', |
| 191 | + system: { type: 'main', category: 'signature', keywords: new Set(['magic', 'ranged']) } }, |
| 192 | + ] }, |
| 193 | + }; |
| 194 | + const json = extractCollectionField(actor, { |
| 195 | + key: 'abilities_json', type: 'string', |
| 196 | + foundry_collection: 'items', foundry_item_type: 'ability', |
| 197 | + foundry_item_fields: { name: 'name', type: 'system.type', keywords: 'system.keywords' }, |
| 198 | + }); |
| 199 | + const abilities = JSON.parse(json); |
| 200 | + assert.deepEqual(abilities[0], { name: 'Ray of Wrath', type: 'main', keywords: ['magic', 'ranged'] }); |
| 201 | +}); |
| 202 | + |
| 203 | +test('buildChronicleFields serializes a Set scalar on a string field to JSON (skills)', () => { |
| 204 | + const actor = { system: { skills: { value: new Set(['alchemy', 'timescape']) } } }; |
| 205 | + const out = buildChronicleFields(actor, [ |
| 206 | + { key: 'skills_json', foundry_path: 'system.skills.value', type: 'string' }, |
| 207 | + ]); |
| 208 | + assert.equal(typeof out.skills_json, 'string'); |
| 209 | + assert.deepEqual(JSON.parse(out.skills_json), ['alchemy', 'timescape']); |
| 210 | +}); |
| 211 | + |
| 212 | +test('buildChronicleFields serializes actor.statuses (Set) for conditions', () => { |
| 213 | + const actor = { system: {}, statuses: new Set(['slowed', 'weakened']) }; |
| 214 | + const out = buildChronicleFields(actor, [ |
| 215 | + { key: 'conditions_json', foundry_path: 'statuses', type: 'string' }, |
| 216 | + ]); |
| 217 | + assert.deepEqual(JSON.parse(out.conditions_json), ['slowed', 'weakened']); |
| 218 | +}); |
0 commit comments