From 0cada42bbe0f13ea37f464da8c9108dc0def3b53 Mon Sep 17 00:00:00 2001 From: ravendevhub Date: Sat, 29 Aug 2026 09:28:47 +0630 Subject: [PATCH] fix(mock-client): unify mock client implementation and add pagination support (#588) - Update MOCK_HISTORY to generate 25 deterministic transaction records for multi-page pagination testing - Implement page and limit slicing in getHistory to support TransactionHistory page navigation - Scope mutable state (activeNetwork, connectedAddress) to instance closures within createMockClient to prevent cross-test contamination - Document mock strategy clearly in mock-client.ts - Add unit tests verifying pagination slicing across multiple pages and instance isolation --- src/lib/__tests__/mock-client.test.ts | 24 ++++++++++++++++++ src/lib/mock-client.test.ts | 35 +++++++++++++++++++++++++++ src/lib/mock-client.ts | 34 +++++++++++++++++--------- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/lib/__tests__/mock-client.test.ts b/src/lib/__tests__/mock-client.test.ts index 6accc86..137f345 100644 --- a/src/lib/__tests__/mock-client.test.ts +++ b/src/lib/__tests__/mock-client.test.ts @@ -124,4 +124,28 @@ describe('Mock Client - Issue #30 Fixes', () => { expect(snapshot1).toEqual(snapshot2); }); }); + + describe('Fix 4: Pagination support in getHistory (Issue #588)', () => { + it('should paginate results by page and limit with accurate total', async () => { + const client = createMockClient() as SorokitClient; + + const page1 = await client.transaction.getHistory('GADDR', 1, 10); + const page2 = await client.transaction.getHistory('GADDR', 2, 10); + const page3 = await client.transaction.getHistory('GADDR', 3, 10); + + expect(page1.data).toHaveLength(10); + expect(page2.data).toHaveLength(10); + expect(page3.data).toHaveLength(5); + + expect(page1.total).toBe(25); + expect(page2.total).toBe(25); + expect(page3.total).toBe(25); + + // Verify that page 1 and page 2 return completely different transactions + const page1Ids = page1.data?.map(t => t.id); + const page2Ids = page2.data?.map(t => t.id); + expect(page1Ids).not.toEqual(page2Ids); + }); + }); }); + diff --git a/src/lib/mock-client.test.ts b/src/lib/mock-client.test.ts index e1aec9b..646a59a 100644 --- a/src/lib/mock-client.test.ts +++ b/src/lib/mock-client.test.ts @@ -51,4 +51,39 @@ describe("mock-client", () => { expect(res.data).toBeDefined(); expect(res.data?.length).toBe(limit); }); + + it("verifies getHistory paginates correctly across multiple pages", async () => { + const { createMockClient } = await import("./mock-client"); + const client = createMockClient(); + + const page1 = await client.transaction.getHistory("address", 1, 5); + const page2 = await client.transaction.getHistory("address", 2, 5); + + expect(page1.data).toBeDefined(); + expect(page2.data).toBeDefined(); + expect(page1.data?.length).toBe(5); + expect(page2.data?.length).toBe(5); + expect(page1.total).toBe(25); + expect(page2.total).toBe(25); + + // Verify page 1 and page 2 return completely distinct transactions + const page1Hashes = page1.data?.map((tx) => tx.hash); + const page2Hashes = page2.data?.map((tx) => tx.hash); + expect(page1Hashes).not.toEqual(page2Hashes); + expect(page1Hashes?.some((h) => page2Hashes?.includes(h))).toBe(false); + }); + + it("verifies instance isolation between multiple createMockClient invocations", async () => { + const { createMockClient } = await import("./mock-client"); + const clientA = createMockClient("testnet"); + const clientB = createMockClient("public"); + + if ("network" in clientA && "network" in clientB) { + const netA = await clientA.network.getNetwork(); + const netB = await clientB.network.getNetwork(); + expect(netA.data?.name).toBe("testnet"); + expect(netB.data?.name).toBe("mainnet"); + } + }); }); + diff --git a/src/lib/mock-client.ts b/src/lib/mock-client.ts index 0ca6ce5..55b9870 100644 --- a/src/lib/mock-client.ts +++ b/src/lib/mock-client.ts @@ -20,8 +20,8 @@ import { deterministicMock } from "./deterministic-mock"; export const MOCK_ADDRESS = "GBRPYHIL2CI3WHGSUJGY6O7SROQOMJG7QBCACN4QPKUOQNXJDGONXHPA"; -// Generate deterministic mock data (consistent across test runs) -export const MOCK_HISTORY = deterministicMock.generateMockHistory(5); +// Generate deterministic mock data (consistent across test runs, 25 items to support multi-page pagination) +export const MOCK_HISTORY = deterministicMock.generateMockHistory(25); export const MOCK_EVENTS = deterministicMock.generateMockEvents(3); export const NETWORKS = { @@ -230,9 +230,17 @@ const MOCK_ALLOWANCES: AllowanceEntry[] = [ ]; /** - * Create a mock client that satisfies the SorokitClient interface. - * If called with an invalid network name, returns the error object - * (backward compatible with simple-error tests). + * Mock Strategy: + * + * Provides a canonical, standalone implementation of SorokitClient for development, + * demo mode (in main.tsx), and component/screen unit testing. + * + * Design: + * - Single source of truth for mock blockchain data across the entire repository. + * - Instance-scoped state: Each invocation of createMockClient() creates an independent + * instance with its own network and connection state, preventing test cross-contamination. + * - Proper pagination support: getHistory slices deterministic transaction records by + * page and limit, providing distinct pages and accurate total count for TransactionHistory. */ export function createMockClient(): SorokitClient; export function createMockClient( @@ -241,8 +249,9 @@ export function createMockClient( export function createMockClient( networkName?: string, ): SorokitClient | { data: null; error: string } { - const activeNetwork = + let activeNetwork = networkName && networkName in NETWORKS ? networkName : "testnet"; + let connectedAddress = MOCK_ADDRESS; if (networkName && !(networkName in NETWORKS)) { const validNetworks = Object.keys(NETWORKS).join(", "); @@ -255,12 +264,12 @@ export function createMockClient( return { wallet: { connect: async () => ({ - data: { address: MOCK_ADDRESS }, + data: { address: connectedAddress }, error: null, status: "success" as const, }), disconnect: async () => {}, - getAddress: async () => ({ data: MOCK_ADDRESS, error: null }), + getAddress: async () => ({ data: connectedAddress, error: null }), }, account: { getAccount: async () => ({ @@ -283,9 +292,12 @@ export function createMockClient( status: "success", }), getStatus: async () => ({ data: "success" as TxStatus, error: null }), - getHistory: async (_address: string, _page?: number, limit?: number) => { - const history = MOCK_HISTORY.slice(0, limit ?? MOCK_HISTORY.length); - return { data: history, error: null, total: history.length }; + getHistory: async (_address: string, page: number = 1, limit: number = 10) => { + const p = Math.max(1, page || 1); + const l = Math.max(1, limit || 10); + const start = (p - 1) * l; + const pageItems = MOCK_HISTORY.slice(start, start + l); + return { data: pageItems, error: null, total: MOCK_HISTORY.length }; }, estimateFee: async () => ({ data: { baseFee: "100", recommended: "1000" },