diff --git a/packages/ui-components/src/__tests__/SelectToken.test.ts b/packages/ui-components/src/__tests__/SelectToken.test.ts index 1b010f0f09..7e22c0ce00 100644 --- a/packages/ui-components/src/__tests__/SelectToken.test.ts +++ b/packages/ui-components/src/__tests__/SelectToken.test.ts @@ -78,7 +78,7 @@ describe('SelectToken', () => { expect(getByText('Select a token...')).toBeInTheDocument(); }); - it('calls setSelectToken and updates token info when input changes', async () => { + it('calls setSelectToken when token is selected from modal', async () => { const user = userEvent.setup(); const mockGuiWithNoToken = { ...mockGui, @@ -87,25 +87,29 @@ describe('SelectToken', () => { (useGui as Mock).mockReturnValue(mockGuiWithNoToken); - const { getByTestId, getByRole } = render(SelectToken, { + const { getByText } = render(SelectToken, { ...mockProps }); - const customButton = getByTestId('custom-mode-button'); - await user.click(customButton); + // Click the token selection button to open modal + const selectButton = getByText('Select a token...'); + await user.click(selectButton); - const input = getByRole('textbox'); + // Wait for modal to load tokens and then select one + await waitFor(() => { + expect(getByText('Test Token 1')).toBeInTheDocument(); + }); - await userEvent.clear(input); - await user.paste('0x456'); + const firstToken = getByText('Test Token 1'); + await user.click(firstToken); await waitFor(() => { - expect(mockGuiWithNoToken.setSelectToken).toHaveBeenCalledWith('input', '0x456'); + expect(mockGuiWithNoToken.setSelectToken).toHaveBeenCalledWith('input', '0x1234567890123456789012345678901234567890'); }); expect(mockStateUpdateCallback).toHaveBeenCalledTimes(1); }); - it('shows error message for invalid address, and removes the selectToken', async () => { + it('shows error message for invalid token selection', async () => { const user = userEvent.setup(); const mockGuiWithError = { ...mockGui, @@ -118,178 +122,54 @@ describe('SelectToken', () => { ...mockProps }); - const customButton = screen.getByTestId('custom-mode-button'); - await user.click(customButton); + // Click the token selection button to open modal + const selectButton = screen.getByText('Select a token...'); + await user.click(selectButton); - const input = screen.getByRole('textbox'); - await userEvent.clear(input); - await user.paste('invalid'); + // Wait for modal to load tokens and then select one await waitFor(() => { - expect(screen.getByTestId('error')).toBeInTheDocument(); - }); - }); - - it('replaces the token and triggers state update twice if the token is already set', async () => { - const mockGuiWithTokenSet = { - ...mockGui, - isSelectTokenSet: vi.fn().mockResolvedValue(true) - } as unknown as DotrainOrderGui; - - (useGui as Mock).mockReturnValue(mockGuiWithTokenSet); - - const user = userEvent.setup(); - - const { getByRole, getByTestId } = render(SelectToken, { - ...mockProps + expect(screen.getByText('Test Token 1')).toBeInTheDocument(); }); - const customButton = getByTestId('custom-mode-button'); - await user.click(customButton); + const firstToken = screen.getByText('Test Token 1'); + await user.click(firstToken); - const input = getByRole('textbox'); - await userEvent.clear(input); - await user.paste('invalid'); await waitFor(() => { - expect(mockGuiWithTokenSet.setSelectToken).toHaveBeenCalled(); - expect(mockStateUpdateCallback).toHaveBeenCalledTimes(1); + expect(screen.getByTestId('error')).toBeInTheDocument(); }); }); - it('calls onSelectTokenSelect after input changes', async () => { + it('calls onSelectTokenSelect after token selection', async () => { const user = userEvent.setup(); - const { getByRole, getByTestId } = render(SelectToken, mockProps); + const { getByText } = render(SelectToken, mockProps); - const customButton = getByTestId('custom-mode-button'); - await user.click(customButton); + // Click the token selection button to open modal + const selectButton = getByText('Select a token...'); + await user.click(selectButton); - const input = getByRole('textbox'); + // Wait for modal to load tokens and then select one + await waitFor(() => { + expect(getByText('Test Token 1')).toBeInTheDocument(); + }); - await userEvent.clear(input); - await user.paste('0x456'); + const firstToken = getByText('Test Token 1'); + await user.click(firstToken); await waitFor(() => { expect(mockProps.onSelectTokenSelect).toHaveBeenCalled(); }); }); - describe('Dropdown Mode', () => { + describe('Token Selection', () => { beforeEach(() => { (useGui as Mock).mockReturnValue(mockGui); }); - it('shows dropdown and custom mode buttons when tokens are available', () => { - render(SelectToken, mockProps); - - expect(screen.getByTestId('dropdown-mode-button')).toBeInTheDocument(); - expect(screen.getByTestId('custom-mode-button')).toBeInTheDocument(); - }); - - it('shows dropdown mode as active by default', () => { - render(SelectToken, mockProps); - - const dropdownButton = screen.getByTestId('dropdown-mode-button'); - const customButton = screen.getByTestId('custom-mode-button'); - - expect(dropdownButton).toHaveClass('border-blue-300'); - expect(customButton).not.toHaveClass('border-blue-300'); - }); - - it('switches to custom mode when custom button is clicked', async () => { - const user = userEvent.setup(); - render(SelectToken, mockProps); - - const customButton = screen.getByTestId('custom-mode-button'); - await user.click(customButton); - - expect(customButton).toHaveClass('border-blue-300'); - expect(screen.getByTestId('dropdown-mode-button')).not.toHaveClass('border-blue-300'); - }); - - it('shows TokenSelectionModal component in dropdown mode', () => { + it('shows token selection modal', () => { render(SelectToken, mockProps); - expect(screen.getByText('Select a token...')).toBeInTheDocument(); }); - it('shows custom input in custom mode', async () => { - const user = userEvent.setup(); - render(SelectToken, mockProps); - - const customButton = screen.getByTestId('custom-mode-button'); - await user.click(customButton); - - expect(screen.getByPlaceholderText('Enter token address (0x...)')).toBeInTheDocument(); - }); - - it('clears state when switching from dropdown to custom mode', async () => { - const user = userEvent.setup(); - const mockGuiNoToken = { - ...mockGui, - getTokenInfo: vi.fn().mockResolvedValue({ value: null }) - } as unknown as DotrainOrderGui; - - (useGui as Mock).mockReturnValue(mockGuiNoToken); - - render(SelectToken, { - ...mockProps - }); - - const dropdownButton = screen.getByText('Select a token...'); - await user.click(dropdownButton); - - const firstToken = screen.getByText('Test Token 1'); - await user.click(firstToken); - - const customButton = screen.getByTestId('custom-mode-button'); - await user.click(customButton); - - const customInput = screen.getByPlaceholderText('Enter token address (0x...)'); - expect(customInput).toHaveValue(''); - - expect(mockGuiNoToken.unsetSelectToken).toHaveBeenCalledWith('input'); - }); - - it('clears state when switching from custom to dropdown mode', async () => { - const user = userEvent.setup(); - render(SelectToken, mockProps); - - const customButton = screen.getByTestId('custom-mode-button'); - await user.click(customButton); - - const customInput = screen.getByPlaceholderText('Enter token address (0x...)'); - await user.type(customInput, '0x1234567890123456789012345678901234567890'); - - const dropdownButton = screen.getByTestId('dropdown-mode-button'); - await user.click(dropdownButton); - - expect(mockGui.unsetSelectToken).toHaveBeenCalledWith('input'); - }); - - it('handles token selection from dropdown', async () => { - const user = userEvent.setup(); - const mockGuiNoToken = { - ...mockGui, - getTokenInfo: vi.fn().mockResolvedValue({ value: null }) - } as unknown as DotrainOrderGui; - - (useGui as Mock).mockReturnValue(mockGuiNoToken); - - render(SelectToken, { - ...mockProps - }); - - const dropdownButton = screen.getByText('Select a token...'); - await user.click(dropdownButton); - - const secondToken = screen.getByText('Another Token'); - await user.click(secondToken); - - expect(mockGuiNoToken.setSelectToken).toHaveBeenCalledWith( - 'input', - '0x0987654321098765432109876543210987654321' - ); - }); - it('displays selected token info when token is selected', async () => { mockGui.getTokenInfo = vi.fn().mockResolvedValue({ value: { diff --git a/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts b/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts index 0c8ad912da..4c8d67bde2 100644 --- a/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts +++ b/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts @@ -92,7 +92,7 @@ describe('TokenSelectionModal', () => { const button = screen.getByRole('button'); await user.click(button); - expect(screen.getByPlaceholderText('Search tokens...')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Search tokens or enter address (0x...)')).toBeInTheDocument(); }); it('loads tokens on mount', async () => { @@ -155,7 +155,7 @@ describe('TokenSelectionModal', () => { const button = screen.getByRole('button'); await user.click(button); - const searchInput = screen.getByPlaceholderText('Search tokens...'); + const searchInput = screen.getByPlaceholderText('Search tokens or enter address (0x...)'); await user.type(searchInput, 'TEST'); await waitFor(() => { @@ -183,7 +183,7 @@ describe('TokenSelectionModal', () => { const button = screen.getByRole('button'); await user.click(button); - const searchInput = screen.getByPlaceholderText('Search tokens...'); + const searchInput = screen.getByPlaceholderText('Search tokens or enter address (0x...)'); await user.type(searchInput, 'TEST'); expect(screen.getByText('Searching tokens...')).toBeInTheDocument(); @@ -252,8 +252,114 @@ describe('TokenSelectionModal', () => { await user.click(button); await waitFor(() => { - const searchInput = screen.getByPlaceholderText('Search tokens...'); + const searchInput = screen.getByPlaceholderText('Search tokens or enter address (0x...)'); expect(searchInput).toHaveFocus(); }); }); + + it('validates custom token address when entered', async () => { + const mockGuiWithCustomToken = { + getAllTokens: vi.fn().mockResolvedValue({ value: [] }), + setSelectToken: vi.fn().mockResolvedValue(undefined), + getTokenInfo: vi.fn().mockResolvedValue({ + value: { + address: '0x1234567890123456789012345678901234567890', + name: 'Custom Token', + symbol: 'CUSTOM', + decimals: 18 + } + }), + unsetSelectToken: vi.fn().mockResolvedValue(undefined) + } as unknown as DotrainOrderGui; + + (useGui as Mock).mockReturnValue(mockGuiWithCustomToken); + + const user = userEvent.setup(); + render(TokenSelectionModal, { + ...defaultProps, + onSelect: mockOnSelect + }); + + const button = screen.getByRole('button'); + await user.click(button); + + const searchInput = screen.getByPlaceholderText('Search tokens or enter address (0x...)'); + await user.type(searchInput, '0x1234567890123456789012345678901234567890'); + + await waitFor(() => { + expect(screen.getByText('Custom Token')).toBeInTheDocument(); + expect(screen.getByText('Custom token (not in list)')).toBeInTheDocument(); + }); + }); + + it('shows error for invalid custom token address', async () => { + const mockGuiWithError = { + getAllTokens: vi.fn().mockResolvedValue({ value: [] }), + setSelectToken: vi.fn().mockRejectedValue(new Error('Invalid token address')), + unsetSelectToken: vi.fn().mockResolvedValue(undefined) + } as unknown as DotrainOrderGui; + + (useGui as Mock).mockReturnValue(mockGuiWithError); + + const user = userEvent.setup(); + render(TokenSelectionModal, { + ...defaultProps, + onSelect: mockOnSelect + }); + + const button = screen.getByRole('button'); + await user.click(button); + + const searchInput = screen.getByPlaceholderText('Search tokens or enter address (0x...)'); + await user.type(searchInput, '0x1234567890123456789012345678901234567890'); + + await waitFor(() => { + expect(screen.getByText('Invalid token address')).toBeInTheDocument(); + }); + }); + + it('allows selection of custom token', async () => { + const mockGuiWithCustomToken = { + getAllTokens: vi.fn().mockResolvedValue({ value: [] }), + setSelectToken: vi.fn().mockResolvedValue(undefined), + getTokenInfo: vi.fn().mockResolvedValue({ + value: { + address: '0x1234567890123456789012345678901234567890', + name: 'Custom Token', + symbol: 'CUSTOM', + decimals: 18 + } + }), + unsetSelectToken: vi.fn().mockResolvedValue(undefined) + } as unknown as DotrainOrderGui; + + (useGui as Mock).mockReturnValue(mockGuiWithCustomToken); + + const user = userEvent.setup(); + render(TokenSelectionModal, { + ...defaultProps, + onSelect: mockOnSelect + }); + + const button = screen.getByRole('button'); + await user.click(button); + + const searchInput = screen.getByPlaceholderText('Search tokens or enter address (0x...)'); + await user.type(searchInput, '0x1234567890123456789012345678901234567890'); + + await waitFor(() => { + expect(screen.getByText('Custom Token')).toBeInTheDocument(); + }); + + const customTokenItem = screen.getByText('Custom Token'); + await user.click(customTokenItem); + + expect(mockOnSelect).toHaveBeenCalledWith({ + key: 'temp-validation-token', + address: '0x1234567890123456789012345678901234567890', + name: 'Custom Token', + symbol: 'CUSTOM', + decimals: 18 + }); + }); }); diff --git a/packages/ui-components/src/lib/components/deployment/SelectToken.svelte b/packages/ui-components/src/lib/components/deployment/SelectToken.svelte index 068f9faabe..465917a356 100644 --- a/packages/ui-components/src/lib/components/deployment/SelectToken.svelte +++ b/packages/ui-components/src/lib/components/deployment/SelectToken.svelte @@ -1,192 +1,116 @@
- {token.description} -
- {/if} -+ {token.description} +
+ {/if} +Searching tokens...
-No tokens found matching your search.
- -{isValidatingCustomToken ? 'Validating token...' : 'Searching tokens...'}
+No tokens found matching your search.
+ +