diff --git a/src/components/send/QuoteCard.test.tsx b/src/components/send/QuoteCard.test.tsx new file mode 100644 index 0000000..8aa0da8 --- /dev/null +++ b/src/components/send/QuoteCard.test.tsx @@ -0,0 +1,57 @@ +import { describe, it, expect, vi } from 'vitest' +import { render, screen, fireEvent } from '@testing-library/react' +import React from 'react' +import { QuoteCard } from './QuoteCard' +import type { Quote } from '@/types' +import { NATIVE_XLM } from '@/types' + +const mockQuote: Quote = { + id: 'quote_123', + sourceAsset: NATIVE_XLM, + destinationAsset: { ...NATIVE_XLM, code: 'USDC', issuer: 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5' }, + sendAmount: '100', + receiveAmount: '12.5', + exchangeRate: '0.125', + networkFee: '0.00001', + serviceFee: '0', + totalFee: '0.00001', + estimatedSeconds: 5, + slippageTolerance: '0.5', + priceImpact: '0.01', + expiresAt: new Date(Date.now() + 60_000).toISOString(), + path: [], +} + +describe('QuoteCard', () => { + it('renders quote details correctly', () => { + render() + expect(screen.getByText('Exchange Quote')).toBeInTheDocument() + expect(screen.getByText('100.0000')).toBeInTheDocument() + expect(screen.getByText('12.5000')).toBeInTheDocument() + }) + + it('renders Refresh button when onRefresh is provided and calls it when clicked (#39)', () => { + const onRefresh = vi.fn() + render() + + const refreshButton = screen.getByRole('button', { name: /refresh/i }) + expect(refreshButton).toBeInTheDocument() + + fireEvent.click(refreshButton) + expect(onRefresh).toHaveBeenCalledTimes(1) + }) + + it('does not render Refresh button when onRefresh is not provided', () => { + render() + expect(screen.queryByRole('button', { name: /refresh/i })).not.toBeInTheDocument() + }) + + it('shows expired warning when quote is expired', () => { + const expiredQuote = { + ...mockQuote, + expiresAt: new Date(Date.now() - 10_000).toISOString(), + } + render() + expect(screen.getByText(/This quote has expired/i)).toBeInTheDocument() + }) +}) diff --git a/src/hooks/useSendPayment.ts b/src/hooks/useSendPayment.ts index 5e954ba..d812dff 100644 --- a/src/hooks/useSendPayment.ts +++ b/src/hooks/useSendPayment.ts @@ -159,6 +159,26 @@ export function useSendPayment() { [isConnected, publicKey, supportedAssets, quoteMutation], ) + const refreshQuote = useCallback(() => { + if (!state.quote || !state.formValues) return + + if (state.quote.id) { + setState((s) => ({ ...s, step: 'quoting', error: null })) + quoteApi + .refreshQuote(state.quote.id) + .then((newQuote) => { + setState((s) => ({ ...s, step: 'review', quote: newQuote, error: null })) + }) + .catch(() => { + if (state.formValues) { + requestQuote(state.formValues) + } + }) + } else if (state.formValues) { + requestQuote(state.formValues) + } + }, [state.quote, state.formValues, requestQuote]) + const confirmSend = useCallback(() => { sendMutation.mutate() }, [sendMutation]) @@ -186,6 +206,7 @@ export function useSendPayment() { return { state, requestQuote, + refreshQuote, confirmSend, reset, goBack, diff --git a/src/pages/PayRequest.tsx b/src/pages/PayRequest.tsx index 4990e08..07be189 100644 --- a/src/pages/PayRequest.tsx +++ b/src/pages/PayRequest.tsx @@ -23,6 +23,7 @@ export default function PayRequest() { const { state, requestQuote, + refreshQuote, confirmSend, reset, goBack, @@ -141,7 +142,14 @@ export default function PayRequest() { )} {state.step === 'review' && state.quote && ( - + )} {state.quote && state.formValues && ( diff --git a/src/pages/Send.tsx b/src/pages/Send.tsx index 1986652..5656872 100644 --- a/src/pages/Send.tsx +++ b/src/pages/Send.tsx @@ -76,6 +76,7 @@ export default function Send() { const { state, requestQuote, + refreshQuote, confirmSend, reset, goBack, @@ -152,6 +153,7 @@ export default function Send() {