@partylayer/testing 1.0.0

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.
@@ -0,0 +1,146 @@
1
+ import { CIP0103Provider, CIP0103TxChangedEvent } from '@partylayer/core';
2
+ import { SessionStore, SessionStoreOptions } from '@partylayer/session';
3
+ import { ProviderRpcError, createProviderBridge } from '@partylayer/provider';
4
+
5
+ /**
6
+ * Failure scenarios for the mock CIP-0103 wallet.
7
+ *
8
+ * IMPORTANT: every scenario maps to an EXISTING code in the repo's error
9
+ * model (`@partylayer/provider` ProviderRpcError + RPC_ERRORS / JSON_RPC_ERRORS).
10
+ * No new error codes are invented here. The named presets below are a
11
+ * convenience layer over the existing convenience constructors; you can also
12
+ * pass a raw `ProviderRpcError` or a `{ code, message }` pair to model any
13
+ * other failure the provider error model already supports.
14
+ *
15
+ * Scenario → code mapping (all pre-existing codes):
16
+ * userRejected → 4001 (RPC_ERRORS.USER_REJECTED) via userRejected()
17
+ * insufficientTraffic → -32002 (JSON_RPC_ERRORS.RESOURCE_UNAVAILABLE) via resourceUnavailable()
18
+ * synchronizerError → 4901 (RPC_ERRORS.CHAIN_DISCONNECTED) via chainDisconnected()
19
+ * transactionTimeout → -32003 (JSON_RPC_ERRORS.TRANSACTION_REJECTED) via transactionRejected()
20
+ * genericError → -32603 (JSON_RPC_ERRORS.INTERNAL_ERROR) via internalError()
21
+ */
22
+
23
+ /** Built-in named failure scenarios. */
24
+ type MockScenarioName = 'userRejected' | 'insufficientTraffic' | 'synchronizerError' | 'transactionTimeout' | 'genericError';
25
+ /**
26
+ * A scenario is either a built-in name, a fully-formed `ProviderRpcError`, or
27
+ * a `{ code, message }` pair (which must use an existing numeric code).
28
+ */
29
+ type MockScenario = MockScenarioName | ProviderRpcError | {
30
+ code: number;
31
+ message: string;
32
+ };
33
+ /** All built-in scenario names (useful for table-driven tests). */
34
+ declare const MOCK_SCENARIO_NAMES: MockScenarioName[];
35
+ /** Resolve a `MockScenario` into the `ProviderRpcError` the mock will throw. */
36
+ declare function scenarioToError(scenario: MockScenario): ProviderRpcError;
37
+
38
+ /**
39
+ * Mock CIP-0103 wallet provider.
40
+ *
41
+ * `createMockWallet(config)` returns a real, CIP-0103-compliant
42
+ * `CIP0103Provider`. It is built by wrapping a configurable in-memory client
43
+ * in the repo's canonical `createProviderBridge` from `@partylayer/provider`,
44
+ * so the default/happy config passes `runCIP0103ConformanceTests` by
45
+ * construction (it IS the conformance reference implementation, just with a
46
+ * mock backend instead of a live wallet).
47
+ *
48
+ * Failure scenarios are toggled per-method (see ./scenarios). A test can make
49
+ * `connect` succeed while `submitTransaction` fails, etc.
50
+ *
51
+ * Everything here is in-memory and synchronous-by-default — no DevNet, no live
52
+ * wallet, no network. Optional per-method `delays` use `setTimeout` and are
53
+ * fake-timer friendly (see ./offline).
54
+ */
55
+
56
+ /**
57
+ * The client shape `createProviderBridge` accepts. `@partylayer/provider` does
58
+ * not export its `BridgeableClient` type publicly, so we derive it from the
59
+ * factory signature — this stays correct automatically if the bridge's
60
+ * contract changes, and contextually types the mock object literal below.
61
+ */
62
+ type MockWalletClient = Parameters<typeof createProviderBridge>[0];
63
+ /** Methods on the mock client that can carry a scenario / delay. */
64
+ type MockMethod = 'connect' | 'disconnect' | 'getActiveSession' | 'signMessage' | 'signTransaction' | 'submitTransaction' | 'ledgerApi';
65
+ interface MockWalletConfig {
66
+ /** Party id reported by the mock session/accounts. */
67
+ partyId?: string;
68
+ /** Network id the bridge maps to CAIP-2 (e.g. 'devnet'). */
69
+ network?: string;
70
+ /** Whether a session is already active before `connect()` is called. */
71
+ connected?: boolean;
72
+ /** Per-method failure scenarios. Absent ⇒ that method succeeds. */
73
+ scenarios?: Partial<Record<MockMethod, MockScenario>>;
74
+ /** Per-method artificial delay in ms (fake-timer friendly). Default 0. */
75
+ delays?: Partial<Record<MockMethod, number>>;
76
+ }
77
+ /**
78
+ * Build the underlying `BridgeableClient`. Exposed as an extension point for
79
+ * tests that want to wrap it differently or inspect it; most callers should
80
+ * use `createMockWallet` instead.
81
+ */
82
+ declare function createMockWalletClient(config?: MockWalletConfig): MockWalletClient;
83
+ /**
84
+ * Create a CIP-0103-compliant mock provider.
85
+ *
86
+ * Default config ⇒ a fully conformant happy-path provider. Pass `scenarios`
87
+ * to make specific methods fail with the repo's existing error codes.
88
+ *
89
+ * @example
90
+ * const provider = createMockWallet(); // happy path
91
+ * const provider = createMockWallet({ // connect ok, submit fails
92
+ * scenarios: { submitTransaction: 'synchronizerError' },
93
+ * });
94
+ */
95
+ declare function createMockWallet(config?: MockWalletConfig): CIP0103Provider;
96
+
97
+ /**
98
+ * Offline test helpers.
99
+ *
100
+ * These let unit/integration tests run with NO DevNet / live-wallet
101
+ * dependency. Everything is deterministic and fake-timer friendly: the mock
102
+ * wallet and lifecycle use `setTimeout` only for optional configured delays,
103
+ * so `vi.useFakeTimers()` + `vi.advanceTimersByTimeAsync()` fully control time.
104
+ *
105
+ * See ./__tests__/offline-example.test.ts for a full connect → submit →
106
+ * finalize assertion against the mock with zero network access.
107
+ */
108
+
109
+ interface TxEventRecorder {
110
+ /** All `txChanged` events captured, in emission order. */
111
+ readonly events: CIP0103TxChangedEvent[];
112
+ /** Just the `status` field of each captured event, in order. */
113
+ statuses(): CIP0103TxChangedEvent['status'][];
114
+ /** Stop recording (removes the listener). */
115
+ stop(): void;
116
+ }
117
+ /**
118
+ * Subscribe to a provider's `txChanged` stream and collect every event.
119
+ * Returns a recorder whose `events` array fills as events fire.
120
+ */
121
+ declare function recordTxEvents(provider: CIP0103Provider): TxEventRecorder;
122
+ /**
123
+ * Convenience: connect a mock provider via the CIP-0103 `connect` method.
124
+ * Returns the `CIP0103ConnectResult`-shaped response.
125
+ */
126
+ declare function connectMock(provider: CIP0103Provider): Promise<{
127
+ isConnected: boolean;
128
+ }>;
129
+ /** A fully offline mock wallet + session store, wired together. */
130
+ interface OfflineHarness {
131
+ readonly provider: CIP0103Provider;
132
+ readonly store: SessionStore;
133
+ destroy(): void;
134
+ }
135
+ /**
136
+ * Compose a mock CIP-0103 wallet and a real `@partylayer/session` store with NO
137
+ * network/DevNet. `wallet` configures the mock (failure scenarios, delays,
138
+ * party); `session` overrides store options (storage defaults to in-memory).
139
+ * For TanStack Query-inclusive composition, see `@partylayer/testing/query`.
140
+ */
141
+ declare function createOfflineHarness(config?: {
142
+ wallet?: MockWalletConfig;
143
+ session?: Partial<SessionStoreOptions>;
144
+ }): OfflineHarness;
145
+
146
+ export { MOCK_SCENARIO_NAMES as M, type OfflineHarness as O, type TxEventRecorder as T, type MockMethod as a, type MockScenario as b, type MockScenarioName as c, type MockWalletClient as d, type MockWalletConfig as e, connectMock as f, createMockWallet as g, createMockWalletClient as h, createOfflineHarness as i, recordTxEvents as r, scenarioToError as s };
@@ -0,0 +1,146 @@
1
+ import { CIP0103Provider, CIP0103TxChangedEvent } from '@partylayer/core';
2
+ import { SessionStore, SessionStoreOptions } from '@partylayer/session';
3
+ import { ProviderRpcError, createProviderBridge } from '@partylayer/provider';
4
+
5
+ /**
6
+ * Failure scenarios for the mock CIP-0103 wallet.
7
+ *
8
+ * IMPORTANT: every scenario maps to an EXISTING code in the repo's error
9
+ * model (`@partylayer/provider` ProviderRpcError + RPC_ERRORS / JSON_RPC_ERRORS).
10
+ * No new error codes are invented here. The named presets below are a
11
+ * convenience layer over the existing convenience constructors; you can also
12
+ * pass a raw `ProviderRpcError` or a `{ code, message }` pair to model any
13
+ * other failure the provider error model already supports.
14
+ *
15
+ * Scenario → code mapping (all pre-existing codes):
16
+ * userRejected → 4001 (RPC_ERRORS.USER_REJECTED) via userRejected()
17
+ * insufficientTraffic → -32002 (JSON_RPC_ERRORS.RESOURCE_UNAVAILABLE) via resourceUnavailable()
18
+ * synchronizerError → 4901 (RPC_ERRORS.CHAIN_DISCONNECTED) via chainDisconnected()
19
+ * transactionTimeout → -32003 (JSON_RPC_ERRORS.TRANSACTION_REJECTED) via transactionRejected()
20
+ * genericError → -32603 (JSON_RPC_ERRORS.INTERNAL_ERROR) via internalError()
21
+ */
22
+
23
+ /** Built-in named failure scenarios. */
24
+ type MockScenarioName = 'userRejected' | 'insufficientTraffic' | 'synchronizerError' | 'transactionTimeout' | 'genericError';
25
+ /**
26
+ * A scenario is either a built-in name, a fully-formed `ProviderRpcError`, or
27
+ * a `{ code, message }` pair (which must use an existing numeric code).
28
+ */
29
+ type MockScenario = MockScenarioName | ProviderRpcError | {
30
+ code: number;
31
+ message: string;
32
+ };
33
+ /** All built-in scenario names (useful for table-driven tests). */
34
+ declare const MOCK_SCENARIO_NAMES: MockScenarioName[];
35
+ /** Resolve a `MockScenario` into the `ProviderRpcError` the mock will throw. */
36
+ declare function scenarioToError(scenario: MockScenario): ProviderRpcError;
37
+
38
+ /**
39
+ * Mock CIP-0103 wallet provider.
40
+ *
41
+ * `createMockWallet(config)` returns a real, CIP-0103-compliant
42
+ * `CIP0103Provider`. It is built by wrapping a configurable in-memory client
43
+ * in the repo's canonical `createProviderBridge` from `@partylayer/provider`,
44
+ * so the default/happy config passes `runCIP0103ConformanceTests` by
45
+ * construction (it IS the conformance reference implementation, just with a
46
+ * mock backend instead of a live wallet).
47
+ *
48
+ * Failure scenarios are toggled per-method (see ./scenarios). A test can make
49
+ * `connect` succeed while `submitTransaction` fails, etc.
50
+ *
51
+ * Everything here is in-memory and synchronous-by-default — no DevNet, no live
52
+ * wallet, no network. Optional per-method `delays` use `setTimeout` and are
53
+ * fake-timer friendly (see ./offline).
54
+ */
55
+
56
+ /**
57
+ * The client shape `createProviderBridge` accepts. `@partylayer/provider` does
58
+ * not export its `BridgeableClient` type publicly, so we derive it from the
59
+ * factory signature — this stays correct automatically if the bridge's
60
+ * contract changes, and contextually types the mock object literal below.
61
+ */
62
+ type MockWalletClient = Parameters<typeof createProviderBridge>[0];
63
+ /** Methods on the mock client that can carry a scenario / delay. */
64
+ type MockMethod = 'connect' | 'disconnect' | 'getActiveSession' | 'signMessage' | 'signTransaction' | 'submitTransaction' | 'ledgerApi';
65
+ interface MockWalletConfig {
66
+ /** Party id reported by the mock session/accounts. */
67
+ partyId?: string;
68
+ /** Network id the bridge maps to CAIP-2 (e.g. 'devnet'). */
69
+ network?: string;
70
+ /** Whether a session is already active before `connect()` is called. */
71
+ connected?: boolean;
72
+ /** Per-method failure scenarios. Absent ⇒ that method succeeds. */
73
+ scenarios?: Partial<Record<MockMethod, MockScenario>>;
74
+ /** Per-method artificial delay in ms (fake-timer friendly). Default 0. */
75
+ delays?: Partial<Record<MockMethod, number>>;
76
+ }
77
+ /**
78
+ * Build the underlying `BridgeableClient`. Exposed as an extension point for
79
+ * tests that want to wrap it differently or inspect it; most callers should
80
+ * use `createMockWallet` instead.
81
+ */
82
+ declare function createMockWalletClient(config?: MockWalletConfig): MockWalletClient;
83
+ /**
84
+ * Create a CIP-0103-compliant mock provider.
85
+ *
86
+ * Default config ⇒ a fully conformant happy-path provider. Pass `scenarios`
87
+ * to make specific methods fail with the repo's existing error codes.
88
+ *
89
+ * @example
90
+ * const provider = createMockWallet(); // happy path
91
+ * const provider = createMockWallet({ // connect ok, submit fails
92
+ * scenarios: { submitTransaction: 'synchronizerError' },
93
+ * });
94
+ */
95
+ declare function createMockWallet(config?: MockWalletConfig): CIP0103Provider;
96
+
97
+ /**
98
+ * Offline test helpers.
99
+ *
100
+ * These let unit/integration tests run with NO DevNet / live-wallet
101
+ * dependency. Everything is deterministic and fake-timer friendly: the mock
102
+ * wallet and lifecycle use `setTimeout` only for optional configured delays,
103
+ * so `vi.useFakeTimers()` + `vi.advanceTimersByTimeAsync()` fully control time.
104
+ *
105
+ * See ./__tests__/offline-example.test.ts for a full connect → submit →
106
+ * finalize assertion against the mock with zero network access.
107
+ */
108
+
109
+ interface TxEventRecorder {
110
+ /** All `txChanged` events captured, in emission order. */
111
+ readonly events: CIP0103TxChangedEvent[];
112
+ /** Just the `status` field of each captured event, in order. */
113
+ statuses(): CIP0103TxChangedEvent['status'][];
114
+ /** Stop recording (removes the listener). */
115
+ stop(): void;
116
+ }
117
+ /**
118
+ * Subscribe to a provider's `txChanged` stream and collect every event.
119
+ * Returns a recorder whose `events` array fills as events fire.
120
+ */
121
+ declare function recordTxEvents(provider: CIP0103Provider): TxEventRecorder;
122
+ /**
123
+ * Convenience: connect a mock provider via the CIP-0103 `connect` method.
124
+ * Returns the `CIP0103ConnectResult`-shaped response.
125
+ */
126
+ declare function connectMock(provider: CIP0103Provider): Promise<{
127
+ isConnected: boolean;
128
+ }>;
129
+ /** A fully offline mock wallet + session store, wired together. */
130
+ interface OfflineHarness {
131
+ readonly provider: CIP0103Provider;
132
+ readonly store: SessionStore;
133
+ destroy(): void;
134
+ }
135
+ /**
136
+ * Compose a mock CIP-0103 wallet and a real `@partylayer/session` store with NO
137
+ * network/DevNet. `wallet` configures the mock (failure scenarios, delays,
138
+ * party); `session` overrides store options (storage defaults to in-memory).
139
+ * For TanStack Query-inclusive composition, see `@partylayer/testing/query`.
140
+ */
141
+ declare function createOfflineHarness(config?: {
142
+ wallet?: MockWalletConfig;
143
+ session?: Partial<SessionStoreOptions>;
144
+ }): OfflineHarness;
145
+
146
+ export { MOCK_SCENARIO_NAMES as M, type OfflineHarness as O, type TxEventRecorder as T, type MockMethod as a, type MockScenario as b, type MockScenarioName as c, type MockWalletClient as d, type MockWalletConfig as e, connectMock as f, createMockWallet as g, createMockWalletClient as h, createOfflineHarness as i, recordTxEvents as r, scenarioToError as s };
@@ -0,0 +1,53 @@
1
+ import { QueryClient, QueryClientConfig, QueryKey } from '@tanstack/query-core';
2
+ import { O as OfflineHarness, e as MockWalletConfig } from './offline-CELeTEq9.mjs';
3
+ import { SessionStoreOptions } from '@partylayer/session';
4
+ import '@partylayer/core';
5
+ import '@partylayer/provider';
6
+
7
+ /**
8
+ * TanStack Query test utilities — `@partylayer/testing/query`.
9
+ *
10
+ * Subpath entry so the main package stays dependency-free for non-Query
11
+ * consumers. `@tanstack/query-core` is an OPTIONAL peer; install it only if you
12
+ * import from here.
13
+ */
14
+
15
+ /** A QueryClient with test-friendly defaults (no retries, no caching window). */
16
+ declare function createTestQueryClient(overrides?: QueryClientConfig): QueryClient;
17
+ interface QueryStateView<T> {
18
+ readonly data: T | undefined;
19
+ readonly status: 'pending' | 'error' | 'success';
20
+ readonly fetchStatus: 'fetching' | 'paused' | 'idle';
21
+ readonly isInvalidated: boolean;
22
+ }
23
+ /** Read a query's current cache state (data + status + invalidation flag). */
24
+ declare function getQueryState<T>(client: QueryClient, key: QueryKey): QueryStateView<T>;
25
+ /** True when the query at `key` is currently marked invalidated. */
26
+ declare function expectInvalidated(client: QueryClient, key: QueryKey): boolean;
27
+ interface OptimisticRollback<T> {
28
+ /** The cache value captured before the optimistic write. */
29
+ readonly snapshot: T | undefined;
30
+ /** Apply an optimistic value to the cache. */
31
+ apply(next: T): void;
32
+ /** Restore the captured snapshot (rollback). */
33
+ rollback(): void;
34
+ /** Current cache value at the key. */
35
+ current(): T | undefined;
36
+ }
37
+ /**
38
+ * Capture a key's value, then drive an optimistic apply → rollback so a test can
39
+ * assert both the optimistic write and that rollback restored the snapshot.
40
+ */
41
+ declare function trackOptimisticRollback<T>(client: QueryClient, key: QueryKey): OptimisticRollback<T>;
42
+ /** Offline harness (mock wallet + session store) plus a test QueryClient. */
43
+ interface QueryHarness extends OfflineHarness {
44
+ readonly queryClient: QueryClient;
45
+ }
46
+ /** Query-inclusive composition of {@link createOfflineHarness}. */
47
+ declare function createQueryHarness(config?: {
48
+ wallet?: MockWalletConfig;
49
+ session?: Partial<SessionStoreOptions>;
50
+ query?: QueryClientConfig;
51
+ }): QueryHarness;
52
+
53
+ export { type OptimisticRollback, type QueryHarness, type QueryStateView, createQueryHarness, createTestQueryClient, expectInvalidated, getQueryState, trackOptimisticRollback };
@@ -0,0 +1,53 @@
1
+ import { QueryClient, QueryClientConfig, QueryKey } from '@tanstack/query-core';
2
+ import { O as OfflineHarness, e as MockWalletConfig } from './offline-CELeTEq9.js';
3
+ import { SessionStoreOptions } from '@partylayer/session';
4
+ import '@partylayer/core';
5
+ import '@partylayer/provider';
6
+
7
+ /**
8
+ * TanStack Query test utilities — `@partylayer/testing/query`.
9
+ *
10
+ * Subpath entry so the main package stays dependency-free for non-Query
11
+ * consumers. `@tanstack/query-core` is an OPTIONAL peer; install it only if you
12
+ * import from here.
13
+ */
14
+
15
+ /** A QueryClient with test-friendly defaults (no retries, no caching window). */
16
+ declare function createTestQueryClient(overrides?: QueryClientConfig): QueryClient;
17
+ interface QueryStateView<T> {
18
+ readonly data: T | undefined;
19
+ readonly status: 'pending' | 'error' | 'success';
20
+ readonly fetchStatus: 'fetching' | 'paused' | 'idle';
21
+ readonly isInvalidated: boolean;
22
+ }
23
+ /** Read a query's current cache state (data + status + invalidation flag). */
24
+ declare function getQueryState<T>(client: QueryClient, key: QueryKey): QueryStateView<T>;
25
+ /** True when the query at `key` is currently marked invalidated. */
26
+ declare function expectInvalidated(client: QueryClient, key: QueryKey): boolean;
27
+ interface OptimisticRollback<T> {
28
+ /** The cache value captured before the optimistic write. */
29
+ readonly snapshot: T | undefined;
30
+ /** Apply an optimistic value to the cache. */
31
+ apply(next: T): void;
32
+ /** Restore the captured snapshot (rollback). */
33
+ rollback(): void;
34
+ /** Current cache value at the key. */
35
+ current(): T | undefined;
36
+ }
37
+ /**
38
+ * Capture a key's value, then drive an optimistic apply → rollback so a test can
39
+ * assert both the optimistic write and that rollback restored the snapshot.
40
+ */
41
+ declare function trackOptimisticRollback<T>(client: QueryClient, key: QueryKey): OptimisticRollback<T>;
42
+ /** Offline harness (mock wallet + session store) plus a test QueryClient. */
43
+ interface QueryHarness extends OfflineHarness {
44
+ readonly queryClient: QueryClient;
45
+ }
46
+ /** Query-inclusive composition of {@link createOfflineHarness}. */
47
+ declare function createQueryHarness(config?: {
48
+ wallet?: MockWalletConfig;
49
+ session?: Partial<SessionStoreOptions>;
50
+ query?: QueryClientConfig;
51
+ }): QueryHarness;
52
+
53
+ export { type OptimisticRollback, type QueryHarness, type QueryStateView, createQueryHarness, createTestQueryClient, expectInvalidated, getQueryState, trackOptimisticRollback };
package/dist/query.js ADDED
@@ -0,0 +1,206 @@
1
+ 'use strict';
2
+
3
+ var queryCore = require('@tanstack/query-core');
4
+ require('@partylayer/core');
5
+ var session = require('@partylayer/session');
6
+ var provider = require('@partylayer/provider');
7
+
8
+ // src/query.ts
9
+ var PRESETS = {
10
+ userRejected: () => provider.userRejected("User rejected the request"),
11
+ insufficientTraffic: () => provider.resourceUnavailable("Insufficient traffic credits to submit the transaction"),
12
+ synchronizerError: () => provider.chainDisconnected("Synchronizer error"),
13
+ transactionTimeout: () => provider.transactionRejected("Transaction timed out"),
14
+ genericError: () => provider.internalError("RPC handler error")
15
+ };
16
+ function scenarioToError(scenario) {
17
+ if (scenario instanceof provider.ProviderRpcError) return scenario;
18
+ if (typeof scenario === "string") return PRESETS[scenario]();
19
+ return new provider.ProviderRpcError(scenario.message, scenario.code);
20
+ }
21
+
22
+ // src/mock-wallet.ts
23
+ var DEFAULT_PARTY = "party::mock-1";
24
+ var DEFAULT_NETWORK = "devnet";
25
+ function wait(ms) {
26
+ return new Promise((resolve) => setTimeout(resolve, ms));
27
+ }
28
+ function createMockWalletClient(config = {}) {
29
+ const partyId = config.partyId ?? DEFAULT_PARTY;
30
+ const network = config.network ?? DEFAULT_NETWORK;
31
+ let connected = config.connected ?? false;
32
+ const handlers = /* @__PURE__ */ new Map();
33
+ function makeSession() {
34
+ return {
35
+ sessionId: "sess-mock-1",
36
+ walletId: "mock",
37
+ partyId,
38
+ network,
39
+ expiresAt: Number.MAX_SAFE_INTEGER,
40
+ capabilitiesSnapshot: [
41
+ "connect",
42
+ "signMessage",
43
+ "signTransaction",
44
+ "submitTransaction",
45
+ "ledgerApi"
46
+ ]
47
+ };
48
+ }
49
+ async function gate(method) {
50
+ const delay = config.delays?.[method];
51
+ if (delay && delay > 0) await wait(delay);
52
+ const scenario = config.scenarios?.[method];
53
+ if (scenario) throw scenarioToError(scenario);
54
+ }
55
+ function fire(event, payload) {
56
+ handlers.get(event)?.forEach((handler) => handler(payload));
57
+ }
58
+ return {
59
+ async connect() {
60
+ await gate("connect");
61
+ connected = true;
62
+ const session = makeSession();
63
+ fire("session:connected", { type: "session:connected", session });
64
+ return session;
65
+ },
66
+ async disconnect() {
67
+ await gate("disconnect");
68
+ connected = false;
69
+ fire("session:disconnected", { type: "session:disconnected" });
70
+ },
71
+ async getActiveSession() {
72
+ await gate("getActiveSession");
73
+ return connected ? makeSession() : null;
74
+ },
75
+ async signMessage() {
76
+ await gate("signMessage");
77
+ return { signature: "mock-signature" };
78
+ },
79
+ async signTransaction() {
80
+ await gate("signTransaction");
81
+ return {
82
+ transactionHash: "mock-tx-hash",
83
+ signedTx: { data: "mock-signed-payload" },
84
+ partyId
85
+ };
86
+ },
87
+ async submitTransaction() {
88
+ await gate("submitTransaction");
89
+ return {
90
+ transactionHash: "mock-tx-hash",
91
+ submittedAt: 0,
92
+ commandId: "mock-command-1",
93
+ updateId: "mock-update-1"
94
+ };
95
+ },
96
+ async ledgerApi(params) {
97
+ await gate("ledgerApi");
98
+ return {
99
+ response: JSON.stringify({
100
+ requestMethod: params.requestMethod,
101
+ resource: params.resource
102
+ })
103
+ };
104
+ },
105
+ getRegistryStatus() {
106
+ return null;
107
+ },
108
+ on(event, handler) {
109
+ let set = handlers.get(event);
110
+ if (!set) {
111
+ set = /* @__PURE__ */ new Set();
112
+ handlers.set(event, set);
113
+ }
114
+ set.add(handler);
115
+ return () => {
116
+ handlers.get(event)?.delete(handler);
117
+ };
118
+ }
119
+ };
120
+ }
121
+ function createMockWallet(config = {}) {
122
+ return provider.createProviderBridge(createMockWalletClient(config));
123
+ }
124
+
125
+ // src/offline.ts
126
+ function createOfflineHarness(config = {}) {
127
+ const provider = createMockWallet(config.wallet);
128
+ const store = session.createSessionStore(provider, {
129
+ storage: session.createMemoryStorage(),
130
+ ...config.session
131
+ });
132
+ return {
133
+ provider,
134
+ store,
135
+ destroy() {
136
+ store.destroy();
137
+ }
138
+ };
139
+ }
140
+
141
+ // src/query.ts
142
+ function createTestQueryClient(overrides) {
143
+ return new queryCore.QueryClient({
144
+ ...overrides,
145
+ defaultOptions: {
146
+ ...overrides?.defaultOptions,
147
+ queries: {
148
+ retry: false,
149
+ gcTime: 0,
150
+ staleTime: 0,
151
+ ...overrides?.defaultOptions?.queries
152
+ },
153
+ mutations: {
154
+ retry: false,
155
+ ...overrides?.defaultOptions?.mutations
156
+ }
157
+ }
158
+ });
159
+ }
160
+ function getQueryState(client, key) {
161
+ const state = client.getQueryState(key);
162
+ return {
163
+ data: state?.data,
164
+ status: state?.status ?? "pending",
165
+ fetchStatus: state?.fetchStatus ?? "idle",
166
+ isInvalidated: state?.isInvalidated ?? false
167
+ };
168
+ }
169
+ function expectInvalidated(client, key) {
170
+ return client.getQueryState(key)?.isInvalidated === true;
171
+ }
172
+ function trackOptimisticRollback(client, key) {
173
+ const snapshot = client.getQueryData(key);
174
+ return {
175
+ snapshot,
176
+ apply(next) {
177
+ client.setQueryData(key, next);
178
+ },
179
+ rollback() {
180
+ client.setQueryData(key, snapshot);
181
+ },
182
+ current() {
183
+ return client.getQueryData(key);
184
+ }
185
+ };
186
+ }
187
+ function createQueryHarness(config = {}) {
188
+ const base = createOfflineHarness({ wallet: config.wallet, session: config.session });
189
+ const queryClient = createTestQueryClient(config.query);
190
+ return {
191
+ ...base,
192
+ queryClient,
193
+ destroy() {
194
+ queryClient.clear();
195
+ base.destroy();
196
+ }
197
+ };
198
+ }
199
+
200
+ exports.createQueryHarness = createQueryHarness;
201
+ exports.createTestQueryClient = createTestQueryClient;
202
+ exports.expectInvalidated = expectInvalidated;
203
+ exports.getQueryState = getQueryState;
204
+ exports.trackOptimisticRollback = trackOptimisticRollback;
205
+ //# sourceMappingURL=query.js.map
206
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scenarios.ts","../src/mock-wallet.ts","../src/offline.ts","../src/query.ts"],"names":["userRejected","resourceUnavailable","chainDisconnected","transactionRejected","internalError","ProviderRpcError","createProviderBridge","createSessionStore","createMemoryStorage","QueryClient"],"mappings":"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","file":"query.js","sourcesContent":["/**\n * Failure scenarios for the mock CIP-0103 wallet.\n *\n * IMPORTANT: every scenario maps to an EXISTING code in the repo's error\n * model (`@partylayer/provider` ProviderRpcError + RPC_ERRORS / JSON_RPC_ERRORS).\n * No new error codes are invented here. The named presets below are a\n * convenience layer over the existing convenience constructors; you can also\n * pass a raw `ProviderRpcError` or a `{ code, message }` pair to model any\n * other failure the provider error model already supports.\n *\n * Scenario → code mapping (all pre-existing codes):\n * userRejected → 4001 (RPC_ERRORS.USER_REJECTED) via userRejected()\n * insufficientTraffic → -32002 (JSON_RPC_ERRORS.RESOURCE_UNAVAILABLE) via resourceUnavailable()\n * synchronizerError → 4901 (RPC_ERRORS.CHAIN_DISCONNECTED) via chainDisconnected()\n * transactionTimeout → -32003 (JSON_RPC_ERRORS.TRANSACTION_REJECTED) via transactionRejected()\n * genericError → -32603 (JSON_RPC_ERRORS.INTERNAL_ERROR) via internalError()\n */\n\nimport {\n ProviderRpcError,\n chainDisconnected,\n internalError,\n resourceUnavailable,\n transactionRejected,\n userRejected,\n} from '@partylayer/provider';\n\n/** Built-in named failure scenarios. */\nexport type MockScenarioName =\n | 'userRejected'\n | 'insufficientTraffic'\n | 'synchronizerError'\n | 'transactionTimeout'\n | 'genericError';\n\n/**\n * A scenario is either a built-in name, a fully-formed `ProviderRpcError`, or\n * a `{ code, message }` pair (which must use an existing numeric code).\n */\nexport type MockScenario =\n | MockScenarioName\n | ProviderRpcError\n | { code: number; message: string };\n\nconst PRESETS: Record<MockScenarioName, () => ProviderRpcError> = {\n userRejected: () => userRejected('User rejected the request'),\n insufficientTraffic: () =>\n resourceUnavailable('Insufficient traffic credits to submit the transaction'),\n synchronizerError: () => chainDisconnected('Synchronizer error'),\n transactionTimeout: () => transactionRejected('Transaction timed out'),\n genericError: () => internalError('RPC handler error'),\n};\n\n/** All built-in scenario names (useful for table-driven tests). */\nexport const MOCK_SCENARIO_NAMES = Object.keys(PRESETS) as MockScenarioName[];\n\n/** Resolve a `MockScenario` into the `ProviderRpcError` the mock will throw. */\nexport function scenarioToError(scenario: MockScenario): ProviderRpcError {\n if (scenario instanceof ProviderRpcError) return scenario;\n if (typeof scenario === 'string') return PRESETS[scenario]();\n return new ProviderRpcError(scenario.message, scenario.code);\n}\n","/**\n * Mock CIP-0103 wallet provider.\n *\n * `createMockWallet(config)` returns a real, CIP-0103-compliant\n * `CIP0103Provider`. It is built by wrapping a configurable in-memory client\n * in the repo's canonical `createProviderBridge` from `@partylayer/provider`,\n * so the default/happy config passes `runCIP0103ConformanceTests` by\n * construction (it IS the conformance reference implementation, just with a\n * mock backend instead of a live wallet).\n *\n * Failure scenarios are toggled per-method (see ./scenarios). A test can make\n * `connect` succeed while `submitTransaction` fails, etc.\n *\n * Everything here is in-memory and synchronous-by-default — no DevNet, no live\n * wallet, no network. Optional per-method `delays` use `setTimeout` and are\n * fake-timer friendly (see ./offline).\n */\n\nimport type { CIP0103Provider } from '@partylayer/core';\nimport { createProviderBridge } from '@partylayer/provider';\nimport { scenarioToError, type MockScenario } from './scenarios';\n\n/**\n * The client shape `createProviderBridge` accepts. `@partylayer/provider` does\n * not export its `BridgeableClient` type publicly, so we derive it from the\n * factory signature — this stays correct automatically if the bridge's\n * contract changes, and contextually types the mock object literal below.\n */\nexport type MockWalletClient = Parameters<typeof createProviderBridge>[0];\n\n/** Methods on the mock client that can carry a scenario / delay. */\nexport type MockMethod =\n | 'connect'\n | 'disconnect'\n | 'getActiveSession'\n | 'signMessage'\n | 'signTransaction'\n | 'submitTransaction'\n | 'ledgerApi';\n\nexport interface MockWalletConfig {\n /** Party id reported by the mock session/accounts. */\n partyId?: string;\n /** Network id the bridge maps to CAIP-2 (e.g. 'devnet'). */\n network?: string;\n /** Whether a session is already active before `connect()` is called. */\n connected?: boolean;\n /** Per-method failure scenarios. Absent ⇒ that method succeeds. */\n scenarios?: Partial<Record<MockMethod, MockScenario>>;\n /** Per-method artificial delay in ms (fake-timer friendly). Default 0. */\n delays?: Partial<Record<MockMethod, number>>;\n}\n\nconst DEFAULT_PARTY = 'party::mock-1';\nconst DEFAULT_NETWORK = 'devnet';\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Build the underlying `BridgeableClient`. Exposed as an extension point for\n * tests that want to wrap it differently or inspect it; most callers should\n * use `createMockWallet` instead.\n */\nexport function createMockWalletClient(config: MockWalletConfig = {}): MockWalletClient {\n const partyId = config.partyId ?? DEFAULT_PARTY;\n const network = config.network ?? DEFAULT_NETWORK;\n let connected = config.connected ?? false;\n\n const handlers = new Map<string, Set<(event: unknown) => void>>();\n\n function makeSession() {\n return {\n sessionId: 'sess-mock-1',\n walletId: 'mock',\n partyId,\n network,\n expiresAt: Number.MAX_SAFE_INTEGER,\n capabilitiesSnapshot: [\n 'connect',\n 'signMessage',\n 'signTransaction',\n 'submitTransaction',\n 'ledgerApi',\n ],\n };\n }\n\n /** Apply the configured delay, then throw the configured scenario (if any). */\n async function gate(method: MockMethod): Promise<void> {\n const delay = config.delays?.[method];\n if (delay && delay > 0) await wait(delay);\n const scenario = config.scenarios?.[method];\n if (scenario) throw scenarioToError(scenario);\n }\n\n function fire(event: string, payload: unknown): void {\n handlers.get(event)?.forEach((handler) => handler(payload));\n }\n\n return {\n async connect() {\n await gate('connect');\n connected = true;\n const session = makeSession();\n fire('session:connected', { type: 'session:connected', session });\n return session;\n },\n async disconnect() {\n await gate('disconnect');\n connected = false;\n fire('session:disconnected', { type: 'session:disconnected' });\n },\n async getActiveSession() {\n await gate('getActiveSession');\n return connected ? makeSession() : null;\n },\n async signMessage() {\n await gate('signMessage');\n return { signature: 'mock-signature' };\n },\n async signTransaction() {\n await gate('signTransaction');\n return {\n transactionHash: 'mock-tx-hash',\n signedTx: { data: 'mock-signed-payload' },\n partyId,\n };\n },\n async submitTransaction() {\n await gate('submitTransaction');\n return {\n transactionHash: 'mock-tx-hash',\n submittedAt: 0,\n commandId: 'mock-command-1',\n updateId: 'mock-update-1',\n };\n },\n async ledgerApi(params) {\n await gate('ledgerApi');\n return {\n response: JSON.stringify({\n requestMethod: params.requestMethod,\n resource: params.resource,\n }),\n };\n },\n getRegistryStatus() {\n return null;\n },\n on(event, handler) {\n let set = handlers.get(event);\n if (!set) {\n set = new Set();\n handlers.set(event, set);\n }\n set.add(handler as (event: unknown) => void);\n return () => {\n handlers.get(event)?.delete(handler as (event: unknown) => void);\n };\n },\n };\n}\n\n/**\n * Create a CIP-0103-compliant mock provider.\n *\n * Default config ⇒ a fully conformant happy-path provider. Pass `scenarios`\n * to make specific methods fail with the repo's existing error codes.\n *\n * @example\n * const provider = createMockWallet(); // happy path\n * const provider = createMockWallet({ // connect ok, submit fails\n * scenarios: { submitTransaction: 'synchronizerError' },\n * });\n */\nexport function createMockWallet(config: MockWalletConfig = {}): CIP0103Provider {\n return createProviderBridge(createMockWalletClient(config));\n}\n","/**\n * Offline test helpers.\n *\n * These let unit/integration tests run with NO DevNet / live-wallet\n * dependency. Everything is deterministic and fake-timer friendly: the mock\n * wallet and lifecycle use `setTimeout` only for optional configured delays,\n * so `vi.useFakeTimers()` + `vi.advanceTimersByTimeAsync()` fully control time.\n *\n * See ./__tests__/offline-example.test.ts for a full connect → submit →\n * finalize assertion against the mock with zero network access.\n */\n\nimport { CIP0103_EVENTS } from '@partylayer/core';\nimport type { CIP0103Provider, CIP0103TxChangedEvent } from '@partylayer/core';\nimport {\n createSessionStore,\n createMemoryStorage,\n type SessionStore,\n type SessionStoreOptions,\n} from '@partylayer/session';\nimport { createMockWallet, type MockWalletConfig } from './mock-wallet';\n\nexport interface TxEventRecorder {\n /** All `txChanged` events captured, in emission order. */\n readonly events: CIP0103TxChangedEvent[];\n /** Just the `status` field of each captured event, in order. */\n statuses(): CIP0103TxChangedEvent['status'][];\n /** Stop recording (removes the listener). */\n stop(): void;\n}\n\n/**\n * Subscribe to a provider's `txChanged` stream and collect every event.\n * Returns a recorder whose `events` array fills as events fire.\n */\nexport function recordTxEvents(provider: CIP0103Provider): TxEventRecorder {\n const events: CIP0103TxChangedEvent[] = [];\n const listener = (event: CIP0103TxChangedEvent): void => {\n events.push(event);\n };\n provider.on(CIP0103_EVENTS.TX_CHANGED, listener);\n return {\n events,\n statuses() {\n return events.map((e) => e.status);\n },\n stop() {\n provider.removeListener(CIP0103_EVENTS.TX_CHANGED, listener);\n },\n };\n}\n\n/**\n * Convenience: connect a mock provider via the CIP-0103 `connect` method.\n * Returns the `CIP0103ConnectResult`-shaped response.\n */\nexport async function connectMock(\n provider: CIP0103Provider,\n): Promise<{ isConnected: boolean }> {\n return provider.request<{ isConnected: boolean }>({ method: 'connect' });\n}\n\n/** A fully offline mock wallet + session store, wired together. */\nexport interface OfflineHarness {\n readonly provider: CIP0103Provider;\n readonly store: SessionStore;\n destroy(): void;\n}\n\n/**\n * Compose a mock CIP-0103 wallet and a real `@partylayer/session` store with NO\n * network/DevNet. `wallet` configures the mock (failure scenarios, delays,\n * party); `session` overrides store options (storage defaults to in-memory).\n * For TanStack Query-inclusive composition, see `@partylayer/testing/query`.\n */\nexport function createOfflineHarness(\n config: { wallet?: MockWalletConfig; session?: Partial<SessionStoreOptions> } = {},\n): OfflineHarness {\n const provider = createMockWallet(config.wallet);\n const store = createSessionStore(provider, {\n storage: createMemoryStorage(),\n ...config.session,\n });\n return {\n provider,\n store,\n destroy() {\n store.destroy();\n },\n };\n}\n","/**\n * TanStack Query test utilities — `@partylayer/testing/query`.\n *\n * Subpath entry so the main package stays dependency-free for non-Query\n * consumers. `@tanstack/query-core` is an OPTIONAL peer; install it only if you\n * import from here.\n */\nimport { QueryClient, type QueryClientConfig, type QueryKey } from '@tanstack/query-core';\nimport {\n createOfflineHarness,\n type OfflineHarness,\n} from './offline';\nimport type { MockWalletConfig } from './mock-wallet';\nimport type { SessionStoreOptions } from '@partylayer/session';\n\n/** A QueryClient with test-friendly defaults (no retries, no caching window). */\nexport function createTestQueryClient(overrides?: QueryClientConfig): QueryClient {\n return new QueryClient({\n ...overrides,\n defaultOptions: {\n ...overrides?.defaultOptions,\n queries: {\n retry: false,\n gcTime: 0,\n staleTime: 0,\n ...overrides?.defaultOptions?.queries,\n },\n mutations: {\n retry: false,\n ...overrides?.defaultOptions?.mutations,\n },\n },\n });\n}\n\nexport interface QueryStateView<T> {\n readonly data: T | undefined;\n readonly status: 'pending' | 'error' | 'success';\n readonly fetchStatus: 'fetching' | 'paused' | 'idle';\n readonly isInvalidated: boolean;\n}\n\n/** Read a query's current cache state (data + status + invalidation flag). */\nexport function getQueryState<T>(client: QueryClient, key: QueryKey): QueryStateView<T> {\n const state = client.getQueryState<T>(key);\n return {\n data: state?.data,\n status: state?.status ?? 'pending',\n fetchStatus: state?.fetchStatus ?? 'idle',\n isInvalidated: state?.isInvalidated ?? false,\n };\n}\n\n/** True when the query at `key` is currently marked invalidated. */\nexport function expectInvalidated(client: QueryClient, key: QueryKey): boolean {\n return client.getQueryState(key)?.isInvalidated === true;\n}\n\nexport interface OptimisticRollback<T> {\n /** The cache value captured before the optimistic write. */\n readonly snapshot: T | undefined;\n /** Apply an optimistic value to the cache. */\n apply(next: T): void;\n /** Restore the captured snapshot (rollback). */\n rollback(): void;\n /** Current cache value at the key. */\n current(): T | undefined;\n}\n\n/**\n * Capture a key's value, then drive an optimistic apply → rollback so a test can\n * assert both the optimistic write and that rollback restored the snapshot.\n */\nexport function trackOptimisticRollback<T>(client: QueryClient, key: QueryKey): OptimisticRollback<T> {\n const snapshot = client.getQueryData<T>(key);\n return {\n snapshot,\n apply(next: T) {\n client.setQueryData<T>(key, next);\n },\n rollback() {\n client.setQueryData<T>(key, snapshot);\n },\n current() {\n return client.getQueryData<T>(key);\n },\n };\n}\n\n/** Offline harness (mock wallet + session store) plus a test QueryClient. */\nexport interface QueryHarness extends OfflineHarness {\n readonly queryClient: QueryClient;\n}\n\n/** Query-inclusive composition of {@link createOfflineHarness}. */\nexport function createQueryHarness(\n config: {\n wallet?: MockWalletConfig;\n session?: Partial<SessionStoreOptions>;\n query?: QueryClientConfig;\n } = {},\n): QueryHarness {\n const base = createOfflineHarness({ wallet: config.wallet, session: config.session });\n const queryClient = createTestQueryClient(config.query);\n return {\n ...base,\n queryClient,\n destroy() {\n queryClient.clear();\n base.destroy();\n },\n };\n}\n"]}