diff --git a/client/src/viewmodel/__tests__/costLabel.test.ts b/client/src/viewmodel/__tests__/costLabel.test.ts index 19afcc4da6..407c6da900 100644 --- a/client/src/viewmodel/__tests__/costLabel.test.ts +++ b/client/src/viewmodel/__tests__/costLabel.test.ts @@ -275,3 +275,28 @@ describe("formatAbilityCost", () => { })).toBe("{1} or Pay 2 life"); }); }); + +describe("additionalCostChoices — Choice with a malformed (short) engine payload", () => { + const manaCost = { + type: "Mana", + cost: { type: "Cost", shards: [], generic: 2 }, + }; + + it("labels both options for a well-formed two-cost Choice", () => { + const cost = { type: "Choice", data: [manaCost, manaCost] } as AdditionalCost; + const { options } = additionalCostChoices(cost); + expect(options.find((o) => o.id === "pay")!.label).toBeTruthy(); + expect(options.find((o) => o.id === "decline")!.label).toBeTruthy(); + }); + + it("does not throw when a Choice payload is missing its second cost", () => { + // The tuple type promises two costs, but `data` is deserialized from the + // engine — a short array must degrade to a fallback label, not crash. + const cost = { type: "Choice", data: [manaCost] } as unknown as AdditionalCost; + expect(() => additionalCostChoices(cost)).not.toThrow(); + const decline = additionalCostChoices(cost).options.find( + (o) => o.id === "decline", + )!; + expect(decline.label).toBe("Decline"); + }); +}); diff --git a/client/src/viewmodel/costLabel.ts b/client/src/viewmodel/costLabel.ts index 9b18d222fb..b23ceadc01 100644 --- a/client/src/viewmodel/costLabel.ts +++ b/client/src/viewmodel/costLabel.ts @@ -513,14 +513,20 @@ export function additionalCostChoices( { id: "decline", label: "Cancel" }, ], }; - case "Choice": + case "Choice": { + // `data` is typed as a 2-tuple, but it is deserialized from the engine, so + // a malformed/short payload leaves an element `undefined`. formatAbilityCost + // switches on `cost.type`, so passing `undefined` throws — guard each option + // (mirroring the `Kicker` branch's `first ? … : …` defense). + const [pay, decline] = cost.data; return { title: "Choose additional cost", options: [ - { id: "pay", label: formatAbilityCost(cost.data[0]) }, - { id: "decline", label: formatAbilityCost(cost.data[1]) }, + { id: "pay", label: pay ? formatAbilityCost(pay) : "Pay" }, + { id: "decline", label: decline ? formatAbilityCost(decline) : "Decline" }, ], }; + } } }