@0xsequence/marketplace-sdk 0.8.4 → 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.
Files changed (67) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/{chunk-VF3LWBQB.js → chunk-6SEJI7YS.js} +170 -9
  3. package/dist/chunk-6SEJI7YS.js.map +1 -0
  4. package/dist/{chunk-25CAMYCG.js → chunk-BB2PTJHI.js} +22 -20
  5. package/dist/chunk-BB2PTJHI.js.map +1 -0
  6. package/dist/{chunk-XUNDLCEH.js → chunk-LDZZUYG7.js} +2 -2
  7. package/dist/{chunk-44YGZVBS.js → chunk-QMWMJVTX.js} +2 -2
  8. package/dist/{chunk-HRL2TMXU.js → chunk-TGFX3TMV.js} +44 -34
  9. package/dist/{chunk-HRL2TMXU.js.map → chunk-TGFX3TMV.js.map} +1 -1
  10. package/dist/{chunk-VBRJ2OPM.js → chunk-V3NVAVHV.js} +2 -2
  11. package/dist/index.css +171 -36
  12. package/dist/index.css.map +1 -1
  13. package/dist/index.js +6 -6
  14. package/dist/react/_internal/databeat/index.js +5 -5
  15. package/dist/react/_internal/index.d.ts +1 -1
  16. package/dist/react/_internal/index.js +3 -1
  17. package/dist/react/_internal/wagmi/index.d.ts +3 -2
  18. package/dist/react/_internal/wagmi/index.js +3 -1
  19. package/dist/react/hooks/index.d.ts +246 -29
  20. package/dist/react/hooks/index.js +8 -4
  21. package/dist/react/hooks/options/index.js +2 -2
  22. package/dist/react/index.d.ts +2 -1
  23. package/dist/react/index.js +11 -7
  24. package/dist/react/queries/index.js +1 -1
  25. package/dist/react/ssr/index.js +1 -1
  26. package/dist/react/ui/components/collectible-card/index.d.ts +3 -2
  27. package/dist/react/ui/components/collectible-card/index.js +7 -7
  28. package/dist/react/ui/icons/index.js +2 -2
  29. package/dist/react/ui/index.js +7 -7
  30. package/dist/react/ui/modals/_internal/components/actionModal/index.js +5 -5
  31. package/dist/types/index.js +1 -1
  32. package/dist/utils/abi/index.js +5 -5
  33. package/dist/utils/index.js +6 -6
  34. package/package.json +20 -19
  35. package/src/react/_internal/wagmi/__tests__/create-config.test.ts +1 -11
  36. package/src/react/_internal/wagmi/get-connectors.ts +27 -24
  37. package/src/react/hooks/__tests__/useAutoSelectFeeOption.test.tsx +21 -75
  38. package/src/react/hooks/__tests__/useCurrencyBalance.test.tsx +4 -25
  39. package/src/react/hooks/index.ts +1 -0
  40. package/src/react/hooks/useFilterState.tsx +181 -0
  41. package/src/react/hooks/useFilters.tsx +24 -0
  42. package/src/react/ui/components/_internals/action-button/__tests__/ActionButtonBody.test.tsx +27 -94
  43. package/src/react/ui/components/_internals/action-button/__tests__/NonOwnerActions.test.tsx +59 -0
  44. package/src/react/ui/components/_internals/action-button/__tests__/OwnerActions.test.tsx +73 -0
  45. package/src/react/ui/components/_internals/action-button/__tests__/useActionButtonLogic.test.tsx +77 -0
  46. package/src/react/ui/components/_internals/action-button/components/ActionButtonBody.tsx +3 -2
  47. package/src/react/ui/components/_internals/action-button/hooks/useActionButtonLogic.ts +4 -3
  48. package/src/react/ui/components/collectible-card/CollectibleAsset.tsx +1 -0
  49. package/src/react/ui/components/collectible-card/CollectibleCard.tsx +18 -12
  50. package/src/react/ui/components/collectible-card/__tests__/CollectibleAsset.test.tsx +200 -0
  51. package/src/react/ui/components/collectible-card/__tests__/CollectibleCard.test.tsx +92 -123
  52. package/src/react/ui/components/collectible-card/__tests__/Footer.test.tsx +136 -0
  53. package/src/react/ui/modals/BuyModal/__tests__/Modal.test.tsx +2 -8
  54. package/src/react/ui/modals/SellModal/__tests__/Modal.test.tsx +72 -135
  55. package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/ActionButtons.test.tsx +72 -0
  56. package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/BalanceIndicator.test.tsx +50 -0
  57. package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/SelectWaasFeeOptions.test.tsx +193 -0
  58. package/src/react/ui/modals/_internal/components/switchChainModal/__tests__/SwitchChainModal.test.tsx +38 -58
  59. package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +3 -1
  60. package/test/test-utils.tsx +12 -22
  61. package/tsconfig.tsbuildinfo +1 -1
  62. package/dist/chunk-25CAMYCG.js.map +0 -1
  63. package/dist/chunk-VF3LWBQB.js.map +0 -1
  64. package/src/react/ui/components/_internals/action-button/__tests__/ActionButton.test.tsx +0 -107
  65. /package/dist/{chunk-XUNDLCEH.js.map → chunk-LDZZUYG7.js.map} +0 -0
  66. /package/dist/{chunk-44YGZVBS.js.map → chunk-QMWMJVTX.js.map} +0 -0
  67. /package/dist/{chunk-VBRJ2OPM.js.map → chunk-V3NVAVHV.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import { WebSdkWrapper, cleanup, render, screen } from '@test';
1
+ import { cleanup, render, screen } from '@test';
2
2
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3
3
 
4
4
  import { server } from '@test';
@@ -46,9 +46,7 @@ describe('BuyModal', () => {
46
46
  // },
47
47
  // });
48
48
  // render(
49
- // <WebSdkWrapper>
50
49
  // <BuyModal />
51
- // </WebSdkWrapper>
52
50
  // );
53
51
  // // Should show error modal
54
52
  // await waitFor(() => {
@@ -98,11 +96,7 @@ describe('BuyModal', () => {
98
96
  },
99
97
  });
100
98
 
101
- render(
102
- <WebSdkWrapper>
103
- <BuyModal />
104
- </WebSdkWrapper>,
105
- );
99
+ render(<BuyModal />);
106
100
 
107
101
  // Should show loading modal
108
102
  expect(screen.getByText('Loading Sequence Pay')).toBeInTheDocument();
@@ -1,166 +1,103 @@
1
- import { cleanup, fireEvent, render, screen } from '@test';
1
+ import { cleanup, render, renderHook, screen, waitFor } from '@test';
2
+ import { TEST_COLLECTIBLE } from '@test/const';
3
+ import { createMockWallet } from '@test/mocks/wallet';
2
4
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { useCollection, useCurrency } from '../../../../hooks';
5
+ import { useSellModal } from '..';
6
+ import { StepType, WalletKind } from '../../../../_internal';
7
+ import { createMockStep } from '../../../../_internal/api/__mocks__/marketplace.msw';
8
+ import { mockOrder } from '../../../../_internal/api/__mocks__/marketplace.msw';
9
+ import * as walletModule from '../../../../_internal/wallet/useWallet';
4
10
  import { SellModal } from '../Modal';
5
- import { type OpenSellModalArgs, sellModal$ } from '../store';
11
+ import * as useGetTokenApprovalDataModule from '../hooks/useGetTokenApproval';
6
12
 
7
- import { MarketplaceKind, type Order } from '../../../../_internal';
8
-
9
- import { server } from '@test';
10
- import { http, HttpResponse } from 'msw';
11
- import { mockMarketplaceEndpoint } from '../../../../_internal/api/__mocks__/marketplace.msw';
12
- import { useSell } from '../hooks/useSell';
13
-
14
- // Test data
15
- const mockOrder = {
16
- orderId: '1',
17
- priceAmount: '1000000000000000000',
18
- priceCurrencyAddress: '0x0',
19
- quantityRemaining: '1',
20
- createdAt: new Date().toISOString(),
21
- marketplace: MarketplaceKind.sequence_marketplace_v2,
22
- } as Order;
23
-
24
- const mockModalProps = {
25
- collectionAddress: '0x123',
26
- chainId: 1,
27
- tokenId: '1',
13
+ const defaultArgs = {
14
+ collectionAddress: TEST_COLLECTIBLE.collectionAddress,
15
+ chainId: TEST_COLLECTIBLE.chainId,
16
+ tokenId: TEST_COLLECTIBLE.collectibleId,
28
17
  order: mockOrder,
29
- } satisfies OpenSellModalArgs;
30
-
31
- // TODO: remove when there is mocks for more endpoints
32
- vi.mock(import('../../../../hooks'), async (importOriginal) => {
33
- const mod = await importOriginal();
34
- return {
35
- ...mod,
36
- useCollection: vi.fn().mockImplementation(mod.useCollection),
37
- useCurrency: vi.fn().mockImplementation(mod.useCurrency),
38
- };
39
- });
40
-
41
- vi.mock('@0xsequence/kit', () => ({
42
- useWaasFeeOptions: vi.fn().mockReturnValue([]),
43
- }));
44
-
45
- beforeEach(() => {
46
- cleanup();
47
- vi.clearAllMocks();
48
- vi.mock('../hooks/useSell', () => ({
49
- useSell: vi.fn().mockReturnValue({
50
- isLoading: false,
51
- executeApproval: vi.fn(),
52
- sell: vi.fn(),
53
- }),
54
- }));
55
- });
18
+ };
56
19
 
57
- describe.skip('SellModal', () => {
58
- it('should not render when modal is closed', () => {
59
- render(<SellModal />);
60
- expect(screen.queryByText('You have an offer')).toBeNull();
61
- });
20
+ describe('MakeOfferModal', () => {
21
+ const mockWallet = createMockWallet();
62
22
 
63
- it.skip('should render error state', async () => {
64
- // Override MSW to return error
65
- server.use(
66
- http.post(mockMarketplaceEndpoint('GetCollectible'), () => {
67
- return HttpResponse.error();
68
- }),
69
- );
70
- sellModal$.open(mockModalProps);
71
- render(<SellModal />);
72
- const errorModal = await screen.findByTestId('error-modal');
73
- expect(errorModal).toBeVisible();
23
+ beforeEach(() => {
24
+ cleanup();
25
+ // Reset all mocks
26
+ vi.clearAllMocks();
27
+ vi.resetAllMocks();
28
+ vi.restoreAllMocks();
74
29
  });
75
30
 
76
- it.skip('should render main modal when data is loaded', async () => {
77
- vi.mocked(useCollection).mockReturnValue({
78
- // @ts-expect-error - TODO: mock this better
79
- data: {},
31
+ it('should show main button if there is no approval step', async () => {
32
+ // Mock sequence wallet
33
+ const sequenceWallet = {
34
+ ...mockWallet,
35
+ walletKind: WalletKind.sequence,
36
+ };
37
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
38
+ wallet: sequenceWallet,
80
39
  isLoading: false,
81
40
  isError: false,
82
41
  });
83
-
84
- vi.mocked(useCurrency).mockReturnValue({
85
- // @ts-expect-error - TODO: mock this better
86
- data: {},
42
+ vi.spyOn(
43
+ useGetTokenApprovalDataModule,
44
+ 'useGetTokenApprovalData',
45
+ ).mockReturnValue({
46
+ data: {
47
+ step: null,
48
+ },
87
49
  isLoading: false,
88
- isError: false,
50
+ isSuccess: true,
89
51
  });
90
52
 
91
- sellModal$.open(mockModalProps);
53
+ // Render the modal
54
+ const { result } = renderHook(() => useSellModal());
55
+ result.current.show(defaultArgs);
56
+
92
57
  render(<SellModal />);
93
- const text = await screen.findByText('Offer received');
94
- expect(text).toBeInTheDocument();
58
+
59
+ // Wait for the component to update
60
+ await waitFor(() => {
61
+ // The Approve TOKEN button should not exist
62
+ expect(screen.queryByText('Approve TOKEN')).toBeNull();
63
+
64
+ // The Accept button should exist
65
+ expect(screen.getByRole('button', { name: 'Accept' })).toBeDefined();
66
+ });
95
67
  });
96
- });
97
68
 
98
- describe.skip('Modal Actions', () => {
99
- it('should handle approval step correctly', async () => {
100
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
101
- vi.mocked(useCollection as any).mockReturnValue({
102
- data: {
103
- name: 'Test Collection',
104
- decimals: 0,
105
- },
69
+ it('(non-sequence wallets) should show approve token button if there is an approval step, disable main button', async () => {
70
+ const nonSequenceWallet = {
71
+ ...mockWallet,
72
+ walletKind: 'unknown' as WalletKind,
73
+ };
74
+ vi.spyOn(walletModule, 'useWallet').mockReturnValue({
75
+ wallet: nonSequenceWallet,
106
76
  isLoading: false,
107
77
  isError: false,
108
78
  });
109
-
110
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
111
- vi.mocked(useCurrency as any).mockReturnValue({
79
+ vi.spyOn(
80
+ useGetTokenApprovalDataModule,
81
+ 'useGetTokenApprovalData',
82
+ ).mockReturnValue({
112
83
  data: {
113
- name: 'Test Currency',
114
- imageUrl: 'test-url',
84
+ step: createMockStep(StepType.tokenApproval),
115
85
  },
116
86
  isLoading: false,
117
- isError: false,
87
+ isSuccess: true,
118
88
  });
119
89
 
120
- const mockExecuteApproval = vi.fn();
90
+ // Render the modal
91
+ const { result } = renderHook(() => useSellModal());
92
+ result.current.show(defaultArgs);
121
93
 
122
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
123
- (useSell as any).mockReturnValue({
124
- isLoading: false,
125
- executeApproval: mockExecuteApproval,
126
- sell: vi.fn(),
127
- });
128
-
129
- sellModal$.open({
130
- ...mockModalProps,
131
- order: {
132
- ...mockOrder,
133
- quantityRemaining: '1',
134
- },
135
- });
136
- sellModal$.steps.approval.exist.set(true);
137
- sellModal$.steps.approval.isExecuting.set(false);
138
- sellModal$.steps.transaction.isExecuting.set(false);
139
94
  render(<SellModal />);
140
95
 
141
- const approveButton = await screen.findByText('Approve TOKEN');
142
- expect(approveButton).not.toBeDisabled();
143
- fireEvent.click(approveButton);
144
- expect(mockExecuteApproval).toHaveBeenCalled();
145
- });
146
- });
96
+ await waitFor(() => {
97
+ expect(screen.getByText('Approve TOKEN')).toBeDefined();
147
98
 
148
- it.skip('should handle sell action correctly', async () => {
149
- const mockSell = vi.fn();
150
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
151
- (useSell as any).mockReturnValue({
152
- isLoading: false,
153
- executeApproval: vi.fn(),
154
- sell: mockSell,
99
+ expect(screen.getByRole('button', { name: 'Accept' })).toBeDefined();
100
+ expect(screen.getByRole('button', { name: 'Accept' })).toBeDisabled();
101
+ });
155
102
  });
156
-
157
- sellModal$.open(mockModalProps);
158
- sellModal$.steps.approval.exist.set(false);
159
- sellModal$.steps.approval.isExecuting.set(false);
160
- sellModal$.steps.transaction.isExecuting.set(false);
161
- render(<SellModal />);
162
-
163
- const acceptButton = screen.getByText('Accept');
164
- fireEvent.click(acceptButton);
165
- expect(mockSell).toHaveBeenCalled();
166
103
  });
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+
3
+ import { fireEvent, render, screen } from '@test';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import ActionButtons from '../_components/ActionButtons';
6
+
7
+ describe('ActionButtons', () => {
8
+ const mockOnCancel = vi.fn();
9
+ const mockOnConfirm = vi.fn();
10
+
11
+ const defaultProps = {
12
+ onCancel: mockOnCancel,
13
+ onConfirm: mockOnConfirm,
14
+ disabled: false,
15
+ loading: false,
16
+ confirmed: false,
17
+ };
18
+
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ });
22
+
23
+ it('should render both buttons', () => {
24
+ render(<ActionButtons {...defaultProps} />);
25
+
26
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
27
+ expect(screen.getByText('Continue with')).toBeInTheDocument();
28
+ });
29
+
30
+ it('should call onCancel when cancel button is clicked', () => {
31
+ render(<ActionButtons {...defaultProps} />);
32
+
33
+ const cancelButton = screen.getByText('Cancel');
34
+ fireEvent.click(cancelButton);
35
+
36
+ expect(mockOnCancel).toHaveBeenCalledTimes(1);
37
+ });
38
+
39
+ it('should call onConfirm when confirm button is clicked', () => {
40
+ render(<ActionButtons {...defaultProps} />);
41
+
42
+ const confirmButton = screen.getByText('Continue with');
43
+ fireEvent.click(confirmButton);
44
+
45
+ expect(mockOnConfirm).toHaveBeenCalledTimes(1);
46
+ });
47
+
48
+ it('should disable confirm button when disabled prop is true', () => {
49
+ render(<ActionButtons {...defaultProps} disabled={true} />);
50
+
51
+ const confirmButton = screen.getByText('Continue with').closest('button');
52
+ expect(confirmButton).toBeDisabled();
53
+
54
+ if (confirmButton) {
55
+ fireEvent.click(confirmButton);
56
+ }
57
+ expect(mockOnConfirm).not.toHaveBeenCalled();
58
+ });
59
+
60
+ it('should display token symbol when provided', () => {
61
+ render(<ActionButtons {...defaultProps} tokenSymbol="ETH" />);
62
+
63
+ expect(screen.getByText('Continue with ETH')).toBeInTheDocument();
64
+ });
65
+
66
+ it('should show skeleton loading when tokenSymbol is not provided', () => {
67
+ render(<ActionButtons {...defaultProps} />);
68
+
69
+ expect(screen.getByText('Continue with')).toBeInTheDocument();
70
+ expect(document.querySelector('.animate-shimmer')).toBeInTheDocument();
71
+ });
72
+ });
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+
3
+ import { render, screen } from '@test';
4
+ import { describe, expect, it } from 'vitest';
5
+ import BalanceIndicator from '../_components/BalanceIndicator';
6
+
7
+ describe('BalanceIndicator', () => {
8
+ it('should render warning icon and negative text when insufficient balance', () => {
9
+ render(
10
+ <BalanceIndicator
11
+ insufficientBalance={true}
12
+ currencyBalance={{ formatted: '0.5' }}
13
+ selectedFeeOption={{ token: { symbol: 'ETH' } }}
14
+ />,
15
+ );
16
+
17
+ expect(document.querySelector('.text-negative')).toBeInTheDocument();
18
+ expect(screen.getByText('You have 0.5 ETH')).toBeInTheDocument();
19
+ expect(screen.getByText('You have 0.5 ETH').className).toContain(
20
+ 'text-negative',
21
+ );
22
+ });
23
+
24
+ it('should render checkmark icon and normal text when balance is sufficient', () => {
25
+ render(
26
+ <BalanceIndicator
27
+ insufficientBalance={false}
28
+ currencyBalance={{ formatted: '1.5' }}
29
+ selectedFeeOption={{ token: { symbol: 'ETH' } }}
30
+ />,
31
+ );
32
+
33
+ expect(document.querySelector('.text-positive')).toBeInTheDocument();
34
+ expect(screen.getByText('You have 1.5 ETH')).toBeInTheDocument();
35
+ expect(screen.getByText('You have 1.5 ETH').className).not.toContain(
36
+ 'text-negative',
37
+ );
38
+ });
39
+
40
+ it('should handle undefined currencyBalance gracefully', () => {
41
+ render(
42
+ <BalanceIndicator
43
+ insufficientBalance={false}
44
+ selectedFeeOption={{ token: { symbol: 'ETH' } }}
45
+ />,
46
+ );
47
+
48
+ expect(screen.getByText('You have 0 ETH')).toBeInTheDocument();
49
+ });
50
+ });
@@ -0,0 +1,193 @@
1
+ import { TokenType } from '@0xsequence/api';
2
+ import * as useNetworkModule from '@0xsequence/connect';
3
+ import * as useWaasFeeOptionsModule from '@0xsequence/connect';
4
+ import { NetworkType } from '@0xsequence/network';
5
+ import { observable } from '@legendapp/state';
6
+ import { render, screen } from '@test';
7
+ import { TEST_CURRENCY } from '@test/const';
8
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
9
+ import SelectWaasFeeOptions from '..';
10
+ import type {
11
+ FeeOption,
12
+ FeeOptionExtended,
13
+ WaasFeeOptionConfirmation,
14
+ } from '../../../../../../../types/waas-types';
15
+ import { selectWaasFeeOptions$ } from '../store';
16
+ import * as useWaasFeeOptionManagerModule from '../useWaasFeeOptionManager';
17
+
18
+ const mockFeeOption: FeeOptionExtended = {
19
+ gasLimit: 21000,
20
+ to: '0x123',
21
+ value: '1000000000000000',
22
+ token: {
23
+ chainId: 1,
24
+ contractAddress: TEST_CURRENCY.contractAddress,
25
+ decimals: TEST_CURRENCY.decimals,
26
+ logoURL: TEST_CURRENCY.imageUrl,
27
+ name: TEST_CURRENCY.name,
28
+ symbol: TEST_CURRENCY.symbol,
29
+ tokenID: null,
30
+ type: TokenType.ERC20,
31
+ },
32
+ balance: '1000000000000000',
33
+ balanceFormatted: '1',
34
+ hasEnoughBalanceForFee: true,
35
+ };
36
+
37
+ const mockPendingFeeOptionConfirmation: WaasFeeOptionConfirmation = {
38
+ id: 'fee-confirmation-id',
39
+ options: [mockFeeOption],
40
+ chainId: 1,
41
+ };
42
+
43
+ // Mock currency balance with formatted value
44
+ const mockCurrencyBalance = {
45
+ value: 2000000000000000n,
46
+ formatted: '0.002',
47
+ };
48
+
49
+ describe('SelectWaasFeeOptions', () => {
50
+ const mockOnCancel = vi.fn();
51
+ const mockHandleConfirmFeeOption = vi.fn();
52
+
53
+ beforeEach(() => {
54
+ vi.resetAllMocks();
55
+ vi.resetModules();
56
+
57
+ selectWaasFeeOptions$.isVisible.set(true);
58
+ selectWaasFeeOptions$.selectedFeeOption.set(undefined);
59
+ selectWaasFeeOptions$.pendingFeeOptionConfirmation.set(undefined);
60
+ });
61
+
62
+ it('should not render when isVisible is false', () => {
63
+ vi.spyOn(useWaasFeeOptionManagerModule, 'default').mockReturnValue({
64
+ selectedFeeOption$: observable<FeeOptionExtended | undefined>(
65
+ mockFeeOption,
66
+ ),
67
+ selectedFeeOption: mockFeeOption,
68
+ // @ts-expect-error - types are not compatible
69
+ pendingFeeOptionConfirmation: mockPendingFeeOptionConfirmation,
70
+ currencyBalance: mockCurrencyBalance,
71
+ currencyBalanceLoading: false,
72
+ insufficientBalance: false,
73
+ feeOptionsConfirmed: false,
74
+ handleConfirmFeeOption: mockHandleConfirmFeeOption,
75
+ });
76
+
77
+ // Set isVisible to false
78
+ selectWaasFeeOptions$.isVisible.set(false);
79
+
80
+ const { container } = render(
81
+ <SelectWaasFeeOptions chainId={1} onCancel={mockOnCancel} />,
82
+ );
83
+
84
+ expect(container.firstChild).toBeNull();
85
+ });
86
+
87
+ it('should not render on testnet', () => {
88
+ vi.spyOn(useWaasFeeOptionManagerModule, 'default').mockReturnValue({
89
+ selectedFeeOption$: observable<FeeOption | undefined>(mockFeeOption),
90
+ selectedFeeOption: mockFeeOption,
91
+ // @ts-expect-error - types are not compatible
92
+ pendingFeeOptionConfirmation: mockPendingFeeOptionConfirmation,
93
+ currencyBalance: mockCurrencyBalance,
94
+ currencyBalanceLoading: false,
95
+ insufficientBalance: false,
96
+ feeOptionsConfirmed: false,
97
+ handleConfirmFeeOption: mockHandleConfirmFeeOption,
98
+ });
99
+
100
+ vi.spyOn(useNetworkModule, 'getNetwork').mockReturnValue({
101
+ type: NetworkType.TESTNET,
102
+ chainId: 1,
103
+ name: 'Testnet',
104
+ nativeToken: TEST_CURRENCY,
105
+ });
106
+ vi.spyOn(useWaasFeeOptionsModule, 'useWaasFeeOptions').mockReturnValue([
107
+ // @ts-expect-error - types are not compatible
108
+ mockPendingFeeOptionConfirmation,
109
+ vi.fn(),
110
+ ]);
111
+
112
+ const { container } = render(
113
+ <SelectWaasFeeOptions chainId={1} onCancel={mockOnCancel} />,
114
+ );
115
+
116
+ expect(container.firstChild).toBeNull();
117
+ });
118
+
119
+ it('should render loading skeleton when fee options are loading', () => {
120
+ // Mock the hook with loading state
121
+ vi.spyOn(useWaasFeeOptionManagerModule, 'default').mockReturnValue({
122
+ selectedFeeOption$: observable<FeeOption | undefined>(undefined),
123
+ selectedFeeOption: mockFeeOption,
124
+ pendingFeeOptionConfirmation: undefined,
125
+ currencyBalance: undefined,
126
+ currencyBalanceLoading: true,
127
+ insufficientBalance: false,
128
+ feeOptionsConfirmed: false,
129
+ handleConfirmFeeOption: mockHandleConfirmFeeOption,
130
+ });
131
+ vi.spyOn(useWaasFeeOptionsModule, 'useWaasFeeOptions').mockReturnValue([
132
+ // @ts-expect-error - types are not compatible
133
+ mockPendingFeeOptionConfirmation,
134
+ vi.fn(),
135
+ ]);
136
+ vi.spyOn(useNetworkModule, 'getNetwork').mockReturnValue({
137
+ type: NetworkType.MAINNET,
138
+ chainId: 1,
139
+ name: 'Mainnet',
140
+ nativeToken: TEST_CURRENCY,
141
+ });
142
+
143
+ render(<SelectWaasFeeOptions chainId={1} onCancel={mockOnCancel} />);
144
+
145
+ expect(screen.getByText('Select a fee option')).toBeInTheDocument();
146
+
147
+ // Should render skeleton
148
+ const skeleton = document.querySelector('.h-\\[52px\\]');
149
+ expect(skeleton).toBeInTheDocument();
150
+ expect(skeleton).toHaveClass('animate-shimmer');
151
+ });
152
+
153
+ it('should render fee options when loaded', () => {
154
+ selectWaasFeeOptions$.pendingFeeOptionConfirmation.set(
155
+ mockPendingFeeOptionConfirmation,
156
+ );
157
+ selectWaasFeeOptions$.selectedFeeOption.set(mockFeeOption);
158
+
159
+ vi.spyOn(useWaasFeeOptionManagerModule, 'default').mockReturnValue({
160
+ selectedFeeOption$: selectWaasFeeOptions$.selectedFeeOption,
161
+ selectedFeeOption: mockFeeOption,
162
+ // @ts-expect-error - types are not compatible
163
+ pendingFeeOptionConfirmation: mockPendingFeeOptionConfirmation,
164
+ currencyBalance: mockCurrencyBalance,
165
+ currencyBalanceLoading: false,
166
+ insufficientBalance: false,
167
+ feeOptionsConfirmed: false,
168
+ handleConfirmFeeOption: mockHandleConfirmFeeOption,
169
+ });
170
+ vi.spyOn(useWaasFeeOptionsModule, 'useWaasFeeOptions').mockReturnValue([
171
+ // @ts-expect-error - types are not compatible
172
+ mockPendingFeeOptionConfirmation,
173
+ vi.fn(),
174
+ ]);
175
+ vi.spyOn(useNetworkModule, 'getNetwork').mockReturnValue({
176
+ type: NetworkType.MAINNET,
177
+ chainId: 1,
178
+ name: 'Mainnet',
179
+ nativeToken: TEST_CURRENCY,
180
+ });
181
+
182
+ render(
183
+ <SelectWaasFeeOptions
184
+ chainId={1}
185
+ onCancel={mockOnCancel}
186
+ titleOnConfirm="Confirm fee option"
187
+ />,
188
+ );
189
+
190
+ expect(screen.getByText('Select a fee option')).toBeInTheDocument();
191
+ expect(screen.queryByText('Confirm fee option')).not.toBeInTheDocument();
192
+ });
193
+ });