👁️
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});