@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.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/index.d.mts +177 -0
- package/dist/index.d.ts +177 -0
- package/dist/index.js +481 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +467 -0
- package/dist/index.mjs.map +1 -0
- package/dist/offline-CELeTEq9.d.mts +146 -0
- package/dist/offline-CELeTEq9.d.ts +146 -0
- package/dist/query.d.mts +53 -0
- package/dist/query.d.ts +53 -0
- package/dist/query.js +206 -0
- package/dist/query.js.map +1 -0
- package/dist/query.mjs +200 -0
- package/dist/query.mjs.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PartyLayer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @partylayer/testing
|
|
2
|
+
|
|
3
|
+
Offline test foundation for PartyLayer: a **mock CIP-0103 wallet provider** with
|
|
4
|
+
configurable failure scenarios, a **controllable transaction lifecycle**, a
|
|
5
|
+
**session-lifecycle harness** over the real `@partylayer/session` store, **TanStack
|
|
6
|
+
Query** test utilities, and **browser/e2e primitives** — so unit, integration, and
|
|
7
|
+
real-browser tests run with no DevNet or live-wallet dependency.
|
|
8
|
+
|
|
9
|
+
The TanStack Query utilities live in the `@partylayer/testing/query` subpath
|
|
10
|
+
(`@tanstack/query-core` is an optional peer) so the main entry stays
|
|
11
|
+
dependency-free for non-Query consumers.
|
|
12
|
+
|
|
13
|
+
## A. Mock CIP-0103 wallet — `createMockWallet(config?)`
|
|
14
|
+
|
|
15
|
+
Returns a real `CIP0103Provider`, built by wrapping a configurable in-memory
|
|
16
|
+
client in the repo's canonical `createProviderBridge`. **The default/happy
|
|
17
|
+
config passes `runCIP0103ConformanceTests` by construction** (it is the
|
|
18
|
+
conformance reference implementation with a mock backend).
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createMockWallet } from '@partylayer/testing';
|
|
22
|
+
|
|
23
|
+
const provider = createMockWallet(); // happy path
|
|
24
|
+
await provider.request({ method: 'connect' }); // { isConnected: true }
|
|
25
|
+
|
|
26
|
+
// connect succeeds but submission fails:
|
|
27
|
+
const flaky = createMockWallet({ scenarios: { submitTransaction: 'synchronizerError' } });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Failure scenarios (per-method, existing error codes only)
|
|
31
|
+
|
|
32
|
+
Scenarios are toggled per method. Every named scenario maps to a code that
|
|
33
|
+
**already exists** in `@partylayer/provider`'s error model — no new codes are
|
|
34
|
+
invented. You may also pass a raw `ProviderRpcError` or a `{ code, message }`.
|
|
35
|
+
|
|
36
|
+
| scenario name | code | constructor |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `userRejected` | `4001` (USER_REJECTED) | `userRejected()` |
|
|
39
|
+
| `insufficientTraffic` | `-32002` (RESOURCE_UNAVAILABLE) | `resourceUnavailable()` |
|
|
40
|
+
| `synchronizerError` | `4901` (CHAIN_DISCONNECTED) | `chainDisconnected()` |
|
|
41
|
+
| `transactionTimeout` | `-32003` (TRANSACTION_REJECTED) | `transactionRejected()` |
|
|
42
|
+
| `genericError` | `-32603` (INTERNAL_ERROR) | `internalError()` |
|
|
43
|
+
|
|
44
|
+
`createMockWalletClient(config?)` exposes the underlying `BridgeableClient` as
|
|
45
|
+
an extension point for advanced wrapping/inspection.
|
|
46
|
+
|
|
47
|
+
## B. Simulated transaction lifecycle — `createTransactionLifecycle(config?)`
|
|
48
|
+
|
|
49
|
+
A controllable lifecycle with phase flags
|
|
50
|
+
`isPreparing → isSubmitting → isConfirming → isFinalized` plus a `failed`
|
|
51
|
+
terminal, emitting the same CIP-0103 `txChanged` events the real provider does.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createTransactionLifecycle } from '@partylayer/testing';
|
|
55
|
+
|
|
56
|
+
// manual stepping — deterministic, phase by phase
|
|
57
|
+
const lc = createTransactionLifecycle({ commandId: 'cmd-1' });
|
|
58
|
+
lc.on('txChanged', (e) => console.log(e.status));
|
|
59
|
+
lc.advance(); // → 'preparing' emits { status: 'pending' }
|
|
60
|
+
lc.advance(); // → 'submitting' emits { status: 'signed', payload }
|
|
61
|
+
lc.advance(); // → 'confirming' (no CIP-0103 event — see below)
|
|
62
|
+
lc.advance(); // → 'finalized' emits { status: 'executed', payload }
|
|
63
|
+
// or lc.fail() at any point → emits { status: 'failed' }
|
|
64
|
+
|
|
65
|
+
// auto mode — fake-timer friendly
|
|
66
|
+
const auto = createTransactionLifecycle({ delays: { preparing: 10, finalized: 50 } });
|
|
67
|
+
await auto.start(); // walks every phase using the delays
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Phase → `txChanged.status`: `preparing→pending`, `submitting→signed`,
|
|
71
|
+
`confirming→`(none)`, `finalized→executed`, `failed→failed`. CIP-0103 has no
|
|
72
|
+
"confirming" status (the union goes signed → executed); `isConfirming` is the
|
|
73
|
+
post-signed waiting flag the session layer surfaces.
|
|
74
|
+
|
|
75
|
+
## C. Offline helpers
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { createMockWallet, recordTxEvents, connectMock } from '@partylayer/testing';
|
|
79
|
+
|
|
80
|
+
const provider = createMockWallet();
|
|
81
|
+
const rec = recordTxEvents(provider); // collect txChanged
|
|
82
|
+
await connectMock(provider);
|
|
83
|
+
await provider.request({ method: 'prepareExecute', params: { tx: {} } });
|
|
84
|
+
rec.statuses(); // ['pending', 'signed', 'executed']
|
|
85
|
+
rec.stop();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Optional `delays` use `setTimeout`, so `vi.useFakeTimers()` +
|
|
89
|
+
`vi.advanceTimersByTimeAsync()` give tests full control over time.
|
|
90
|
+
|
|
91
|
+
## D. Session-lifecycle harness — `createSessionHarness(config?)`
|
|
92
|
+
|
|
93
|
+
Drives a **real** `@partylayer/session` store through a controllable provider, so
|
|
94
|
+
each scenario exercises the store's own machinery (no synthetic shortcuts).
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { createSessionHarness } from '@partylayer/testing';
|
|
98
|
+
import { vi } from 'vitest';
|
|
99
|
+
|
|
100
|
+
vi.useFakeTimers();
|
|
101
|
+
const h = createSessionHarness({ ttlMs: 30_000, onReauthRequired, advanceTimers: vi.advanceTimersByTimeAsync });
|
|
102
|
+
await h.connect();
|
|
103
|
+
await h.expire(); // advances the store's REAL expiry timer → session:expired
|
|
104
|
+
h.switchParty('party::b'); // real accountsChanged → party:changed
|
|
105
|
+
h.dropConnection(); // real statusChanged(false) → transient reconnect
|
|
106
|
+
const tabB = h.openTab(); // a 2nd store sharing the broadcast hub (multi-tab)
|
|
107
|
+
h.destroy(); tabB.destroy(); // per-harness teardown (children are separate)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`expire()` advances the store's real `setTimeout`-based expiry — it never emits a
|
|
111
|
+
fake `session:expired`, so pass `advanceTimers` (e.g. `vi.advanceTimersByTimeAsync`)
|
|
112
|
+
and install fake timers.
|
|
113
|
+
|
|
114
|
+
## E. Offline composition — `createOfflineHarness({ wallet?, session? })`
|
|
115
|
+
|
|
116
|
+
Wires a mock wallet to a real session store, fully offline:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { createOfflineHarness } from '@partylayer/testing';
|
|
120
|
+
const { provider, store, destroy } = createOfflineHarness({ wallet: { partyId: 'party::a' } });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## F. TanStack Query utilities — `@partylayer/testing/query`
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import {
|
|
127
|
+
createTestQueryClient, getQueryState, expectInvalidated, trackOptimisticRollback, createQueryHarness,
|
|
128
|
+
} from '@partylayer/testing/query';
|
|
129
|
+
|
|
130
|
+
const qc = createTestQueryClient(); // no retries, gcTime 0
|
|
131
|
+
const t = trackOptimisticRollback<number>(qc, ['count']);
|
|
132
|
+
t.apply(99); /* assert */ t.rollback(); /* assert restore */
|
|
133
|
+
const h = createQueryHarness({ wallet, session, query }); // offline harness + QueryClient
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## G. Browser / e2e primitives
|
|
137
|
+
|
|
138
|
+
Framework-agnostic script strings (no Playwright dependency) for a real-browser
|
|
139
|
+
smoke (`mockWalletInjectionScript()`, `idbEntryCountScript(db)`, `sessionKeyDbName(origin)`),
|
|
140
|
+
injected via Playwright's `page.addInitScript` / `page.evaluate`. The smoke itself
|
|
141
|
+
lives in `apps/demo/e2e` and runs nightly.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export { M as MOCK_SCENARIO_NAMES, a as MockMethod, b as MockScenario, c as MockScenarioName, d as MockWalletClient, e as MockWalletConfig, O as OfflineHarness, T as TxEventRecorder, f as connectMock, g as createMockWallet, h as createMockWalletClient, i as createOfflineHarness, r as recordTxEvents, s as scenarioToError } from './offline-CELeTEq9.mjs';
|
|
2
|
+
import { CIP0103Provider, CIP0103TxChangedEvent } from '@partylayer/core';
|
|
3
|
+
import { ChannelFactory, SessionStore, SessionState, ExpiryOptions, RetryPolicy } from '@partylayer/session';
|
|
4
|
+
import '@partylayer/provider';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Simulated, controllable transaction lifecycle.
|
|
8
|
+
*
|
|
9
|
+
* Exposes the session-layer view (boolean phase flags
|
|
10
|
+
* isPreparing → isSubmitting → isConfirming → isFinalized, plus a `failed`
|
|
11
|
+
* terminal) AND emits the SAME CIP-0103 `txChanged` events the real provider
|
|
12
|
+
* emits, so tests can assert against either view.
|
|
13
|
+
*
|
|
14
|
+
* Two drive modes:
|
|
15
|
+
* - manual: `advance()` steps one phase at a time; `fail()` terminates.
|
|
16
|
+
* - auto: `start()` walks all phases using configurable per-phase delays
|
|
17
|
+
* (uses setTimeout — fake-timer friendly).
|
|
18
|
+
*
|
|
19
|
+
* Phase → CIP-0103 `txChanged.status` mapping:
|
|
20
|
+
* preparing → 'pending'
|
|
21
|
+
* submitting → 'signed' (payload: { signature, signedBy, party })
|
|
22
|
+
* confirming → (no CIP-0103 status — see note)
|
|
23
|
+
* finalized → 'executed' (payload: { updateId, completionOffset })
|
|
24
|
+
* failed → 'failed'
|
|
25
|
+
*
|
|
26
|
+
* NOTE — 'confirming' has no CIP-0103 `txChanged` status: the spec's tx union
|
|
27
|
+
* goes signed → executed with no intermediate "confirming" state. We still
|
|
28
|
+
* model `isConfirming` as the post-signed waiting window because the session
|
|
29
|
+
* layer surfaces it as a UI flag. The session-lifecycle harness
|
|
30
|
+
* (`createSessionHarness`) and the TanStack Query utilities
|
|
31
|
+
* (`@partylayer/testing/query`) build on top of this controller.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
type LifecyclePhase = 'idle' | 'preparing' | 'submitting' | 'confirming' | 'finalized' | 'failed';
|
|
35
|
+
type LifecycleDelays = Partial<Record<'preparing' | 'submitting' | 'confirming' | 'finalized', number>>;
|
|
36
|
+
interface LifecycleConfig {
|
|
37
|
+
/** Command id stamped on every emitted event. */
|
|
38
|
+
commandId?: string;
|
|
39
|
+
/** Party id used in the 'signed' payload. */
|
|
40
|
+
party?: string;
|
|
41
|
+
/** Signature used in the 'signed' payload. */
|
|
42
|
+
signature?: string;
|
|
43
|
+
/** Update id used in the 'executed' payload. */
|
|
44
|
+
updateId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional provider to ALSO emit `txChanged` onto (in addition to this
|
|
47
|
+
* controller's own listeners), so events surface on a mock wallet's bus.
|
|
48
|
+
*/
|
|
49
|
+
provider?: CIP0103Provider;
|
|
50
|
+
/** Per-phase delays for auto mode (`start()`). Default 0. */
|
|
51
|
+
delays?: LifecycleDelays;
|
|
52
|
+
}
|
|
53
|
+
interface TransactionLifecycle {
|
|
54
|
+
readonly commandId: string;
|
|
55
|
+
readonly phase: LifecyclePhase;
|
|
56
|
+
readonly isPreparing: boolean;
|
|
57
|
+
readonly isSubmitting: boolean;
|
|
58
|
+
readonly isConfirming: boolean;
|
|
59
|
+
readonly isFinalized: boolean;
|
|
60
|
+
readonly isFailed: boolean;
|
|
61
|
+
/** Subscribe to `txChanged`. Returns an unsubscribe function. */
|
|
62
|
+
on(event: string, listener: (event: CIP0103TxChangedEvent) => void): () => void;
|
|
63
|
+
/** Manual step to the next phase; emits the mapped event. Returns the new phase. */
|
|
64
|
+
advance(): LifecyclePhase;
|
|
65
|
+
/** Terminal failure: emits `txChanged` `{ status: 'failed' }`. */
|
|
66
|
+
fail(): void;
|
|
67
|
+
/** Auto mode: walk every phase with configured delays. Resolves at 'finalized'. */
|
|
68
|
+
start(): Promise<void>;
|
|
69
|
+
/** Reset back to 'idle' (does not emit). */
|
|
70
|
+
reset(): void;
|
|
71
|
+
}
|
|
72
|
+
declare function createTransactionLifecycle(config?: LifecycleConfig): TransactionLifecycle;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Session-lifecycle simulation harness.
|
|
76
|
+
*
|
|
77
|
+
* Drives a REAL `@partylayer/session` store through a controllable CIP-0103
|
|
78
|
+
* provider, so every scenario exercises the store's own machinery — not
|
|
79
|
+
* synthetic shortcuts:
|
|
80
|
+
* - `expire()` advances the store's REAL expiry timer (the
|
|
81
|
+
* `session:expired` / `onReauthRequired` path); it never
|
|
82
|
+
* emits a fake `session:expired`. Requires fake-timer
|
|
83
|
+
* control via `advanceTimers` (the store arms expiry
|
|
84
|
+
* with `setTimeout`).
|
|
85
|
+
* - `dropConnection()` / emit the provider's real `statusChanged` CIP-0103
|
|
86
|
+
* `restoreConnection()` event — the same signal a live wallet sends — driving
|
|
87
|
+
* the store's transient-disconnect / reconnect path.
|
|
88
|
+
* - `switchParty()` emits a real `accountsChanged` with a new primary,
|
|
89
|
+
* driving the store's `party:changed` detection.
|
|
90
|
+
* - `openTab()` returns a second harness whose store shares this
|
|
91
|
+
* harness's in-memory BroadcastChannel hub, so a
|
|
92
|
+
* disconnect in one tab propagates to the other.
|
|
93
|
+
*
|
|
94
|
+
* Lifecycle: each harness OWNS its own store; `destroy()` is per-harness (a
|
|
95
|
+
* child from `openTab()` must be destroyed by the caller — it is not torn down
|
|
96
|
+
* with its parent).
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
interface SessionHarnessConfig {
|
|
100
|
+
/** Initial primary party id. Default `party::harness-1`. */
|
|
101
|
+
partyId?: string;
|
|
102
|
+
/** CAIP-2 network id reported by the provider. Default `canton:da-devnet`. */
|
|
103
|
+
networkId?: string;
|
|
104
|
+
/** Expiry TTL (ms) armed on connect. Default `60_000`. Drive it with `expire()`. */
|
|
105
|
+
ttlMs?: number;
|
|
106
|
+
/** App re-auth hook invoked by the store's real expiry path. */
|
|
107
|
+
onReauthRequired?: ExpiryOptions['onReauthRequired'];
|
|
108
|
+
/** Reconnect policy passed straight to the store (default: store default). */
|
|
109
|
+
reconnect?: RetryPolicy | false;
|
|
110
|
+
/**
|
|
111
|
+
* Advance timers to fire the store's REAL `setTimeout`-based expiry/backoff —
|
|
112
|
+
* e.g. `vi.advanceTimersByTimeAsync`. Required for `expire()` and for
|
|
113
|
+
* deterministic reconnect timing. Omit only if you don't call `expire()`.
|
|
114
|
+
*/
|
|
115
|
+
advanceTimers?: (ms: number) => void | Promise<void>;
|
|
116
|
+
/** @internal shared multi-tab hub (set by `openTab`). */
|
|
117
|
+
_hub?: ChannelHub;
|
|
118
|
+
}
|
|
119
|
+
interface SessionHarness {
|
|
120
|
+
/** The live session store under test. */
|
|
121
|
+
readonly store: SessionStore;
|
|
122
|
+
/** The controllable CIP-0103 provider backing the store. */
|
|
123
|
+
readonly provider: CIP0103Provider;
|
|
124
|
+
/** Connect via the real store flow (arms the expiry timer). */
|
|
125
|
+
connect(): Promise<SessionState>;
|
|
126
|
+
/** Fire a real transient `statusChanged(false)` (NOT an explicit disconnect). */
|
|
127
|
+
dropConnection(): void;
|
|
128
|
+
/** Fire a real `statusChanged(true)` to restore the connection. */
|
|
129
|
+
restoreConnection(): void;
|
|
130
|
+
/** Fire a real `accountsChanged` with a new primary → `party:changed`. */
|
|
131
|
+
switchParty(partyId: string): void;
|
|
132
|
+
/** Advance the store's REAL expiry timer past its TTL (no synthetic emit). */
|
|
133
|
+
expire(): Promise<void>;
|
|
134
|
+
/** A second harness sharing this one's broadcast hub (simulates another tab). */
|
|
135
|
+
openTab(config?: Omit<SessionHarnessConfig, '_hub'>): SessionHarness;
|
|
136
|
+
/** Tear down THIS harness's store/provider (per-harness; children are separate). */
|
|
137
|
+
destroy(): void;
|
|
138
|
+
}
|
|
139
|
+
/** A synchronous in-memory BroadcastChannel hub shared across tabs. */
|
|
140
|
+
interface ChannelHub {
|
|
141
|
+
factory: ChannelFactory;
|
|
142
|
+
}
|
|
143
|
+
/** Build a hub whose channels deliver to OTHER instances only (no echo to sender). */
|
|
144
|
+
declare function createChannelHub(): ChannelHub;
|
|
145
|
+
declare function createSessionHarness(config?: SessionHarnessConfig): SessionHarness;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Reusable browser-test primitives for a real-browser (Playwright) smoke —
|
|
149
|
+
* framework-agnostic: every helper returns a STRING of JS to run via
|
|
150
|
+
* `page.addInitScript(...)` / `page.evaluate(...)`, so this package takes NO
|
|
151
|
+
* dependency on Playwright. The actual smoke lives in `apps/demo/e2e`.
|
|
152
|
+
*/
|
|
153
|
+
interface MockWalletInjectionOptions {
|
|
154
|
+
/** Key on `window.canton` the wallet registers under. Default `mock`. */
|
|
155
|
+
walletId?: string;
|
|
156
|
+
/** Primary party id the mock reports. Default `party::e2e-1`. */
|
|
157
|
+
partyId?: string;
|
|
158
|
+
/** CAIP-2 network id. Default `canton:da-devnet`. */
|
|
159
|
+
networkId?: string;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* An init script that installs a minimal CIP-0103-shaped provider at
|
|
163
|
+
* `window.canton[walletId]` BEFORE the app loads, so a real-browser test can
|
|
164
|
+
* drive the connect flow with no extension/live wallet. Inject via
|
|
165
|
+
* `page.addInitScript({ content: mockWalletInjectionScript() })`.
|
|
166
|
+
*/
|
|
167
|
+
declare function mockWalletInjectionScript(options?: MockWalletInjectionOptions): string;
|
|
168
|
+
/**
|
|
169
|
+
* A script returning the number of object stores' total entries for an IndexedDB
|
|
170
|
+
* database (or -1 if the DB does not exist) — used to assert encrypted-session
|
|
171
|
+
* persistence engaged after a connect. Run via `page.evaluate(idbEntryCountScript(db))`.
|
|
172
|
+
*/
|
|
173
|
+
declare function idbEntryCountScript(dbName: string): string;
|
|
174
|
+
/** The origin-bound IndexedDB name the session key store uses for a given origin. */
|
|
175
|
+
declare function sessionKeyDbName(origin: string): string;
|
|
176
|
+
|
|
177
|
+
export { type ChannelHub, type LifecycleConfig, type LifecycleDelays, type LifecyclePhase, type MockWalletInjectionOptions, type SessionHarness, type SessionHarnessConfig, type TransactionLifecycle, createChannelHub, createSessionHarness, createTransactionLifecycle, idbEntryCountScript, mockWalletInjectionScript, sessionKeyDbName };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export { M as MOCK_SCENARIO_NAMES, a as MockMethod, b as MockScenario, c as MockScenarioName, d as MockWalletClient, e as MockWalletConfig, O as OfflineHarness, T as TxEventRecorder, f as connectMock, g as createMockWallet, h as createMockWalletClient, i as createOfflineHarness, r as recordTxEvents, s as scenarioToError } from './offline-CELeTEq9.js';
|
|
2
|
+
import { CIP0103Provider, CIP0103TxChangedEvent } from '@partylayer/core';
|
|
3
|
+
import { ChannelFactory, SessionStore, SessionState, ExpiryOptions, RetryPolicy } from '@partylayer/session';
|
|
4
|
+
import '@partylayer/provider';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Simulated, controllable transaction lifecycle.
|
|
8
|
+
*
|
|
9
|
+
* Exposes the session-layer view (boolean phase flags
|
|
10
|
+
* isPreparing → isSubmitting → isConfirming → isFinalized, plus a `failed`
|
|
11
|
+
* terminal) AND emits the SAME CIP-0103 `txChanged` events the real provider
|
|
12
|
+
* emits, so tests can assert against either view.
|
|
13
|
+
*
|
|
14
|
+
* Two drive modes:
|
|
15
|
+
* - manual: `advance()` steps one phase at a time; `fail()` terminates.
|
|
16
|
+
* - auto: `start()` walks all phases using configurable per-phase delays
|
|
17
|
+
* (uses setTimeout — fake-timer friendly).
|
|
18
|
+
*
|
|
19
|
+
* Phase → CIP-0103 `txChanged.status` mapping:
|
|
20
|
+
* preparing → 'pending'
|
|
21
|
+
* submitting → 'signed' (payload: { signature, signedBy, party })
|
|
22
|
+
* confirming → (no CIP-0103 status — see note)
|
|
23
|
+
* finalized → 'executed' (payload: { updateId, completionOffset })
|
|
24
|
+
* failed → 'failed'
|
|
25
|
+
*
|
|
26
|
+
* NOTE — 'confirming' has no CIP-0103 `txChanged` status: the spec's tx union
|
|
27
|
+
* goes signed → executed with no intermediate "confirming" state. We still
|
|
28
|
+
* model `isConfirming` as the post-signed waiting window because the session
|
|
29
|
+
* layer surfaces it as a UI flag. The session-lifecycle harness
|
|
30
|
+
* (`createSessionHarness`) and the TanStack Query utilities
|
|
31
|
+
* (`@partylayer/testing/query`) build on top of this controller.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
type LifecyclePhase = 'idle' | 'preparing' | 'submitting' | 'confirming' | 'finalized' | 'failed';
|
|
35
|
+
type LifecycleDelays = Partial<Record<'preparing' | 'submitting' | 'confirming' | 'finalized', number>>;
|
|
36
|
+
interface LifecycleConfig {
|
|
37
|
+
/** Command id stamped on every emitted event. */
|
|
38
|
+
commandId?: string;
|
|
39
|
+
/** Party id used in the 'signed' payload. */
|
|
40
|
+
party?: string;
|
|
41
|
+
/** Signature used in the 'signed' payload. */
|
|
42
|
+
signature?: string;
|
|
43
|
+
/** Update id used in the 'executed' payload. */
|
|
44
|
+
updateId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional provider to ALSO emit `txChanged` onto (in addition to this
|
|
47
|
+
* controller's own listeners), so events surface on a mock wallet's bus.
|
|
48
|
+
*/
|
|
49
|
+
provider?: CIP0103Provider;
|
|
50
|
+
/** Per-phase delays for auto mode (`start()`). Default 0. */
|
|
51
|
+
delays?: LifecycleDelays;
|
|
52
|
+
}
|
|
53
|
+
interface TransactionLifecycle {
|
|
54
|
+
readonly commandId: string;
|
|
55
|
+
readonly phase: LifecyclePhase;
|
|
56
|
+
readonly isPreparing: boolean;
|
|
57
|
+
readonly isSubmitting: boolean;
|
|
58
|
+
readonly isConfirming: boolean;
|
|
59
|
+
readonly isFinalized: boolean;
|
|
60
|
+
readonly isFailed: boolean;
|
|
61
|
+
/** Subscribe to `txChanged`. Returns an unsubscribe function. */
|
|
62
|
+
on(event: string, listener: (event: CIP0103TxChangedEvent) => void): () => void;
|
|
63
|
+
/** Manual step to the next phase; emits the mapped event. Returns the new phase. */
|
|
64
|
+
advance(): LifecyclePhase;
|
|
65
|
+
/** Terminal failure: emits `txChanged` `{ status: 'failed' }`. */
|
|
66
|
+
fail(): void;
|
|
67
|
+
/** Auto mode: walk every phase with configured delays. Resolves at 'finalized'. */
|
|
68
|
+
start(): Promise<void>;
|
|
69
|
+
/** Reset back to 'idle' (does not emit). */
|
|
70
|
+
reset(): void;
|
|
71
|
+
}
|
|
72
|
+
declare function createTransactionLifecycle(config?: LifecycleConfig): TransactionLifecycle;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Session-lifecycle simulation harness.
|
|
76
|
+
*
|
|
77
|
+
* Drives a REAL `@partylayer/session` store through a controllable CIP-0103
|
|
78
|
+
* provider, so every scenario exercises the store's own machinery — not
|
|
79
|
+
* synthetic shortcuts:
|
|
80
|
+
* - `expire()` advances the store's REAL expiry timer (the
|
|
81
|
+
* `session:expired` / `onReauthRequired` path); it never
|
|
82
|
+
* emits a fake `session:expired`. Requires fake-timer
|
|
83
|
+
* control via `advanceTimers` (the store arms expiry
|
|
84
|
+
* with `setTimeout`).
|
|
85
|
+
* - `dropConnection()` / emit the provider's real `statusChanged` CIP-0103
|
|
86
|
+
* `restoreConnection()` event — the same signal a live wallet sends — driving
|
|
87
|
+
* the store's transient-disconnect / reconnect path.
|
|
88
|
+
* - `switchParty()` emits a real `accountsChanged` with a new primary,
|
|
89
|
+
* driving the store's `party:changed` detection.
|
|
90
|
+
* - `openTab()` returns a second harness whose store shares this
|
|
91
|
+
* harness's in-memory BroadcastChannel hub, so a
|
|
92
|
+
* disconnect in one tab propagates to the other.
|
|
93
|
+
*
|
|
94
|
+
* Lifecycle: each harness OWNS its own store; `destroy()` is per-harness (a
|
|
95
|
+
* child from `openTab()` must be destroyed by the caller — it is not torn down
|
|
96
|
+
* with its parent).
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
interface SessionHarnessConfig {
|
|
100
|
+
/** Initial primary party id. Default `party::harness-1`. */
|
|
101
|
+
partyId?: string;
|
|
102
|
+
/** CAIP-2 network id reported by the provider. Default `canton:da-devnet`. */
|
|
103
|
+
networkId?: string;
|
|
104
|
+
/** Expiry TTL (ms) armed on connect. Default `60_000`. Drive it with `expire()`. */
|
|
105
|
+
ttlMs?: number;
|
|
106
|
+
/** App re-auth hook invoked by the store's real expiry path. */
|
|
107
|
+
onReauthRequired?: ExpiryOptions['onReauthRequired'];
|
|
108
|
+
/** Reconnect policy passed straight to the store (default: store default). */
|
|
109
|
+
reconnect?: RetryPolicy | false;
|
|
110
|
+
/**
|
|
111
|
+
* Advance timers to fire the store's REAL `setTimeout`-based expiry/backoff —
|
|
112
|
+
* e.g. `vi.advanceTimersByTimeAsync`. Required for `expire()` and for
|
|
113
|
+
* deterministic reconnect timing. Omit only if you don't call `expire()`.
|
|
114
|
+
*/
|
|
115
|
+
advanceTimers?: (ms: number) => void | Promise<void>;
|
|
116
|
+
/** @internal shared multi-tab hub (set by `openTab`). */
|
|
117
|
+
_hub?: ChannelHub;
|
|
118
|
+
}
|
|
119
|
+
interface SessionHarness {
|
|
120
|
+
/** The live session store under test. */
|
|
121
|
+
readonly store: SessionStore;
|
|
122
|
+
/** The controllable CIP-0103 provider backing the store. */
|
|
123
|
+
readonly provider: CIP0103Provider;
|
|
124
|
+
/** Connect via the real store flow (arms the expiry timer). */
|
|
125
|
+
connect(): Promise<SessionState>;
|
|
126
|
+
/** Fire a real transient `statusChanged(false)` (NOT an explicit disconnect). */
|
|
127
|
+
dropConnection(): void;
|
|
128
|
+
/** Fire a real `statusChanged(true)` to restore the connection. */
|
|
129
|
+
restoreConnection(): void;
|
|
130
|
+
/** Fire a real `accountsChanged` with a new primary → `party:changed`. */
|
|
131
|
+
switchParty(partyId: string): void;
|
|
132
|
+
/** Advance the store's REAL expiry timer past its TTL (no synthetic emit). */
|
|
133
|
+
expire(): Promise<void>;
|
|
134
|
+
/** A second harness sharing this one's broadcast hub (simulates another tab). */
|
|
135
|
+
openTab(config?: Omit<SessionHarnessConfig, '_hub'>): SessionHarness;
|
|
136
|
+
/** Tear down THIS harness's store/provider (per-harness; children are separate). */
|
|
137
|
+
destroy(): void;
|
|
138
|
+
}
|
|
139
|
+
/** A synchronous in-memory BroadcastChannel hub shared across tabs. */
|
|
140
|
+
interface ChannelHub {
|
|
141
|
+
factory: ChannelFactory;
|
|
142
|
+
}
|
|
143
|
+
/** Build a hub whose channels deliver to OTHER instances only (no echo to sender). */
|
|
144
|
+
declare function createChannelHub(): ChannelHub;
|
|
145
|
+
declare function createSessionHarness(config?: SessionHarnessConfig): SessionHarness;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Reusable browser-test primitives for a real-browser (Playwright) smoke —
|
|
149
|
+
* framework-agnostic: every helper returns a STRING of JS to run via
|
|
150
|
+
* `page.addInitScript(...)` / `page.evaluate(...)`, so this package takes NO
|
|
151
|
+
* dependency on Playwright. The actual smoke lives in `apps/demo/e2e`.
|
|
152
|
+
*/
|
|
153
|
+
interface MockWalletInjectionOptions {
|
|
154
|
+
/** Key on `window.canton` the wallet registers under. Default `mock`. */
|
|
155
|
+
walletId?: string;
|
|
156
|
+
/** Primary party id the mock reports. Default `party::e2e-1`. */
|
|
157
|
+
partyId?: string;
|
|
158
|
+
/** CAIP-2 network id. Default `canton:da-devnet`. */
|
|
159
|
+
networkId?: string;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* An init script that installs a minimal CIP-0103-shaped provider at
|
|
163
|
+
* `window.canton[walletId]` BEFORE the app loads, so a real-browser test can
|
|
164
|
+
* drive the connect flow with no extension/live wallet. Inject via
|
|
165
|
+
* `page.addInitScript({ content: mockWalletInjectionScript() })`.
|
|
166
|
+
*/
|
|
167
|
+
declare function mockWalletInjectionScript(options?: MockWalletInjectionOptions): string;
|
|
168
|
+
/**
|
|
169
|
+
* A script returning the number of object stores' total entries for an IndexedDB
|
|
170
|
+
* database (or -1 if the DB does not exist) — used to assert encrypted-session
|
|
171
|
+
* persistence engaged after a connect. Run via `page.evaluate(idbEntryCountScript(db))`.
|
|
172
|
+
*/
|
|
173
|
+
declare function idbEntryCountScript(dbName: string): string;
|
|
174
|
+
/** The origin-bound IndexedDB name the session key store uses for a given origin. */
|
|
175
|
+
declare function sessionKeyDbName(origin: string): string;
|
|
176
|
+
|
|
177
|
+
export { type ChannelHub, type LifecycleConfig, type LifecycleDelays, type LifecyclePhase, type MockWalletInjectionOptions, type SessionHarness, type SessionHarnessConfig, type TransactionLifecycle, createChannelHub, createSessionHarness, createTransactionLifecycle, idbEntryCountScript, mockWalletInjectionScript, sessionKeyDbName };
|