Skip to content

Commit 6777950

Browse files
committed
Merge branch 'rn-upgrade/0.81.5-no-unit-tests' of https://github.com/MetaMask/metamask-mobile into rn-upgrade/0.81.5-no-unit-tests
2 parents f33be1f + 4f5f141 commit 6777950

38 files changed

Lines changed: 1209 additions & 222 deletions

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ jobs:
125125
uses: actions/setup-node@v4
126126
with:
127127
node-version-file: '.nvmrc'
128+
cache: 'yarn'
128129
- run: yarn install --immutable
129130
- run: node scripts/validate-build-config.js
130131

.github/workflows/run-performance-e2e.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ jobs:
142142
needs: [determine-branch-name]
143143
outputs:
144144
android_matrix: ${{ steps.read-matrix.outputs.android_matrix }}
145+
android_mm_connect_matrix: ${{ steps.read-matrix.outputs.android_mm_connect_matrix }}
145146
ios_matrix: ${{ steps.read-matrix.outputs.ios_matrix }}
146147
steps:
147148
- name: Checkout code
@@ -165,18 +166,23 @@ jobs:
165166
fi
166167
167168
ANDROID_MATRIX=$(jq ".android_devices | $FILTER" "$FILE")
169+
ANDROID_MM_CONNECT_MATRIX=$(jq '[.android_devices[] | select(.name | contains("Samsung"))]' "$FILE")
168170
IOS_MATRIX=$(jq ".ios_devices | $FILTER" "$FILE")
169171
170172
{
171173
echo "android_matrix<<EOF"
172174
echo "$ANDROID_MATRIX"
173175
echo "EOF"
176+
echo "android_mm_connect_matrix<<EOF"
177+
echo "$ANDROID_MM_CONNECT_MATRIX"
178+
echo "EOF"
174179
echo "ios_matrix<<EOF"
175180
echo "$IOS_MATRIX"
176181
echo "EOF"
177182
} >> "$GITHUB_OUTPUT"
178183
179184
echo "Selected: $(echo "$ANDROID_MATRIX" | jq length) Android, $(echo "$IOS_MATRIX" | jq length) iOS"
185+
echo "Selected for Android MM-Connect: $(echo "$ANDROID_MM_CONNECT_MATRIX" | jq length)"
180186
181187
set-build-names:
182188
name: Set Unified BrowserStack Build Names
@@ -333,7 +339,7 @@ jobs:
333339
name: Fetch RN Playground APK and Upload to BrowserStack
334340
runs-on: ubuntu-latest
335341
needs: [wait-for-onboarding-completion]
336-
if: always() && !cancelled()
342+
if: always() && !cancelled() && (inputs.build_variant || 'rc') == 'rc'
337343
outputs:
338344
browserstack-playground-url: ${{ steps.upload-playground.outputs.browserstack-url }}
339345
steps:
@@ -376,13 +382,13 @@ jobs:
376382
set-build-names,
377383
determine-branch-name,
378384
]
379-
if: always() && !cancelled() && (needs.trigger-android-dual-versions.result == 'skipped' || needs.trigger-android-dual-versions.result == 'success') && (inputs.browserstack_app_url_android_imported_wallet != '' || needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url != '')
385+
if: always() && !cancelled() && (inputs.build_variant || 'rc') == 'rc' && (needs.trigger-android-dual-versions.result == 'skipped' || needs.trigger-android-dual-versions.result == 'success') && (inputs.browserstack_app_url_android_imported_wallet != '' || needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url != '')
380386
with:
381387
platform: android
382388
build_type: mm-connect
383389
sentry_target: ${{ inputs.sentry_target || 'test' }}
384390
build_variant: ${{ inputs.build_variant || 'rc' }}
385-
device_matrix: ${{ needs.read-device-matrix.outputs.android_matrix }}
391+
device_matrix: ${{ needs.read-device-matrix.outputs.android_mm_connect_matrix }}
386392
browserstack_app_url: ${{ needs.trigger-android-dual-versions.outputs.with-srp-browserstack-url || inputs.browserstack_app_url_android_imported_wallet }}
387393
app_version: ${{ needs.trigger-android-dual-versions.outputs.with-srp-version || 'Manual-Input' }}
388394
branch_name: ${{ needs.determine-branch-name.outputs.branch_name }}

app/components/UI/Predict/components/PredictMarket/PredictMarket.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ interface PredictMarketProps {
1212
testID?: string;
1313
entryPoint?: PredictEntryPoint;
1414
isCarousel?: boolean;
15+
/** Called synchronously before the card's navigation press fires. */
16+
onCardPress?: () => void;
17+
/** Called when the user taps a buy button (before betslip opens). */
18+
onBuyButtonPress?: (marketId: string) => void;
1519
}
1620

1721
const PredictMarket: React.FC<PredictMarketProps> = ({
1822
market,
1923
testID,
2024
entryPoint: propEntryPoint,
2125
isCarousel = false,
26+
onCardPress,
27+
onBuyButtonPress,
2228
}) => {
2329
const contextEntryPoint = usePredictEntryPoint();
2430
const entryPoint =
@@ -32,6 +38,8 @@ const PredictMarket: React.FC<PredictMarketProps> = ({
3238
testID={testID}
3339
entryPoint={entryPoint}
3440
isCarousel={isCarousel}
41+
onCardPress={onCardPress}
42+
onBuyButtonPress={onBuyButtonPress}
3543
/>
3644
);
3745
}
@@ -43,6 +51,8 @@ const PredictMarket: React.FC<PredictMarketProps> = ({
4351
testID={testID}
4452
entryPoint={entryPoint}
4553
isCarousel={isCarousel}
54+
onCardPress={onCardPress}
55+
onBuyButtonPress={onBuyButtonPress}
4656
/>
4757
);
4858
}
@@ -53,6 +63,8 @@ const PredictMarket: React.FC<PredictMarketProps> = ({
5363
testID={testID}
5464
entryPoint={entryPoint}
5565
isCarousel={isCarousel}
66+
onCardPress={onCardPress}
67+
onBuyButtonPress={onBuyButtonPress}
5668
/>
5769
);
5870
};

app/components/UI/Predict/components/PredictMarketMultiple/PredictMarketMultiple.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,19 @@ interface PredictMarketMultipleProps {
5050
testID?: string;
5151
entryPoint?: PredictEntryPoint;
5252
isCarousel?: boolean;
53+
/** Called synchronously before the card's navigation press fires. */
54+
onCardPress?: () => void;
55+
/** Called when the user taps a buy button (before betslip opens). */
56+
onBuyButtonPress?: (marketId: string) => void;
5357
}
5458

5559
const PredictMarketMultiple: React.FC<PredictMarketMultipleProps> = ({
5660
market,
5761
testID,
5862
entryPoint: propEntryPoint,
5963
isCarousel = false,
64+
onCardPress,
65+
onBuyButtonPress,
6066
}) => {
6167
const contextEntryPoint = usePredictEntryPoint();
6268
const baseEntryPoint =
@@ -137,6 +143,7 @@ const PredictMarketMultiple: React.FC<PredictMarketMultipleProps> = ({
137143
outcome: PredictOutcome,
138144
outcomeToken: PredictOutcomeToken,
139145
) => {
146+
onBuyButtonPress?.(market.id);
140147
executeGuardedAction(
141148
() => {
142149
openBuySheet({
@@ -161,6 +168,7 @@ const PredictMarketMultiple: React.FC<PredictMarketMultipleProps> = ({
161168
<TouchableOpacity
162169
testID={testID}
163170
onPress={() => {
171+
onCardPress?.();
164172
navigation.navigate(Routes.PREDICT.ROOT, {
165173
screen: Routes.PREDICT.MARKET_DETAILS,
166174
params: {

app/components/UI/Predict/components/PredictMarketSingle/PredictMarketSingle.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,19 @@ interface PredictMarketSingleProps {
128128
testID?: string;
129129
entryPoint?: PredictEntryPoint;
130130
isCarousel?: boolean;
131+
/** Called synchronously before the card's navigation press fires. */
132+
onCardPress?: () => void;
133+
/** Called when the user taps a buy button (before betslip opens). */
134+
onBuyButtonPress?: (marketId: string) => void;
131135
}
132136

133137
const PredictMarketSingle: React.FC<PredictMarketSingleProps> = ({
134138
market,
135139
testID,
136140
entryPoint: propEntryPoint,
137141
isCarousel = false,
142+
onCardPress,
143+
onBuyButtonPress,
138144
}) => {
139145
const contextEntryPoint = usePredictEntryPoint();
140146
const baseEntryPoint =
@@ -185,6 +191,7 @@ const PredictMarketSingle: React.FC<PredictMarketSingleProps> = ({
185191
const yesPercentage = getYesPercentage();
186192

187193
const handleBuy = (token: PredictOutcomeToken) => {
194+
onBuyButtonPress?.(market.id);
188195
executeGuardedAction(
189196
() => {
190197
openBuySheet({
@@ -204,6 +211,7 @@ const PredictMarketSingle: React.FC<PredictMarketSingleProps> = ({
204211
<TouchableOpacity
205212
testID={testID}
206213
onPress={() => {
214+
onCardPress?.();
207215
navigation.navigate(Routes.PREDICT.ROOT, {
208216
screen: Routes.PREDICT.MARKET_DETAILS,
209217
params: {

app/components/UI/Predict/components/PredictMarketSportCard/PredictMarketSportCard.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ interface PredictMarketSportCardProps {
2727
entryPoint?: PredictEntryPoint;
2828
onDismiss?: () => void;
2929
isCarousel?: boolean;
30+
/** Called synchronously before the card's navigation press fires. */
31+
onCardPress?: () => void;
32+
/** Called when the user taps a buy button (before betslip opens). */
33+
onBuyButtonPress?: (marketId: string) => void;
3034
}
3135

3236
const PredictMarketSportCard: React.FC<PredictMarketSportCardProps> = ({
@@ -35,6 +39,8 @@ const PredictMarketSportCard: React.FC<PredictMarketSportCardProps> = ({
3539
entryPoint: propEntryPoint,
3640
onDismiss,
3741
isCarousel,
42+
onCardPress,
43+
onBuyButtonPress,
3844
}) => {
3945
const tw = useTailwind();
4046
const contextEntryPoint = usePredictEntryPoint();
@@ -57,6 +63,7 @@ const PredictMarketSportCard: React.FC<PredictMarketSportCardProps> = ({
5763
style={tw.style(isCarousel ? '' : 'my-[8px]')}
5864
testID={testID}
5965
onPress={() => {
66+
onCardPress?.();
6067
navigation.navigate(Routes.PREDICT.ROOT, {
6168
screen: Routes.PREDICT.MARKET_DETAILS,
6269
params: {
@@ -102,6 +109,7 @@ const PredictMarketSportCard: React.FC<PredictMarketSportCardProps> = ({
102109
entryPoint={resolvedEntryPoint}
103110
testID={testID ? `${testID}-footer` : undefined}
104111
isCarousel={isCarousel}
112+
onBuyButtonPress={onBuyButtonPress}
105113
/>
106114
</Box>
107115
</Box>

app/components/UI/Predict/components/PredictSportCardFooter/PredictSportCardFooter.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ interface PredictSportCardFooterProps {
2727
testID?: string;
2828
entryPoint?: PredictEntryPoint;
2929
isCarousel?: boolean;
30+
/** Called when the user taps a buy button (before betslip opens). */
31+
onBuyButtonPress?: (marketId: string) => void;
3032
}
3133

3234
const PredictSportCardFooter: React.FC<PredictSportCardFooterProps> = ({
3335
market,
3436
testID,
3537
entryPoint: propEntryPoint,
3638
isCarousel,
39+
onBuyButtonPress,
3740
}) => {
3841
const tw = useTailwind();
3942
const navigation =
@@ -82,6 +85,7 @@ const PredictSportCardFooter: React.FC<PredictSportCardFooterProps> = ({
8285
),
8386
) ?? market.outcomes?.[0];
8487

88+
onBuyButtonPress?.(market.id);
8589
executeGuardedAction(
8690
() => {
8791
openBuySheet({
@@ -96,7 +100,13 @@ const PredictSportCardFooter: React.FC<PredictSportCardFooterProps> = ({
96100
},
97101
);
98102
},
99-
[executeGuardedAction, resolvedEntryPoint, openBuySheet, market],
103+
[
104+
executeGuardedAction,
105+
resolvedEntryPoint,
106+
openBuySheet,
107+
market,
108+
onBuyButtonPress,
109+
],
100110
);
101111

102112
const handleClaimPress = useCallback(async () => {

app/components/UI/Ramp/Views/BuildQuote/BuildQuote.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import {
6969

7070
import TruncatedError from '../../components/TruncatedError';
7171
import { PROVIDER_LINKS } from '../../Aggregator/types';
72+
import { failSession } from '../../headless/sessionRegistry';
7273
const BAILED_ORDER_STATUSES = new Set<RampsOrderStatus>([
7374
RampsOrderStatus.Precreated,
7475
RampsOrderStatus.IdExpired,
@@ -159,10 +160,24 @@ function BuildQuote() {
159160

160161
useEffect(() => {
161162
if (params?.nativeFlowError) {
163+
if (
164+
params.headlessSessionId &&
165+
failSession(
166+
params.headlessSessionId,
167+
{
168+
code: 'AUTH_FAILED',
169+
message: params.nativeFlowError,
170+
},
171+
'AUTH_FAILED',
172+
)
173+
) {
174+
navigation.setParams({ nativeFlowError: undefined });
175+
return;
176+
}
162177
setRampsError(params.nativeFlowError);
163178
navigation.setParams({ nativeFlowError: undefined });
164179
}
165-
}, [params?.nativeFlowError, navigation]);
180+
}, [params?.headlessSessionId, params?.nativeFlowError, navigation]);
166181

167182
const {
168183
userRegion,
@@ -627,6 +642,9 @@ function BuildQuote() {
627642
assetId: selectedToken?.assetId ?? '',
628643
});
629644
} catch (err) {
645+
if (failSession(params?.headlessSessionId, err)) {
646+
return;
647+
}
630648
setRampsError((err as Error).message);
631649
} finally {
632650
setIsContinueLoading(false);
@@ -642,6 +660,7 @@ function BuildQuote() {
642660
selectedPaymentMethod?.id,
643661
rampRoutingDecision,
644662
userRegion?.regionCode,
663+
params?.headlessSessionId,
645664
trackEvent,
646665
createEventBuilder,
647666
continueWithQuote,

app/components/UI/Ramp/Views/Checkout/Checkout.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ jest.mock('../../utils/v2OrderToast', () => ({
5858
jest.mock('../../headless/sessionRegistry', () => ({
5959
getSession: jest.fn(),
6060
closeSession: jest.fn(),
61+
failSession: jest.fn(),
6162
}));
6263

6364
jest.mock('../../../../../util/Logger', () => ({
@@ -618,6 +619,8 @@ describe('Checkout', () => {
618619
.getSession as jest.Mock;
619620
const mockCloseSession = jest.requireMock('../../headless/sessionRegistry')
620621
.closeSession as jest.Mock;
622+
const mockFailSession = jest.requireMock('../../headless/sessionRegistry')
623+
.failSession as jest.Mock;
621624
const showV2OrderToastMock = jest.requireMock('../../utils/v2OrderToast')
622625
.showV2OrderToast as jest.Mock;
623626

@@ -641,6 +644,7 @@ describe('Checkout', () => {
641644
beforeEach(() => {
642645
mockGetSession.mockReset();
643646
mockCloseSession.mockReset();
647+
mockFailSession.mockReset();
644648
mockParentPop = jest.fn();
645649
mockNavigation.getParent.mockReturnValue({ pop: mockParentPop });
646650
mockGetOrderFromCallback.mockResolvedValue(mockOrder);
@@ -740,6 +744,52 @@ describe('Checkout', () => {
740744
expect(mockParentPop).toHaveBeenCalled();
741745
});
742746

747+
it('surfaces callback processing failures through onError and skips the ErrorView', async () => {
748+
mockUseParams.mockReturnValue(callbackFlowParams);
749+
mockGetOrderFromCallback.mockRejectedValueOnce(
750+
new Error('callback failed'),
751+
);
752+
mockFailSession.mockReturnValue({
753+
code: 'UNKNOWN',
754+
message: 'callback failed',
755+
});
756+
757+
const { getByTestId, queryByText } = renderWithProvider(
758+
<Checkout />,
759+
{},
760+
true,
761+
false,
762+
);
763+
764+
await act(async () => {
765+
fireEvent.press(getByTestId('trigger-callback-navigation'));
766+
});
767+
768+
await waitFor(() => {
769+
expect(mockFailSession).toHaveBeenCalledWith('hs-1', expect.any(Error));
770+
});
771+
expect(mockParentPop).toHaveBeenCalled();
772+
expect(showV2OrderToastMock).not.toHaveBeenCalled();
773+
expect(queryByText('callback failed')).toBeNull();
774+
});
775+
776+
it('surfaces provider WebView HTTP errors through onError when headless', async () => {
777+
mockUseParams.mockReturnValue(callbackFlowParams);
778+
mockFailSession.mockReturnValue({
779+
code: 'UNKNOWN',
780+
message: 'fiat_on_ramp_aggregator.webview_received_error',
781+
});
782+
783+
const { getByTestId } = renderWithProvider(<Checkout />, {}, true, false);
784+
785+
await act(async () => {
786+
fireEvent.press(getByTestId('trigger-http-error-main-uri'));
787+
});
788+
789+
expect(mockFailSession).toHaveBeenCalledWith('hs-1', expect.any(Error));
790+
expect(mockParentPop).toHaveBeenCalled();
791+
});
792+
743793
it('falls back to the regular reset + toast when session id is present but session is missing from registry', async () => {
744794
mockGetSession.mockReturnValue(undefined);
745795
mockUseParams.mockReturnValue(callbackFlowParams);

0 commit comments

Comments
 (0)