@sylphx/lens-react 1.0.2
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/dist/context.d.ts +52 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/hooks.d.ts +190 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2876 -0
- package/package.json +47 -0
- package/src/context.tsx +90 -0
- package/src/hooks.test.tsx +358 -0
- package/src/hooks.ts +392 -0
- package/src/index.ts +30 -0
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sylphx/lens-react",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "React bindings for Lens API framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target browser && bun run build:types",
|
|
16
|
+
"build:types": "tsc --emitDeclarationOnly --outDir ./dist",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"test": "bun test"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"lens",
|
|
26
|
+
"react",
|
|
27
|
+
"hooks",
|
|
28
|
+
"reactive"
|
|
29
|
+
],
|
|
30
|
+
"author": "SylphxAI",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@sylphx/lens-client": "workspace:*"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@happy-dom/global-registrator": "^20.0.10",
|
|
40
|
+
"@testing-library/react": "^16.2.0",
|
|
41
|
+
"@types/react": "^18.3.12",
|
|
42
|
+
"happy-dom": "^17.4.7",
|
|
43
|
+
"react": "^18.3.1",
|
|
44
|
+
"react-dom": "^18.3.1",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sylphx/lens-react - Context Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides Lens client to React component tree.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LensClient } from "@sylphx/lens-client";
|
|
8
|
+
import { type ReactNode, createContext, useContext } from "react";
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Context
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Context for Lens client
|
|
16
|
+
* Using any for internal storage to avoid type constraint issues
|
|
17
|
+
*/
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const LensContext = createContext<LensClient<any, any> | null>(null);
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Provider
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export interface LensProviderProps {
|
|
26
|
+
/** Lens client instance */
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
client: LensClient<any, any>;
|
|
29
|
+
/** Children */
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Provides Lens client to component tree
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* import { createClient, http } from '@sylphx/lens-client';
|
|
39
|
+
* import { LensProvider } from '@sylphx/lens-react';
|
|
40
|
+
* import type { AppRouter } from './server';
|
|
41
|
+
*
|
|
42
|
+
* const client = createClient<AppRouter>({
|
|
43
|
+
* transport: http({ url: '/api' }),
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* function App() {
|
|
47
|
+
* return (
|
|
48
|
+
* <LensProvider client={client}>
|
|
49
|
+
* <UserProfile />
|
|
50
|
+
* </LensProvider>
|
|
51
|
+
* );
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function LensProvider({ client, children }: LensProviderProps) {
|
|
56
|
+
return <LensContext.Provider value={client}>{children}</LensContext.Provider>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Hook
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get Lens client from context
|
|
65
|
+
*
|
|
66
|
+
* @throws Error if used outside LensProvider
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* function UserProfile({ userId }: { userId: string }) {
|
|
71
|
+
* const client = useLensClient<AppRouter>();
|
|
72
|
+
* const { data } = useQuery(client.user.get({ id: userId }));
|
|
73
|
+
* return <h1>{data?.name}</h1>;
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
export function useLensClient<TRouter = any>(): LensClient<any, any> & TRouter {
|
|
79
|
+
const client = useContext(LensContext);
|
|
80
|
+
|
|
81
|
+
if (!client) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"useLensClient must be used within a <LensProvider>. " +
|
|
84
|
+
"Make sure to wrap your app with <LensProvider client={client}>.",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
return client as LensClient<any, any> & TRouter;
|
|
90
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for React Hooks (Operations-based API)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { signal } from "@sylphx/lens-client";
|
|
7
|
+
import type { MutationResult, QueryResult } from "@sylphx/lens-client";
|
|
8
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
9
|
+
import { useLazyQuery, useMutation, useQuery } from "./hooks";
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Mock QueryResult
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
function createMockQueryResult<T>(initialValue: T | null = null): QueryResult<T> & {
|
|
16
|
+
_setValue: (value: T) => void;
|
|
17
|
+
_setError: (error: Error) => void;
|
|
18
|
+
} {
|
|
19
|
+
let currentValue = initialValue;
|
|
20
|
+
let currentError: Error | null = null;
|
|
21
|
+
const subscribers: Array<(value: T) => void> = [];
|
|
22
|
+
let resolved = false;
|
|
23
|
+
let resolvePromise: ((value: T) => void) | null = null;
|
|
24
|
+
let rejectPromise: ((error: Error) => void) | null = null;
|
|
25
|
+
|
|
26
|
+
const promise = new Promise<T>((resolve, reject) => {
|
|
27
|
+
resolvePromise = resolve;
|
|
28
|
+
rejectPromise = reject;
|
|
29
|
+
if (initialValue !== null) {
|
|
30
|
+
resolved = true;
|
|
31
|
+
resolve(initialValue);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const result = {
|
|
36
|
+
get value() {
|
|
37
|
+
return currentValue;
|
|
38
|
+
},
|
|
39
|
+
signal: signal(currentValue),
|
|
40
|
+
loading: signal(initialValue === null),
|
|
41
|
+
error: signal<Error | null>(null),
|
|
42
|
+
subscribe(callback?: (data: T) => void): () => void {
|
|
43
|
+
if (callback) {
|
|
44
|
+
subscribers.push(callback);
|
|
45
|
+
if (currentValue !== null) {
|
|
46
|
+
callback(currentValue);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return () => {
|
|
50
|
+
const idx = subscribers.indexOf(callback!);
|
|
51
|
+
if (idx >= 0) subscribers.splice(idx, 1);
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
select() {
|
|
55
|
+
return result as unknown as QueryResult<T>;
|
|
56
|
+
},
|
|
57
|
+
then<TResult1 = T, TResult2 = never>(
|
|
58
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
59
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
|
60
|
+
): Promise<TResult1 | TResult2> {
|
|
61
|
+
return promise.then(onfulfilled, onrejected);
|
|
62
|
+
},
|
|
63
|
+
// Test helpers
|
|
64
|
+
_setValue(value: T) {
|
|
65
|
+
currentValue = value;
|
|
66
|
+
result.signal.value = value;
|
|
67
|
+
result.loading.value = false;
|
|
68
|
+
result.error.value = null;
|
|
69
|
+
subscribers.forEach((cb) => cb(value));
|
|
70
|
+
if (!resolved && resolvePromise) {
|
|
71
|
+
resolved = true;
|
|
72
|
+
resolvePromise(value);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
_setError(error: Error) {
|
|
76
|
+
currentError = error;
|
|
77
|
+
result.loading.value = false;
|
|
78
|
+
result.error.value = error;
|
|
79
|
+
if (!resolved && rejectPromise) {
|
|
80
|
+
resolved = true;
|
|
81
|
+
rejectPromise(error);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return result as QueryResult<T> & {
|
|
87
|
+
_setValue: (value: T) => void;
|
|
88
|
+
_setError: (error: Error) => void;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// Tests: useQuery
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
describe("useQuery", () => {
|
|
97
|
+
test("returns loading state initially", () => {
|
|
98
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>();
|
|
99
|
+
|
|
100
|
+
const { result } = renderHook(() => useQuery(mockQuery));
|
|
101
|
+
|
|
102
|
+
expect(result.current.loading).toBe(true);
|
|
103
|
+
expect(result.current.data).toBe(null);
|
|
104
|
+
expect(result.current.error).toBe(null);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("returns data when query resolves", async () => {
|
|
108
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>();
|
|
109
|
+
|
|
110
|
+
const { result } = renderHook(() => useQuery(mockQuery));
|
|
111
|
+
|
|
112
|
+
// Simulate data loading
|
|
113
|
+
act(() => {
|
|
114
|
+
mockQuery._setValue({ id: "123", name: "John" });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await waitFor(() => {
|
|
118
|
+
expect(result.current.loading).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(result.current.data).toEqual({ id: "123", name: "John" });
|
|
122
|
+
expect(result.current.error).toBe(null);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("returns error when query fails", async () => {
|
|
126
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>();
|
|
127
|
+
|
|
128
|
+
const { result } = renderHook(() => useQuery(mockQuery));
|
|
129
|
+
|
|
130
|
+
// Simulate error
|
|
131
|
+
act(() => {
|
|
132
|
+
mockQuery._setError(new Error("Query failed"));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await waitFor(() => {
|
|
136
|
+
expect(result.current.loading).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(result.current.error?.message).toBe("Query failed");
|
|
140
|
+
expect(result.current.data).toBe(null);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("skips query when skip option is true", () => {
|
|
144
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>();
|
|
145
|
+
|
|
146
|
+
const { result } = renderHook(() => useQuery(mockQuery, { skip: true }));
|
|
147
|
+
|
|
148
|
+
expect(result.current.loading).toBe(false);
|
|
149
|
+
expect(result.current.data).toBe(null);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("updates when query subscription emits", async () => {
|
|
153
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>();
|
|
154
|
+
|
|
155
|
+
const { result } = renderHook(() => useQuery(mockQuery));
|
|
156
|
+
|
|
157
|
+
// First value
|
|
158
|
+
act(() => {
|
|
159
|
+
mockQuery._setValue({ id: "123", name: "John" });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
expect(result.current.data?.name).toBe("John");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Update value via subscription
|
|
167
|
+
act(() => {
|
|
168
|
+
mockQuery._setValue({ id: "123", name: "Jane" });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await waitFor(() => {
|
|
172
|
+
expect(result.current.data?.name).toBe("Jane");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// =============================================================================
|
|
178
|
+
// Tests: useMutation
|
|
179
|
+
// =============================================================================
|
|
180
|
+
|
|
181
|
+
describe("useMutation", () => {
|
|
182
|
+
test("executes mutation and returns result", async () => {
|
|
183
|
+
const mutationFn = async (input: { name: string }): Promise<
|
|
184
|
+
MutationResult<{ id: string; name: string }>
|
|
185
|
+
> => {
|
|
186
|
+
return {
|
|
187
|
+
data: { id: "new-id", name: input.name },
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const { result } = renderHook(() => useMutation(mutationFn));
|
|
192
|
+
|
|
193
|
+
expect(result.current.loading).toBe(false);
|
|
194
|
+
expect(result.current.data).toBe(null);
|
|
195
|
+
|
|
196
|
+
let mutationResult: MutationResult<{ id: string; name: string }> | undefined;
|
|
197
|
+
await act(async () => {
|
|
198
|
+
mutationResult = await result.current.mutate({ name: "New User" });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(mutationResult?.data).toEqual({ id: "new-id", name: "New User" });
|
|
202
|
+
expect(result.current.data).toEqual({ id: "new-id", name: "New User" });
|
|
203
|
+
expect(result.current.loading).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("handles mutation error", async () => {
|
|
207
|
+
const mutationFn = async (_input: { name: string }): Promise<
|
|
208
|
+
MutationResult<{ id: string; name: string }>
|
|
209
|
+
> => {
|
|
210
|
+
throw new Error("Mutation failed");
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const { result } = renderHook(() => useMutation(mutationFn));
|
|
214
|
+
|
|
215
|
+
await act(async () => {
|
|
216
|
+
try {
|
|
217
|
+
await result.current.mutate({ name: "New User" });
|
|
218
|
+
} catch {
|
|
219
|
+
// Expected error
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(result.current.error?.message).toBe("Mutation failed");
|
|
224
|
+
expect(result.current.loading).toBe(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("shows loading state during mutation", async () => {
|
|
228
|
+
let resolveMutation: ((value: MutationResult<{ id: string }>) => void) | null = null;
|
|
229
|
+
const mutationFn = async (_input: { name: string }): Promise<
|
|
230
|
+
MutationResult<{ id: string }>
|
|
231
|
+
> => {
|
|
232
|
+
return new Promise((resolve) => {
|
|
233
|
+
resolveMutation = resolve;
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const { result } = renderHook(() => useMutation(mutationFn));
|
|
238
|
+
|
|
239
|
+
// Start mutation (don't await)
|
|
240
|
+
let mutationPromise: Promise<MutationResult<{ id: string }>> | undefined;
|
|
241
|
+
act(() => {
|
|
242
|
+
mutationPromise = result.current.mutate({ name: "New User" });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Should be loading
|
|
246
|
+
expect(result.current.loading).toBe(true);
|
|
247
|
+
|
|
248
|
+
// Resolve mutation
|
|
249
|
+
await act(async () => {
|
|
250
|
+
resolveMutation?.({ data: { id: "new-id" } });
|
|
251
|
+
await mutationPromise;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(result.current.loading).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("reset clears mutation state", async () => {
|
|
258
|
+
const mutationFn = async (input: { name: string }): Promise<
|
|
259
|
+
MutationResult<{ id: string; name: string }>
|
|
260
|
+
> => {
|
|
261
|
+
return { data: { id: "new-id", name: input.name } };
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const { result } = renderHook(() => useMutation(mutationFn));
|
|
265
|
+
|
|
266
|
+
await act(async () => {
|
|
267
|
+
await result.current.mutate({ name: "New User" });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(result.current.data).not.toBe(null);
|
|
271
|
+
|
|
272
|
+
act(() => {
|
|
273
|
+
result.current.reset();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(result.current.data).toBe(null);
|
|
277
|
+
expect(result.current.error).toBe(null);
|
|
278
|
+
expect(result.current.loading).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// =============================================================================
|
|
283
|
+
// Tests: useLazyQuery
|
|
284
|
+
// =============================================================================
|
|
285
|
+
|
|
286
|
+
describe("useLazyQuery", () => {
|
|
287
|
+
test("does not execute query on mount", () => {
|
|
288
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>();
|
|
289
|
+
|
|
290
|
+
const { result } = renderHook(() => useLazyQuery(mockQuery));
|
|
291
|
+
|
|
292
|
+
expect(result.current.loading).toBe(false);
|
|
293
|
+
expect(result.current.data).toBe(null);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("executes query when execute is called", async () => {
|
|
297
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>({
|
|
298
|
+
id: "123",
|
|
299
|
+
name: "John",
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const { result } = renderHook(() => useLazyQuery(mockQuery));
|
|
303
|
+
|
|
304
|
+
let queryResult: { id: string; name: string } | undefined;
|
|
305
|
+
await act(async () => {
|
|
306
|
+
queryResult = await result.current.execute();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
expect(queryResult).toEqual({ id: "123", name: "John" });
|
|
310
|
+
expect(result.current.data).toEqual({ id: "123", name: "John" });
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("handles query error", async () => {
|
|
314
|
+
// Create a mock query that rejects
|
|
315
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>();
|
|
316
|
+
|
|
317
|
+
const { result } = renderHook(() => useLazyQuery(mockQuery));
|
|
318
|
+
|
|
319
|
+
// Set error before execute
|
|
320
|
+
act(() => {
|
|
321
|
+
mockQuery._setError(new Error("Query failed"));
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Execute should throw
|
|
325
|
+
await act(async () => {
|
|
326
|
+
try {
|
|
327
|
+
await result.current.execute();
|
|
328
|
+
} catch {
|
|
329
|
+
// Expected error
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
expect(result.current.error?.message).toBe("Query failed");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("reset clears query state", async () => {
|
|
337
|
+
const mockQuery = createMockQueryResult<{ id: string; name: string }>({
|
|
338
|
+
id: "123",
|
|
339
|
+
name: "John",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const { result } = renderHook(() => useLazyQuery(mockQuery));
|
|
343
|
+
|
|
344
|
+
await act(async () => {
|
|
345
|
+
await result.current.execute();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
expect(result.current.data).not.toBe(null);
|
|
349
|
+
|
|
350
|
+
act(() => {
|
|
351
|
+
result.current.reset();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(result.current.data).toBe(null);
|
|
355
|
+
expect(result.current.error).toBe(null);
|
|
356
|
+
expect(result.current.loading).toBe(false);
|
|
357
|
+
});
|
|
358
|
+
});
|