From d3fa63789be38c48c332c1821d1d1a11ddc1100c Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Wed, 4 Nov 2026 22:49:57 -0800 Subject: [PATCH] Hide the swap provider picker for a single quote The Select Swap Provider modal listed the same quote twice when only one provider answered (Selected Quote plus its Fixed or Variable section). With a single quote there is nothing to choose, so the Powered by card is now static: tapping it does nothing and it shows no chevron or Tap to Change Provider hint, as Stealth Swap already does. --- CHANGELOG.md | 1 + .../scenes/SwapConfirmationScene.test.tsx | 68 ++++++++++ .../SwapConfirmationScene.test.tsx.snap | 119 +++--------------- .../scenes/SwapConfirmationScene.tsx | 7 +- 4 files changed, 93 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea74eb21598..51527c164af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - added: `AGENT_TEST_MODE` config setting and iOS debug-build `EdgeTestAnimations` switch that quiet the app for automated UI tests - changed: Blur on iOS now uses `expo-blur`, the same backend as Android 12+, so `rn-id-blurview` (an old-architecture view manager with no new-architecture support) is no longer a dependency. - changed: Stealth Swap works with Houdini switched off in the exchange settings, as Stealth Send already does +- changed: Swaps with a single provider quote no longer offer the provider picker - changed: Draw gradients with expo-linear-gradient and scroll around the keyboard with react-native-keyboard-controller, replacing two libraries that rendered through the new architecture's legacy compatibility layers (react-native-linear-gradient and the unmaintained react-native-keyboard-aware-scroll-view). - changed: The feedback survey now uses the same keyboard offset on both platforms, replacing a per-platform workaround the old scroll library required. - changed: Upgrade react-native-sound to 0.13.0 and react-native-haptic-feedback to 3.0.0, both now codegen-native under the new architecture. diff --git a/src/__tests__/scenes/SwapConfirmationScene.test.tsx b/src/__tests__/scenes/SwapConfirmationScene.test.tsx index 8c8532a751b..dcef563a493 100644 --- a/src/__tests__/scenes/SwapConfirmationScene.test.tsx +++ b/src/__tests__/scenes/SwapConfirmationScene.test.tsx @@ -22,6 +22,7 @@ import { import process from 'process' import * as React from 'react' +import { PoweredByCard } from '../../components/cards/PoweredByCard' import * as SwapVerifyTermsModal from '../../components/modals/SwapVerifyTermsModal' import { pickBestQuote, @@ -468,6 +469,73 @@ describe('SwapConfirmationScene', () => { }) }) + describe('the powered-by card', () => { + const makePickerQuote = (pluginId: string): EdgeSwapQuote => { + const fakeRequest: any = { fromWallet: btcWallet, toWallet: ethWallet } + return { + swapInfo: dummySwapInfo, + request: fakeRequest, + isEstimate: false, + fromNativeAmount: '10000', + toNativeAmount: '10000', + networkFee: { currencyCode: 'BTC', nativeAmount: '1', tokenId: null }, + pluginId, + approve: async () => { + throw new Error('unreachable') + }, + close: async () => {} + } + } + + const renderWithQuotes = ( + sceneQuotes: EdgeSwapQuote[] + ): ReturnType => { + const rootState: FakeState = { ...fakeRootState, core: { account } } + return render( + + undefined, + swapRequest: sceneQuotes[0].request, + swapRequestOptions: {} + })} + /> + + ) + } + + it('cannot open the provider picker with a single quote', () => { + if (btcWallet == null || ethWallet == null) return + const rendered = renderWithQuotes([makePickerQuote('bitcoin')]) + + expect(rendered.UNSAFE_getByType(PoweredByCard).props.onPress).toBe( + undefined + ) + expect(rendered.queryByText(lstrings.tap_to_change_provider)).toBe(null) + + rendered.unmount() + }) + + it('opens the provider picker with several quotes', () => { + if (btcWallet == null || ethWallet == null) return + const rendered = renderWithQuotes([ + makePickerQuote('bitcoin'), + makePickerQuote('ethereum') + ]) + + expect( + rendered.UNSAFE_getByType(PoweredByCard).props.onPress + ).toBeInstanceOf(Function) + expect(rendered.queryByText(lstrings.tap_to_change_provider)).not.toBe( + null + ) + + rendered.unmount() + }) + }) + let quotes: TestSwapQuote[] let bestQuote: EdgeSwapQuote diff --git a/src/__tests__/scenes/__snapshots__/SwapConfirmationScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SwapConfirmationScene.test.tsx.snap index 2c877304194..9c0ae883765 100644 --- a/src/__tests__/scenes/__snapshots__/SwapConfirmationScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SwapConfirmationScene.test.tsx.snap @@ -1663,47 +1663,26 @@ exports[`SwapConfirmationScene should render with loading props 1`] = ` } > - - - Tap to Change Provider - - - -  - diff --git a/src/components/scenes/SwapConfirmationScene.tsx b/src/components/scenes/SwapConfirmationScene.tsx index 8bc5e011567..4ab36e2200f 100644 --- a/src/components/scenes/SwapConfirmationScene.tsx +++ b/src/components/scenes/SwapConfirmationScene.tsx @@ -102,8 +102,11 @@ export const SwapConfirmationScene: React.FC = (props: Props) => { swapRequest, swapRequestOptions } = route.params - // A Stealth Swap's provider is fixed, so its powered-by card is not tappable: + // The powered-by card opens the provider picker only when there is another + // quote to pick. A Stealth Swap's provider is fixed, and a single quote + // leaves nothing to choose: const isStealth = swapRequest.privacy === 'required' + const canChangeProvider = !isStealth && quotes.length > 1 const dispatch = useDispatch() const theme = useTheme() @@ -541,7 +544,7 @@ export const SwapConfirmationScene: React.FC = (props: Props) => { {selectedQuote.isEstimate && !showPriceImpact ? (