@0xsequence/marketplace-sdk 0.8.3 → 0.8.5

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 (97) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/{chunk-25CAMYCG.js → chunk-BB2PTJHI.js} +22 -20
  3. package/dist/chunk-BB2PTJHI.js.map +1 -0
  4. package/dist/{chunk-5ATGT5S4.js → chunk-EZFCQZHU.js} +14 -6
  5. package/dist/chunk-EZFCQZHU.js.map +1 -0
  6. package/dist/{chunk-DFI52A2E.js → chunk-KCLMSSPS.js} +364 -242
  7. package/dist/chunk-KCLMSSPS.js.map +1 -0
  8. package/dist/{chunk-XUNDLCEH.js → chunk-LDZZUYG7.js} +2 -2
  9. package/dist/{chunk-QTV77W42.js → chunk-SFSFIGHM.js} +45 -35
  10. package/dist/chunk-SFSFIGHM.js.map +1 -0
  11. package/dist/{chunk-FSJKN4YN.js → chunk-ZSCZLHKX.js} +194 -2
  12. package/dist/chunk-ZSCZLHKX.js.map +1 -0
  13. package/dist/{chunk-FH4TZRDV.js → chunk-ZVTG6US2.js} +2 -2
  14. package/dist/index.css +4 -4
  15. package/dist/index.css.map +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/{lowestListing-DUZ_nYml.d.ts → lowestListing-W7P4EkC3.d.ts} +34 -11
  18. package/dist/react/_internal/databeat/index.js +5 -5
  19. package/dist/react/_internal/index.d.ts +1 -1
  20. package/dist/react/_internal/index.js +3 -1
  21. package/dist/react/_internal/wagmi/index.d.ts +3 -2
  22. package/dist/react/_internal/wagmi/index.js +3 -1
  23. package/dist/react/hooks/index.d.ts +8 -5
  24. package/dist/react/hooks/index.js +6 -4
  25. package/dist/react/hooks/options/index.js +2 -2
  26. package/dist/react/index.d.ts +2 -2
  27. package/dist/react/index.js +9 -7
  28. package/dist/react/queries/index.d.ts +1 -1
  29. package/dist/react/queries/index.js +6 -2
  30. package/dist/react/ssr/index.js +1 -1
  31. package/dist/react/ui/components/collectible-card/index.d.ts +3 -2
  32. package/dist/react/ui/components/collectible-card/index.js +7 -7
  33. package/dist/react/ui/icons/index.js +1 -1
  34. package/dist/react/ui/index.js +7 -7
  35. package/dist/react/ui/modals/_internal/components/actionModal/index.js +5 -5
  36. package/dist/types/index.js +1 -1
  37. package/dist/utils/index.js +1 -1
  38. package/package.json +19 -19
  39. package/src/react/_internal/api/__mocks__/marketplace.msw.ts +35 -21
  40. package/src/react/_internal/wagmi/__tests__/create-config.test.ts +1 -11
  41. package/src/react/_internal/wagmi/get-connectors.ts +27 -24
  42. package/src/react/hooks/__tests__/useCancelTransactionSteps.test.tsx +4 -9
  43. package/src/react/hooks/__tests__/useGenerateCancelTransaction.test.tsx +5 -4
  44. package/src/react/hooks/__tests__/useGenerateListingTransaction.test.tsx +14 -10
  45. package/src/react/hooks/__tests__/useGenerateOfferTransaction.test.tsx +115 -65
  46. package/src/react/hooks/__tests__/useGenerateSellTransaction.test.tsx +10 -7
  47. package/src/react/hooks/__tests__/useInventory.test.tsx +294 -0
  48. package/src/react/hooks/index.ts +1 -0
  49. package/src/react/hooks/useAutoSelectFeeOption.tsx +10 -3
  50. package/src/react/hooks/useCancelOrder.tsx +1 -0
  51. package/src/react/hooks/useCancelTransactionSteps.tsx +18 -4
  52. package/src/react/hooks/useGenerateOfferTransaction.tsx +11 -1
  53. package/src/react/hooks/useInventory.tsx +15 -0
  54. package/src/react/hooks/util/optimisticCancelUpdates.ts +115 -0
  55. package/src/react/queries/index.ts +1 -0
  56. package/src/react/queries/inventory.ts +303 -0
  57. package/src/react/queries/listBalances.ts +1 -8
  58. package/src/react/queries/listCollectibles.ts +12 -3
  59. package/src/react/ui/components/_internals/action-button/__tests__/ActionButtonBody.test.tsx +27 -94
  60. package/src/react/ui/components/_internals/action-button/__tests__/NonOwnerActions.test.tsx +59 -0
  61. package/src/react/ui/components/_internals/action-button/__tests__/OwnerActions.test.tsx +73 -0
  62. package/src/react/ui/components/_internals/action-button/__tests__/useActionButtonLogic.test.tsx +77 -0
  63. package/src/react/ui/components/_internals/action-button/components/ActionButtonBody.tsx +3 -2
  64. package/src/react/ui/components/_internals/action-button/hooks/useActionButtonLogic.ts +4 -3
  65. package/src/react/ui/components/collectible-card/CollectibleAsset.tsx +1 -0
  66. package/src/react/ui/components/collectible-card/CollectibleCard.tsx +18 -12
  67. package/src/react/ui/components/collectible-card/__tests__/CollectibleAsset.test.tsx +200 -0
  68. package/src/react/ui/components/collectible-card/__tests__/CollectibleCard.test.tsx +92 -123
  69. package/src/react/ui/components/collectible-card/__tests__/Footer.test.tsx +136 -0
  70. package/src/react/ui/modals/BuyModal/__tests__/Modal.test.tsx +2 -8
  71. package/src/react/ui/modals/CreateListingModal/__tests__/Modal.test.tsx +74 -104
  72. package/src/react/ui/modals/MakeOfferModal/__tests__/Modal.test.tsx +108 -78
  73. package/src/react/ui/modals/SellModal/__tests__/Modal.test.tsx +72 -135
  74. package/src/react/ui/modals/_internal/components/actionModal/ActionModal.test.tsx +286 -0
  75. package/src/react/ui/modals/_internal/components/actionModal/ActionModal.tsx +16 -4
  76. package/src/react/ui/modals/_internal/components/currencyOptionsSelect/__tests__/index.test.tsx +35 -132
  77. package/src/react/ui/modals/_internal/components/floorPriceText/__tests__/FloorPriceText.test.tsx +199 -0
  78. package/src/react/ui/modals/_internal/components/priceInput/__tests__/PriceInput.test.tsx +55 -0
  79. package/src/react/ui/modals/_internal/components/priceInput/index.tsx +1 -1
  80. package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/ActionButtons.test.tsx +72 -0
  81. package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/BalanceIndicator.test.tsx +50 -0
  82. package/src/react/ui/modals/_internal/components/selectWaasFeeOptions/__tests__/SelectWaasFeeOptions.test.tsx +193 -0
  83. package/src/react/ui/modals/_internal/components/switchChainModal/index.tsx +2 -2
  84. package/test/const.ts +24 -0
  85. package/test/test-utils.tsx +85 -47
  86. package/.changeset/flat-parks-clean.md +0 -8
  87. package/.changeset/red-buckets-deny.md +0 -6
  88. package/.changeset/seven-doors-taste.md +0 -5
  89. package/dist/chunk-25CAMYCG.js.map +0 -1
  90. package/dist/chunk-5ATGT5S4.js.map +0 -1
  91. package/dist/chunk-DFI52A2E.js.map +0 -1
  92. package/dist/chunk-FSJKN4YN.js.map +0 -1
  93. package/dist/chunk-QTV77W42.js.map +0 -1
  94. package/src/react/ui/components/_internals/action-button/__tests__/ActionButton.test.tsx +0 -107
  95. package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +0 -164
  96. /package/dist/{chunk-XUNDLCEH.js.map → chunk-LDZZUYG7.js.map} +0 -0
  97. /package/dist/{chunk-FH4TZRDV.js.map → chunk-ZVTG6US2.js.map} +0 -0
@@ -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
  });
@@ -0,0 +1,199 @@
1
+ import { render } from '@test';
2
+ import type { Hex } from 'viem';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import type { Currency } from '../../../../../../_internal/api/marketplace.gen';
5
+ import * as hooks from '../../../../../../hooks';
6
+ import FloorPriceText from '../index';
7
+
8
+ describe('FloorPriceText', () => {
9
+ const mockCurrency: Currency = {
10
+ contractAddress: '0x0000000000000000000000000000000000000000' as Hex,
11
+ symbol: 'ETH',
12
+ decimals: 18,
13
+ name: 'Ethereum',
14
+ chainId: 1,
15
+ status: 'VERIFIED' as Currency['status'],
16
+ imageUrl: '',
17
+ exchangeRate: 0,
18
+ defaultChainCurrency: false,
19
+ nativeCurrency: true,
20
+ createdAt: '',
21
+ updatedAt: '',
22
+ };
23
+
24
+ const mockProps = {
25
+ chainId: 1,
26
+ collectionAddress: '0x1234567890123456789012345678901234567890' as Hex,
27
+ tokenId: '1',
28
+ price: {
29
+ amountRaw: '1000000000000000000',
30
+ currency: mockCurrency,
31
+ },
32
+ onBuyNow: vi.fn(),
33
+ };
34
+
35
+ beforeEach(() => {
36
+ vi.clearAllMocks();
37
+ vi.restoreAllMocks();
38
+ });
39
+
40
+ it('should render null when loading', () => {
41
+ const useLowestListingSpy = vi.spyOn(hooks, 'useLowestListing');
42
+ const useComparePricesSpy = vi.spyOn(hooks, 'useComparePrices');
43
+
44
+ const lowestListingMock = {
45
+ data: undefined,
46
+ isLoading: true,
47
+ status: 'pending',
48
+ fetchStatus: 'fetching',
49
+ } as unknown as ReturnType<typeof hooks.useLowestListing>;
50
+
51
+ const comparePricesMock = {
52
+ data: undefined,
53
+ isLoading: true,
54
+ status: 'pending',
55
+ fetchStatus: 'fetching',
56
+ } as unknown as ReturnType<typeof hooks.useComparePrices>;
57
+
58
+ useLowestListingSpy.mockReturnValue(lowestListingMock);
59
+ useComparePricesSpy.mockReturnValue(comparePricesMock);
60
+
61
+ const { container } = render(<FloorPriceText {...mockProps} />);
62
+ expect(container.firstChild).toBeNull();
63
+ });
64
+
65
+ it('should render null when floorPriceRaw is undefined even when not loading', () => {
66
+ const useLowestListingSpy = vi.spyOn(hooks, 'useLowestListing');
67
+ const useComparePricesSpy = vi.spyOn(hooks, 'useComparePrices');
68
+
69
+ const lowestListingMock = {
70
+ data: undefined,
71
+ isLoading: false,
72
+ status: 'success',
73
+ fetchStatus: 'idle',
74
+ } as ReturnType<typeof hooks.useLowestListing>;
75
+
76
+ const comparePricesMock = {
77
+ data: undefined,
78
+ isLoading: false,
79
+ status: 'success',
80
+ fetchStatus: 'idle',
81
+ } as unknown as ReturnType<typeof hooks.useComparePrices>;
82
+
83
+ useLowestListingSpy.mockReturnValue(lowestListingMock);
84
+ useComparePricesSpy.mockReturnValue(comparePricesMock);
85
+
86
+ const { container } = render(<FloorPriceText {...mockProps} />);
87
+ expect(container.firstChild).toBeNull();
88
+ });
89
+
90
+ it('should display "Same as floor price" and Buy Now button when price is same as floor price', () => {
91
+ const useLowestListingSpy = vi.spyOn(hooks, 'useLowestListing');
92
+ const useComparePricesSpy = vi.spyOn(hooks, 'useComparePrices');
93
+
94
+ const lowestListingMock = {
95
+ data: {
96
+ priceAmount: '1000000000000000000',
97
+ priceAmountFormatted: '1',
98
+ priceCurrencyAddress:
99
+ '0x0000000000000000000000000000000000000000' as Hex,
100
+ },
101
+ isLoading: false,
102
+ status: 'success',
103
+ fetchStatus: 'idle',
104
+ } as ReturnType<typeof hooks.useLowestListing>;
105
+
106
+ const comparePricesMock = {
107
+ data: {
108
+ status: 'same',
109
+ percentageDifferenceFormatted: '0',
110
+ },
111
+ isLoading: false,
112
+ status: 'success',
113
+ fetchStatus: 'idle',
114
+ } as ReturnType<typeof hooks.useComparePrices>;
115
+
116
+ useLowestListingSpy.mockReturnValue(lowestListingMock);
117
+ useComparePricesSpy.mockReturnValue(comparePricesMock);
118
+
119
+ const { getByText } = render(<FloorPriceText {...mockProps} />);
120
+
121
+ expect(getByText('Same as floor price')).toBeInTheDocument();
122
+ expect(getByText('Buy for 1 ETH')).toBeInTheDocument();
123
+ expect(mockProps.onBuyNow).not.toHaveBeenCalled();
124
+ });
125
+
126
+ it('should display percentage below floor price and not show Buy Now button when price is below floor price', () => {
127
+ const useLowestListingSpy = vi.spyOn(hooks, 'useLowestListing');
128
+ const useComparePricesSpy = vi.spyOn(hooks, 'useComparePrices');
129
+
130
+ const lowestListingMock = {
131
+ data: {
132
+ priceAmount: '1200000000000000000', // Floor price is higher
133
+ priceAmountFormatted: '1.2',
134
+ priceCurrencyAddress:
135
+ '0x0000000000000000000000000000000000000000' as Hex,
136
+ },
137
+ isLoading: false,
138
+ status: 'success',
139
+ fetchStatus: 'idle',
140
+ } as ReturnType<typeof hooks.useLowestListing>;
141
+
142
+ const comparePricesMock = {
143
+ data: {
144
+ status: 'below',
145
+ percentageDifferenceFormatted: '20',
146
+ },
147
+ isLoading: false,
148
+ status: 'success',
149
+ fetchStatus: 'idle',
150
+ } as ReturnType<typeof hooks.useComparePrices>;
151
+
152
+ useLowestListingSpy.mockReturnValue(lowestListingMock);
153
+ useComparePricesSpy.mockReturnValue(comparePricesMock);
154
+
155
+ const { getByText, queryByText } = render(
156
+ <FloorPriceText {...mockProps} />,
157
+ );
158
+
159
+ expect(getByText('20% below floor price')).toBeInTheDocument();
160
+ expect(queryByText('Buy for 1.2 ETH')).not.toBeInTheDocument();
161
+ expect(mockProps.onBuyNow).not.toHaveBeenCalled();
162
+ });
163
+
164
+ it('should display percentage above floor price and show Buy Now button when price is above floor price', () => {
165
+ const useLowestListingSpy = vi.spyOn(hooks, 'useLowestListing');
166
+ const useComparePricesSpy = vi.spyOn(hooks, 'useComparePrices');
167
+
168
+ const lowestListingMock = {
169
+ data: {
170
+ priceAmount: '800000000000000000', // Floor price is lower
171
+ priceAmountFormatted: '0.8',
172
+ priceCurrencyAddress:
173
+ '0x0000000000000000000000000000000000000000' as Hex,
174
+ },
175
+ isLoading: false,
176
+ status: 'success',
177
+ fetchStatus: 'idle',
178
+ } as ReturnType<typeof hooks.useLowestListing>;
179
+
180
+ const comparePricesMock = {
181
+ data: {
182
+ status: 'above',
183
+ percentageDifferenceFormatted: '25',
184
+ },
185
+ isLoading: false,
186
+ status: 'success',
187
+ fetchStatus: 'idle',
188
+ } as ReturnType<typeof hooks.useComparePrices>;
189
+
190
+ useLowestListingSpy.mockReturnValue(lowestListingMock);
191
+ useComparePricesSpy.mockReturnValue(comparePricesMock);
192
+
193
+ const { getByText } = render(<FloorPriceText {...mockProps} />);
194
+
195
+ expect(getByText('25% above floor price')).toBeInTheDocument();
196
+ expect(getByText('Buy for 0.8 ETH')).toBeInTheDocument();
197
+ expect(mockProps.onBuyNow).not.toHaveBeenCalled();
198
+ });
199
+ });
@@ -0,0 +1,55 @@
1
+ import { observable } from '@legendapp/state';
2
+ import { TEST_CURRENCY } from '@test/const';
3
+ import { fireEvent, render, screen } from '@test/test-utils';
4
+ import { zeroAddress } from 'viem';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+ import PriceInput from '..';
7
+
8
+ const defaultProps = {
9
+ chainId: 1,
10
+ collectionAddress: zeroAddress,
11
+ $price: observable({
12
+ amountRaw: '0',
13
+ currency: TEST_CURRENCY,
14
+ }),
15
+ };
16
+
17
+ describe('PriceInput', () => {
18
+ it('should render with initial value of 0', () => {
19
+ render(<PriceInput {...defaultProps} />);
20
+ const input = screen.getByRole('textbox', { name: 'Enter price' });
21
+ expect(input).toHaveValue('0');
22
+ });
23
+
24
+ it('should update the price when the input changes', () => {
25
+ render(<PriceInput {...defaultProps} />);
26
+ const input = screen.getByRole('textbox', { name: 'Enter price' });
27
+ fireEvent.change(input, { target: { value: '100' } });
28
+ expect(input).toHaveValue('100');
29
+ });
30
+
31
+ it('should call onPriceChange when the input changes', () => {
32
+ const onPriceChange = vi.fn();
33
+ render(<PriceInput {...defaultProps} onPriceChange={onPriceChange} />);
34
+ const input = screen.getByRole('textbox', { name: 'Enter price' });
35
+ fireEvent.change(input, { target: { value: '100' } });
36
+ expect(onPriceChange).toHaveBeenCalledTimes(1);
37
+ });
38
+
39
+ it('should not call onPriceChange when the input is 0', () => {
40
+ const onPriceChange = vi.fn();
41
+ render(<PriceInput {...defaultProps} onPriceChange={onPriceChange} />);
42
+ const input = screen.getByRole('textbox', { name: 'Enter price' });
43
+ fireEvent.change(input, { target: { value: '0' } });
44
+ expect(onPriceChange).toHaveBeenCalledTimes(0);
45
+ });
46
+
47
+ it('should handle disabled prop', () => {
48
+ render(<PriceInput {...defaultProps} disabled />);
49
+
50
+ const priceInputWrapper = screen
51
+ .getByRole('textbox', { name: 'Enter price' })
52
+ .closest('.price-input');
53
+ expect(priceInputWrapper).toHaveClass('pointer-events-none opacity-50');
54
+ });
55
+ });
@@ -105,7 +105,7 @@ export default function PriceInput({
105
105
  return (
106
106
  <div
107
107
  className={cn(
108
- 'relative flex w-full flex-col',
108
+ 'price-input relative flex w-full flex-col',
109
109
  disabled && 'pointer-events-none opacity-50',
110
110
  )}
111
111
  >
@@ -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
+ });