@0xsequence/marketplace-sdk 0.8.2 → 0.8.4

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{builder-types-Wrqq6YoW.d.ts → builder-types-BY6eD6vD.d.ts} +1 -1
  3. package/dist/{chunk-IZ44XPBH.js → chunk-25CAMYCG.js} +2 -2
  4. package/dist/{chunk-LDHGFXPJ.js → chunk-44YGZVBS.js} +2 -2
  5. package/dist/{chunk-BNAUZXPV.js → chunk-ALICO7NG.js} +3 -3
  6. package/dist/chunk-ALICO7NG.js.map +1 -0
  7. package/dist/{chunk-3II5GLHE.js → chunk-HGKWWZWY.js} +2 -2
  8. package/dist/{chunk-S2UFNIYX.js → chunk-HRL2TMXU.js} +12 -9
  9. package/dist/chunk-HRL2TMXU.js.map +1 -0
  10. package/dist/{chunk-XOHAZXBZ.js → chunk-VBRJ2OPM.js} +14 -6
  11. package/dist/chunk-VBRJ2OPM.js.map +1 -0
  12. package/dist/{chunk-F6CUGMI4.js → chunk-VF3LWBQB.js} +378 -263
  13. package/dist/chunk-VF3LWBQB.js.map +1 -0
  14. package/dist/{chunk-GBQVYNCD.js → chunk-XUNDLCEH.js} +3 -3
  15. package/dist/{chunk-4DFOSZTE.js → chunk-ZSCZLHKX.js} +195 -3
  16. package/dist/chunk-ZSCZLHKX.js.map +1 -0
  17. package/dist/{create-config-B58hoCDv.d.ts → create-config-qDML4ZNn.d.ts} +1 -1
  18. package/dist/{index-PhhCRKUH.d.ts → index-DtWR0b_l.d.ts} +1 -1
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +7 -9
  21. package/dist/{lowestListing-CuLxIWxy.d.ts → lowestListing-W7P4EkC3.d.ts} +34 -11
  22. package/dist/{marketplace.gen-De2-sxiG.d.ts → marketplace.gen-DS-MmGEB.d.ts} +35 -4
  23. package/dist/react/_internal/api/index.d.ts +2 -2
  24. package/dist/react/_internal/api/index.js +1 -1
  25. package/dist/react/_internal/databeat/index.d.ts +1 -1
  26. package/dist/react/_internal/databeat/index.js +8 -8
  27. package/dist/react/_internal/index.d.ts +5 -5
  28. package/dist/react/_internal/index.js +2 -2
  29. package/dist/react/_internal/wagmi/index.d.ts +3 -3
  30. package/dist/react/_internal/wagmi/index.js +2 -2
  31. package/dist/react/hooks/index.d.ts +72 -49
  32. package/dist/react/hooks/index.js +9 -7
  33. package/dist/react/hooks/options/index.d.ts +3 -3
  34. package/dist/react/hooks/options/index.js +4 -4
  35. package/dist/react/index.d.ts +8 -8
  36. package/dist/react/index.js +12 -10
  37. package/dist/react/queries/index.d.ts +2 -2
  38. package/dist/react/queries/index.js +7 -3
  39. package/dist/react/ssr/index.d.ts +2 -2
  40. package/dist/react/ssr/index.js +3 -3
  41. package/dist/react/ui/components/collectible-card/index.d.ts +1 -1
  42. package/dist/react/ui/components/collectible-card/index.js +10 -10
  43. package/dist/react/ui/icons/index.js +3 -3
  44. package/dist/react/ui/index.d.ts +1 -1
  45. package/dist/react/ui/index.js +10 -10
  46. package/dist/react/ui/modals/_internal/components/actionModal/index.js +8 -8
  47. package/dist/{services-BdzIAR9w.d.ts → services-BOX67E7W.d.ts} +1 -1
  48. package/dist/types/index.d.ts +3 -3
  49. package/dist/types/index.js +2 -4
  50. package/dist/{types-CmHOStH3.d.ts → types-CJLhc2VZ.d.ts} +2 -2
  51. package/dist/utils/abi/index.js +5 -5
  52. package/dist/utils/index.d.ts +1 -1
  53. package/dist/utils/index.js +7 -7
  54. package/package.json +1 -1
  55. package/src/react/_internal/api/__mocks__/marketplace.msw.ts +35 -18
  56. package/src/react/_internal/api/marketplace.gen.ts +39 -6
  57. package/src/react/_internal/api/zod-schema.ts +7 -18
  58. package/src/react/hooks/__tests__/useCancelTransactionSteps.test.tsx +4 -9
  59. package/src/react/hooks/__tests__/useGenerateCancelTransaction.test.tsx +5 -4
  60. package/src/react/hooks/__tests__/useGenerateListingTransaction.test.tsx +14 -2
  61. package/src/react/hooks/__tests__/useGenerateOfferTransaction.test.tsx +115 -57
  62. package/src/react/hooks/__tests__/useGenerateSellTransaction.test.tsx +10 -3
  63. package/src/react/hooks/__tests__/useInventory.test.tsx +294 -0
  64. package/src/react/hooks/index.ts +1 -0
  65. package/src/react/hooks/useAutoSelectFeeOption.tsx +10 -3
  66. package/src/react/hooks/useCancelOrder.tsx +1 -0
  67. package/src/react/hooks/useCancelTransactionSteps.tsx +20 -6
  68. package/src/react/hooks/useGenerateOfferTransaction.tsx +11 -1
  69. package/src/react/hooks/useInventory.tsx +15 -0
  70. package/src/react/hooks/util/optimisticCancelUpdates.ts +115 -0
  71. package/src/react/queries/index.ts +1 -0
  72. package/src/react/queries/inventory.ts +303 -0
  73. package/src/react/queries/listBalances.ts +1 -8
  74. package/src/react/queries/listCollectibles.ts +12 -3
  75. package/src/react/ui/modals/CreateListingModal/__tests__/Modal.test.tsx +74 -104
  76. package/src/react/ui/modals/CreateListingModal/hooks/useTransactionSteps.tsx +2 -2
  77. package/src/react/ui/modals/MakeOfferModal/__tests__/Modal.test.tsx +108 -78
  78. package/src/react/ui/modals/MakeOfferModal/hooks/useTransactionSteps.tsx +2 -2
  79. package/src/react/ui/modals/SellModal/hooks/useTransactionSteps.tsx +2 -2
  80. package/src/react/ui/modals/_internal/components/actionModal/ActionModal.test.tsx +286 -0
  81. package/src/react/ui/modals/_internal/components/actionModal/ActionModal.tsx +16 -4
  82. package/src/react/ui/modals/_internal/components/currencyOptionsSelect/__tests__/index.test.tsx +35 -132
  83. package/src/react/ui/modals/_internal/components/floorPriceText/__tests__/FloorPriceText.test.tsx +199 -0
  84. package/src/react/ui/modals/_internal/components/priceInput/__tests__/PriceInput.test.tsx +55 -0
  85. package/src/react/ui/modals/_internal/components/priceInput/index.tsx +1 -1
  86. package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +2 -2
  87. package/src/types/api-types.ts +0 -1
  88. package/test/const.ts +24 -0
  89. package/test/test-utils.tsx +74 -26
  90. package/.changeset/flat-parks-clean.md +0 -8
  91. package/.changeset/seven-doors-taste.md +0 -5
  92. package/dist/chunk-4DFOSZTE.js.map +0 -1
  93. package/dist/chunk-BNAUZXPV.js.map +0 -1
  94. package/dist/chunk-F6CUGMI4.js.map +0 -1
  95. package/dist/chunk-S2UFNIYX.js.map +0 -1
  96. package/dist/chunk-XOHAZXBZ.js.map +0 -1
  97. package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +0 -164
  98. /package/dist/{chunk-IZ44XPBH.js.map → chunk-25CAMYCG.js.map} +0 -0
  99. /package/dist/{chunk-LDHGFXPJ.js.map → chunk-44YGZVBS.js.map} +0 -0
  100. /package/dist/{chunk-3II5GLHE.js.map → chunk-HGKWWZWY.js.map} +0 -0
  101. /package/dist/{chunk-GBQVYNCD.js.map → chunk-XUNDLCEH.js.map} +0 -0
@@ -0,0 +1,286 @@
1
+ import { fireEvent, render, screen, waitFor } from '@test';
2
+ import { type Address, custom, zeroAddress } from 'viem';
3
+ import { mainnet, polygon } from 'viem/chains';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { WalletKind } from '../../../../../_internal';
6
+ import * as walletModule from '../../../../../_internal/wallet/useWallet';
7
+ import { ActionModal } from './ActionModal';
8
+
9
+ const mockShowSwitchChainModal = vi.fn();
10
+ vi.mock('../switchChainModal', () => ({
11
+ useSwitchChainModal: () => ({
12
+ show: mockShowSwitchChainModal,
13
+ close: vi.fn(),
14
+ isSwitching$: { get: () => false },
15
+ }),
16
+ }));
17
+
18
+ describe('ActionModal', () => {
19
+ const mockOnClose = vi.fn();
20
+ const mockOnClick = vi.fn();
21
+
22
+ const defaultProps = {
23
+ isOpen: true,
24
+ onClose: mockOnClose,
25
+ title: 'Test Modal',
26
+ children: <div>Modal Content</div>,
27
+ ctas: [
28
+ {
29
+ label: 'Test Button',
30
+ onClick: mockOnClick,
31
+ testid: 'test-button',
32
+ },
33
+ ],
34
+ chainId: polygon.id,
35
+ };
36
+
37
+ beforeEach(() => {
38
+ vi.clearAllMocks();
39
+ });
40
+
41
+ describe('Loading states', () => {
42
+ it('should show a loading spinner when modalLoading prop is true', async () => {
43
+ render(<ActionModal {...defaultProps} modalLoading={true} />);
44
+
45
+ expect(screen.getByTestId('spinner')).toBeInTheDocument();
46
+ });
47
+
48
+ it('should show a loading spinner when isLoading from useWallet is true', async () => {
49
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
50
+ wallet: null,
51
+ isLoading: true,
52
+ isError: false,
53
+ });
54
+
55
+ render(<ActionModal {...defaultProps} />);
56
+
57
+ expect(screen.getByTestId('spinner')).toBeInTheDocument();
58
+ });
59
+
60
+ it('should show error message when useWallet returns an error', () => {
61
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
62
+ wallet: null,
63
+ isLoading: false,
64
+ isError: true,
65
+ });
66
+
67
+ render(<ActionModal {...defaultProps} />);
68
+
69
+ expect(screen.getByTestId('error-loading-text')).toBeInTheDocument();
70
+ expect(screen.getByText('Error loading modal')).toBeInTheDocument();
71
+ expect(screen.queryByText('Modal Content')).not.toBeInTheDocument();
72
+ expect(screen.queryByTestId('test-button')).not.toBeInTheDocument();
73
+ });
74
+
75
+ it('should show modal content if loading states is false and no error', async () => {
76
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
77
+ wallet: null,
78
+ isLoading: false,
79
+ isError: false,
80
+ });
81
+
82
+ render(<ActionModal {...defaultProps} modalLoading={false} />);
83
+
84
+ expect(screen.getByText('Modal Content')).toBeInTheDocument();
85
+ expect(screen.getByTestId('test-button')).toBeInTheDocument();
86
+ });
87
+ });
88
+
89
+ describe('Chain switching', () => {
90
+ it('should automatically switch chain for Sequence WaaS wallets', async () => {
91
+ const switchChainMock = vi.fn();
92
+
93
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
94
+ wallet: {
95
+ address: () => Promise.resolve(zeroAddress as Address),
96
+ getChainId: vi.fn().mockResolvedValue(mainnet.id),
97
+ switchChain: switchChainMock,
98
+ transport: custom({ request: vi.fn() }),
99
+ walletKind: WalletKind.sequence,
100
+ isWaaS: true,
101
+ handleConfirmTransactionStep: vi.fn(),
102
+ handleSendTransactionStep: vi.fn(),
103
+ handleSignMessageStep: vi.fn(),
104
+ hasTokenApproval: vi.fn(),
105
+ },
106
+ isLoading: false,
107
+ isError: false,
108
+ });
109
+
110
+ render(<ActionModal {...defaultProps} />);
111
+
112
+ expect(switchChainMock).toHaveBeenCalledWith(polygon.id);
113
+ });
114
+
115
+ it('should show switch chain modal when CTA is clicked with chain mismatch', async () => {
116
+ mockShowSwitchChainModal.mockClear();
117
+
118
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
119
+ wallet: {
120
+ address: () => Promise.resolve(zeroAddress as Address),
121
+ getChainId: vi.fn().mockResolvedValue(mainnet.id), // different from defaultProps.chainId
122
+ switchChain: vi.fn(),
123
+ transport: custom({ request: vi.fn() }),
124
+ walletKind: WalletKind.sequence,
125
+ isWaaS: false,
126
+ handleConfirmTransactionStep: vi.fn(),
127
+ handleSendTransactionStep: vi.fn(),
128
+ handleSignMessageStep: vi.fn(),
129
+ hasTokenApproval: vi.fn(),
130
+ },
131
+ isLoading: false,
132
+ isError: false,
133
+ });
134
+
135
+ render(<ActionModal {...defaultProps} />);
136
+
137
+ const button = screen.getByTestId('test-button');
138
+ fireEvent.click(button);
139
+
140
+ await waitFor(() => {
141
+ expect(mockShowSwitchChainModal).toHaveBeenCalledWith({
142
+ chainIdToSwitchTo: polygon.id,
143
+ onSuccess: expect.any(Function),
144
+ });
145
+ });
146
+ });
147
+
148
+ it('should directly execute callback when chain already matches', async () => {
149
+ mockOnClick.mockClear();
150
+
151
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
152
+ wallet: {
153
+ address: () => Promise.resolve(zeroAddress as Address),
154
+ getChainId: vi.fn().mockResolvedValue(polygon.id), // Same as defaultProps.chainId
155
+ switchChain: vi.fn(),
156
+ transport: custom({ request: vi.fn() }),
157
+ walletKind: WalletKind.sequence,
158
+ isWaaS: false,
159
+ handleConfirmTransactionStep: vi.fn(),
160
+ handleSendTransactionStep: vi.fn(),
161
+ handleSignMessageStep: vi.fn(),
162
+ hasTokenApproval: vi.fn(),
163
+ },
164
+ isLoading: false,
165
+ isError: false,
166
+ });
167
+
168
+ render(<ActionModal {...defaultProps} />);
169
+
170
+ const button = screen.getByTestId('test-button');
171
+ fireEvent.click(button);
172
+
173
+ await waitFor(() => {
174
+ expect(mockOnClick).toHaveBeenCalled();
175
+ });
176
+ });
177
+ });
178
+
179
+ describe('CTA buttons', () => {
180
+ it('should call onClick when CTA button is clicked', async () => {
181
+ const onClick = vi.fn();
182
+ render(
183
+ <ActionModal
184
+ {...defaultProps}
185
+ ctas={[{ label: 'Click Me', onClick, testid: 'cta-button' }]}
186
+ />,
187
+ );
188
+
189
+ const button = screen.getByTestId('cta-button');
190
+ fireEvent.click(button);
191
+
192
+ await waitFor(() => {
193
+ expect(onClick).toHaveBeenCalled();
194
+ });
195
+ });
196
+
197
+ it('should disable the button when disabled prop is true', () => {
198
+ render(
199
+ <ActionModal
200
+ {...defaultProps}
201
+ ctas={[
202
+ {
203
+ label: 'Disabled Button',
204
+ onClick: mockOnClick,
205
+ disabled: true,
206
+ testid: 'disabled-button',
207
+ },
208
+ ]}
209
+ />,
210
+ );
211
+
212
+ const button = screen.getByTestId('disabled-button');
213
+ expect(button).toBeDisabled();
214
+
215
+ fireEvent.click(button);
216
+ expect(mockOnClick).not.toHaveBeenCalled();
217
+ });
218
+
219
+ it('should show spinner when pending prop is true', () => {
220
+ render(
221
+ <ActionModal
222
+ {...defaultProps}
223
+ ctas={[
224
+ {
225
+ label: 'Loading Button',
226
+ onClick: mockOnClick,
227
+ pending: true,
228
+ testid: 'pending-button',
229
+ },
230
+ ]}
231
+ />,
232
+ );
233
+
234
+ expect(screen.getByTestId('pending-button')).toBeInTheDocument();
235
+ expect(screen.getByTestId('pending-button-spinner')).toBeInTheDocument(); // wrapper of spinner has data-testid of {testid}-spinner
236
+ });
237
+
238
+ it('should not render hidden buttons', () => {
239
+ render(
240
+ <ActionModal
241
+ {...defaultProps}
242
+ ctas={[
243
+ {
244
+ label: 'Visible Button',
245
+ onClick: vi.fn(),
246
+ testid: 'visible-button',
247
+ },
248
+ {
249
+ label: 'Hidden Button',
250
+ onClick: vi.fn(),
251
+ hidden: true,
252
+ testid: 'hidden-button',
253
+ },
254
+ ]}
255
+ />,
256
+ );
257
+
258
+ expect(screen.getByTestId('visible-button')).toBeInTheDocument();
259
+ expect(screen.queryByTestId('hidden-button')).not.toBeInTheDocument();
260
+ });
261
+
262
+ it('should render multiple CTA buttons', () => {
263
+ render(
264
+ <ActionModal
265
+ {...defaultProps}
266
+ ctas={[
267
+ {
268
+ label: 'Primary CTA',
269
+ onClick: vi.fn(),
270
+ testid: 'primary-cta',
271
+ },
272
+ {
273
+ label: 'Secondary CTA',
274
+ onClick: vi.fn(),
275
+ variant: 'secondary',
276
+ testid: 'secondary-cta',
277
+ },
278
+ ]}
279
+ />,
280
+ );
281
+
282
+ expect(screen.getByTestId('primary-cta')).toBeInTheDocument();
283
+ expect(screen.getByTestId('secondary-cta')).toBeInTheDocument();
284
+ });
285
+ });
286
+ });
@@ -52,7 +52,7 @@ export const ActionModal = observer(
52
52
  const chainMismatch = walletChainId !== Number(chainId);
53
53
  if (chainMismatch) {
54
54
  showSwitchChainModal({
55
- chainIdToSwitchTo: Number(chainId),
55
+ chainIdToSwitchTo: chainId,
56
56
  onSuccess,
57
57
  });
58
58
  } else {
@@ -84,13 +84,21 @@ export const ActionModal = observer(
84
84
  {modalLoading || isLoading || isError ? (
85
85
  <div
86
86
  className={`flex ${spinnerContainerClassname} w-full items-center justify-center`}
87
+ data-testid="error-loading-wrapper"
87
88
  >
88
89
  {isError && (
89
- <Text className="text-center font-body text-error100 text-small">
90
+ <Text
91
+ data-testid="error-loading-text"
92
+ className="text-center font-body text-error100 text-small"
93
+ >
90
94
  Error loading modal
91
95
  </Text>
92
96
  )}
93
- {isLoading && <Spinner size="lg" />}
97
+ {(isLoading || modalLoading) && (
98
+ <div data-testid="spinner">
99
+ <Spinner size="lg" />
100
+ </div>
101
+ )}
94
102
  </div>
95
103
  ) : (
96
104
  children
@@ -118,7 +126,11 @@ export const ActionModal = observer(
118
126
  data-testid={cta.testid}
119
127
  label={
120
128
  <div className="flex items-center justify-center gap-2">
121
- {cta.pending && <Spinner size="sm" />}
129
+ {cta.pending && (
130
+ <div data-testid={`${cta.testid}-spinner`}>
131
+ <Spinner size="sm" />
132
+ </div>
133
+ )}
122
134
 
123
135
  {cta.label}
124
136
  </div>
@@ -1,151 +1,54 @@
1
1
  import { observable } from '@legendapp/state';
2
2
  import type { UseQueryResult } from '@tanstack/react-query';
3
3
  import { render } from '@test';
4
- import { screen, waitFor } from '@testing-library/react';
5
- import { describe, expect, it, vi } from 'vitest';
4
+ import { TEST_CURRENCIES, TEST_CURRENCY } from '@test/const';
5
+ import { screen } from '@testing-library/react';
6
+ import { zeroAddress } from 'viem';
7
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
6
8
  import CurrencyOptionsSelect from '..';
7
9
  import type { Currency } from '../../../../../../_internal';
8
- import { mockCurrencies } from '../../../../../../_internal/api/__mocks__/marketplace.msw';
9
- import { useCurrencies } from '../../../../../../hooks';
10
-
11
- describe.skip('CurrencyOptionsSelect', () => {
12
- const createDefaultProps = () => ({
13
- collectionAddress: '0xCollection' as `0x${string}`,
14
- chainId: 1,
15
- selectedCurrency$: observable<Currency | null | undefined>(null),
16
- secondCurrencyAsDefault: false,
17
- includeNativeCurrency: false,
10
+ import * as hooks from '../../../../../../hooks';
11
+
12
+ const defaultProps = {
13
+ collectionAddress: zeroAddress,
14
+ chainId: 1,
15
+ selectedCurrency$: observable<Currency | null | undefined>(TEST_CURRENCY),
16
+ secondCurrencyAsDefault: false,
17
+ includeNativeCurrency: false,
18
+ };
19
+
20
+ describe('CurrencyOptionsSelect', () => {
21
+ beforeEach(() => {
22
+ vi.restoreAllMocks();
18
23
  });
19
24
 
20
25
  it('should render loading skeleton when currencies are loading', () => {
21
- const props = createDefaultProps();
22
- render(<CurrencyOptionsSelect {...props} />);
23
- expect(screen.getByTestId('skeleton')).toBeInTheDocument();
24
- });
25
-
26
- it('should set first currency as default when currencies load', async () => {
27
- vi.mocked(useCurrencies).mockReturnValue({
28
- data: mockCurrencies,
29
- isLoading: false,
30
- } as UseQueryResult<Currency[], Error>);
31
-
32
- const props = createDefaultProps();
33
- render(<CurrencyOptionsSelect {...props} />);
34
-
35
- await waitFor(() => {
36
- const selectedCurrency = props.selectedCurrency$.get();
37
- expect(selectedCurrency).toBeDefined();
38
- expect(selectedCurrency?.contractAddress).toBe(
39
- mockCurrencies[0].contractAddress,
40
- );
41
- expect(selectedCurrency?.symbol).toBe(mockCurrencies[0].symbol);
42
- });
43
-
44
- expect(screen.getByText(mockCurrencies[0].symbol)).toBeInTheDocument();
45
- });
46
-
47
- it('should set second currency as default when secondCurrencyAsDefault is true', async () => {
48
- vi.mocked(useCurrencies).mockReturnValue({
49
- data: mockCurrencies,
50
- isLoading: false,
51
- } as UseQueryResult<Currency[], Error>);
52
-
53
- const props = createDefaultProps();
54
- render(<CurrencyOptionsSelect {...props} secondCurrencyAsDefault={true} />);
55
-
56
- await waitFor(() => {
57
- const selectedCurrency = props.selectedCurrency$.get();
58
- expect(selectedCurrency).toBeDefined();
59
- expect(selectedCurrency?.contractAddress).toBe(
60
- mockCurrencies[1].contractAddress,
61
- );
62
- expect(selectedCurrency?.symbol).toBe(mockCurrencies[1].symbol);
63
- });
64
-
65
- expect(screen.getByText(mockCurrencies[1].symbol)).toBeInTheDocument();
66
- });
67
-
68
- it('should update selected currency when changed programmatically', async () => {
69
- vi.mocked(useCurrencies).mockReturnValue({
70
- data: mockCurrencies,
71
- isLoading: false,
26
+ const useCurrenciesSpy = vi.spyOn(hooks, 'useCurrencies');
27
+ useCurrenciesSpy.mockReturnValue({
28
+ isLoading: true,
29
+ data: undefined,
30
+ error: null,
72
31
  } as UseQueryResult<Currency[], Error>);
73
32
 
74
- const props = createDefaultProps();
75
- render(<CurrencyOptionsSelect {...props} />);
76
-
77
- // Wait for initial currency to be set
78
- await waitFor(() => {
79
- expect(props.selectedCurrency$.get()).toBeDefined();
80
- });
81
-
82
- // Programmatically change the selected currency
83
- props.selectedCurrency$.set(mockCurrencies[1]);
33
+ render(<CurrencyOptionsSelect {...defaultProps} />);
84
34
 
85
- // Verify the new selection is reflected in the observable
86
- expect(props.selectedCurrency$.get()?.contractAddress).toBe(
87
- mockCurrencies[1].contractAddress,
88
- );
89
- expect(props.selectedCurrency$.get()?.symbol).toBe(
90
- mockCurrencies[1].symbol,
91
- );
35
+ const skeleton = document.querySelector('.h-7.w-20.rounded-2xl');
36
+ expect(skeleton).toBeInTheDocument();
37
+ expect(skeleton).toHaveClass('animate-skeleton');
92
38
  });
93
39
 
94
- it('should maintain selected currency when currencies reload', async () => {
95
- const useCurrenciesMock = vi.mocked(useCurrencies);
96
-
97
- // Initial load with currencies
98
- useCurrenciesMock.mockReturnValue({
99
- data: mockCurrencies,
40
+ it('should set first currency as default when currencies load', async () => {
41
+ const useCurrenciesSpy = vi.spyOn(hooks, 'useCurrencies');
42
+ useCurrenciesSpy.mockReturnValue({
100
43
  isLoading: false,
101
- } as UseQueryResult<Currency[], Error>);
102
-
103
- const props = createDefaultProps();
104
- render(<CurrencyOptionsSelect {...props} />);
105
-
106
- // Wait for initial currency to be set
107
- await waitFor(() => {
108
- expect(props.selectedCurrency$.get()).toBeDefined();
109
- });
110
-
111
- // Programmatically set the second currency
112
- props.selectedCurrency$.set(mockCurrencies[1]);
113
-
114
- // Verify second currency is selected
115
- expect(props.selectedCurrency$.get()?.contractAddress).toBe(
116
- mockCurrencies[1].contractAddress,
117
- );
118
-
119
- // Simulate reload by setting loading state
120
- useCurrenciesMock.mockReturnValue({
121
- data: undefined,
122
- isLoading: true,
44
+ data: TEST_CURRENCIES,
123
45
  error: null,
124
- } as unknown as UseQueryResult<Currency[], Error>);
125
-
126
- // Verify the selected currency remains the same during loading
127
- expect(props.selectedCurrency$.get()?.contractAddress).toBe(
128
- mockCurrencies[1].contractAddress,
129
- );
130
- expect(props.selectedCurrency$.get()?.symbol).toBe(
131
- mockCurrencies[1].symbol,
132
- );
46
+ } as UseQueryResult<Currency[], Error>);
133
47
 
134
- // Simulate reload completion
135
- useCurrenciesMock.mockReturnValue({
136
- data: mockCurrencies,
137
- isLoading: false,
138
- error: null,
139
- } as unknown as UseQueryResult<Currency[], Error>);
48
+ render(<CurrencyOptionsSelect {...defaultProps} />);
140
49
 
141
- // Verify the same currency is still selected after reload
142
- await waitFor(() => {
143
- expect(props.selectedCurrency$.get()?.contractAddress).toBe(
144
- mockCurrencies[1].contractAddress,
145
- );
146
- expect(props.selectedCurrency$.get()?.symbol).toBe(
147
- mockCurrencies[1].symbol,
148
- );
149
- });
50
+ const trigger = screen.getByTestId('currency-select-trigger');
51
+ expect(trigger).toBeInTheDocument();
52
+ expect(trigger).toHaveTextContent(TEST_CURRENCY.symbol);
150
53
  });
151
54
  });