👁️
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

at dev 721 lines 20 kB view raw
1import { describe, expect, it, vi } from "vitest"; 2import type { CardDataProvider } from "../card-data-provider"; 3import type { Deck } from "../deck-types"; 4import { 5 findAllCanonicalPrintings, 6 findAllCheapestPrintings, 7 findCheapestPrinting, 8 getCheapestPrice, 9 updateDeckPrintings, 10} from "../printing-selection"; 11import type { 12 Card, 13 OracleId, 14 ScryfallId, 15 VolatileData, 16} from "../scryfall-types"; 17import { asOracleId, asScryfallId } from "../scryfall-types"; 18 19function mockVolatileData(overrides: Partial<VolatileData> = {}): VolatileData { 20 return { 21 edhrecRank: null, 22 usd: null, 23 usdFoil: null, 24 usdEtched: null, 25 eur: null, 26 eurFoil: null, 27 tix: null, 28 ...overrides, 29 }; 30} 31 32function mockDeck( 33 cards: Array<{ 34 scryfallId: ScryfallId; 35 oracleId?: OracleId; 36 section?: "mainboard" | "sideboard" | "commander" | "maybeboard"; 37 }>, 38): Deck { 39 return { 40 $type: "com.deckbelcher.deck.list", 41 name: "Test Deck", 42 format: "commander", 43 cards: cards.map((c) => ({ 44 scryfallId: c.scryfallId, 45 oracleId: 46 c.oracleId ?? asOracleId("00000000-0000-0000-0000-000000000000"), 47 quantity: 1, 48 section: c.section ?? "mainboard", 49 tags: [], 50 })), 51 createdAt: new Date().toISOString(), 52 }; 53} 54 55describe("getCheapestPrice", () => { 56 it("returns usd when it's the only price", () => { 57 const v = mockVolatileData({ usd: 1.5 }); 58 expect(getCheapestPrice(v)).toBe(1.5); 59 }); 60 61 it("returns cheapest among usd/foil/etched", () => { 62 const v = mockVolatileData({ usd: 2.0, usdFoil: 1.5, usdEtched: 3.0 }); 63 expect(getCheapestPrice(v)).toBe(1.5); 64 }); 65 66 it("returns null when all prices are null", () => { 67 const v = mockVolatileData(); 68 expect(getCheapestPrice(v)).toBeNull(); 69 }); 70 71 it("ignores null values in comparison", () => { 72 const v = mockVolatileData({ usdFoil: 5.0 }); 73 expect(getCheapestPrice(v)).toBe(5.0); 74 }); 75 76 it("returns etched price when it's cheapest", () => { 77 const v = mockVolatileData({ usd: 10.0, usdFoil: 15.0, usdEtched: 5.0 }); 78 expect(getCheapestPrice(v)).toBe(5.0); 79 }); 80 81 it("handles zero price correctly", () => { 82 const v = mockVolatileData({ usd: 0, usdFoil: 1.0 }); 83 expect(getCheapestPrice(v)).toBe(0); 84 }); 85}); 86 87describe("findCheapestPrinting", () => { 88 const id1 = asScryfallId("00000000-0000-0000-0000-000000000001"); 89 const id2 = asScryfallId("00000000-0000-0000-0000-000000000002"); 90 const id3 = asScryfallId("00000000-0000-0000-0000-000000000003"); 91 92 it("returns printing with lowest price", () => { 93 const volatileData = new Map<ScryfallId, VolatileData | null>([ 94 [id1, mockVolatileData({ usd: 5.0 })], 95 [id2, mockVolatileData({ usd: 1.0 })], 96 [id3, mockVolatileData({ usd: 3.0 })], 97 ]); 98 expect(findCheapestPrinting([id1, id2, id3], volatileData)).toBe(id2); 99 }); 100 101 it("considers foil prices", () => { 102 const volatileData = new Map<ScryfallId, VolatileData | null>([ 103 [id1, mockVolatileData({ usd: 5.0 })], 104 [id2, mockVolatileData({ usdFoil: 0.5 })], 105 ]); 106 expect(findCheapestPrinting([id1, id2], volatileData)).toBe(id2); 107 }); 108 109 it("returns null when no prices available", () => { 110 const volatileData = new Map<ScryfallId, VolatileData | null>([ 111 [id1, mockVolatileData()], 112 ]); 113 expect(findCheapestPrinting([id1], volatileData)).toBeNull(); 114 }); 115 116 it("returns null for empty printing list", () => { 117 expect(findCheapestPrinting([], new Map())).toBeNull(); 118 }); 119 120 it("skips printings with null volatile data", () => { 121 const volatileData = new Map<ScryfallId, VolatileData | null>([ 122 [id1, null], 123 [id2, mockVolatileData({ usd: 2.0 })], 124 ]); 125 expect(findCheapestPrinting([id1, id2], volatileData)).toBe(id2); 126 }); 127 128 it("skips printings not in volatile data map", () => { 129 const volatileData = new Map<ScryfallId, VolatileData | null>([ 130 [id2, mockVolatileData({ usd: 2.0 })], 131 ]); 132 expect(findCheapestPrinting([id1, id2], volatileData)).toBe(id2); 133 }); 134}); 135 136describe("updateDeckPrintings", () => { 137 const oldId = asScryfallId("00000000-0000-0000-0000-000000000001"); 138 const newId = asScryfallId("00000000-0000-0000-0000-000000000002"); 139 const keepId = asScryfallId("00000000-0000-0000-0000-000000000003"); 140 141 it("updates scryfallIds based on mapping", () => { 142 const deck = mockDeck([{ scryfallId: oldId }]); 143 const updates = new Map([[oldId, newId]]); 144 const result = updateDeckPrintings(deck, updates); 145 146 expect(result.cards[0].scryfallId).toBe(newId); 147 }); 148 149 it("preserves cards not in update map", () => { 150 const deck = mockDeck([{ scryfallId: keepId }]); 151 const result = updateDeckPrintings(deck, new Map()); 152 153 expect(result.cards[0].scryfallId).toBe(keepId); 154 }); 155 156 it("handles empty deck", () => { 157 const deck = mockDeck([]); 158 const result = updateDeckPrintings(deck, new Map()); 159 160 expect(result.cards).toEqual([]); 161 }); 162 163 it("updates only cards in the map", () => { 164 const deck = mockDeck([{ scryfallId: oldId }, { scryfallId: keepId }]); 165 const updates = new Map([[oldId, newId]]); 166 const result = updateDeckPrintings(deck, updates); 167 168 expect(result.cards[0].scryfallId).toBe(newId); 169 expect(result.cards[1].scryfallId).toBe(keepId); 170 }); 171 172 it("preserves other card properties", () => { 173 const deck: Deck = { 174 $type: "com.deckbelcher.deck.list", 175 name: "Test Deck", 176 format: "commander", 177 cards: [ 178 { 179 scryfallId: oldId, 180 oracleId: asOracleId("00000000-0000-0000-0000-000000000000"), 181 quantity: 4, 182 section: "sideboard", 183 tags: ["removal", "instant"], 184 }, 185 ], 186 createdAt: new Date().toISOString(), 187 }; 188 const updates = new Map([[oldId, newId]]); 189 const result = updateDeckPrintings(deck, updates); 190 191 expect(result.cards[0]).toEqual({ 192 scryfallId: newId, 193 oracleId: asOracleId("00000000-0000-0000-0000-000000000000"), 194 quantity: 4, 195 section: "sideboard", 196 tags: ["removal", "instant"], 197 }); 198 }); 199 200 it("returns same deck reference when no updates", () => { 201 const deck = mockDeck([{ scryfallId: keepId }]); 202 const result = updateDeckPrintings(deck, new Map()); 203 204 expect(result).toBe(deck); 205 }); 206 207 it("sets updatedAt when changes are made", () => { 208 const deck = mockDeck([{ scryfallId: oldId }]); 209 const originalUpdatedAt = deck.updatedAt; 210 const updates = new Map([[oldId, newId]]); 211 212 const result = updateDeckPrintings(deck, updates); 213 214 expect(result.updatedAt).not.toBe(originalUpdatedAt); 215 }); 216}); 217 218describe("findAllCheapestPrintings", () => { 219 const oracle1 = asOracleId("11111111-1111-1111-1111-111111111111"); 220 const oracle2 = asOracleId("22222222-2222-2222-2222-222222222222"); 221 222 const card1a = asScryfallId("1a1a1a1a-1a1a-1a1a-1a1a-1a1a1a1a1a1a"); 223 const card1b = asScryfallId("1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b"); 224 const card2a = asScryfallId("2a2a2a2a-2a2a-2a2a-2a2a-2a2a2a2a2a2a"); 225 226 function mockProvider(config: { 227 cards: Record<string, { oracle_id: OracleId }>; 228 printings: Record<string, ScryfallId[]>; 229 volatileData: Record<string, VolatileData | null>; 230 canonical?: Record<string, ScryfallId>; 231 }): CardDataProvider { 232 return { 233 getCardById: vi.fn(async (id: ScryfallId) => { 234 const data = config.cards[id]; 235 if (!data) return undefined; 236 return { id, oracle_id: data.oracle_id, name: "Test Card" } as Card; 237 }), 238 getPrintingsByOracleId: vi.fn( 239 async (oracleId: OracleId) => config.printings[oracleId] ?? [], 240 ), 241 getVolatileData: vi.fn( 242 async (id: ScryfallId) => config.volatileData[id] ?? null, 243 ), 244 getCanonicalPrinting: vi.fn( 245 async (oracleId: OracleId) => config.canonical?.[oracleId], 246 ), 247 getMetadata: vi.fn(async () => ({ version: "test", cardCount: 100 })), 248 }; 249 } 250 251 it("finds cheapest printing for each card", async () => { 252 const provider = mockProvider({ 253 cards: { 254 [card1a]: { oracle_id: oracle1 }, 255 }, 256 printings: { 257 [oracle1]: [card1a, card1b], 258 }, 259 volatileData: { 260 [card1a]: mockVolatileData({ usd: 10.0 }), 261 [card1b]: mockVolatileData({ usd: 2.0 }), 262 }, 263 }); 264 265 const deck = mockDeck([{ scryfallId: card1a, oracleId: oracle1 }]); 266 const updates = await findAllCheapestPrintings(deck, provider); 267 268 expect(updates.get(card1a)).toBe(card1b); 269 }); 270 271 it("skips cards already at cheapest", async () => { 272 const provider = mockProvider({ 273 cards: { 274 [card1a]: { oracle_id: oracle1 }, 275 }, 276 printings: { 277 [oracle1]: [card1a, card1b], 278 }, 279 volatileData: { 280 [card1a]: mockVolatileData({ usd: 1.0 }), 281 [card1b]: mockVolatileData({ usd: 10.0 }), 282 }, 283 }); 284 285 const deck = mockDeck([{ scryfallId: card1a, oracleId: oracle1 }]); 286 const updates = await findAllCheapestPrintings(deck, provider); 287 288 expect(updates.size).toBe(0); 289 }); 290 291 it("handles multiple cards with same oracle", async () => { 292 const provider = mockProvider({ 293 cards: { 294 [card1a]: { oracle_id: oracle1 }, 295 [card1b]: { oracle_id: oracle1 }, 296 }, 297 printings: { 298 [oracle1]: [card1a, card1b], 299 }, 300 volatileData: { 301 [card1a]: mockVolatileData({ usd: 10.0 }), 302 [card1b]: mockVolatileData({ usd: 2.0 }), 303 }, 304 }); 305 306 const deck = mockDeck([ 307 { scryfallId: card1a, oracleId: oracle1 }, 308 { scryfallId: card1a, oracleId: oracle1, section: "sideboard" }, 309 ]); 310 const updates = await findAllCheapestPrintings(deck, provider); 311 312 expect(updates.get(card1a)).toBe(card1b); 313 }); 314 315 it("handles cards with no price data", async () => { 316 const provider = mockProvider({ 317 cards: { 318 [card1a]: { oracle_id: oracle1 }, 319 }, 320 printings: { 321 [oracle1]: [card1a, card1b], 322 }, 323 volatileData: { 324 [card1a]: mockVolatileData(), 325 [card1b]: mockVolatileData(), 326 }, 327 }); 328 329 const deck = mockDeck([{ scryfallId: card1a, oracleId: oracle1 }]); 330 const updates = await findAllCheapestPrintings(deck, provider); 331 332 expect(updates.size).toBe(0); 333 }); 334 335 it("handles multiple different cards", async () => { 336 const provider = mockProvider({ 337 cards: { 338 [card1a]: { oracle_id: oracle1 }, 339 [card2a]: { oracle_id: oracle2 }, 340 }, 341 printings: { 342 [oracle1]: [card1a, card1b], 343 [oracle2]: [card2a], 344 }, 345 volatileData: { 346 [card1a]: mockVolatileData({ usd: 10.0 }), 347 [card1b]: mockVolatileData({ usd: 2.0 }), 348 [card2a]: mockVolatileData({ usd: 5.0 }), 349 }, 350 }); 351 352 const deck = mockDeck([ 353 { scryfallId: card1a, oracleId: oracle1 }, 354 { scryfallId: card2a, oracleId: oracle2 }, 355 ]); 356 const updates = await findAllCheapestPrintings(deck, provider); 357 358 expect(updates.get(card1a)).toBe(card1b); 359 expect(updates.has(card2a)).toBe(false); 360 }); 361}); 362 363describe("findAllCheapestPrintings edge cases", () => { 364 const oracle1 = asOracleId("11111111-1111-1111-1111-111111111111"); 365 366 const cardExpensive = asScryfallId("eeee-eeee-eeee-eeee-eeeeeeeeeeee"); 367 const cardCheap = asScryfallId("cccc-cccc-cccc-cccc-cccccccccccc"); 368 const cardMid = asScryfallId("mmmm-mmmm-mmmm-mmmm-mmmmmmmmmmmm"); 369 370 function mockProvider(): CardDataProvider { 371 return { 372 getCardById: vi.fn(async (id: ScryfallId) => { 373 // All cards have the same oracle_id 374 return { id, oracle_id: oracle1, name: "Lightning Bolt" } as Card; 375 }), 376 getPrintingsByOracleId: vi.fn(async () => [ 377 cardExpensive, 378 cardCheap, 379 cardMid, 380 ]), 381 getVolatileData: vi.fn(async (id: ScryfallId) => { 382 if (id === cardExpensive) return mockVolatileData({ usd: 100.0 }); 383 if (id === cardCheap) return mockVolatileData({ usd: 0.25 }); 384 if (id === cardMid) return mockVolatileData({ usd: 5.0 }); 385 return null; 386 }), 387 getCanonicalPrinting: vi.fn(async () => cardExpensive), 388 getMetadata: vi.fn(async () => ({ version: "test", cardCount: 100 })), 389 }; 390 } 391 392 it("updates multiple cards with different printings of same oracle to same cheapest", async () => { 393 const provider = mockProvider(); 394 const deck: Deck = { 395 $type: "com.deckbelcher.deck.list", 396 name: "Test Deck", 397 format: "modern", 398 cards: [ 399 // Same oracle, different printings in same section 400 { 401 scryfallId: cardExpensive, 402 oracleId: oracle1, 403 quantity: 2, 404 section: "mainboard", 405 tags: [], 406 }, 407 { 408 scryfallId: cardMid, 409 oracleId: oracle1, 410 quantity: 2, 411 section: "mainboard", 412 tags: ["burn"], 413 }, 414 ], 415 createdAt: new Date().toISOString(), 416 }; 417 418 const updates = await findAllCheapestPrintings(deck, provider); 419 420 // Both should update to the cheapest 421 expect(updates.get(cardExpensive)).toBe(cardCheap); 422 expect(updates.get(cardMid)).toBe(cardCheap); 423 }); 424 425 it("handles same card in different sections with different printings", async () => { 426 const provider = mockProvider(); 427 const deck: Deck = { 428 $type: "com.deckbelcher.deck.list", 429 name: "Test Deck", 430 format: "modern", 431 cards: [ 432 { 433 scryfallId: cardExpensive, 434 oracleId: oracle1, 435 quantity: 4, 436 section: "mainboard", 437 tags: [], 438 }, 439 { 440 scryfallId: cardMid, 441 oracleId: oracle1, 442 quantity: 2, 443 section: "sideboard", 444 tags: ["sb"], 445 }, 446 ], 447 createdAt: new Date().toISOString(), 448 }; 449 450 const updates = await findAllCheapestPrintings(deck, provider); 451 452 // Both should update to cheapest 453 expect(updates.get(cardExpensive)).toBe(cardCheap); 454 expect(updates.get(cardMid)).toBe(cardCheap); 455 }); 456 457 it("handles same printing in same section with different tag entries", async () => { 458 const provider = mockProvider(); 459 // Note: This is technically invalid deck state (same scryfallId+section twice) 460 // but we should handle it gracefully 461 const deck: Deck = { 462 $type: "com.deckbelcher.deck.list", 463 name: "Test Deck", 464 format: "modern", 465 cards: [ 466 { 467 scryfallId: cardExpensive, 468 oracleId: oracle1, 469 quantity: 2, 470 section: "mainboard", 471 tags: ["burn"], 472 }, 473 { 474 scryfallId: cardExpensive, 475 oracleId: oracle1, 476 quantity: 2, 477 section: "mainboard", 478 tags: ["removal"], 479 }, 480 ], 481 createdAt: new Date().toISOString(), 482 }; 483 484 const updates = await findAllCheapestPrintings(deck, provider); 485 486 // Both entries should get the update mapping 487 expect(updates.get(cardExpensive)).toBe(cardCheap); 488 }); 489 490 it("preserves card already at cheapest among mixed printings", async () => { 491 const provider = mockProvider(); 492 const deck: Deck = { 493 $type: "com.deckbelcher.deck.list", 494 name: "Test Deck", 495 format: "modern", 496 cards: [ 497 { 498 scryfallId: cardCheap, 499 oracleId: oracle1, 500 quantity: 2, 501 section: "mainboard", 502 tags: [], 503 }, 504 { 505 scryfallId: cardExpensive, 506 oracleId: oracle1, 507 quantity: 2, 508 section: "mainboard", 509 tags: [], 510 }, 511 ], 512 createdAt: new Date().toISOString(), 513 }; 514 515 const updates = await findAllCheapestPrintings(deck, provider); 516 517 // cardCheap should NOT be in updates (it's already cheapest) 518 expect(updates.has(cardCheap)).toBe(false); 519 // cardExpensive should update to cardCheap 520 expect(updates.get(cardExpensive)).toBe(cardCheap); 521 }); 522}); 523 524describe("updateDeckPrintings edge cases", () => { 525 const oldPrinting = asScryfallId("oooo-oooo-oooo-oooo-oooooooooooo"); 526 const newPrinting = asScryfallId("nnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn"); 527 528 it("updates all instances of a printing regardless of section", () => { 529 const deck: Deck = { 530 $type: "com.deckbelcher.deck.list", 531 name: "Test Deck", 532 format: "modern", 533 cards: [ 534 { 535 scryfallId: oldPrinting, 536 oracleId: asOracleId("00000000-0000-0000-0000-000000000000"), 537 quantity: 4, 538 section: "mainboard", 539 tags: [], 540 }, 541 { 542 scryfallId: oldPrinting, 543 oracleId: asOracleId("00000000-0000-0000-0000-000000000000"), 544 quantity: 2, 545 section: "sideboard", 546 tags: ["sb"], 547 }, 548 ], 549 createdAt: new Date().toISOString(), 550 }; 551 552 const updates = new Map([[oldPrinting, newPrinting]]); 553 const result = updateDeckPrintings(deck, updates); 554 555 expect(result.cards[0].scryfallId).toBe(newPrinting); 556 expect(result.cards[1].scryfallId).toBe(newPrinting); 557 }); 558 559 it("preserves tags when updating printings", () => { 560 const deck: Deck = { 561 $type: "com.deckbelcher.deck.list", 562 name: "Test Deck", 563 format: "modern", 564 cards: [ 565 { 566 scryfallId: oldPrinting, 567 oracleId: asOracleId("00000000-0000-0000-0000-000000000000"), 568 quantity: 4, 569 section: "mainboard", 570 tags: ["burn", "instant"], 571 }, 572 ], 573 createdAt: new Date().toISOString(), 574 }; 575 576 const updates = new Map([[oldPrinting, newPrinting]]); 577 const result = updateDeckPrintings(deck, updates); 578 579 expect(result.cards[0].tags).toEqual(["burn", "instant"]); 580 }); 581 582 it("preserves quantity when updating printings", () => { 583 const deck: Deck = { 584 $type: "com.deckbelcher.deck.list", 585 name: "Test Deck", 586 format: "modern", 587 cards: [ 588 { 589 scryfallId: oldPrinting, 590 oracleId: asOracleId("00000000-0000-0000-0000-000000000000"), 591 quantity: 4, 592 section: "mainboard", 593 tags: [], 594 }, 595 ], 596 createdAt: new Date().toISOString(), 597 }; 598 599 const updates = new Map([[oldPrinting, newPrinting]]); 600 const result = updateDeckPrintings(deck, updates); 601 602 expect(result.cards[0].quantity).toBe(4); 603 }); 604 605 it("does NOT merge separate entries that end up with same printing", () => { 606 const printingA = asScryfallId("aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); 607 const printingB = asScryfallId("bbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); 608 const cheapest = asScryfallId("cccc-cccc-cccc-cccc-cccccccccccc"); 609 const oracle = asOracleId("00000000-0000-0000-0000-000000000000"); 610 611 const deck: Deck = { 612 $type: "com.deckbelcher.deck.list", 613 name: "Test Deck", 614 format: "modern", 615 cards: [ 616 { 617 scryfallId: printingA, 618 oracleId: oracle, 619 quantity: 2, 620 section: "mainboard", 621 tags: ["burn"], 622 }, 623 { 624 scryfallId: printingB, 625 oracleId: oracle, 626 quantity: 3, 627 section: "mainboard", 628 tags: ["removal"], 629 }, 630 ], 631 createdAt: new Date().toISOString(), 632 }; 633 634 const updates = new Map([ 635 [printingA, cheapest], 636 [printingB, cheapest], 637 ]); 638 const result = updateDeckPrintings(deck, updates); 639 640 // Should have 2 separate entries, NOT merged into 1 641 expect(result.cards.length).toBe(2); 642 expect(result.cards[0].scryfallId).toBe(cheapest); 643 expect(result.cards[0].quantity).toBe(2); 644 expect(result.cards[0].tags).toEqual(["burn"]); 645 expect(result.cards[1].scryfallId).toBe(cheapest); 646 expect(result.cards[1].quantity).toBe(3); 647 expect(result.cards[1].tags).toEqual(["removal"]); 648 }); 649}); 650 651describe("findAllCanonicalPrintings", () => { 652 const oracle1 = asOracleId("11111111-1111-1111-1111-111111111111"); 653 654 const card1a = asScryfallId("1a1a1a1a-1a1a-1a1a-1a1a-1a1a1a1a1a1a"); 655 const card1b = asScryfallId("1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b"); 656 657 function mockProvider(config: { 658 cards: Record<string, { oracle_id: OracleId }>; 659 canonical: Record<string, ScryfallId>; 660 }): CardDataProvider { 661 return { 662 getCardById: vi.fn(async (id: ScryfallId) => { 663 const data = config.cards[id]; 664 if (!data) return undefined; 665 return { id, oracle_id: data.oracle_id, name: "Test Card" } as Card; 666 }), 667 getPrintingsByOracleId: vi.fn(async () => []), 668 getVolatileData: vi.fn(async () => null), 669 getCanonicalPrinting: vi.fn( 670 async (oracleId: OracleId) => config.canonical[oracleId], 671 ), 672 getMetadata: vi.fn(async () => ({ version: "test", cardCount: 100 })), 673 }; 674 } 675 676 it("finds canonical printing for each card", async () => { 677 const provider = mockProvider({ 678 cards: { 679 [card1a]: { oracle_id: oracle1 }, 680 }, 681 canonical: { 682 [oracle1]: card1b, 683 }, 684 }); 685 686 const deck = mockDeck([{ scryfallId: card1a, oracleId: oracle1 }]); 687 const updates = await findAllCanonicalPrintings(deck, provider); 688 689 expect(updates.get(card1a)).toBe(card1b); 690 }); 691 692 it("skips cards already at canonical", async () => { 693 const provider = mockProvider({ 694 cards: { 695 [card1a]: { oracle_id: oracle1 }, 696 }, 697 canonical: { 698 [oracle1]: card1a, 699 }, 700 }); 701 702 const deck = mockDeck([{ scryfallId: card1a, oracleId: oracle1 }]); 703 const updates = await findAllCanonicalPrintings(deck, provider); 704 705 expect(updates.size).toBe(0); 706 }); 707 708 it("handles cards with no canonical printing", async () => { 709 const provider = mockProvider({ 710 cards: { 711 [card1a]: { oracle_id: oracle1 }, 712 }, 713 canonical: {}, 714 }); 715 716 const deck = mockDeck([{ scryfallId: card1a, oracleId: oracle1 }]); 717 const updates = await findAllCanonicalPrintings(deck, provider); 718 719 expect(updates.size).toBe(0); 720 }); 721});