@ethlete/query 6.0.0-next.6 → 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.
- package/CHANGELOG.md +939 -0
- package/fesm2022/ethlete-query-devtools-contract.mjs +1 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs +411 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -0
- package/fesm2022/ethlete-query-testing.mjs +548 -169
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +14680 -7172
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/deprecate-legacy-queries/migration.js +85 -0
- package/generators/deprecate-legacy-queries/schema.json +26 -0
- package/generators/generators.json +30 -0
- package/generators/migrate-from-ngrx-toolkit/args-assignability.js +140 -0
- package/generators/migrate-from-ngrx-toolkit/consumers.js +521 -0
- package/generators/migrate-from-ngrx-toolkit/creators.js +301 -0
- package/generators/migrate-from-ngrx-toolkit/facade.js +295 -0
- package/generators/migrate-from-ngrx-toolkit/feature.js +513 -0
- package/generators/migrate-from-ngrx-toolkit/migration.js +140 -0
- package/generators/migrate-from-ngrx-toolkit/report.js +154 -0
- package/generators/migrate-from-ngrx-toolkit/schema.json +42 -0
- package/generators/migrate-from-ngrx-toolkit/store-removal.js +633 -0
- package/generators/migrate-from-ngrx-toolkit/ts-edits.js +128 -0
- package/generators/migrate-query-client-features/migration.js +144 -0
- package/generators/migrate-query-client-features/report.js +136 -0
- package/generators/migrate-query-client-features/schema.json +26 -0
- package/generators/migrate-query-opt-in-features/migration.js +195 -0
- package/generators/migrate-query-opt-in-features/report.js +143 -0
- package/generators/migrate-query-opt-in-features/schema.json +31 -0
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +160 -0
- package/generators/migrate-to-query-v3/cleanup-migration.js +247 -125
- package/generators/migrate-to-query-v3/http-client-check.js +93 -0
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +129 -40
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +274 -152
- package/generators/migrate-to-query-v3/migration-scope.js +40 -0
- package/generators/migrate-to-query-v3/migration.js +19 -10
- package/generators/migrate-to-query-v3/module-graph.js +165 -0
- package/generators/migrate-to-query-v3/query-client-migration.js +342 -160
- package/generators/migrate-to-query-v3/rename-symbols.js +233 -0
- package/generators/migrate-to-query-v3/report.js +40 -7
- package/generators/migrate-to-query-v3/schema.json +12 -0
- package/generators/migrate-to-query-v3/shared.js +97 -3
- package/generators/move-devtools-contract-imports/migration.js +155 -0
- package/generators/move-devtools-contract-imports/moved-names.js +153 -0
- package/generators/move-devtools-contract-imports/schema.json +26 -0
- package/generators/prep-for-query-v3/migration.js +465 -65
- package/generators/report-legacy-query-apis/migration.js +256 -0
- package/generators/report-legacy-query-apis/schema.json +25 -0
- package/migrations/query-field-default-types.md +43 -0
- package/migrations/search-query-field-string.md +43 -0
- package/migrations.json +87 -0
- package/package.json +34 -14
- package/types/ethlete-query-devtools-contract.d.ts +2 -0
- package/types/ethlete-query-ngrx-toolkit.d.ts +131 -0
- package/types/ethlete-query-testing.d.ts +295 -160
- package/types/ethlete-query.d.ts +9755 -3682
|
@@ -1,178 +1,557 @@
|
|
|
1
|
-
import { DestroyRef, ErrorHandler, Injector } from
|
|
2
|
-
import { TestBed } from
|
|
3
|
-
import { createBearerAuthProvider,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { provideRouter } from
|
|
7
|
-
|
|
8
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
|
+
import { DestroyRef, ErrorHandler, Injector } from "@angular/core";
|
|
2
|
+
import { TestBed } from "@angular/core/testing";
|
|
3
|
+
import { createBearerAuthProvider, createDeleteQuery, createGetQuery, createPatchQuery, createPostQuery, createPutQuery, createQueryClient, isQueryErrorResponse, withAuthenticationQuery, withRefreshQuery } from "@ethlete/query";
|
|
4
|
+
import { HttpClient, HttpErrorResponse, provideHttpClient } from "@angular/common/http";
|
|
5
|
+
import { HttpTestingController, provideHttpClientTesting } from "@angular/common/http/testing";
|
|
6
|
+
import { provideRouter } from "@angular/router";
|
|
9
7
|
const expectAndFlush = (httpTesting, url, response, statusOpts) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
req.flush(response);
|
|
16
|
-
}
|
|
8
|
+
const req = httpTesting.expectOne(url);
|
|
9
|
+
if (statusOpts) req.flush(response, statusOpts);
|
|
10
|
+
else req.flush(response);
|
|
17
11
|
};
|
|
18
12
|
const expectFlushAndWait = (httpTesting, url, response, statusOpts) => {
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
expectAndFlush(httpTesting, url, response, statusOpts);
|
|
14
|
+
TestBed.tick();
|
|
21
15
|
};
|
|
22
|
-
|
|
23
16
|
const setupAuthTest = (config) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
return {
|
|
92
|
-
auth,
|
|
93
|
-
login: loginHelper,
|
|
94
|
-
refresh: refreshHelper,
|
|
95
|
-
makeSecureRequest,
|
|
96
|
-
};
|
|
17
|
+
const { querySetup, loginPath = "/auth/login", refreshPath = "/auth/refresh", autoRetryOn401 = true, extractLoginTokens = (response) => ({
|
|
18
|
+
accessToken: response.accessToken,
|
|
19
|
+
refreshToken: response.refreshToken
|
|
20
|
+
}), extractRefreshTokens = (response) => ({
|
|
21
|
+
accessToken: response.accessToken,
|
|
22
|
+
refreshToken: response.refreshToken
|
|
23
|
+
}), buildRefreshArgs, features, bearerDecryptFn, refreshStrategy, minRefreshInterval, refreshIfExpired, expiresInPropertyName, onRefreshFailure } = config;
|
|
24
|
+
const login = querySetup.createPost(loginPath, {});
|
|
25
|
+
const refresh = querySetup.createPost(refreshPath, {});
|
|
26
|
+
const { inject: injectAuth } = createBearerAuthProvider({
|
|
27
|
+
name: "test-auth",
|
|
28
|
+
queryClientRef: querySetup.queryClientRef,
|
|
29
|
+
queries: [withAuthenticationQuery("login", {
|
|
30
|
+
queryCreator: login,
|
|
31
|
+
extractTokens: extractLoginTokens
|
|
32
|
+
}), withRefreshQuery("refresh", {
|
|
33
|
+
queryCreator: refresh,
|
|
34
|
+
extractTokens: extractRefreshTokens,
|
|
35
|
+
buildArgs: buildRefreshArgs,
|
|
36
|
+
autoRetryOn401,
|
|
37
|
+
refreshStrategy,
|
|
38
|
+
minRefreshInterval,
|
|
39
|
+
refreshIfExpired,
|
|
40
|
+
expiresInPropertyName,
|
|
41
|
+
onRefreshFailure
|
|
42
|
+
})],
|
|
43
|
+
features: features ?? [],
|
|
44
|
+
bearerDecryptFn
|
|
45
|
+
});
|
|
46
|
+
const auth = TestBed.runInInjectionContext(() => {
|
|
47
|
+
const provider = injectAuth();
|
|
48
|
+
if (!provider) throw new Error("Failed to create auth provider in test setup");
|
|
49
|
+
return provider;
|
|
50
|
+
});
|
|
51
|
+
const loginHelper = (credentials, response) => {
|
|
52
|
+
TestBed.runInInjectionContext(() => {
|
|
53
|
+
auth.queries.login.execute({ body: credentials });
|
|
54
|
+
});
|
|
55
|
+
const fullUrl = `${querySetup.baseUrl}${loginPath}`;
|
|
56
|
+
expectFlushAndWait(querySetup.httpTesting, fullUrl, response);
|
|
57
|
+
};
|
|
58
|
+
const refreshHelper = (token, response) => {
|
|
59
|
+
TestBed.runInInjectionContext(() => {
|
|
60
|
+
auth.queries.refresh.execute(buildRefreshArgs?.(token) ?? { body: { token } });
|
|
61
|
+
});
|
|
62
|
+
const fullUrl = `${querySetup.baseUrl}${refreshPath}`;
|
|
63
|
+
expectFlushAndWait(querySetup.httpTesting, fullUrl, response);
|
|
64
|
+
};
|
|
65
|
+
const makeSecureRequest = (route) => {
|
|
66
|
+
TestBed.runInInjectionContext(() => {
|
|
67
|
+
querySetup.queryClient.repository.request({
|
|
68
|
+
route,
|
|
69
|
+
method: "GET",
|
|
70
|
+
isSecure: true,
|
|
71
|
+
consumerDestroyRef: TestBed.inject(DestroyRef)
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
auth,
|
|
77
|
+
login: loginHelper,
|
|
78
|
+
refresh: refreshHelper,
|
|
79
|
+
makeSecureRequest
|
|
80
|
+
};
|
|
97
81
|
};
|
|
98
|
-
|
|
82
|
+
const nodeSetImmediate = globalThis.setImmediate;
|
|
83
|
+
const scheduleTask = nodeSetImmediate ? nodeSetImmediate.bind(globalThis) : setTimeout.bind(globalThis);
|
|
84
|
+
const installFakeBroadcastChannel = () => {
|
|
85
|
+
const original = globalThis.BroadcastChannel;
|
|
86
|
+
const instances = /* @__PURE__ */ new Set();
|
|
87
|
+
const posted = [];
|
|
88
|
+
class FakeBroadcastChannel {
|
|
89
|
+
constructor(name) {
|
|
90
|
+
this.name = name;
|
|
91
|
+
this.closed = false;
|
|
92
|
+
this.onmessage = null;
|
|
93
|
+
instances.add(this);
|
|
94
|
+
}
|
|
95
|
+
postMessage(data) {
|
|
96
|
+
if (this.closed) throw new DOMException("Channel is closed", "InvalidStateError");
|
|
97
|
+
const clone = structuredClone(data);
|
|
98
|
+
posted.push({
|
|
99
|
+
channel: this.name,
|
|
100
|
+
data: clone
|
|
101
|
+
});
|
|
102
|
+
for (const instance of instances) {
|
|
103
|
+
if (instance === this || instance.closed || instance.name !== this.name) continue;
|
|
104
|
+
scheduleTask(() => {
|
|
105
|
+
if (instance.closed) return;
|
|
106
|
+
instance.onmessage?.({ data: structuredClone(clone) });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
close() {
|
|
111
|
+
this.closed = true;
|
|
112
|
+
instances.delete(this);
|
|
113
|
+
}
|
|
114
|
+
addEventListener() {
|
|
115
|
+
throw new Error("The fake BroadcastChannel only supports `onmessage`.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
globalThis.BroadcastChannel = FakeBroadcastChannel;
|
|
119
|
+
return {
|
|
120
|
+
posted,
|
|
121
|
+
restore: () => {
|
|
122
|
+
for (const instance of [...instances]) instance.closed = true;
|
|
123
|
+
instances.clear();
|
|
124
|
+
globalThis.BroadcastChannel = original;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const installFakeWebLocks = () => {
|
|
129
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(navigator, "locks");
|
|
130
|
+
const holders = /* @__PURE__ */ new Map();
|
|
131
|
+
const queues = /* @__PURE__ */ new Map();
|
|
132
|
+
const abortError = () => new DOMException("The lock request was aborted", "AbortError");
|
|
133
|
+
const pump = (name) => {
|
|
134
|
+
if (holders.has(name)) return;
|
|
135
|
+
const waiter = queues.get(name)?.shift();
|
|
136
|
+
if (!waiter) return;
|
|
137
|
+
waiter.granted = true;
|
|
138
|
+
holders.set(name, waiter);
|
|
139
|
+
const settle = (settleWaiter) => {
|
|
140
|
+
if (holders.get(name) === waiter) holders.delete(name);
|
|
141
|
+
waiter.releaseSignal();
|
|
142
|
+
settleWaiter();
|
|
143
|
+
pump(name);
|
|
144
|
+
};
|
|
145
|
+
try {
|
|
146
|
+
Promise.resolve(waiter.callback({
|
|
147
|
+
name,
|
|
148
|
+
mode: "exclusive"
|
|
149
|
+
})).then((value) => settle(() => waiter.resolve(value)), (error) => settle(() => waiter.reject(error)));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
settle(() => waiter.reject(error));
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const request = (name, optionsOrCallback, maybeCallback) => {
|
|
155
|
+
const options = typeof optionsOrCallback === "function" ? {} : optionsOrCallback ?? {};
|
|
156
|
+
const callback = typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback;
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
if (options.signal?.aborted) {
|
|
159
|
+
reject(abortError());
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const waiter = {
|
|
163
|
+
callback,
|
|
164
|
+
resolve,
|
|
165
|
+
reject,
|
|
166
|
+
granted: false,
|
|
167
|
+
releaseSignal: () => void 0
|
|
168
|
+
};
|
|
169
|
+
const signal = options.signal;
|
|
170
|
+
if (signal) {
|
|
171
|
+
const onAbort = () => {
|
|
172
|
+
waiter.releaseSignal();
|
|
173
|
+
if (waiter.granted) return;
|
|
174
|
+
const queue = queues.get(name);
|
|
175
|
+
if (queue) queues.set(name, queue.filter((entry) => entry !== waiter));
|
|
176
|
+
reject(abortError());
|
|
177
|
+
};
|
|
178
|
+
signal.addEventListener("abort", onAbort);
|
|
179
|
+
waiter.releaseSignal = () => {
|
|
180
|
+
waiter.releaseSignal = () => void 0;
|
|
181
|
+
signal.removeEventListener("abort", onAbort);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (options.steal) {
|
|
185
|
+
queueMicrotask(() => {
|
|
186
|
+
const holder = holders.get(name);
|
|
187
|
+
if (holder) {
|
|
188
|
+
holders.delete(name);
|
|
189
|
+
holder.reject(abortError());
|
|
190
|
+
}
|
|
191
|
+
queues.set(name, [waiter, ...queues.get(name) ?? []]);
|
|
192
|
+
pump(name);
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (options.ifAvailable) {
|
|
197
|
+
queueMicrotask(() => {
|
|
198
|
+
if (holders.has(name) || (queues.get(name)?.length ?? 0) > 0) {
|
|
199
|
+
waiter.releaseSignal();
|
|
200
|
+
Promise.resolve(callback(null)).then(resolve, reject);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
queues.set(name, [waiter]);
|
|
204
|
+
pump(name);
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
queues.set(name, [...queues.get(name) ?? [], waiter]);
|
|
209
|
+
queueMicrotask(() => pump(name));
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
const query = () => Promise.resolve({
|
|
213
|
+
held: [...holders.keys()].map((name) => ({
|
|
214
|
+
name,
|
|
215
|
+
mode: "exclusive"
|
|
216
|
+
})),
|
|
217
|
+
pending: [...queues.entries()].flatMap(([name, waiters]) => waiters.map(() => ({
|
|
218
|
+
name,
|
|
219
|
+
mode: "exclusive"
|
|
220
|
+
})))
|
|
221
|
+
});
|
|
222
|
+
Object.defineProperty(navigator, "locks", {
|
|
223
|
+
value: {
|
|
224
|
+
request,
|
|
225
|
+
query
|
|
226
|
+
},
|
|
227
|
+
configurable: true,
|
|
228
|
+
writable: true
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
heldNames: () => [...holders.keys()],
|
|
232
|
+
pendingNames: () => [...queues.entries()].filter(([, queue]) => queue.length > 0).map(([name]) => name),
|
|
233
|
+
restore: () => {
|
|
234
|
+
for (const waiter of [...holders.values(), ...[...queues.values()].flat()]) waiter.releaseSignal();
|
|
235
|
+
holders.clear();
|
|
236
|
+
queues.clear();
|
|
237
|
+
if (originalDescriptor) Object.defineProperty(navigator, "locks", originalDescriptor);
|
|
238
|
+
else delete navigator.locks;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
const flushMultiTabSync = () => new Promise((resolve) => scheduleTask(resolve));
|
|
243
|
+
const quotaError = () => new DOMException("The quota has been exceeded.", "QuotaExceededError");
|
|
244
|
+
const createFakeQueryPersistenceStore = () => {
|
|
245
|
+
const store = /* @__PURE__ */ new Map();
|
|
246
|
+
const calls = {
|
|
247
|
+
loadIndex: 0,
|
|
248
|
+
read: 0,
|
|
249
|
+
write: 0,
|
|
250
|
+
remove: 0,
|
|
251
|
+
clear: 0
|
|
252
|
+
};
|
|
253
|
+
const deferredReads = [];
|
|
254
|
+
const deferredLoadIndexes = [];
|
|
255
|
+
let failingWrites = 0;
|
|
256
|
+
let writeError = null;
|
|
257
|
+
let failingRemoves = 0;
|
|
258
|
+
let removeError = null;
|
|
259
|
+
let loadIndexError = null;
|
|
260
|
+
let isDeferringReads = false;
|
|
261
|
+
let isDeferringLoadIndex = false;
|
|
262
|
+
const clone = (value) => structuredClone(value);
|
|
263
|
+
const loadIndex = async () => {
|
|
264
|
+
calls.loadIndex++;
|
|
265
|
+
if (loadIndexError !== null) {
|
|
266
|
+
const error = loadIndexError;
|
|
267
|
+
loadIndexError = null;
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
const snapshot = Array.from(store.values()).map(({ body: _body, ...meta }) => clone(meta));
|
|
271
|
+
if (isDeferringLoadIndex) await new Promise((resolve) => deferredLoadIndexes.push(resolve));
|
|
272
|
+
return snapshot;
|
|
273
|
+
};
|
|
274
|
+
const read = async (key) => {
|
|
275
|
+
calls.read++;
|
|
276
|
+
const entry = store.get(key);
|
|
277
|
+
const snapshot = entry ? {
|
|
278
|
+
body: clone(entry.body),
|
|
279
|
+
version: entry.version
|
|
280
|
+
} : null;
|
|
281
|
+
if (isDeferringReads) await new Promise((resolve) => deferredReads.push(resolve));
|
|
282
|
+
return snapshot;
|
|
283
|
+
};
|
|
284
|
+
const write = async (entries) => {
|
|
285
|
+
calls.write++;
|
|
286
|
+
if (failingWrites > 0) {
|
|
287
|
+
failingWrites--;
|
|
288
|
+
throw writeError ?? quotaError();
|
|
289
|
+
}
|
|
290
|
+
for (const entry of entries) store.set(entry.key, clone(entry));
|
|
291
|
+
};
|
|
292
|
+
const remove = async (keys) => {
|
|
293
|
+
calls.remove++;
|
|
294
|
+
if (failingRemoves > 0) {
|
|
295
|
+
failingRemoves--;
|
|
296
|
+
throw removeError ?? /* @__PURE__ */ new Error("The removal failed.");
|
|
297
|
+
}
|
|
298
|
+
for (const key of keys) store.delete(key);
|
|
299
|
+
};
|
|
300
|
+
const clear = async () => {
|
|
301
|
+
calls.clear++;
|
|
302
|
+
store.clear();
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
adapter: {
|
|
306
|
+
loadIndex,
|
|
307
|
+
read,
|
|
308
|
+
write,
|
|
309
|
+
remove,
|
|
310
|
+
clear,
|
|
311
|
+
isSupported: true
|
|
312
|
+
},
|
|
313
|
+
seed: (entries) => {
|
|
314
|
+
for (const entry of entries) store.set(entry.key, clone(entry));
|
|
315
|
+
},
|
|
316
|
+
entries: () => Array.from(store.values()).map(clone),
|
|
317
|
+
entry: (key) => {
|
|
318
|
+
const entry = store.get(key);
|
|
319
|
+
return entry ? clone(entry) : void 0;
|
|
320
|
+
},
|
|
321
|
+
calls: () => ({ ...calls }),
|
|
322
|
+
failNextWrites: (count, error) => {
|
|
323
|
+
failingWrites = count;
|
|
324
|
+
writeError = error ?? null;
|
|
325
|
+
},
|
|
326
|
+
failNextRemoves: (count, error) => {
|
|
327
|
+
failingRemoves = count;
|
|
328
|
+
removeError = error ?? null;
|
|
329
|
+
},
|
|
330
|
+
failNextLoadIndex: (error) => {
|
|
331
|
+
loadIndexError = error ?? quotaError();
|
|
332
|
+
},
|
|
333
|
+
deferReads: () => {
|
|
334
|
+
isDeferringReads = true;
|
|
335
|
+
},
|
|
336
|
+
flushReads: async () => {
|
|
337
|
+
isDeferringReads = false;
|
|
338
|
+
const pending = deferredReads.splice(0, deferredReads.length);
|
|
339
|
+
for (const resolve of pending) resolve();
|
|
340
|
+
await Promise.resolve();
|
|
341
|
+
},
|
|
342
|
+
deferLoadIndex: () => {
|
|
343
|
+
isDeferringLoadIndex = true;
|
|
344
|
+
},
|
|
345
|
+
flushLoadIndex: async () => {
|
|
346
|
+
isDeferringLoadIndex = false;
|
|
347
|
+
const pending = deferredLoadIndexes.splice(0, deferredLoadIndexes.length);
|
|
348
|
+
for (const resolve of pending) resolve();
|
|
349
|
+
await Promise.resolve();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
let originalError = null;
|
|
354
|
+
const filteredError = (...args) => {
|
|
355
|
+
const message = args[0];
|
|
356
|
+
if (message && typeof message === "object" && "name" in message && message.name === "HttpErrorResponse") return;
|
|
357
|
+
if (typeof message === "string" && message.includes("Failed to decrypt bearer token")) return;
|
|
358
|
+
if (typeof message === "string" && message.includes("Failed to extract tokens from")) return;
|
|
359
|
+
originalError?.(...args);
|
|
360
|
+
};
|
|
361
|
+
const installConsoleFilters = () => {
|
|
362
|
+
if (console.error !== filteredError) originalError = console.error;
|
|
363
|
+
console.error = filteredError;
|
|
364
|
+
};
|
|
365
|
+
const restoreConsole = () => {
|
|
366
|
+
if (originalError && console.error === filteredError) console.error = originalError;
|
|
367
|
+
originalError = null;
|
|
368
|
+
};
|
|
369
|
+
const isFailedRequestError = (error) => error instanceof HttpErrorResponse || isQueryErrorResponse(error);
|
|
370
|
+
const requestErrorHandler = { handleError: (error) => {
|
|
371
|
+
if (isFailedRequestError(error)) return;
|
|
372
|
+
throw error;
|
|
373
|
+
} };
|
|
99
374
|
const setupQueryTest = (config) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
useValue: { handleError: () => undefined },
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
TestBed.configureTestingModule({ providers });
|
|
142
|
-
const queryClientRef = createQueryClient({ baseUrl, name });
|
|
143
|
-
const setup = TestBed.runInInjectionContext(() => {
|
|
144
|
-
const [, inject] = queryClientRef;
|
|
145
|
-
const queryClient = inject();
|
|
146
|
-
if (!queryClient) {
|
|
147
|
-
throw new Error('Failed to create query client in test setup');
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
queryClient,
|
|
151
|
-
queryClientRef,
|
|
152
|
-
httpClient: TestBed.inject(HttpClient),
|
|
153
|
-
httpTesting: TestBed.inject(HttpTestingController),
|
|
154
|
-
injector: TestBed.inject(Injector),
|
|
155
|
-
baseUrl,
|
|
156
|
-
createGet: createGetQuery(queryClientRef),
|
|
157
|
-
createPost: createPostQuery(queryClientRef),
|
|
158
|
-
createPut: createPutQuery(queryClientRef),
|
|
159
|
-
createPatch: createPatchQuery(queryClientRef),
|
|
160
|
-
createDelete: createDeleteQuery(queryClientRef),
|
|
161
|
-
};
|
|
162
|
-
});
|
|
163
|
-
// Restore console.warn and console.error when tests are done
|
|
164
|
-
const restoreConsole = () => {
|
|
165
|
-
console.warn = originalWarn;
|
|
166
|
-
console.error = originalError;
|
|
167
|
-
};
|
|
168
|
-
// Store the restore function for cleanup
|
|
169
|
-
setup._restoreConsole = restoreConsole;
|
|
170
|
-
return setup;
|
|
375
|
+
const baseUrl = config?.baseUrl ?? "https://api.test.com";
|
|
376
|
+
const name = config?.name ?? "test";
|
|
377
|
+
const mockErrorHandler = config?.mockErrorHandler ?? true;
|
|
378
|
+
installConsoleFilters();
|
|
379
|
+
const providers = [
|
|
380
|
+
provideHttpClient(),
|
|
381
|
+
provideHttpClientTesting(),
|
|
382
|
+
provideRouter([])
|
|
383
|
+
];
|
|
384
|
+
if (mockErrorHandler) providers.push({
|
|
385
|
+
provide: ErrorHandler,
|
|
386
|
+
useValue: mockErrorHandler === "all" ? { handleError: () => void 0 } : requestErrorHandler
|
|
387
|
+
});
|
|
388
|
+
TestBed.configureTestingModule({ providers });
|
|
389
|
+
const queryClientRef = createQueryClient({
|
|
390
|
+
baseUrl,
|
|
391
|
+
name
|
|
392
|
+
});
|
|
393
|
+
return TestBed.runInInjectionContext(() => {
|
|
394
|
+
const { inject } = queryClientRef;
|
|
395
|
+
const queryClient = inject();
|
|
396
|
+
TestBed.inject(DestroyRef).onDestroy(restoreConsole);
|
|
397
|
+
if (!queryClient) throw new Error("Failed to create query client in test setup");
|
|
398
|
+
return {
|
|
399
|
+
queryClient,
|
|
400
|
+
queryClientRef,
|
|
401
|
+
httpClient: TestBed.inject(HttpClient),
|
|
402
|
+
httpTesting: TestBed.inject(HttpTestingController),
|
|
403
|
+
injector: TestBed.inject(Injector),
|
|
404
|
+
baseUrl,
|
|
405
|
+
createGet: createGetQuery(queryClientRef),
|
|
406
|
+
createPost: createPostQuery(queryClientRef),
|
|
407
|
+
createPut: createPutQuery(queryClientRef),
|
|
408
|
+
createPatch: createPatchQuery(queryClientRef),
|
|
409
|
+
createDelete: createDeleteQuery(queryClientRef),
|
|
410
|
+
restoreConsole
|
|
411
|
+
};
|
|
412
|
+
});
|
|
171
413
|
};
|
|
414
|
+
const createWebSocketTestDouble = () => {
|
|
415
|
+
const sent = [];
|
|
416
|
+
const delivered = [];
|
|
417
|
+
const buffered = [];
|
|
418
|
+
const listeners = {
|
|
419
|
+
connect: [],
|
|
420
|
+
disconnect: [],
|
|
421
|
+
connect_error: []
|
|
422
|
+
};
|
|
423
|
+
const anyListeners = [];
|
|
424
|
+
const outgoingListeners = [];
|
|
425
|
+
let connection = null;
|
|
426
|
+
let withCredentials = null;
|
|
427
|
+
let auth;
|
|
428
|
+
const handshakes = [];
|
|
429
|
+
let connectRequested = false;
|
|
430
|
+
let disconnected = false;
|
|
431
|
+
let connected = false;
|
|
432
|
+
let active = false;
|
|
433
|
+
let connectCalls = 0;
|
|
434
|
+
let pingExpired = false;
|
|
435
|
+
let recovered = false;
|
|
436
|
+
const deliver = (message) => {
|
|
437
|
+
for (const listener of outgoingListeners) listener(message.event, message.data);
|
|
438
|
+
delivered.push(message);
|
|
439
|
+
};
|
|
440
|
+
const recordHandshake = () => {
|
|
441
|
+
if (auth) auth((data) => handshakes.push(data));
|
|
442
|
+
else handshakes.push(null);
|
|
443
|
+
};
|
|
444
|
+
const disconnect = (reason) => {
|
|
445
|
+
connected = false;
|
|
446
|
+
pingExpired = false;
|
|
447
|
+
for (const listener of listeners.disconnect) listener(reason);
|
|
448
|
+
};
|
|
449
|
+
const socket = {
|
|
450
|
+
connect: () => {
|
|
451
|
+
connectRequested = true;
|
|
452
|
+
active = true;
|
|
453
|
+
connectCalls++;
|
|
454
|
+
},
|
|
455
|
+
disconnect: () => {
|
|
456
|
+
disconnected = true;
|
|
457
|
+
active = false;
|
|
458
|
+
if (connected) disconnect("io client disconnect");
|
|
459
|
+
},
|
|
460
|
+
emit: (event, data) => {
|
|
461
|
+
sent.push({
|
|
462
|
+
event,
|
|
463
|
+
data
|
|
464
|
+
});
|
|
465
|
+
if (connected && !pingExpired) deliver({
|
|
466
|
+
event,
|
|
467
|
+
data
|
|
468
|
+
});
|
|
469
|
+
else buffered.push({
|
|
470
|
+
event,
|
|
471
|
+
data
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
on: (event, listener) => void listeners[event].push(listener),
|
|
475
|
+
onAny: (listener) => void anyListeners.push(listener),
|
|
476
|
+
onAnyOutgoing: (listener) => void outgoingListeners.push(listener),
|
|
477
|
+
get recovered() {
|
|
478
|
+
return recovered;
|
|
479
|
+
},
|
|
480
|
+
get active() {
|
|
481
|
+
return active;
|
|
482
|
+
},
|
|
483
|
+
get sendBuffer() {
|
|
484
|
+
return buffered;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
return {
|
|
488
|
+
io: (url, options) => {
|
|
489
|
+
connection = {
|
|
490
|
+
url,
|
|
491
|
+
transports: options.transports
|
|
492
|
+
};
|
|
493
|
+
withCredentials = options.withCredentials;
|
|
494
|
+
auth = options.auth;
|
|
495
|
+
return socket;
|
|
496
|
+
},
|
|
497
|
+
connection: () => connection,
|
|
498
|
+
withCredentials: () => withCredentials,
|
|
499
|
+
handshakes: () => [...handshakes],
|
|
500
|
+
sent: () => [...sent],
|
|
501
|
+
delivered: () => [...delivered],
|
|
502
|
+
state: () => ({
|
|
503
|
+
connectRequested,
|
|
504
|
+
disconnected,
|
|
505
|
+
active,
|
|
506
|
+
connectCalls
|
|
507
|
+
}),
|
|
508
|
+
serverConnect: (options) => {
|
|
509
|
+
recordHandshake();
|
|
510
|
+
active = true;
|
|
511
|
+
recovered = options?.recovered ?? false;
|
|
512
|
+
connected = true;
|
|
513
|
+
pingExpired = false;
|
|
514
|
+
for (const message of buffered.splice(0)) deliver(message);
|
|
515
|
+
for (const listener of listeners.connect) listener();
|
|
516
|
+
},
|
|
517
|
+
serverDisconnect: (options) => {
|
|
518
|
+
const reason = options?.reason ?? "transport close";
|
|
519
|
+
if (reason === "io server disconnect") active = false;
|
|
520
|
+
disconnect(reason);
|
|
521
|
+
},
|
|
522
|
+
serverRejectHandshake: (message) => {
|
|
523
|
+
recordHandshake();
|
|
524
|
+
active = false;
|
|
525
|
+
for (const listener of listeners.connect_error) listener(new Error(message ?? "unauthorized"));
|
|
526
|
+
},
|
|
527
|
+
serverPingExpire: () => void (pingExpired = true),
|
|
528
|
+
serverSend: (message) => {
|
|
529
|
+
const frame = JSON.stringify(message);
|
|
530
|
+
for (const listener of anyListeners) listener("message", frame);
|
|
531
|
+
},
|
|
532
|
+
serverSendRaw: (frame) => {
|
|
533
|
+
for (const listener of anyListeners) listener("message", frame);
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
};
|
|
537
|
+
const toBase64Url = (json) => {
|
|
538
|
+
return btoa(encodeURIComponent(json).replace(/%([0-9A-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
539
|
+
};
|
|
540
|
+
let mintCount = 0;
|
|
541
|
+
const mintTestToken = (options = {}) => {
|
|
542
|
+
const { expiresInMs = 9e5, claims = {} } = options;
|
|
543
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
544
|
+
const exp = Math.floor((Date.now() + expiresInMs) / 1e3);
|
|
545
|
+
return `${toBase64Url(JSON.stringify({
|
|
546
|
+
alg: "none",
|
|
547
|
+
typ: "JWT"
|
|
548
|
+
}))}.${toBase64Url(JSON.stringify({
|
|
549
|
+
iat,
|
|
550
|
+
exp,
|
|
551
|
+
jti: ++mintCount,
|
|
552
|
+
...claims
|
|
553
|
+
}))}.`;
|
|
554
|
+
};
|
|
555
|
+
export { createFakeQueryPersistenceStore, createWebSocketTestDouble, expectAndFlush, expectFlushAndWait, flushMultiTabSync, installFakeBroadcastChannel, installFakeWebLocks, mintTestToken, setupAuthTest, setupQueryTest };
|
|
172
556
|
|
|
173
|
-
|
|
174
|
-
* Generated bundle index. Do not edit.
|
|
175
|
-
*/
|
|
176
|
-
|
|
177
|
-
export { expectAndFlush, expectFlushAndWait, setupAuthTest, setupQueryTest };
|
|
178
|
-
//# sourceMappingURL=ethlete-query-testing.mjs.map
|
|
557
|
+
//# sourceMappingURL=ethlete-query-testing.mjs.map
|