@ethlete/query 6.0.0-next.59 → 6.0.0-next.60

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.
@@ -1,103 +1,100 @@
1
- import { AnyCreateQueryClientResult, QueryClientRef, createGetQuery, createPostQuery, createPutQuery, createPatchQuery, createDeleteQuery, BearerAuthProviderFeatureContext, AnyQueryBuilder, AuthQueryBuilder, TokenRefreshQueryBuilder, BearerAuthProvider, FeatureRegistry, TokenRefreshQueryConfig, QueryPersistenceAdapter, PersistedQueryEntry, WebSocketClientIo, SocketMessageView } from '@ethlete/query';
2
- import { HttpClient } from '@angular/common/http';
3
- import { HttpTestingController } from '@angular/common/http/testing';
4
- import { Injector } from '@angular/core';
5
-
6
- type QueryTestSetup = {
7
- queryClient: NonNullable<ReturnType<AnyCreateQueryClientResult['inject']>>;
8
- queryClientRef: QueryClientRef;
9
- httpClient: HttpClient;
10
- httpTesting: HttpTestingController;
11
- injector: Injector;
12
- baseUrl: string;
13
- createGet: ReturnType<typeof createGetQuery>;
14
- createPost: ReturnType<typeof createPostQuery>;
15
- createPut: ReturnType<typeof createPutQuery>;
16
- createPatch: ReturnType<typeof createPatchQuery>;
17
- createDelete: ReturnType<typeof createDeleteQuery>;
18
- /**
19
- * Puts back the `console.error` that was installed before the first {@link setupQueryTest} call. Runs
20
- * on its own when the `TestBed` module is reset; idempotent, and safe to call from any setup.
21
- */
22
- restoreConsole: () => void;
1
+ import { AnyCreateQueryClientResult, AnyQueryBuilder, AuthQueryBuilder, BearerAuthProvider, BearerAuthProviderFeatureContext, FeatureRegistry, PersistedQueryEntry, QueryClientRef, QueryPersistenceAdapter, SocketMessageView, TokenRefreshQueryBuilder, TokenRefreshQueryConfig, WebSocketClientIo, createDeleteQuery, createGetQuery, createPatchQuery, createPostQuery, createPutQuery } from "@ethlete/query";
2
+ import { HttpClient } from "@angular/common/http";
3
+ import { HttpTestingController } from "@angular/common/http/testing";
4
+ import { Injector } from "@angular/core";
5
+ export type QueryTestSetup = {
6
+ queryClient: NonNullable<ReturnType<AnyCreateQueryClientResult['inject']>>;
7
+ queryClientRef: QueryClientRef;
8
+ httpClient: HttpClient;
9
+ httpTesting: HttpTestingController;
10
+ injector: Injector;
11
+ baseUrl: string;
12
+ createGet: ReturnType<typeof createGetQuery>;
13
+ createPost: ReturnType<typeof createPostQuery>;
14
+ createPut: ReturnType<typeof createPutQuery>;
15
+ createPatch: ReturnType<typeof createPatchQuery>;
16
+ createDelete: ReturnType<typeof createDeleteQuery>;
17
+ /**
18
+ * Puts back the `console.error` that was installed before the first {@link setupQueryTest} call. Runs
19
+ * on its own when the `TestBed` module is reset; idempotent, and safe to call from any setup.
20
+ */
21
+ restoreConsole: () => void;
23
22
  };
24
- type QueryTestSetupConfig = {
25
- baseUrl?: string;
26
- name?: string;
27
- /**
28
- * `true` swallows the `ErrorHandler` reports of failed requests and rethrows every other error, `'all'`
29
- * swallows everything, `false` keeps Angular's own handler.
30
- * @default true
31
- */
32
- mockErrorHandler?: boolean | 'all';
23
+ export type QueryTestSetupConfig = {
24
+ baseUrl?: string;
25
+ name?: string;
26
+ /**
27
+ * `true` swallows the `ErrorHandler` reports of failed requests and rethrows every other error, `'all'`
28
+ * swallows everything, `false` keeps Angular's own handler.
29
+ * @default true
30
+ */
31
+ mockErrorHandler?: boolean | 'all';
33
32
  };
34
- declare const setupQueryTest: (config?: QueryTestSetupConfig) => QueryTestSetup;
35
-
36
- type AnyFeatureBuilder = (context: BearerAuthProviderFeatureContext<any, readonly AnyQueryBuilder[]>) => {
37
- type: string;
38
- instance: unknown;
33
+ export declare const setupQueryTest: (config?: QueryTestSetupConfig) => QueryTestSetup;
34
+ export type AnyFeatureBuilder = (context: BearerAuthProviderFeatureContext<any, readonly AnyQueryBuilder[]>) => {
35
+ type: string;
36
+ instance: unknown;
39
37
  };
40
38
  type AuthTestArgs = {
41
- body: Record<string, unknown>;
42
- response: {
43
- accessToken: string;
44
- refreshToken: string;
45
- };
39
+ body: Record<string, unknown>;
40
+ response: {
41
+ accessToken: string;
42
+ refreshToken: string;
43
+ };
46
44
  };
47
45
  /** The query builders of the provider {@link setupAuthTest} builds: a `login` and a `refresh` query. */
48
- type AuthTestQueryBuilders<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs> = [AuthQueryBuilder<'login', TLoginArgs>, TokenRefreshQueryBuilder<'refresh', TRefreshArgs>];
46
+ export type AuthTestQueryBuilders<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs> = [AuthQueryBuilder<'login', TLoginArgs>, TokenRefreshQueryBuilder<'refresh', TRefreshArgs>];
49
47
  /** A feature for the provider {@link setupAuthTest} builds, typed against its `login`/`refresh` queries. */
50
- type AuthTestFeatureBuilder = (context: BearerAuthProviderFeatureContext<any, AuthTestQueryBuilders>) => unknown;
51
- type AuthTestSetupConfig<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = [], TBearerData = unknown> = {
52
- /** The query test setup instance */
53
- querySetup: QueryTestSetup;
54
- /** Login endpoint path. Default: '/auth/login' */
55
- loginPath?: string;
56
- /** Refresh endpoint path. Default: '/auth/refresh' */
57
- refreshPath?: string;
58
- /** Whether to refresh and retry a secure request that answers 401. Default: true, as in production */
59
- autoRetryOn401?: boolean;
60
- /** Function to extract tokens from login response */
61
- extractLoginTokens?: (response: TLoginArgs['response']) => {
62
- accessToken: string;
63
- refreshToken: string;
64
- };
65
- /** Function to extract tokens from refresh response */
66
- extractRefreshTokens?: (response: TRefreshArgs['response']) => {
67
- accessToken: string;
68
- refreshToken: string;
69
- };
70
- /** Builds the refresh request from the refresh token. See TokenRefreshQueryConfig for details */
71
- buildRefreshArgs?: TokenRefreshQueryConfig<TRefreshArgs>['buildArgs'];
72
- /** Feature builders for additional auth functionality */
73
- features?: TFeatures;
74
- /** Custom bearer decrypt function for testing */
75
- bearerDecryptFn?: (token: string) => TBearerData;
76
- /** Refresh strategy configuration. See TokenRefreshQueryConfig for details */
77
- refreshStrategy?: TokenRefreshQueryConfig<TRefreshArgs>['refreshStrategy'];
78
- /** Minimum refresh interval in milliseconds */
79
- minRefreshInterval?: number;
80
- /** Whether to refresh if token is already expired */
81
- refreshIfExpired?: boolean;
82
- /** Property name for expiration in JWT */
83
- expiresInPropertyName?: string;
84
- /** What to do when the refresh query fails for good. Defaults to the provider's own policy */
85
- onRefreshFailure?: TokenRefreshQueryConfig<TRefreshArgs>['onRefreshFailure'];
48
+ export type AuthTestFeatureBuilder = (context: BearerAuthProviderFeatureContext<any, AuthTestQueryBuilders>) => unknown;
49
+ export type AuthTestSetupConfig<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = [], TBearerData = unknown> = {
50
+ /** The query test setup instance */
51
+ querySetup: QueryTestSetup;
52
+ /** Login endpoint path. Default: '/auth/login' */
53
+ loginPath?: string;
54
+ /** Refresh endpoint path. Default: '/auth/refresh' */
55
+ refreshPath?: string;
56
+ /** Whether to refresh and retry a secure request that answers 401. Default: true, as in production */
57
+ autoRetryOn401?: boolean;
58
+ /** Function to extract tokens from login response */
59
+ extractLoginTokens?: (response: TLoginArgs['response']) => {
60
+ accessToken: string;
61
+ refreshToken: string;
62
+ };
63
+ /** Function to extract tokens from refresh response */
64
+ extractRefreshTokens?: (response: TRefreshArgs['response']) => {
65
+ accessToken: string;
66
+ refreshToken: string;
67
+ };
68
+ /** Builds the refresh request from the refresh token. See TokenRefreshQueryConfig for details */
69
+ buildRefreshArgs?: TokenRefreshQueryConfig<TRefreshArgs>['buildArgs'];
70
+ /** Feature builders for additional auth functionality */
71
+ features?: TFeatures;
72
+ /** Custom bearer decrypt function for testing */
73
+ bearerDecryptFn?: (token: string) => TBearerData;
74
+ /** Refresh strategy configuration. See TokenRefreshQueryConfig for details */
75
+ refreshStrategy?: TokenRefreshQueryConfig<TRefreshArgs>['refreshStrategy'];
76
+ /** Minimum refresh interval in milliseconds */
77
+ minRefreshInterval?: number;
78
+ /** Whether to refresh if token is already expired */
79
+ refreshIfExpired?: boolean;
80
+ /** Property name for expiration in JWT */
81
+ expiresInPropertyName?: string;
82
+ /** What to do when the refresh query fails for good. Defaults to the provider's own policy */
83
+ onRefreshFailure?: TokenRefreshQueryConfig<TRefreshArgs>['onRefreshFailure'];
86
84
  };
87
- type AuthTestSetup<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = [], TBearerData = unknown> = {
88
- /** The bearer auth provider instance */
89
- auth: Omit<BearerAuthProvider<AuthTestQueryBuilders<TLoginArgs, TRefreshArgs>, [], TBearerData>, 'features'> & {
90
- features: FeatureRegistry<TFeatures, AuthTestQueryBuilders<TLoginArgs, TRefreshArgs>>;
91
- };
92
- /** Helper to login a user and flush the HTTP request */
93
- login: (credentials: TLoginArgs['body'], response: TLoginArgs['response']) => void;
94
- /** Helper to trigger a refresh and flush the HTTP request. Sends `buildRefreshArgs(token)` when configured, else `{ body: { token } }` */
95
- refresh: (token: string, response: TRefreshArgs['response']) => void;
96
- /** Helper to make a secure request that requires authentication */
97
- makeSecureRequest: (route: string) => void;
85
+ export type AuthTestSetup<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = [], TBearerData = unknown> = {
86
+ /** The bearer auth provider instance */
87
+ auth: Omit<BearerAuthProvider<AuthTestQueryBuilders<TLoginArgs, TRefreshArgs>, [], TBearerData>, 'features'> & {
88
+ features: FeatureRegistry<TFeatures, AuthTestQueryBuilders<TLoginArgs, TRefreshArgs>>;
89
+ };
90
+ /** Helper to login a user and flush the HTTP request */
91
+ login: (credentials: TLoginArgs['body'], response: TLoginArgs['response']) => void;
92
+ /** Helper to trigger a refresh and flush the HTTP request. Sends `buildRefreshArgs(token)` when configured, else `{ body: { token } }` */
93
+ refresh: (token: string, response: TRefreshArgs['response']) => void;
94
+ /** Helper to make a secure request that requires authentication */
95
+ makeSecureRequest: (route: string) => void;
98
96
  };
99
- declare const setupAuthTest: <TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = readonly AuthTestFeatureBuilder[], TBearerData = unknown>(config: AuthTestSetupConfig<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>) => AuthTestSetup<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>;
100
-
97
+ export declare const setupAuthTest: <TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = readonly AuthTestFeatureBuilder[], TBearerData = unknown>(config: AuthTestSetupConfig<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>) => AuthTestSetup<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>;
101
98
  /**
102
99
  * Test doubles for the two browser APIs the query client's multi-tab sync is built on:
103
100
  * `BroadcastChannel` and the Web Locks API (`navigator.locks`).
@@ -111,17 +108,17 @@ declare const setupAuthTest: <TLoginArgs extends AuthTestArgs = AuthTestArgs, TR
111
108
  * delivers to other instances, and no `navigator.locks` at all.
112
109
  */
113
110
  /** Something posted on the fake bus, as seen by a receiver. */
114
- type FakeBroadcastMessage = {
115
- /** The channel name it was posted on. */
116
- channel: string;
117
- /** The structured-cloned payload. */
118
- data: unknown;
111
+ export type FakeBroadcastMessage = {
112
+ /** The channel name it was posted on. */
113
+ channel: string;
114
+ /** The structured-cloned payload. */
115
+ data: unknown;
119
116
  };
120
- type FakeBroadcastChannelHandle = {
121
- /** Every message posted on the bus, in order, across all channel names. */
122
- posted: FakeBroadcastMessage[];
123
- /** Uninstalls the fake and restores whatever `BroadcastChannel` was there before. */
124
- restore: () => void;
117
+ export type FakeBroadcastChannelHandle = {
118
+ /** Every message posted on the bus, in order, across all channel names. */
119
+ posted: FakeBroadcastMessage[];
120
+ /** Uninstalls the fake and restores whatever `BroadcastChannel` was there before. */
121
+ restore: () => void;
125
122
  };
126
123
  /**
127
124
  * Replaces `globalThis.BroadcastChannel` with an in-memory bus.
@@ -131,14 +128,14 @@ type FakeBroadcastChannelHandle = {
131
128
  * never delivered back to the channel that posted them - all three matching browser behavior. Delivery
132
129
  * does not depend on fake timers; `await flushMultiTabSync()` in a spec flushes it.
133
130
  */
134
- declare const installFakeBroadcastChannel: () => FakeBroadcastChannelHandle;
135
- type FakeWebLocksHandle = {
136
- /** The names currently held, in no particular order. */
137
- heldNames: () => string[];
138
- /** The names with at least one request queued behind the holder. */
139
- pendingNames: () => string[];
140
- /** Uninstalls the fake and restores whatever `navigator.locks` was there before. */
141
- restore: () => void;
131
+ export declare const installFakeBroadcastChannel: () => FakeBroadcastChannelHandle;
132
+ export type FakeWebLocksHandle = {
133
+ /** The names currently held, in no particular order. */
134
+ heldNames: () => string[];
135
+ /** The names with at least one request queued behind the holder. */
136
+ pendingNames: () => string[];
137
+ /** Uninstalls the fake and restores whatever `navigator.locks` was there before. */
138
+ restore: () => void;
142
139
  };
143
140
  /**
144
141
  * Installs an in-memory `navigator.locks` implementing exclusive locks: one holder per name, the
@@ -152,14 +149,13 @@ type FakeWebLocksHandle = {
152
149
  *
153
150
  * `mode: 'shared'` is not implemented; nothing in the library uses it.
154
151
  */
155
- declare const installFakeWebLocks: () => FakeWebLocksHandle;
152
+ export declare const installFakeWebLocks: () => FakeWebLocksHandle;
156
153
  /**
157
154
  * Waits for the fakes' pending work - message delivery (a task) and lock grants (a microtask) - even
158
155
  * under fake timers. Awaiting it once is enough for a single hop; a grant that has to wait for another
159
156
  * tab's release, or a reply to a delivered message, needs one await per hop.
160
157
  */
161
- declare const flushMultiTabSync: () => Promise<void>;
162
-
158
+ export declare const flushMultiTabSync: () => Promise<void>;
163
159
  /**
164
160
  * An in-memory stand-in for the query client's persisted response store.
165
161
  *
@@ -170,114 +166,112 @@ declare const flushMultiTabSync: () => Promise<void>;
170
166
  * Everything resolves on a microtask, never synchronously, matching the real thing: `await
171
167
  * Promise.resolve()` (or `flushMicrotasks`) is what lets a hydration land in a spec.
172
168
  */
173
- type FakeQueryPersistenceStoreHandle = {
174
- /** Pass this as `persistence: { adapter }`. Two clients sharing one handle share one store. */
175
- adapter: QueryPersistenceAdapter;
176
- /** Pre-populates the store, as if a previous session had written these entries. */
177
- seed: (entries: PersistedQueryEntry[]) => void;
178
- /** Every entry currently held, in insertion order. */
179
- entries: () => PersistedQueryEntry[];
180
- /** The entry held under `key`, or `undefined`. */
181
- entry: (key: string) => PersistedQueryEntry | undefined;
182
- /** How often each adapter method has been called. */
183
- calls: () => Record<'loadIndex' | 'read' | 'write' | 'remove' | 'clear', number>;
184
- /** Makes the next `count` writes reject - a full disk, by default. */
185
- failNextWrites: (count: number, error?: unknown) => void;
186
- /** Makes the next `count` removals reject. */
187
- failNextRemoves: (count: number, error?: unknown) => void;
188
- /** Makes the next `loadIndex` reject. */
189
- failNextLoadIndex: (error?: unknown) => void;
190
- /**
191
- * Holds every subsequent `read` pending until {@link flushReads} is called, for testing the window
192
- * between a cold mount and its hydration (a slow disk, or a mount that happened before startup
193
- * finished). Each held read returns what the store held when it started, as `loadIndex` does.
194
- */
195
- deferReads: () => void;
196
- /** Settles the reads held by {@link deferReads} and stops deferring. */
197
- flushReads: () => Promise<void>;
198
- /**
199
- * Holds every subsequent `loadIndex` pending until {@link flushLoadIndex} is called, for testing
200
- * what happens to the store while a client's startup is still reading its index (a real disk takes
201
- * milliseconds). The snapshot each held load returns is the one it would have read when it started.
202
- */
203
- deferLoadIndex: () => void;
204
- /** Settles the index loads held by {@link deferLoadIndex} and stops deferring. */
205
- flushLoadIndex: () => Promise<void>;
169
+ export type FakeQueryPersistenceStoreHandle = {
170
+ /** Pass this as `persistence: { adapter }`. Two clients sharing one handle share one store. */
171
+ adapter: QueryPersistenceAdapter;
172
+ /** Pre-populates the store, as if a previous session had written these entries. */
173
+ seed: (entries: PersistedQueryEntry[]) => void;
174
+ /** Every entry currently held, in insertion order. */
175
+ entries: () => PersistedQueryEntry[];
176
+ /** The entry held under `key`, or `undefined`. */
177
+ entry: (key: string) => PersistedQueryEntry | undefined;
178
+ /** How often each adapter method has been called. */
179
+ calls: () => Record<'loadIndex' | 'read' | 'write' | 'remove' | 'clear', number>;
180
+ /** Makes the next `count` writes reject - a full disk, by default. */
181
+ failNextWrites: (count: number, error?: unknown) => void;
182
+ /** Makes the next `count` removals reject. */
183
+ failNextRemoves: (count: number, error?: unknown) => void;
184
+ /** Makes the next `loadIndex` reject. */
185
+ failNextLoadIndex: (error?: unknown) => void;
186
+ /**
187
+ * Holds every subsequent `read` pending until {@link flushReads} is called, for testing the window
188
+ * between a cold mount and its hydration (a slow disk, or a mount that happened before startup
189
+ * finished). Each held read returns what the store held when it started, as `loadIndex` does.
190
+ */
191
+ deferReads: () => void;
192
+ /** Settles the reads held by {@link deferReads} and stops deferring. */
193
+ flushReads: () => Promise<void>;
194
+ /**
195
+ * Holds every subsequent `loadIndex` pending until {@link flushLoadIndex} is called, for testing
196
+ * what happens to the store while a client's startup is still reading its index (a real disk takes
197
+ * milliseconds). The snapshot each held load returns is the one it would have read when it started.
198
+ */
199
+ deferLoadIndex: () => void;
200
+ /** Settles the index loads held by {@link deferLoadIndex} and stops deferring. */
201
+ flushLoadIndex: () => Promise<void>;
206
202
  };
207
- declare const createFakeQueryPersistenceStore: () => FakeQueryPersistenceStoreHandle;
208
-
209
- declare const expectAndFlush: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
210
- status: number;
211
- statusText: string;
203
+ export declare const createFakeQueryPersistenceStore: () => FakeQueryPersistenceStoreHandle;
204
+ export declare const expectAndFlush: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
205
+ status: number;
206
+ statusText: string;
212
207
  }) => void;
213
- declare const expectFlushAndWait: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
214
- status: number;
215
- statusText: string;
208
+ export declare const expectFlushAndWait: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
209
+ status: number;
210
+ statusText: string;
216
211
  }) => void;
217
-
218
212
  /** A scripted stand-in for `socket.io-client`'s `io`, driven from a spec. */
219
- type WebSocketTestDouble = {
220
- /** Pass this as `createWebSocketClient({ io })`. */
221
- io: WebSocketClientIo;
222
- /** The url and options the client called the factory with, or `null` until it did. */
223
- connection: () => {
224
- url: string;
225
- transports: string[] | undefined;
226
- } | null;
227
- /** The `withCredentials` the client asked the factory for, or `null` until it called it. */
228
- withCredentials: () => boolean | null;
229
- /** The auth payload of every handshake, oldest first - `null` for one made without `auth`. */
230
- handshakes: () => (object | null)[];
231
- /** Every message the client emitted, newest last - room joins and leaves included, buffered ones too. */
232
- sent: () => {
233
- event: string;
234
- data: unknown;
235
- }[];
236
- /** Every message that reached the server, newest last. An emit buffered while offline lands here on the next connect. */
237
- delivered: () => {
238
- event: string;
239
- data: unknown;
240
- }[];
241
- /**
242
- * Whether the client asked the socket to connect, and whether it has since disconnected it. `active` is
243
- * socket.io's own flag - `false` after a server disconnect or a rejected handshake, until the next
244
- * `connect()`; `connectCalls` counts every `connect()`.
245
- */
246
- state: () => {
247
- connectRequested: boolean;
248
- disconnected: boolean;
249
- active: boolean;
250
- connectCalls: number;
251
- };
252
- /**
253
- * Complete the handshake: flush every buffered emit, then turn the client's `isConnected` true. Pass
254
- * `recovered: true` for a connection-state-recovered reconnect, in which the server kept the rooms.
255
- */
256
- serverConnect: (options?: {
257
- recovered?: boolean;
258
- }) => void;
259
- /**
260
- * Drop the connection, so `isConnected` turns false. The default reason, `'transport close'`, is one
261
- * socket.io recovers from itself; `'io server disconnect'` is the server kicking the socket, after which
262
- * socket.io only reconnects when `connect()` is called again.
263
- */
264
- serverDisconnect: (options?: {
265
- reason?: 'transport close' | 'io server disconnect';
266
- }) => void;
267
- /**
268
- * Reject the handshake the way a server middleware does: the `auth` payload is recorded in
269
- * {@link handshakes}, `connect_error` fires and the socket stops reconnecting on its own.
270
- */
271
- serverRejectHandshake: (message?: string) => void;
272
- /**
273
- * Let the ping expire: the socket still reports itself connected, but buffers every emit like socket.io
274
- * does until it notices and closes. Follow it with {@link serverDisconnect}.
275
- */
276
- serverPingExpire: () => void;
277
- /** Deliver a message the way the server would. */
278
- serverSend: (message: SocketMessageView) => void;
279
- /** Deliver a raw frame, for the malformed-payload path. */
280
- serverSendRaw: (frame: string) => void;
213
+ export type WebSocketTestDouble = {
214
+ /** Pass this as `createWebSocketClient({ io })`. */
215
+ io: WebSocketClientIo;
216
+ /** The url and options the client called the factory with, or `null` until it did. */
217
+ connection: () => {
218
+ url: string;
219
+ transports: string[] | undefined;
220
+ } | null;
221
+ /** The `withCredentials` the client asked the factory for, or `null` until it called it. */
222
+ withCredentials: () => boolean | null;
223
+ /** The auth payload of every handshake, oldest first - `null` for one made without `auth`. */
224
+ handshakes: () => (object | null)[];
225
+ /** Every message the client emitted, newest last - room joins and leaves included, buffered ones too. */
226
+ sent: () => {
227
+ event: string;
228
+ data: unknown;
229
+ }[];
230
+ /** Every message that reached the server, newest last. An emit buffered while offline lands here on the next connect. */
231
+ delivered: () => {
232
+ event: string;
233
+ data: unknown;
234
+ }[];
235
+ /**
236
+ * Whether the client asked the socket to connect, and whether it has since disconnected it. `active` is
237
+ * socket.io's own flag - `false` after a server disconnect or a rejected handshake, until the next
238
+ * `connect()`; `connectCalls` counts every `connect()`.
239
+ */
240
+ state: () => {
241
+ connectRequested: boolean;
242
+ disconnected: boolean;
243
+ active: boolean;
244
+ connectCalls: number;
245
+ };
246
+ /**
247
+ * Complete the handshake: flush every buffered emit, then turn the client's `isConnected` true. Pass
248
+ * `recovered: true` for a connection-state-recovered reconnect, in which the server kept the rooms.
249
+ */
250
+ serverConnect: (options?: {
251
+ recovered?: boolean;
252
+ }) => void;
253
+ /**
254
+ * Drop the connection, so `isConnected` turns false. The default reason, `'transport close'`, is one
255
+ * socket.io recovers from itself; `'io server disconnect'` is the server kicking the socket, after which
256
+ * socket.io only reconnects when `connect()` is called again.
257
+ */
258
+ serverDisconnect: (options?: {
259
+ reason?: 'transport close' | 'io server disconnect';
260
+ }) => void;
261
+ /**
262
+ * Reject the handshake the way a server middleware does: the `auth` payload is recorded in
263
+ * {@link handshakes}, `connect_error` fires and the socket stops reconnecting on its own.
264
+ */
265
+ serverRejectHandshake: (message?: string) => void;
266
+ /**
267
+ * Let the ping expire: the socket still reports itself connected, but buffers every emit like socket.io
268
+ * does until it notices and closes. Follow it with {@link serverDisconnect}.
269
+ */
270
+ serverPingExpire: () => void;
271
+ /** Deliver a message the way the server would. */
272
+ serverSend: (message: SocketMessageView) => void;
273
+ /** Deliver a raw frame, for the malformed-payload path. */
274
+ serverSendRaw: (frame: string) => void;
281
275
  };
282
276
  /**
283
277
  * A socket io test double, for specs around `createWebSocketClient` - which takes its `io` factory as
@@ -288,13 +282,12 @@ type WebSocketTestDouble = {
288
282
  * the `connect` listener. That split is what makes "joined a room while disconnected, and it was
289
283
  * re-joined after a reconnect" testable.
290
284
  */
291
- declare const createWebSocketTestDouble: () => WebSocketTestDouble;
292
-
293
- type MintTestTokenOptions = {
294
- /** @default 900000 (15 minutes) */
295
- expiresInMs?: number;
296
- /** Extra payload claims, e.g. the user id or roles `bearerData()` and the auth guards read. */
297
- claims?: Record<string, unknown>;
285
+ export declare const createWebSocketTestDouble: () => WebSocketTestDouble;
286
+ export type MintTestTokenOptions = {
287
+ /** @default 900000 (15 minutes) */
288
+ expiresInMs?: number;
289
+ /** Extra payload claims, e.g. the user id or roles `bearerData()` and the auth guards read. */
290
+ claims?: Record<string, unknown>;
298
291
  };
299
292
  /**
300
293
  * Mints an unsigned JWT the bearer auth provider can decode, with `iat`, `exp` and a unique `jti`.
@@ -305,7 +298,5 @@ type MintTestTokenOptions = {
305
298
  * TestBed.runInInjectionContext(() => authProviderRef.inject().setTokens(mintTestToken({ claims: { sub: '1' } }), 'refresh'));
306
299
  * ```
307
300
  */
308
- declare const mintTestToken: (options?: MintTestTokenOptions) => string;
309
-
310
- export { createFakeQueryPersistenceStore, createWebSocketTestDouble, expectAndFlush, expectFlushAndWait, flushMultiTabSync, installFakeBroadcastChannel, installFakeWebLocks, mintTestToken, setupAuthTest, setupQueryTest };
311
- export type { AnyFeatureBuilder, AuthTestFeatureBuilder, AuthTestQueryBuilders, AuthTestSetup, AuthTestSetupConfig, FakeBroadcastChannelHandle, FakeBroadcastMessage, FakeQueryPersistenceStoreHandle, FakeWebLocksHandle, MintTestTokenOptions, QueryTestSetup, QueryTestSetupConfig, WebSocketTestDouble };
301
+ export declare const mintTestToken: (options?: MintTestTokenOptions) => string;
302
+ export {};