@ledgerhq/cryptoassets 13.33.0-nightly.20251125074637 → 13.34.0-nightly.20251126023856
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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +51 -14
- package/lib/cal-client/hooks/useTokensData.d.ts +2 -2
- package/lib/cal-client/persistence.d.ts +17 -10
- package/lib/cal-client/persistence.d.ts.map +1 -1
- package/lib/cal-client/persistence.js +84 -23
- package/lib/cal-client/persistence.js.map +1 -1
- package/lib/cal-client/state-manager/api.d.ts +157 -160
- package/lib/cal-client/state-manager/api.d.ts.map +1 -1
- package/lib/cal-client/state-manager/api.js +25 -8
- package/lib/cal-client/state-manager/api.js.map +1 -1
- package/lib-es/cal-client/hooks/useTokensData.d.ts +2 -2
- package/lib-es/cal-client/persistence.d.ts +17 -10
- package/lib-es/cal-client/persistence.d.ts.map +1 -1
- package/lib-es/cal-client/persistence.js +81 -22
- package/lib-es/cal-client/persistence.js.map +1 -1
- package/lib-es/cal-client/state-manager/api.d.ts +157 -160
- package/lib-es/cal-client/state-manager/api.d.ts.map +1 -1
- package/lib-es/cal-client/state-manager/api.js +26 -9
- package/lib-es/cal-client/state-manager/api.js.map +1 -1
- package/package.json +3 -3
- package/src/cal-client/persistence.test.ts +492 -34
- package/src/cal-client/persistence.ts +112 -32
- package/src/cal-client/state-manager/api.ts +28 -4
|
@@ -7,10 +7,14 @@ import {
|
|
|
7
7
|
toTokenCurrencyRaw,
|
|
8
8
|
fromTokenCurrencyRaw,
|
|
9
9
|
extractTokensFromState,
|
|
10
|
+
extractHashesFromState,
|
|
11
|
+
extractPersistedCALFromState,
|
|
10
12
|
filterExpiredTokens,
|
|
11
13
|
restoreTokensToCache,
|
|
14
|
+
PERSISTENCE_VERSION,
|
|
12
15
|
type TokenCurrencyRaw,
|
|
13
16
|
type PersistedTokenEntry,
|
|
17
|
+
type PersistedCAL,
|
|
14
18
|
type StateWithCryptoAssets,
|
|
15
19
|
} from "./persistence";
|
|
16
20
|
import { cryptoAssetsApi } from "./state-manager/api";
|
|
@@ -215,6 +219,24 @@ describe("Token Persistence", () => {
|
|
|
215
219
|
|
|
216
220
|
expect(tokens).toEqual([]);
|
|
217
221
|
});
|
|
222
|
+
|
|
223
|
+
it("should return empty array if no RTK Query state", () => {
|
|
224
|
+
const mockState = {} as unknown as StateWithCryptoAssets;
|
|
225
|
+
|
|
226
|
+
const tokens = extractTokensFromState(mockState);
|
|
227
|
+
|
|
228
|
+
expect(tokens).toEqual([]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should return empty array if queries is undefined", () => {
|
|
232
|
+
const mockState = {
|
|
233
|
+
[cryptoAssetsApi.reducerPath]: {},
|
|
234
|
+
} as unknown as StateWithCryptoAssets;
|
|
235
|
+
|
|
236
|
+
const tokens = extractTokensFromState(mockState);
|
|
237
|
+
|
|
238
|
+
expect(tokens).toEqual([]);
|
|
239
|
+
});
|
|
218
240
|
});
|
|
219
241
|
|
|
220
242
|
describe("filterExpiredTokens", () => {
|
|
@@ -293,69 +315,505 @@ describe("Token Persistence", () => {
|
|
|
293
315
|
});
|
|
294
316
|
|
|
295
317
|
describe("restoreTokensToCache", () => {
|
|
296
|
-
it("should restore valid tokens to RTK Query cache", () => {
|
|
318
|
+
it("should restore valid tokens to RTK Query cache", async () => {
|
|
297
319
|
const mockDispatch = jest.fn();
|
|
298
320
|
const now = Date.now();
|
|
299
321
|
const ttl = 24 * 60 * 60 * 1000;
|
|
300
322
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
const persistedData: PersistedCAL = {
|
|
324
|
+
version: PERSISTENCE_VERSION,
|
|
325
|
+
tokens: [
|
|
326
|
+
{
|
|
327
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
328
|
+
timestamp: now,
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
};
|
|
307
332
|
|
|
308
|
-
restoreTokensToCache(mockDispatch,
|
|
333
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
309
334
|
|
|
310
335
|
// Should dispatch once with upsertQueryEntries (which contains both ID and address entries)
|
|
311
336
|
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
312
337
|
|
|
313
|
-
// upsertQueryEntries returns action objects, not functions
|
|
314
338
|
expect(mockDispatch).toHaveBeenCalled();
|
|
315
339
|
});
|
|
316
340
|
|
|
317
|
-
it("should skip expired tokens", () => {
|
|
341
|
+
it("should skip expired tokens", async () => {
|
|
318
342
|
const mockDispatch = jest.fn();
|
|
319
343
|
const now = Date.now();
|
|
320
344
|
const ttl = 24 * 60 * 60 * 1000;
|
|
321
345
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
346
|
+
const persistedData: PersistedCAL = {
|
|
347
|
+
version: PERSISTENCE_VERSION,
|
|
348
|
+
tokens: [
|
|
349
|
+
{
|
|
350
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
351
|
+
timestamp: now - 25 * 60 * 60 * 1000,
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
};
|
|
328
355
|
|
|
329
|
-
restoreTokensToCache(mockDispatch,
|
|
356
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
330
357
|
|
|
331
|
-
// Should not dispatch anything for expired tokens
|
|
332
358
|
expect(mockDispatch).not.toHaveBeenCalled();
|
|
333
359
|
});
|
|
334
360
|
|
|
335
|
-
it("should skip tokens with missing parent currency", () => {
|
|
361
|
+
it("should skip tokens with missing parent currency", async () => {
|
|
336
362
|
const mockDispatch = jest.fn();
|
|
337
363
|
const now = Date.now();
|
|
338
364
|
const ttl = 24 * 60 * 60 * 1000;
|
|
339
365
|
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
366
|
+
const persistedData: PersistedCAL = {
|
|
367
|
+
version: PERSISTENCE_VERSION,
|
|
368
|
+
tokens: [
|
|
369
|
+
{
|
|
370
|
+
data: {
|
|
371
|
+
id: "unknown/token/test",
|
|
372
|
+
contractAddress: "0xabc",
|
|
373
|
+
parentCurrencyId: "unknown_currency",
|
|
374
|
+
tokenType: "erc20",
|
|
375
|
+
name: "Test",
|
|
376
|
+
ticker: "TEST",
|
|
377
|
+
units: [{ name: "TEST", code: "TEST", magnitude: 18 }],
|
|
378
|
+
},
|
|
379
|
+
timestamp: now,
|
|
350
380
|
},
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
];
|
|
381
|
+
],
|
|
382
|
+
};
|
|
354
383
|
|
|
355
|
-
restoreTokensToCache(mockDispatch,
|
|
384
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
356
385
|
|
|
357
|
-
// Should not dispatch for tokens with missing parent
|
|
358
386
|
expect(mockDispatch).not.toHaveBeenCalled();
|
|
359
387
|
});
|
|
388
|
+
|
|
389
|
+
it("should restore tokens when hash matches", async () => {
|
|
390
|
+
const mockDispatch = jest.fn();
|
|
391
|
+
const now = Date.now();
|
|
392
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
393
|
+
const storedHash = "hash123";
|
|
394
|
+
|
|
395
|
+
mockDispatch.mockImplementation(async action => {
|
|
396
|
+
if (typeof action === "function") {
|
|
397
|
+
const thunkResult = await action(mockDispatch, () => ({}), undefined);
|
|
398
|
+
return thunkResult;
|
|
399
|
+
}
|
|
400
|
+
const actionStr = String(action);
|
|
401
|
+
if (
|
|
402
|
+
actionStr.includes("getTokensSyncHash") ||
|
|
403
|
+
(action as any)?.type?.includes("getTokensSyncHash")
|
|
404
|
+
) {
|
|
405
|
+
return { data: storedHash, error: undefined };
|
|
406
|
+
}
|
|
407
|
+
return action;
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const persistedData: PersistedCAL = {
|
|
411
|
+
version: PERSISTENCE_VERSION,
|
|
412
|
+
tokens: [
|
|
413
|
+
{
|
|
414
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
415
|
+
timestamp: now,
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
hashes: { ethereum: storedHash },
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
422
|
+
|
|
423
|
+
const invalidateCalls = mockDispatch.mock.calls.filter(call => {
|
|
424
|
+
const action = call[0];
|
|
425
|
+
if (!action) return false;
|
|
426
|
+
const callStr = String(action);
|
|
427
|
+
return (
|
|
428
|
+
callStr.includes("invalidateTags") || (action as any)?.type?.includes("invalidateTags")
|
|
429
|
+
);
|
|
430
|
+
});
|
|
431
|
+
expect(invalidateCalls).toHaveLength(0);
|
|
432
|
+
expect(mockDispatch).toHaveBeenCalled();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("should skip restore when hash changed", async () => {
|
|
436
|
+
const mockDispatch = jest.fn();
|
|
437
|
+
const now = Date.now();
|
|
438
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
439
|
+
const storedHash = "hash123";
|
|
440
|
+
const currentHash = "hash456";
|
|
441
|
+
const upsertActions: any[] = [];
|
|
442
|
+
|
|
443
|
+
mockDispatch.mockImplementation(async action => {
|
|
444
|
+
if (typeof action === "function") {
|
|
445
|
+
const actionStr = String(action);
|
|
446
|
+
if (actionStr.includes("getTokensSyncHash")) {
|
|
447
|
+
return { data: currentHash, error: undefined };
|
|
448
|
+
}
|
|
449
|
+
const result = await action(mockDispatch, () => ({}), undefined);
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
const actionType = (action as any)?.type || "";
|
|
453
|
+
if (actionType.includes("upsertQueryEntries") || actionType.includes("upsert")) {
|
|
454
|
+
upsertActions.push(action);
|
|
455
|
+
}
|
|
456
|
+
return action;
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const persistedData: PersistedCAL = {
|
|
460
|
+
version: PERSISTENCE_VERSION,
|
|
461
|
+
tokens: [
|
|
462
|
+
{
|
|
463
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
464
|
+
timestamp: now,
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
hashes: { ethereum: storedHash },
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
471
|
+
|
|
472
|
+
expect(upsertActions).toHaveLength(0);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("should return early when all tokens are evicted after hash validation", async () => {
|
|
476
|
+
const mockDispatch = jest.fn();
|
|
477
|
+
const now = Date.now();
|
|
478
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
479
|
+
const storedHash = "hash123";
|
|
480
|
+
const currentHash = "hash456";
|
|
481
|
+
const upsertActions: any[] = [];
|
|
482
|
+
|
|
483
|
+
mockDispatch.mockImplementation(async action => {
|
|
484
|
+
if (typeof action === "function") {
|
|
485
|
+
const actionStr = String(action);
|
|
486
|
+
if (actionStr.includes("getTokensSyncHash")) {
|
|
487
|
+
return { data: currentHash, error: undefined };
|
|
488
|
+
}
|
|
489
|
+
const result = await action(mockDispatch, () => ({}), undefined);
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
const actionType = (action as any)?.type || "";
|
|
493
|
+
if (actionType.includes("upsertQueryEntries") || actionType.includes("upsert")) {
|
|
494
|
+
upsertActions.push(action);
|
|
495
|
+
}
|
|
496
|
+
return action;
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const persistedData: PersistedCAL = {
|
|
500
|
+
version: PERSISTENCE_VERSION,
|
|
501
|
+
tokens: [
|
|
502
|
+
{
|
|
503
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
504
|
+
timestamp: now,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
hashes: { ethereum: storedHash },
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
511
|
+
|
|
512
|
+
expect(upsertActions).toHaveLength(0);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("should skip restore when hash fetch fails", async () => {
|
|
516
|
+
const mockDispatch = jest.fn();
|
|
517
|
+
const now = Date.now();
|
|
518
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
519
|
+
const storedHash = "hash123";
|
|
520
|
+
const upsertActions: any[] = [];
|
|
521
|
+
|
|
522
|
+
mockDispatch.mockImplementation(async action => {
|
|
523
|
+
if (typeof action === "function") {
|
|
524
|
+
const actionStr = String(action);
|
|
525
|
+
if (actionStr.includes("getTokensSyncHash")) {
|
|
526
|
+
throw new Error("Network error");
|
|
527
|
+
}
|
|
528
|
+
const result = await action(mockDispatch, () => ({}), undefined);
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
const actionType = (action as any)?.type || "";
|
|
532
|
+
if (actionType.includes("upsertQueryEntries") || actionType.includes("upsert")) {
|
|
533
|
+
upsertActions.push(action);
|
|
534
|
+
}
|
|
535
|
+
return action;
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const persistedData: PersistedCAL = {
|
|
539
|
+
version: PERSISTENCE_VERSION,
|
|
540
|
+
tokens: [
|
|
541
|
+
{
|
|
542
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
543
|
+
timestamp: now,
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
hashes: { ethereum: storedHash },
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
550
|
+
|
|
551
|
+
expect(upsertActions).toHaveLength(0);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it("should restore tokens when currentHash is undefined (cannot validate)", async () => {
|
|
555
|
+
const mockDispatch = jest.fn();
|
|
556
|
+
const now = Date.now();
|
|
557
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
558
|
+
const storedHash = "hash123";
|
|
559
|
+
const upsertActions: any[] = [];
|
|
560
|
+
|
|
561
|
+
mockDispatch.mockImplementation(async action => {
|
|
562
|
+
if (typeof action === "function") {
|
|
563
|
+
const actionStr = String(action);
|
|
564
|
+
if (actionStr.includes("getTokensSyncHash")) {
|
|
565
|
+
return { data: undefined, error: undefined };
|
|
566
|
+
}
|
|
567
|
+
if (actionStr.includes("upsertQueryEntries") || actionStr.includes("upsert")) {
|
|
568
|
+
upsertActions.push(action);
|
|
569
|
+
}
|
|
570
|
+
const result = await action(mockDispatch, () => ({}), undefined);
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
const actionType = (action as any)?.type || "";
|
|
574
|
+
if (actionType.includes("upsertQueryEntries") || actionType.includes("upsert")) {
|
|
575
|
+
upsertActions.push(action);
|
|
576
|
+
}
|
|
577
|
+
return action;
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const persistedData: PersistedCAL = {
|
|
581
|
+
version: PERSISTENCE_VERSION,
|
|
582
|
+
tokens: [
|
|
583
|
+
{
|
|
584
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
585
|
+
timestamp: now,
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
hashes: { ethereum: storedHash },
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
592
|
+
|
|
593
|
+
expect(upsertActions.length).toBeGreaterThan(0);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it("should restore tokens without hash (backward compatibility)", async () => {
|
|
597
|
+
const mockDispatch = jest.fn();
|
|
598
|
+
const now = Date.now();
|
|
599
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
600
|
+
|
|
601
|
+
const persistedData: PersistedCAL = {
|
|
602
|
+
version: PERSISTENCE_VERSION,
|
|
603
|
+
tokens: [
|
|
604
|
+
{
|
|
605
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
606
|
+
timestamp: now,
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
// No hashes field - old persisted data
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
613
|
+
|
|
614
|
+
const invalidateCalls = mockDispatch.mock.calls.filter(call => {
|
|
615
|
+
const action = call[0];
|
|
616
|
+
if (!action) return false;
|
|
617
|
+
const callStr = String(action);
|
|
618
|
+
return (
|
|
619
|
+
callStr.includes("invalidateTags") || (action as any)?.type?.includes("invalidateTags")
|
|
620
|
+
);
|
|
621
|
+
});
|
|
622
|
+
expect(invalidateCalls).toHaveLength(0);
|
|
623
|
+
expect(mockDispatch).toHaveBeenCalled();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it("should handle multiple currencies with different hash states", async () => {
|
|
627
|
+
const mockDispatch = jest.fn();
|
|
628
|
+
const now = Date.now();
|
|
629
|
+
const ttl = 24 * 60 * 60 * 1000;
|
|
630
|
+
let getHashCallCount = 0;
|
|
631
|
+
const upsertActions: any[] = [];
|
|
632
|
+
|
|
633
|
+
const polygonToken: TokenCurrency = {
|
|
634
|
+
...mockToken,
|
|
635
|
+
id: "polygon/erc20/usdt",
|
|
636
|
+
parentCurrency: findCryptoCurrencyById("polygon")!,
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
mockDispatch.mockImplementation(async action => {
|
|
640
|
+
if (typeof action === "function") {
|
|
641
|
+
const actionStr = String(action);
|
|
642
|
+
if (actionStr.includes("getTokensSyncHash")) {
|
|
643
|
+
getHashCallCount++;
|
|
644
|
+
if (getHashCallCount === 2) {
|
|
645
|
+
return { data: "hash999", error: undefined };
|
|
646
|
+
}
|
|
647
|
+
return { data: "hash123", error: undefined };
|
|
648
|
+
}
|
|
649
|
+
if (actionStr.includes("upsertQueryEntries") || actionStr.includes("upsert")) {
|
|
650
|
+
upsertActions.push(action);
|
|
651
|
+
}
|
|
652
|
+
const result = await action(mockDispatch, () => ({}), undefined);
|
|
653
|
+
return result;
|
|
654
|
+
}
|
|
655
|
+
const actionType = (action as any)?.type || "";
|
|
656
|
+
if (actionType.includes("upsertQueryEntries") || actionType.includes("upsert")) {
|
|
657
|
+
upsertActions.push(action);
|
|
658
|
+
}
|
|
659
|
+
return action;
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
const persistedData: PersistedCAL = {
|
|
663
|
+
version: PERSISTENCE_VERSION,
|
|
664
|
+
tokens: [
|
|
665
|
+
{
|
|
666
|
+
data: toTokenCurrencyRaw(mockToken),
|
|
667
|
+
timestamp: now,
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
data: toTokenCurrencyRaw(polygonToken),
|
|
671
|
+
timestamp: now,
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
hashes: {
|
|
675
|
+
ethereum: "hash123",
|
|
676
|
+
polygon: "hash456",
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
await restoreTokensToCache(mockDispatch, persistedData, ttl);
|
|
681
|
+
|
|
682
|
+
expect(upsertActions.length).toBeGreaterThan(0);
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
describe("extractHashesFromState", () => {
|
|
687
|
+
it("should extract hashes from getTokensSyncHash queries", () => {
|
|
688
|
+
const mockState = {
|
|
689
|
+
[cryptoAssetsApi.reducerPath]: {
|
|
690
|
+
queries: {
|
|
691
|
+
'getTokensSyncHash("ethereum")': {
|
|
692
|
+
status: "fulfilled",
|
|
693
|
+
data: "hash123",
|
|
694
|
+
endpointName: "getTokensSyncHash",
|
|
695
|
+
},
|
|
696
|
+
'getTokensSyncHash("polygon")': {
|
|
697
|
+
status: "fulfilled",
|
|
698
|
+
data: "hash456",
|
|
699
|
+
endpointName: "getTokensSyncHash",
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
} as unknown as StateWithCryptoAssets;
|
|
704
|
+
|
|
705
|
+
const hashes = extractHashesFromState(mockState);
|
|
706
|
+
|
|
707
|
+
expect(hashes).toEqual({
|
|
708
|
+
ethereum: "hash123",
|
|
709
|
+
polygon: "hash456",
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("should return empty object when no hash queries exist", () => {
|
|
714
|
+
const mockState = {
|
|
715
|
+
[cryptoAssetsApi.reducerPath]: {
|
|
716
|
+
queries: {
|
|
717
|
+
'findTokenById({"id":"ethereum/erc20/usdt"})': {
|
|
718
|
+
status: "fulfilled",
|
|
719
|
+
data: mockToken,
|
|
720
|
+
endpointName: "findTokenById",
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
} as unknown as StateWithCryptoAssets;
|
|
725
|
+
|
|
726
|
+
const hashes = extractHashesFromState(mockState);
|
|
727
|
+
|
|
728
|
+
expect(hashes).toEqual({});
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it("should return empty object if no RTK Query state", () => {
|
|
732
|
+
const mockState = {} as unknown as StateWithCryptoAssets;
|
|
733
|
+
|
|
734
|
+
const hashes = extractHashesFromState(mockState);
|
|
735
|
+
|
|
736
|
+
expect(hashes).toEqual({});
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it("should return empty object if queries is undefined", () => {
|
|
740
|
+
const mockState = {
|
|
741
|
+
[cryptoAssetsApi.reducerPath]: {},
|
|
742
|
+
} as unknown as StateWithCryptoAssets;
|
|
743
|
+
|
|
744
|
+
const hashes = extractHashesFromState(mockState);
|
|
745
|
+
|
|
746
|
+
expect(hashes).toEqual({});
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
describe("extractPersistedCALFromState", () => {
|
|
751
|
+
it("should extract complete PersistedCAL with tokens and hashes", () => {
|
|
752
|
+
const mockState = {
|
|
753
|
+
[cryptoAssetsApi.reducerPath]: {
|
|
754
|
+
queries: {
|
|
755
|
+
'findTokenById({"id":"ethereum/erc20/usdt"})': {
|
|
756
|
+
status: "fulfilled",
|
|
757
|
+
data: mockToken,
|
|
758
|
+
endpointName: "findTokenById",
|
|
759
|
+
fulfilledTimeStamp: Date.now(),
|
|
760
|
+
},
|
|
761
|
+
'getTokensSyncHash("ethereum")': {
|
|
762
|
+
status: "fulfilled",
|
|
763
|
+
data: "hash123",
|
|
764
|
+
endpointName: "getTokensSyncHash",
|
|
765
|
+
},
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
} as unknown as StateWithCryptoAssets;
|
|
769
|
+
|
|
770
|
+
const persistedData = extractPersistedCALFromState(mockState);
|
|
771
|
+
|
|
772
|
+
expect(persistedData.version).toBe(PERSISTENCE_VERSION);
|
|
773
|
+
expect(persistedData.tokens).toHaveLength(1);
|
|
774
|
+
expect(persistedData.tokens[0].data.id).toBe("ethereum/erc20/usdt");
|
|
775
|
+
expect(persistedData.hashes).toEqual({ ethereum: "hash123" });
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it("should extract PersistedCAL without hashes if none exist", () => {
|
|
779
|
+
const mockState = {
|
|
780
|
+
[cryptoAssetsApi.reducerPath]: {
|
|
781
|
+
queries: {
|
|
782
|
+
'findTokenById({"id":"ethereum/erc20/usdt"})': {
|
|
783
|
+
status: "fulfilled",
|
|
784
|
+
data: mockToken,
|
|
785
|
+
endpointName: "findTokenById",
|
|
786
|
+
fulfilledTimeStamp: Date.now(),
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
} as unknown as StateWithCryptoAssets;
|
|
791
|
+
|
|
792
|
+
const persistedData = extractPersistedCALFromState(mockState);
|
|
793
|
+
|
|
794
|
+
expect(persistedData.version).toBe(PERSISTENCE_VERSION);
|
|
795
|
+
expect(persistedData.tokens).toHaveLength(1);
|
|
796
|
+
expect(persistedData.hashes).toBeUndefined();
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it("should return empty tokens array if no tokens found", () => {
|
|
800
|
+
const mockState = {
|
|
801
|
+
[cryptoAssetsApi.reducerPath]: {
|
|
802
|
+
queries: {
|
|
803
|
+
'getTokensSyncHash("ethereum")': {
|
|
804
|
+
status: "fulfilled",
|
|
805
|
+
data: "hash123",
|
|
806
|
+
endpointName: "getTokensSyncHash",
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
} as unknown as StateWithCryptoAssets;
|
|
811
|
+
|
|
812
|
+
const persistedData = extractPersistedCALFromState(mockState);
|
|
813
|
+
|
|
814
|
+
expect(persistedData.version).toBe(PERSISTENCE_VERSION);
|
|
815
|
+
expect(persistedData.tokens).toHaveLength(0);
|
|
816
|
+
expect(persistedData.hashes).toEqual({ ethereum: "hash123" });
|
|
817
|
+
});
|
|
360
818
|
});
|
|
361
819
|
});
|