diff --git a/miniapps/teleport/src/App.test.tsx b/miniapps/teleport/src/App.test.tsx index 82cdcdfe5..564471a7d 100644 --- a/miniapps/teleport/src/App.test.tsx +++ b/miniapps/teleport/src/App.test.tsx @@ -409,6 +409,74 @@ describe('Teleport App', () => { }) }) + it('should display ratio semantics clearly on confirm step', async () => { + const mockedGetTransmitAssetTypeList = vi.mocked(getTransmitAssetTypeList) + mockedGetTransmitAssetTypeList.mockResolvedValueOnce({ + transmitSupport: { + BFCHAINV2: { + BFT: { + enable: true, + isAirdrop: false, + assetType: 'BFT', + recipientAddress: 'bReceiver', + targetChain: 'BFMCHAIN', + targetAsset: 'BFM', + ratio: { numerator: 416, denominator: 10000 }, + transmitDate: { + startDate: '2020-01-01', + endDate: '2030-12-31', + }, + }, + }, + }, + }) + + mockBio.request.mockImplementation(({ method, params }: { method: string; params?: Array<{ chain?: string }> }) => { + if (method === 'bio_selectAccount') { + return Promise.resolve({ address: 'bSource', chain: params?.[0]?.chain ?? 'BFCHAINV2', name: 'Source' }) + } + if (method === 'bio_getBalance') { + return Promise.resolve('100000000000000') + } + if (method === 'bio_pickWallet') { + return Promise.resolve({ address: 'bTarget', chain: 'bfmeta', name: 'Target' }) + } + if (method === 'bio_closeSplashScreen') { + return Promise.resolve(null) + } + return Promise.resolve(null) + }) + + render(, { wrapper: createWrapper() }) + + await waitFor(() => { + expect(screen.getByRole('button', { name: '启动 BFCHAINV2 传送门' })).toBeInTheDocument() + }) + + fireEvent.click(screen.getByRole('button', { name: '启动 BFCHAINV2 传送门' })) + + await waitFor(() => { + expect(screen.getByTestId('asset-card-BFT')).toBeInTheDocument() + }) + + fireEvent.click(screen.getByTestId('asset-card-BFT')) + await waitFor(() => { + expect(screen.getByTestId('amount-input')).toBeInTheDocument() + }) + fireEvent.change(screen.getByTestId('amount-input'), { target: { value: '10000' } }) + fireEvent.click(screen.getByTestId('next-button')) + + await waitFor(() => { + expect(screen.getByTestId('target-button')).toBeInTheDocument() + }) + fireEvent.click(screen.getByTestId('target-button')) + + await waitFor(() => { + expect(screen.getByText('1 BFT = 0.0416 BFM')).toBeInTheDocument() + expect(screen.getByText(/416 BFM/)).toBeInTheDocument() + }) + }) + it('should show sender/receiver addresses on confirm step and remove free-fee badge', async () => { mockBio.request.mockImplementation(({ method, params }: { method: string; params?: Array<{ chain?: string }> }) => { if (method === 'bio_selectAccount') { diff --git a/miniapps/teleport/src/App.tsx b/miniapps/teleport/src/App.tsx index 503720a46..cadeb854f 100644 --- a/miniapps/teleport/src/App.tsx +++ b/miniapps/teleport/src/App.tsx @@ -259,6 +259,16 @@ const formatRawBalance = (raw: string, decimals: number): string => { return fractionPart.length > 0 ? `${integerWithComma}.${fractionPart}` : integerWithComma; }; +const formatRatioRate = ( + ratio: { numerator: number | string; denominator: number | string } | null | undefined, +): string => { + if (!ratio) return '0'; + const numerator = Number(ratio.numerator); + const denominator = Number(ratio.denominator); + if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator === 0) return '0'; + return (numerator / denominator).toFixed(8).replace(/\.?0+$/, ''); +}; + export default function App() { const { t } = useTranslation(); const [step, setStep] = useState('connect'); @@ -590,9 +600,10 @@ export default function App() { // 计算预期接收金额 const expectedReceive = useMemo(() => { if (!selectedAsset || !amount) return '0'; - const { numerator, denominator } = selectedAsset.ratio; - const amountNum = parseFloat(amount); - const ratioNum = Number(numerator) / Number(denominator); + const amountNum = Number(amount); + if (!Number.isFinite(amountNum)) return '0'; + const ratioNum = Number(selectedAsset.ratio.numerator) / Number(selectedAsset.ratio.denominator); + if (!Number.isFinite(ratioNum)) return '0'; return (amountNum * ratioNum).toFixed(8).replace(/\.?0+$/, ''); }, [selectedAsset, amount]); @@ -734,9 +745,7 @@ export default function App() { ) : ( sortedAvailableAssets.map((asset, i) => { - const rate = (Number(asset.ratio.numerator) / Number(asset.ratio.denominator)) - .toFixed(4) - .replace(/\.?0+$/, ''); + const rate = formatRatioRate(asset.ratio); const routeLabel = `${asset.chain}/${asset.symbol} ${t('common.arrow')} ${asset.targetChain}/${asset.targetAsset}`; return (
{t('confirm.ratio')} - {`${selectedAsset?.ratio.numerator}:${selectedAsset?.ratio.denominator}`} + + {t('asset.ratio', { + from: selectedAsset?.symbol ?? '', + rate: formatRatioRate(selectedAsset?.ratio), + to: selectedAsset?.targetAsset ?? '', + })} +