Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 61 additions & 30 deletions .github/workflows/push-eas-update.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Push OTA Update (EXP Build)
name: Push OTA Update

on:
workflow_dispatch:
Expand All @@ -15,6 +15,15 @@ on:
description: 'EAS update message'
required: true
type: string
channel:
description: 'OTA channel to push update to (exp, rc, production)'
required: true
type: choice
options:
- exp
- rc
- production
default: exp

permissions:
contents: read
Expand All @@ -24,6 +33,7 @@ env:
TARGET_PR_NUMBER: ${{ inputs.pr_number }}
BASE_BRANCH_REF: ${{ inputs.base_branch }}
UPDATE_MESSAGE: ${{ inputs.message }}
TARGET_CHANNEL: ${{ inputs.channel }}

jobs:
fingerprint-comparison:
Expand Down Expand Up @@ -65,8 +75,6 @@ jobs:
FINGERPRINT=$(yarn fingerprint:generate)
echo "fingerprint=$FINGERPRINT" >> "$GITHUB_OUTPUT"
echo "Target PR fingerprint: $FINGERPRINT"
echo "Writing detailed fingerprint file to fingerprint-pr.json"
npx @expo/fingerprint ./ > fingerprint-pr.json

- name: Install dependencies (base branch)
working-directory: main
Expand All @@ -82,8 +90,6 @@ jobs:
FINGERPRINT=$(yarn fingerprint:generate)
echo "fingerprint=$FINGERPRINT" >> "$GITHUB_OUTPUT"
echo "Base branch fingerprint: $FINGERPRINT"
echo "Writing detailed fingerprint file to ../fingerprint-base.json"
npx @expo/fingerprint ./ > ../fingerprint-base.json

- name: Compare fingerprints
id: compare
Expand All @@ -96,23 +102,17 @@ jobs:
exit 1
fi

echo "Target PR fingerprint: $BRANCH_FP"
echo "Base branch fingerprint: $MAIN_FP"

if [ "$BRANCH_FP" = "$MAIN_FP" ]; then
echo "✅ Fingerprints match. No native changes detected."
echo "equal=true" >> "$GITHUB_OUTPUT"
else
echo "⚠️ Fingerprints differ. Native changes detected."
echo "equal=false" >> "$GITHUB_OUTPUT"
if [ -f fingerprint-base.json ] && [ -f fingerprint-pr.json ]; then
echo "Fingerprint differences:"
npx @expo/fingerprint ./ fingerprint-base.json fingerprint-pr.json || true
else
echo "Detailed fingerprint files not found; skipping diff."
fi
fi

echo "Target PR fingerprint: $BRANCH_FP"
echo "Base branch fingerprint: $MAIN_FP"

- name: Record fingerprint summary
env:
BRANCH_FP: ${{ steps.branch_fingerprint.outputs.fingerprint }}
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
push-update:
name: Push EAS Update
runs-on: ubuntu-latest
environment: build-exp
environment: ${{ inputs.channel == 'exp' && 'build-exp' || inputs.channel == 'rc' && 'build-rc' || 'build-production' }}
needs:
- fingerprint-comparison
- approval
Expand All @@ -163,24 +163,40 @@ jobs:
MM_REMOVE_GLOBAL_NETWORK_SELECTOR: 'true'
FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN: ${{ secrets.FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN }}
FEATURES_ANNOUNCEMENTS_SPACE_ID: ${{ secrets.FEATURES_ANNOUNCEMENTS_SPACE_ID }}
SEGMENT_WRITE_KEY_QA: ${{ secrets.SEGMENT_WRITE_KEY_QA }}
SEGMENT_PROXY_URL_QA: ${{ secrets.SEGMENT_PROXY_URL_QA }}
SEGMENT_DELETE_API_SOURCE_ID_QA: ${{ secrets.SEGMENT_DELETE_API_SOURCE_ID_QA }}
SEGMENT_REGULATIONS_ENDPOINT_QA: ${{ secrets.SEGMENT_REGULATIONS_ENDPOINT_QA }}
MM_SENTRY_DSN: ${{ secrets.MM_SENTRY_DSN }} #need to add to secrets
SEGMENT_WRITE_KEY: ${{ secrets.SEGMENT_WRITE_KEY }}
SEGMENT_PROXY_URL: ${{ secrets.SEGMENT_PROXY_URL }}
SEGMENT_DELETE_API_SOURCE_ID: ${{ secrets.SEGMENT_DELETE_API_SOURCE_ID }}
SEGMENT_REGULATIONS_ENDPOINT: ${{ secrets.SEGMENT_REGULATIONS_ENDPOINT }}
MM_SENTRY_DSN: ${{ secrets.MM_SENTRY_DSN }}
MM_SENTRY_AUTH_TOKEN: ${{ secrets.MM_SENTRY_AUTH_TOKEN }}
MAIN_IOS_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_IOS_GOOGLE_CLIENT_ID_UAT }}
MAIN_IOS_GOOGLE_REDIRECT_URI_UAT: ${{ secrets.MAIN_IOS_GOOGLE_REDIRECT_URI_UAT }}
MAIN_ANDROID_APPLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_APPLE_CLIENT_ID_UAT }}
MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_CLIENT_ID_UAT }}
MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT: ${{ secrets.MAIN_ANDROID_GOOGLE_SERVER_CLIENT_ID_UAT }}
IOS_GOOGLE_CLIENT_ID: ${{ secrets.IOS_GOOGLE_CLIENT_ID }}
IOS_GOOGLE_REDIRECT_URI: ${{ secrets.IOS_GOOGLE_REDIRECT_URI }}
ANDROID_APPLE_CLIENT_ID: ${{ secrets.ANDROID_APPLE_CLIENT_ID }}
ANDROID_GOOGLE_CLIENT_ID: ${{ secrets.ANDROID_GOOGLE_CLIENT_ID }}
ANDROID_GOOGLE_SERVER_CLIENT_ID: ${{ secrets.ANDROID_GOOGLE_SERVER_CLIENT_ID }}
GOOGLE_SERVICES_B64_IOS: ${{ secrets.GOOGLE_SERVICES_B64_IOS }}
GOOGLE_SERVICES_B64_ANDROID: ${{ secrets.GOOGLE_SERVICES_B64_ANDROID }}
MM_INFURA_PROJECT_ID: ${{ secrets.MM_INFURA_PROJECT_ID }}
MM_BRANCH_KEY_LIVE: ${{ secrets.MM_BRANCH_KEY_LIVE }} #need to add to secrets
MM_CARD_BAANX_API_CLIENT_KEY_UAT: ${{ secrets.MM_CARD_BAANX_API_CLIENT_KEY_UAT }} #need to add to secrets
WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} #need to add to secrets
MM_FOX_CODE: ${{ secrets.MM_FOX_CODE_TEST }} #need to add to secrets
MM_BRANCH_KEY_LIVE: ${{ secrets.MM_BRANCH_KEY_LIVE }}
MM_BRANCH_KEY_TEST: ${{ secrets.MM_BRANCH_KEY_TEST }}
MM_CARD_BAANX_API_CLIENT_KEY: ${{ secrets.MM_CARD_BAANX_API_CLIENT_KEY }}
WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }}
MM_FOX_CODE: ${{ secrets.MM_FOX_CODE }}
FCM_CONFIG_API_KEY: ${{ secrets.FCM_CONFIG_API_KEY }}
FCM_CONFIG_AUTH_DOMAIN: ${{ secrets.FCM_CONFIG_AUTH_DOMAIN }}
FCM_CONFIG_STORAGE_BUCKET: ${{ secrets.FCM_CONFIG_STORAGE_BUCKET }}
FCM_CONFIG_PROJECT_ID: ${{ secrets.FCM_CONFIG_PROJECT_ID }}
FCM_CONFIG_MESSAGING_SENDER_ID: ${{ secrets.FCM_CONFIG_MESSAGING_SENDER_ID }}
FCM_CONFIG_APP_ID: ${{ secrets.FCM_CONFIG_APP_ID }}
FCM_CONFIG_MEASUREMENT_ID: ${{ secrets.FCM_CONFIG_MEASUREMENT_ID }}
QUICKNODE_MAINNET_URL: ${{ secrets.QUICKNODE_MAINNET_URL }}
QUICKNODE_ARBITRUM_URL: ${{ secrets.QUICKNODE_ARBITRUM_URL }}
QUICKNODE_AVALANCHE_URL: ${{ secrets.QUICKNODE_AVALANCHE_URL }}
QUICKNODE_BASE_URL: ${{ secrets.QUICKNODE_BASE_URL }}
QUICKNODE_LINEA_MAINNET_URL: ${{ secrets.QUICKNODE_LINEA_MAINNET_URL }}
QUICKNODE_MONAD_URL: ${{ secrets.QUICKNODE_MONAD_URL }}
QUICKNODE_OPTIMISM_URL: ${{ secrets.QUICKNODE_OPTIMISM_URL }}
QUICKNODE_POLYGON_URL: ${{ secrets.QUICKNODE_POLYGON_URL }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -230,7 +246,22 @@ jobs:
# Disable LavaMoat sandbox to prevent duplicate bundle executions in CI
EXPO_NO_LAVAMOAT: '1'
run: |
yarn run build:expo-update:main:exp
echo "🚀 Pushing EAS update for channel: ${TARGET_CHANNEL}"
case "${TARGET_CHANNEL}" in
exp)
yarn run build:expo-update:main:exp
;;
rc)
yarn run build:expo-update:main:rc
;;
production)
yarn run build:expo-update:main:prod
;;
*)
echo "❌ Unsupported TARGET_CHANNEL: ${TARGET_CHANNEL}" >&2
exit 1
;;
esac

- name: Update summary
if: success()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({ navigate: mockNavigate }),
}));

// Mock whenEngineReady to prevent Engine access after Jest teardown
jest.mock('../../../../core/Analytics/whenEngineReady', () => ({
whenEngineReady: jest.fn().mockResolvedValue(undefined),
}));

// Mock analytics module
jest.mock('../../../../util/analytics/analytics', () => ({
analytics: {
isEnabled: jest.fn(() => false),
trackEvent: jest.fn(),
optIn: jest.fn().mockResolvedValue(undefined),
optOut: jest.fn().mockResolvedValue(undefined),
getAnalyticsId: jest.fn().mockResolvedValue('test-analytics-id'),
identify: jest.fn(),
trackView: jest.fn(),
isOptedIn: jest.fn().mockResolvedValue(false),
},
}));

describe('MultichainAccountSelectorList', () => {
const mockOnSelectAccount = jest.fn();

Expand All @@ -67,8 +86,13 @@ describe('MultichainAccountSelectorList', () => {
const searchInput = getByTestId(
MULTICHAIN_ACCOUNT_SELECTOR_SEARCH_INPUT_TESTID,
);
fireEvent.changeText(searchInput, searchTerm);

await act(async () => {
fireEvent.changeText(searchInput, searchTerm);
});

// Wait for debounce to complete and filtering to occur
// Check both visible and hidden items to ensure filtering has completed
await waitFor(
() => {
expectedVisible.forEach((text) => {
Expand Down Expand Up @@ -353,19 +377,22 @@ describe('MultichainAccountSelectorList', () => {
const searchInput = getByTestId(
MULTICHAIN_ACCOUNT_SELECTOR_SEARCH_INPUT_TESTID,
);
fireEvent.changeText(searchInput, 'Test');

await act(async () => {
fireEvent.changeText(searchInput, 'Test');
});

// Immediately after typing, both accounts should still be visible (debounced)
expect(queryByText('My Account')).toBeTruthy();
expect(queryByText('Test Account')).toBeTruthy();

// Wait for debounce delay (300ms) and check that filtering has occurred
// Wait for debounce delay (200ms) and check that filtering has occurred
await waitFor(
() => {
expect(queryByText('My Account')).toBeFalsy();
expect(queryByText('Test Account')).toBeTruthy();
},
{ timeout: 500 },
{ timeout: 1000 },
);
});

Expand Down Expand Up @@ -543,7 +570,10 @@ describe('MultichainAccountSelectorList', () => {
const searchInput = getByTestId(
MULTICHAIN_ACCOUNT_SELECTOR_SEARCH_INPUT_TESTID,
);
fireEvent.changeText(searchInput, 'NonExistentAccount');

await act(async () => {
fireEvent.changeText(searchInput, 'NonExistentAccount');
});

// Wait for debounced search to complete and check empty state
await waitFor(
Expand Down Expand Up @@ -589,17 +619,21 @@ describe('MultichainAccountSelectorList', () => {
);

// Test uppercase search
fireEvent.changeText(searchInput, 'MY ACCOUNT');
await act(async () => {
fireEvent.changeText(searchInput, 'MY ACCOUNT');
});
await waitFor(
() => {
expect(queryByText('My Account')).toBeTruthy();
expect(queryByText('Test Account')).toBeFalsy();
},
{ timeout: 500 },
{ timeout: 1000 }, // Increased timeout for debounced search
);

// Test mixed case search
fireEvent.changeText(searchInput, 'tEsT aCcOuNt');
await act(async () => {
fireEvent.changeText(searchInput, 'tEsT aCcOuNt');
});
await waitFor(
() => {
expect(queryByText('My Account')).toBeFalsy();
Expand Down Expand Up @@ -642,23 +676,27 @@ describe('MultichainAccountSelectorList', () => {
expect(queryByText('Test Account')).toBeTruthy();

// Search for something
fireEvent.changeText(searchInput, 'Test');
await act(async () => {
fireEvent.changeText(searchInput, 'Test');
});
await waitFor(
() => {
expect(queryByText('My Account')).toBeFalsy();
expect(queryByText('Test Account')).toBeTruthy();
},
{ timeout: 500 },
{ timeout: 1000 },
);

// Clear search
fireEvent.changeText(searchInput, '');
await act(async () => {
fireEvent.changeText(searchInput, '');
});
await waitFor(
() => {
expect(queryByText('My Account')).toBeTruthy();
expect(queryByText('Test Account')).toBeTruthy();
},
{ timeout: 500 },
{ timeout: 1000 },
);
});

Expand Down Expand Up @@ -691,7 +729,9 @@ describe('MultichainAccountSelectorList', () => {
);

// Search with leading/trailing whitespace
fireEvent.changeText(searchInput, ' My Account ');
await act(async () => {
fireEvent.changeText(searchInput, ' My Account ');
});
await waitFor(
() => {
expect(queryByText('My Account')).toBeTruthy();
Expand Down Expand Up @@ -973,7 +1013,10 @@ describe('MultichainAccountSelectorList', () => {
const searchInput = getByTestId(
MULTICHAIN_ACCOUNT_SELECTOR_SEARCH_INPUT_TESTID,
);
fireEvent.changeText(searchInput, 'Test');

await act(async () => {
fireEvent.changeText(searchInput, 'Test');
});

// Wait for debounce and re-render
await waitFor(
Expand Down
1 change: 1 addition & 0 deletions app/components/Nav/App/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ jest.mock('../../../core/Analytics/MetaMetrics');
const mockMetrics = {
configure: jest.fn(),
addTraitsToUser: jest.fn(),
updateDataRecordingFlag: jest.fn(),
};

const mockAuthType = AUTHENTICATION_TYPE.BIOMETRIC;
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/AccountOverview/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jest.mock('../../../core/Engine', () => {
const { KeyringTypes } = jest.requireActual('@metamask/keyring-controller');

return {
init: () => mockedEngine.init({}),
init: () => mockedEngine.init(''),
context: {
KeyringController: {
getQRKeyringState: async () => ({ subscribe: () => ({}) }),
Expand Down
Loading
Loading