@0xsequence/marketplace-sdk 0.8.5 → 0.8.6
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 +8 -0
- package/dist/{chunk-KCLMSSPS.js → chunk-6SEJI7YS.js} +165 -4
- package/dist/chunk-6SEJI7YS.js.map +1 -0
- package/dist/{chunk-ZVTG6US2.js → chunk-QMWMJVTX.js} +2 -2
- package/dist/{chunk-SFSFIGHM.js → chunk-TGFX3TMV.js} +4 -4
- package/dist/{chunk-EZFCQZHU.js → chunk-V3NVAVHV.js} +2 -2
- package/dist/index.css +168 -33
- package/dist/index.css.map +1 -1
- package/dist/react/_internal/databeat/index.js +2 -2
- package/dist/react/hooks/index.d.ts +246 -29
- package/dist/react/hooks/index.js +5 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +8 -4
- package/dist/react/ui/components/collectible-card/index.js +4 -4
- package/dist/react/ui/index.js +4 -4
- package/dist/react/ui/modals/_internal/components/actionModal/index.js +2 -2
- package/package.json +2 -1
- package/src/react/hooks/__tests__/useAutoSelectFeeOption.test.tsx +21 -75
- package/src/react/hooks/__tests__/useCurrencyBalance.test.tsx +4 -25
- package/src/react/hooks/index.ts +1 -0
- package/src/react/hooks/useFilterState.tsx +181 -0
- package/src/react/hooks/useFilters.tsx +24 -0
- package/src/react/ui/modals/_internal/components/switchChainModal/__tests__/SwitchChainModal.test.tsx +38 -58
- package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +3 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/chunk-KCLMSSPS.js.map +0 -1
- /package/dist/{chunk-ZVTG6US2.js.map → chunk-QMWMJVTX.js.map} +0 -0
- /package/dist/{chunk-SFSFIGHM.js.map → chunk-TGFX3TMV.js.map} +0 -0
- /package/dist/{chunk-EZFCQZHU.js.map → chunk-V3NVAVHV.js.map} +0 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { useChain } from '@0xsequence/connect';
|
|
2
1
|
import { renderHook, server, waitFor } from '@test';
|
|
3
2
|
import { http, HttpResponse } from 'msw';
|
|
4
3
|
import { zeroAddress } from 'viem';
|
|
5
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
-
import {
|
|
7
|
-
import { mainnet } from 'wagmi/chains';
|
|
5
|
+
import { useDisconnect } from 'wagmi';
|
|
8
6
|
import type { FeeOption } from '../../../types/waas-types';
|
|
9
7
|
import {
|
|
10
8
|
mockIndexerEndpoint,
|
|
@@ -13,22 +11,7 @@ import {
|
|
|
13
11
|
} from '../../_internal/api/__mocks__/indexer.msw';
|
|
14
12
|
import { useAutoSelectFeeOption } from '../useAutoSelectFeeOption';
|
|
15
13
|
|
|
16
|
-
// Mock wagmi hooks
|
|
17
|
-
vi.mock('wagmi', async () => {
|
|
18
|
-
const actual = await vi.importActual('wagmi');
|
|
19
|
-
return {
|
|
20
|
-
...actual,
|
|
21
|
-
useAccount: vi.fn(),
|
|
22
|
-
};
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Mock @0xsequence/connect
|
|
26
|
-
vi.mock('@0xsequence/connect', () => ({
|
|
27
|
-
useChain: vi.fn(),
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
14
|
describe('useAutoSelectFeeOption', () => {
|
|
31
|
-
const mockUserAddress = '0x1234567890123456789012345678901234567890';
|
|
32
15
|
const mockChainId = 1;
|
|
33
16
|
|
|
34
17
|
const mockFeeOptions: FeeOption[] = [
|
|
@@ -73,32 +56,6 @@ describe('useAutoSelectFeeOption', () => {
|
|
|
73
56
|
};
|
|
74
57
|
|
|
75
58
|
beforeEach(() => {
|
|
76
|
-
// Mock useAccount hook with complete wagmi account type
|
|
77
|
-
vi.mocked(useAccount).mockReturnValue({
|
|
78
|
-
address: mockUserAddress as `0x${string}`,
|
|
79
|
-
addresses: [mockUserAddress as `0x${string}`],
|
|
80
|
-
chain: mainnet,
|
|
81
|
-
chainId: mockChainId,
|
|
82
|
-
connector: undefined,
|
|
83
|
-
isConnected: true,
|
|
84
|
-
isConnecting: false,
|
|
85
|
-
isDisconnected: false,
|
|
86
|
-
isReconnecting: true,
|
|
87
|
-
status: 'reconnecting',
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Mock useChain hook with required chain properties
|
|
91
|
-
vi.mocked(useChain).mockReturnValue({
|
|
92
|
-
...mainnet,
|
|
93
|
-
id: mockChainId,
|
|
94
|
-
name: 'Ethereum',
|
|
95
|
-
nativeCurrency: {
|
|
96
|
-
name: 'Ether',
|
|
97
|
-
symbol: 'ETH',
|
|
98
|
-
decimals: 18,
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
|
|
102
59
|
// Set up default handler for successful balance check
|
|
103
60
|
server.use(
|
|
104
61
|
mockIndexerHandler('GetTokenBalancesDetails', {
|
|
@@ -180,37 +137,6 @@ describe('useAutoSelectFeeOption', () => {
|
|
|
180
137
|
});
|
|
181
138
|
});
|
|
182
139
|
|
|
183
|
-
it('should return UserNotConnected error when wallet is not connected', async () => {
|
|
184
|
-
// Mock useAccount to return no address (user not connected)
|
|
185
|
-
vi.mocked(useAccount).mockReturnValue({
|
|
186
|
-
address: undefined,
|
|
187
|
-
addresses: [],
|
|
188
|
-
chain: mainnet,
|
|
189
|
-
chainId: mockChainId,
|
|
190
|
-
connector: undefined,
|
|
191
|
-
isConnected: false,
|
|
192
|
-
isConnecting: false,
|
|
193
|
-
isDisconnected: false,
|
|
194
|
-
isReconnecting: true,
|
|
195
|
-
status: 'reconnecting',
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const { result } = renderHook(() => useAutoSelectFeeOption(defaultArgs));
|
|
199
|
-
|
|
200
|
-
// Wait for the hook to complete
|
|
201
|
-
await waitFor(async () => {
|
|
202
|
-
const response = await result.current;
|
|
203
|
-
expect(response.error).toBe('User not connected');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Verify final state
|
|
207
|
-
const finalResponse = await result.current;
|
|
208
|
-
expect(finalResponse).toEqual({
|
|
209
|
-
selectedOption: null,
|
|
210
|
-
error: 'User not connected',
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
140
|
it('should select second fee option when user has insufficient balance for first but sufficient for second', async () => {
|
|
215
141
|
// Override handler for mixed balance scenario
|
|
216
142
|
server.use(
|
|
@@ -307,4 +233,24 @@ describe('useAutoSelectFeeOption', () => {
|
|
|
307
233
|
error: 'Failed to check balances',
|
|
308
234
|
});
|
|
309
235
|
});
|
|
236
|
+
|
|
237
|
+
it('should return UserNotConnected error when wallet is not connected', async () => {
|
|
238
|
+
const { result: disconnect } = renderHook(() => useDisconnect());
|
|
239
|
+
await disconnect.current.disconnectAsync();
|
|
240
|
+
|
|
241
|
+
const { result } = renderHook(() => useAutoSelectFeeOption(defaultArgs));
|
|
242
|
+
|
|
243
|
+
// Wait for the hook to complete
|
|
244
|
+
await waitFor(async () => {
|
|
245
|
+
const response = await result.current;
|
|
246
|
+
expect(response.error).toBe('User not connected');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Verify final state
|
|
250
|
+
const finalResponse = await result.current;
|
|
251
|
+
expect(finalResponse).toEqual({
|
|
252
|
+
selectedOption: null,
|
|
253
|
+
error: 'User not connected',
|
|
254
|
+
});
|
|
255
|
+
});
|
|
310
256
|
});
|
|
@@ -63,38 +63,17 @@ describe('useCurrencyBalance', () => {
|
|
|
63
63
|
`);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it
|
|
66
|
+
it('should return skipToken when required parameters are missing', () => {
|
|
67
67
|
const { result } = renderHook(() =>
|
|
68
|
+
// @ts-expect-error - missing params
|
|
68
69
|
useCurrencyBalance({
|
|
69
70
|
chainId: undefined,
|
|
70
|
-
userAddress: undefined,
|
|
71
|
-
currencyAddress: undefined,
|
|
72
71
|
}),
|
|
73
72
|
);
|
|
74
73
|
|
|
75
74
|
expect(result.current.data).toBeUndefined();
|
|
76
75
|
expect(result.current.isLoading).toBe(false);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it.skip('should handle errors from public client', async () => {
|
|
82
|
-
// Mock error response
|
|
83
|
-
// const mockError = new Error('Failed to fetch balance');
|
|
84
|
-
// const mockPublicClient = createMockPublicClient({
|
|
85
|
-
// getBalance: vi.fn().mockRejectedValue(mockError),
|
|
86
|
-
// });
|
|
87
|
-
|
|
88
|
-
// Override the mock for this test
|
|
89
|
-
// vi.mocked(getPublicRpcClient).mockReturnValue(mockPublicClient);
|
|
90
|
-
|
|
91
|
-
const { result } = renderHook(() => useCurrencyBalance(defaultArgs));
|
|
92
|
-
|
|
93
|
-
await waitFor(() => {
|
|
94
|
-
expect(result.current.isError).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
expect(result.current.error).toBeDefined();
|
|
98
|
-
expect(result.current.data).toBeUndefined();
|
|
76
|
+
expect(result.current.isSuccess).toBe(false);
|
|
77
|
+
expect(result.current.isError).toBe(false);
|
|
99
78
|
});
|
|
100
79
|
});
|
package/src/react/hooks/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './useConvertPriceToUSD';
|
|
|
10
10
|
export * from './useCurrencies';
|
|
11
11
|
export * from './useCurrency';
|
|
12
12
|
export * from './useFilters';
|
|
13
|
+
export * from './useFilterState';
|
|
13
14
|
export * from './useFloorOrder';
|
|
14
15
|
export * from './useHighestOffer';
|
|
15
16
|
export * from './useInventory';
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createSerializer,
|
|
5
|
+
parseAsBoolean,
|
|
6
|
+
parseAsJson,
|
|
7
|
+
parseAsString,
|
|
8
|
+
useQueryState,
|
|
9
|
+
} from 'nuqs';
|
|
10
|
+
import { type PropertyFilter, PropertyType } from '../_internal';
|
|
11
|
+
|
|
12
|
+
interface StringFilterValues {
|
|
13
|
+
type: PropertyType.STRING;
|
|
14
|
+
values: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface IntFilterValues {
|
|
18
|
+
type: PropertyType.INT;
|
|
19
|
+
min: number;
|
|
20
|
+
max: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type FilterValues = StringFilterValues | IntFilterValues;
|
|
24
|
+
|
|
25
|
+
const validateFilters = (value: unknown): PropertyFilter[] => {
|
|
26
|
+
if (!Array.isArray(value)) return [];
|
|
27
|
+
return value.filter(
|
|
28
|
+
(f): f is PropertyFilter =>
|
|
29
|
+
typeof f === 'object' &&
|
|
30
|
+
typeof f.name === 'string' &&
|
|
31
|
+
Object.values(PropertyType).includes(f.type),
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const filtersParser = parseAsJson(validateFilters).withDefault([]);
|
|
36
|
+
const searchParser = parseAsString.withDefault('');
|
|
37
|
+
const listedOnlyParser = parseAsBoolean.withDefault(false);
|
|
38
|
+
|
|
39
|
+
const serialize = createSerializer(
|
|
40
|
+
{
|
|
41
|
+
filters: filtersParser,
|
|
42
|
+
search: searchParser,
|
|
43
|
+
listedOnly: listedOnlyParser,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
urlKeys: {
|
|
47
|
+
filters: 'f',
|
|
48
|
+
search: 'q',
|
|
49
|
+
listedOnly: 'l',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
export function useFilterState() {
|
|
55
|
+
const [filterOptions, setFilterOptions] = useQueryState(
|
|
56
|
+
'filters',
|
|
57
|
+
filtersParser,
|
|
58
|
+
);
|
|
59
|
+
const [searchText, setSearchText] = useQueryState('search', searchParser);
|
|
60
|
+
const [showListedOnly, setShowListedOnly] = useQueryState(
|
|
61
|
+
'listedOnly',
|
|
62
|
+
listedOnlyParser,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const helpers = useMemo(
|
|
66
|
+
() => ({
|
|
67
|
+
getFilter: (name: string): PropertyFilter | undefined => {
|
|
68
|
+
return filterOptions?.find((f) => f.name === name);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
getFilterValues: (name: string): FilterValues | undefined => {
|
|
72
|
+
const filter = filterOptions?.find((f) => f.name === name);
|
|
73
|
+
if (!filter) return undefined;
|
|
74
|
+
|
|
75
|
+
if (filter.type === PropertyType.INT) {
|
|
76
|
+
return {
|
|
77
|
+
type: PropertyType.INT,
|
|
78
|
+
min: filter.min ?? 0,
|
|
79
|
+
max: filter.max ?? 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
type: PropertyType.STRING,
|
|
85
|
+
values: (filter.values as string[]) ?? [],
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
isFilterActive: (name: string): boolean => {
|
|
90
|
+
return !!filterOptions?.find((f) => f.name === name);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
isStringValueSelected: (name: string, value: string): boolean => {
|
|
94
|
+
const filter = filterOptions?.find((f) => f.name === name);
|
|
95
|
+
if (!filter || filter.type !== PropertyType.STRING) return false;
|
|
96
|
+
return (filter.values as string[])?.includes(value) ?? false;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
isIntFilterActive: (name: string): boolean => {
|
|
100
|
+
const filter = filterOptions?.find((f) => f.name === name);
|
|
101
|
+
return !!filter && filter.type === PropertyType.INT;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
getIntFilterRange: (name: string): [number, number] | undefined => {
|
|
105
|
+
const filter = filterOptions?.find((f) => f.name === name);
|
|
106
|
+
if (!filter || filter.type !== PropertyType.INT) return undefined;
|
|
107
|
+
return [filter.min ?? 0, filter.max ?? 0];
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
deleteFilter: (name: string) => {
|
|
111
|
+
const otherFilters =
|
|
112
|
+
filterOptions?.filter((f) => !(f.name === name)) ?? [];
|
|
113
|
+
setFilterOptions(otherFilters);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
toggleStringFilterValue: (name: string, value: string) => {
|
|
117
|
+
const otherFilters =
|
|
118
|
+
filterOptions?.filter((f) => !(f.name === name)) ?? [];
|
|
119
|
+
const filter = filterOptions?.find((f) => f.name === name);
|
|
120
|
+
const existingValues =
|
|
121
|
+
filter?.type === PropertyType.STRING
|
|
122
|
+
? ((filter.values as string[]) ?? [])
|
|
123
|
+
: [];
|
|
124
|
+
|
|
125
|
+
if (existingValues.includes(value)) {
|
|
126
|
+
const newValues = existingValues.filter((v) => v !== value);
|
|
127
|
+
if (newValues.length === 0) {
|
|
128
|
+
setFilterOptions(otherFilters);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
setFilterOptions([
|
|
132
|
+
...otherFilters,
|
|
133
|
+
{ name, type: PropertyType.STRING, values: newValues },
|
|
134
|
+
]);
|
|
135
|
+
} else {
|
|
136
|
+
setFilterOptions([
|
|
137
|
+
...otherFilters,
|
|
138
|
+
{
|
|
139
|
+
name,
|
|
140
|
+
type: PropertyType.STRING,
|
|
141
|
+
values: [...existingValues, value],
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
setIntFilterValue: (name: string, min: number, max: number) => {
|
|
148
|
+
if (min === max && min === 0) {
|
|
149
|
+
const otherFilters =
|
|
150
|
+
filterOptions?.filter((f) => !(f.name === name)) ?? [];
|
|
151
|
+
setFilterOptions(otherFilters);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const otherFilters =
|
|
155
|
+
filterOptions?.filter((f) => !(f.name === name)) ?? [];
|
|
156
|
+
setFilterOptions([
|
|
157
|
+
...otherFilters,
|
|
158
|
+
{ name, type: PropertyType.INT, min, max },
|
|
159
|
+
]);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
clearAllFilters: () => {
|
|
163
|
+
void setShowListedOnly(false);
|
|
164
|
+
void setFilterOptions([]);
|
|
165
|
+
void setSearchText('');
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
[filterOptions, setFilterOptions, setShowListedOnly, setSearchText],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
filterOptions,
|
|
173
|
+
searchText,
|
|
174
|
+
showListedOnly,
|
|
175
|
+
setFilterOptions,
|
|
176
|
+
setSearchText,
|
|
177
|
+
setShowListedOnly,
|
|
178
|
+
...helpers,
|
|
179
|
+
serialize,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -18,6 +18,7 @@ const UseFiltersSchema = z.object({
|
|
|
18
18
|
collectionAddress: AddressSchema,
|
|
19
19
|
showAllFilters: z.boolean().default(false).optional(),
|
|
20
20
|
query: QueryArgSchema,
|
|
21
|
+
excludePropertyValues: z.boolean().default(false).optional(),
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
export type UseFiltersArgs = z.infer<typeof UseFiltersSchema>;
|
|
@@ -33,6 +34,7 @@ export const fetchFilters = async (args: UseFiltersArgs, config: SdkConfig) => {
|
|
|
33
34
|
chainID: parsedArgs.chainId.toString(),
|
|
34
35
|
contractAddress: parsedArgs.collectionAddress,
|
|
35
36
|
excludeProperties: [], // TODO: We can leverage this for some of the exclusion logic
|
|
37
|
+
excludePropertyValues: parsedArgs.excludePropertyValues,
|
|
36
38
|
})
|
|
37
39
|
.then((resp) => resp.filters);
|
|
38
40
|
|
|
@@ -117,3 +119,25 @@ export const useFilters = (args: UseFiltersArgs) => {
|
|
|
117
119
|
const config = useConfig();
|
|
118
120
|
return useQuery(filtersOptions(args, config));
|
|
119
121
|
};
|
|
122
|
+
|
|
123
|
+
export const useFiltersProgressive = (args: UseFiltersArgs) => {
|
|
124
|
+
const config = useConfig();
|
|
125
|
+
|
|
126
|
+
const namesQuery = useQuery(
|
|
127
|
+
filtersOptions({ ...args, excludePropertyValues: true }, config),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const fullQuery = useQuery({
|
|
131
|
+
...filtersOptions(args, config),
|
|
132
|
+
placeholderData: namesQuery.data,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const isLoadingNames = namesQuery.isLoading;
|
|
136
|
+
const isFetchingValues = fullQuery.isPlaceholderData && fullQuery.isFetching;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
...fullQuery,
|
|
140
|
+
isFetchingValues,
|
|
141
|
+
isLoadingNames,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
@@ -6,19 +6,32 @@ import {
|
|
|
6
6
|
wagmiConfig,
|
|
7
7
|
waitFor,
|
|
8
8
|
} from '@test';
|
|
9
|
-
import { describe, expect, test, vi } from 'vitest';
|
|
10
|
-
import {
|
|
9
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
10
|
+
import { useChainId } from 'wagmi';
|
|
11
11
|
import SwitchChainModal, { useSwitchChainModal } from '../index';
|
|
12
12
|
import { switchChainModal$ } from '../store';
|
|
13
13
|
|
|
14
14
|
const chainToSwitchTo = wagmiConfig.chains[1];
|
|
15
15
|
|
|
16
16
|
describe('SwitchChainModal', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
switchChainModal$.state.isSwitching.set(false);
|
|
19
|
+
switchChainModal$.state.chainIdToSwitchTo.set(undefined);
|
|
20
|
+
switchChainModal$.state.onError.set(undefined);
|
|
21
|
+
switchChainModal$.state.onSuccess.set(undefined);
|
|
22
|
+
switchChainModal$.state.onClose.set(undefined);
|
|
23
|
+
});
|
|
24
|
+
|
|
17
25
|
test('opens switch chain modal with correct chain', async () => {
|
|
26
|
+
const { result } = renderHook(() => useSwitchChainModal());
|
|
27
|
+
|
|
28
|
+
result.current.show({ chainIdToSwitchTo: chainToSwitchTo.id });
|
|
18
29
|
render(<SwitchChainModal />);
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
await waitFor(() => {
|
|
32
|
+
const titleElement = screen.getByText('Wrong network');
|
|
33
|
+
expect(titleElement).toBeInTheDocument();
|
|
34
|
+
});
|
|
22
35
|
|
|
23
36
|
const titleElement = await screen.findByText('Wrong network');
|
|
24
37
|
expect(titleElement).toBeInTheDocument();
|
|
@@ -35,13 +48,12 @@ describe('SwitchChainModal', () => {
|
|
|
35
48
|
});
|
|
36
49
|
|
|
37
50
|
test('closes switch chain modal using close callback', async () => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const { show, close } = useSwitchChainModal();
|
|
51
|
+
const { result } = renderHook(() => useSwitchChainModal());
|
|
41
52
|
|
|
42
|
-
show({ chainIdToSwitchTo: chainToSwitchTo.id });
|
|
53
|
+
result.current.show({ chainIdToSwitchTo: chainToSwitchTo.id });
|
|
54
|
+
render(<SwitchChainModal />);
|
|
43
55
|
|
|
44
|
-
close();
|
|
56
|
+
result.current.close();
|
|
45
57
|
|
|
46
58
|
const titleElement = screen.queryByText('Wrong network');
|
|
47
59
|
await waitFor(() => {
|
|
@@ -68,66 +80,34 @@ describe('SwitchChainModal', () => {
|
|
|
68
80
|
});
|
|
69
81
|
});
|
|
70
82
|
|
|
71
|
-
test
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
show({ chainIdToSwitchTo: chainToSwitchTo.id });
|
|
76
|
-
|
|
77
|
-
const switchButton = await screen.findByRole('button', {
|
|
78
|
-
name: /switch network/i,
|
|
79
|
-
});
|
|
80
|
-
expect(switchButton).toBeInTheDocument();
|
|
81
|
-
|
|
82
|
-
fireEvent.click(switchButton);
|
|
83
|
+
test('shows spinner while switching chain', async () => {
|
|
84
|
+
switchChainModal$.state.isSwitching.set(true);
|
|
85
|
+
switchChainModal$.state.chainIdToSwitchTo.set(chainToSwitchTo.id);
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
const spinner = screen.findByTestId('switch-chain-spinner');
|
|
86
|
-
expect(spinner).toBeInTheDocument();
|
|
87
|
-
});
|
|
87
|
+
render(<SwitchChainModal />);
|
|
88
88
|
|
|
89
|
-
const spinner =
|
|
89
|
+
const spinner = screen.getByTestId('switch-chain-spinner');
|
|
90
90
|
expect(spinner).toBeInTheDocument();
|
|
91
|
-
|
|
92
|
-
await waitFor(() => {
|
|
93
|
-
expect(switchChainModal$.state.isSwitching.get()).toBe(false);
|
|
94
|
-
expect(document.querySelector('.spinner')).not.toBeInTheDocument();
|
|
95
|
-
});
|
|
96
91
|
});
|
|
97
92
|
|
|
98
|
-
test
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
const mockConnector = {
|
|
102
|
-
...wagmiConfig.connectors[0],
|
|
103
|
-
features: {
|
|
104
|
-
// @ts-expect-error
|
|
105
|
-
...wagmiConfig.connectors[0].features,
|
|
106
|
-
switchChainError: true,
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
const wagmiConfigWithSwitchChainError = {
|
|
110
|
-
...wagmiConfig,
|
|
111
|
-
connectors: [mockConnector],
|
|
112
|
-
} as Config;
|
|
113
|
-
|
|
114
|
-
render(<SwitchChainModal />, {
|
|
115
|
-
wagmiConfig: wagmiConfigWithSwitchChainError,
|
|
116
|
-
});
|
|
93
|
+
test('shows error message when chain switch fails', async () => {
|
|
94
|
+
const mockOnError = vi.fn();
|
|
95
|
+
const { result } = renderHook(() => useSwitchChainModal());
|
|
117
96
|
|
|
118
|
-
|
|
119
|
-
|
|
97
|
+
result.current.show({
|
|
98
|
+
// @ts-expect-error - invalid chain id to trigger error
|
|
99
|
+
chainIdToSwitchTo: 'invalid-chain-id',
|
|
100
|
+
onError: mockOnError,
|
|
101
|
+
});
|
|
102
|
+
render(<SwitchChainModal />);
|
|
120
103
|
|
|
121
|
-
const
|
|
104
|
+
const button = await screen.findByRole('button', {
|
|
122
105
|
name: /switch network/i,
|
|
123
106
|
});
|
|
124
|
-
fireEvent.click(
|
|
107
|
+
fireEvent.click(button);
|
|
125
108
|
|
|
126
109
|
await waitFor(() => {
|
|
127
|
-
|
|
128
|
-
// expect(chainId).not.toBe(chainToSwitchTo.id)
|
|
129
|
-
expect(onError).toHaveBeenCalled();
|
|
130
|
-
expect(switchChainModal$.isOpen.get()).toBe(true);
|
|
110
|
+
expect(mockOnError).toHaveBeenCalledOnce();
|
|
131
111
|
});
|
|
132
112
|
});
|
|
133
113
|
});
|
|
@@ -101,7 +101,9 @@ const SwitchChainModal = observer(() => {
|
|
|
101
101
|
size="sm"
|
|
102
102
|
label={
|
|
103
103
|
isSwitching$.get() ? (
|
|
104
|
-
<
|
|
104
|
+
<div data-testid="switch-chain-spinner">
|
|
105
|
+
<Spinner className="spinner" />
|
|
106
|
+
</div>
|
|
105
107
|
) : (
|
|
106
108
|
'Switch Network'
|
|
107
109
|
)
|