diff --git a/.cursor/rules/pr-creation-guidelines.mdc b/.cursor/rules/pr-creation-guidelines.mdc
index 4398b24702b..3f3bee63469 100644
--- a/.cursor/rules/pr-creation-guidelines.mdc
+++ b/.cursor/rules/pr-creation-guidelines.mdc
@@ -1,3 +1,8 @@
+---
+description: Pull Request (PR) Creation Guidelines. Use these rules when creating pull requests
+alwaysApply: false
+---
+
# MetaMask Mobile Pull Request (PR) Creation Guidelines
These rules apply whenever creating PRs in the MetaMask Mobile repository or suggesting PR creation steps to others. Follow them to ensure consistent quality and smooth reviews.
@@ -85,11 +90,44 @@ CHANGELOG entry: null
- **Always assign the PR to yourself (the author)** immediately after creation.
- This ensures proper ownership tracking and notifications.
+### Automatic Team Label Detection
+
+When creating PRs, automatically detect and apply the correct team label based on:
+
+1. **Author's GitHub team membership** - Check which MetaMask teams the author belongs to
+2. **Available team labels in the repository** - Match against existing `team-*` labels
+3. **Context awareness** - Consider files changed when author belongs to multiple teams
+
+#### Generic Team Label Detection Process
+
+```bash
+# Step 1: Get author's MetaMask teams
+USER_TEAMS=$(gh api user/teams --paginate | jq -r '.[] | select(.organization.login == "MetaMask") | .slug')
+
+# Step 2: Get available team labels in the repository
+REPO_TEAM_LABELS=$(gh label list --search "team-" --limit 100 | cut -f1)
+
+# Step 3: Try to find a matching team label
+# Common mappings to check:
+# - Exact match: team-{github-team} (e.g., mobile-platform → team-mobile-platform)
+# - Without suffix: design-system-engineers → team-design-system
+# - With wallet prefix: wallet-ux → team-wallet-ux
+
+# If no clear match is found, DO NOT add a team label
+# It's better to have no team label than an incorrect one
+```
+
### Required Labels
Apply labels to enable automation and proper routing. Some labels can block merging.
-- **Team labels** (for internal devs): `team-mobile-ux`, `team-mobile-platform`, etc.
+- **Team labels** (OPTIONAL - only add if there's a clear match):
+ - Auto-detect based on author's GitHub team membership when possible
+ - Follow the pattern `team-*`
+ - Only add if you can confidently match the GitHub team to a label
+ - If unsure, leave it out - no team label is better than a wrong one
+ - Check for deprecated labels in the label description
+ - To see all available team labels: `gh label list --search "team-" --limit 100`
- **QA labels**:
- `needs-qa` (blocks merging until QA passes)
- `No QA Needed`
@@ -102,10 +140,15 @@ Apply labels to enable automation and proper routing. Some labels can block merg
### Label and Assignment DO / DON'T
- ✅ DO: Assign the PR to yourself as the author
+- ✅ DO: Try to auto-detect team label based on GitHub team membership
+- ✅ DO: Leave team label empty if no clear match exists
+- ✅ DO: Verify team labels aren't deprecated (check label descriptions)
- ✅ DO: Add a QA label for every PR
-- ✅ DO: Add the correct team label for routing
- ✅ DO: Remove blocking labels when conditions are resolved
- ❌ DON'T: Leave PR unassigned
+- ❌ DON'T: Force a team label if there's no clear match - it's optional
+- ❌ DON'T: Use deprecated team labels (check label descriptions for deprecation notices)
+- ❌ DON'T: Guess team labels - only use ones you're confident about
- ❌ DON'T: Merge while `needs-qa` or `DO-NOT-MERGE` is present
## 4. Branch Naming Conventions
@@ -153,13 +196,35 @@ Use descriptive branch names prefixed by the commit type.
## Examples for MetaMask Mobile Context
+### Example with Auto-Detected Team Label
+
- Title: `feat: add NFT gallery to collectibles tab`
- Branch: `feat/add-nft-gallery`
- Assignee: The PR author (yourself)
-- Labels: `team-mobile-ux`, `needs-qa`, `Run E2E Smoke`
+- Team label: `team-design-system` (IF clear match found) or none (if no match)
+- Required Labels: `needs-qa` or `No QA Needed`, plus E2E label
- Changelog: `Added NFT gallery to Collectibles tab.`
- Manual testing (Gherkin): see example above
+### GitHub CLI Command Example
+
+```bash
+# Create PR with auto team detection
+gh pr create \
+ --title "feat: add NFT gallery to collectibles tab" \
+ --assignee @me \
+ --draft
+
+# Try to detect team label (optional)
+USER_TEAMS=$(gh api user/teams --paginate | jq -r '.[] | select(.organization.login == "MetaMask") | .slug')
+
+# Only add team label if there's a confident match
+# Examples of clear matches:
+# - mobile-platform → team-mobile-platform ✓
+# - wallet-ux → team-wallet-ux ✓
+# If no clear match, skip the team label
+```
+
## Enforcement
- PRs must use `.github/pull-request-template.md` with all sections completed.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cd714dc822b..bf6dcff02b8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -124,6 +124,12 @@ jobs:
else
echo "No changes detected"
fi
+ e2e-tests:
+ permissions:
+ contents: read
+ id-token: write
+ uses: ./.github/workflows/run-e2e.yml
+ secrets: inherit
merge-unit-tests:
runs-on: ubuntu-latest
needs: unit-tests
diff --git a/.github/workflows/run-e2e-workflow.yml b/.github/workflows/run-e2e-workflow.yml
index aa300b8672d..556a67a6a97 100644
--- a/.github/workflows/run-e2e-workflow.yml
+++ b/.github/workflows/run-e2e-workflow.yml
@@ -77,6 +77,7 @@ jobs:
setup-simulator: ${{ inputs.platform == 'ios' }}
android-avd-name: emulator
configure-keystores: false
+ sd-card-size: 8192
- name: Build Detox framework cache (iOS)
if: ${{ inputs.platform == 'ios' }}
@@ -181,7 +182,10 @@ jobs:
if: failure()
run: |
echo "Removing APKs from artifacts..."
- find artifacts/ -name "*app-*" -delete 2>/dev/null || true
+ # Remove APKs, but ignore coverage folders and ios-bundle
+ find artifacts/ \( -type d \( -name "coverage-*" -o -name "ios-bundle" \) -prune \) -o -type f -name "*app-*" -delete 2>/dev/null || true
+ # Also remove artifact directories that contain APK/AAB files (download-artifact creates directories)
+ find artifacts/ \( -type d \( -name "coverage-*" -o -name "ios-bundle" \) -prune \) -o -type d -name "*app-*" -exec rm -rf {} + 2>/dev/null || true
continue-on-error: true
- name: Upload screenshots
diff --git a/.github/workflows/run-e2e.yml b/.github/workflows/run-e2e.yml
index b30a3840fa1..0e760e8fbdd 100644
--- a/.github/workflows/run-e2e.yml
+++ b/.github/workflows/run-e2e.yml
@@ -6,11 +6,9 @@ name: Mobile E2E Smoke Tests
on:
workflow_call:
workflow_dispatch:
- pull_request:
- types: [opened, synchronize]
permissions:
- contents: write
+ contents: read
id-token: write
jobs:
@@ -198,6 +196,27 @@ jobs:
test_suite_tag: ".*Performance.*"
secrets: inherit
+ smoke-card-ios:
+ name: "Card Smoke (iOS)"
+ if: false
+ uses: ./.github/workflows/run-e2e-workflow.yml
+ with:
+ test-suite-name: smoke-card-ios
+ platform: ios
+ test_suite_tag: ".*SmokeCard.*"
+ secrets: inherit
+
+ smoke-card-android:
+ name: "Card Smoke (Android)"
+ needs: build-android-apks
+ if: always() && needs.build-android-apks.result != 'failure'
+ uses: ./.github/workflows/run-e2e-workflow.yml
+ with:
+ test-suite-name: smoke-card-android
+ platform: android
+ test_suite_tag: ".*SmokeCard.*"
+ secrets: inherit
+
smoke-api-specs:
name: "API Specs E2E"
if: false
@@ -207,7 +226,7 @@ jobs:
report-e2e-smoke-tests:
name: Report E2E Smoke Tests
runs-on: ubuntu-latest
- if: false
+ if: always()
needs:
- smoke-confirmations-ios
- smoke-confirmations-android
@@ -226,6 +245,8 @@ jobs:
- smoke-network-expansion-android
- smoke-performance-ios
- smoke-performance-android
+ - smoke-card-ios
+ - smoke-card-android
- smoke-api-specs
steps:
diff --git a/README.md b/README.md
index 53de2524787..6f6b5788e51 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ To learn how to contribute to the MetaMask codebase, visit our [Contributor Docs
- [Testing](./docs/readme/testing.md)
- [Debugging](./docs/readme/debugging.md)
- [Performance](./docs/readme/performance.md)
+- [Release Build Profiling](./docs/readme/release-build-profiler.md)
- [API Call Logging for Debugging](./docs/readme/api-logging.md)
- [Storybook](./docs/readme/storybook.md)
- [Miscellaneous](./docs/readme/miscellaneous.md)
diff --git a/app/actions/wizard/index.js b/app/actions/wizard/index.js
deleted file mode 100644
index 3cc5acb70e3..00000000000
--- a/app/actions/wizard/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * Sets onboarding wizard step
- */
-export default function setOnboardingWizardStep(step) {
- return {
- type: 'SET_ONBOARDING_WIZARD_STEP',
- step,
- };
-}
diff --git a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx
index 95d90ea3818..1442c995289 100644
--- a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx
+++ b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.test.tsx
@@ -1,9 +1,18 @@
import React from 'react';
import { AccountGroupObject } from '@metamask/account-tree-controller';
-import { AccountCell } from './AccountCell';
+import AccountCell from './AccountCell';
import renderWithProvider from '../../../../util/test/renderWithProvider';
+import { fireEvent } from '@testing-library/react-native';
import { createMockAccountGroup } from '../test-utils';
+// Mock navigation
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => ({
+ ...jest.requireActual('@react-navigation/native'),
+ useNavigation: () => ({ navigate: mockNavigate }),
+}));
+
const mockAccountGroup = createMockAccountGroup(
'keyring:test-group/ethereum',
'Test Account Group',
@@ -56,4 +65,13 @@ describe('AccountCell', () => {
expect(getByText('Test Account Group')).toBeTruthy();
expect(getByText('$1234567890.00')).toBeTruthy();
});
+
+ it('navigates to account actions when menu button is pressed', () => {
+ const { getByTestId } = renderAccountCell();
+ const menuButton = getByTestId('multichain-account-cell-menu');
+ fireEvent.press(menuButton);
+ expect(mockNavigate).toHaveBeenCalledWith('MultichainAccountActions', {
+ accountGroup: mockAccountGroup,
+ });
+ });
});
diff --git a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx
index d6b9a0c06d4..bf2c24d5560 100644
--- a/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx
+++ b/app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx
@@ -1,6 +1,7 @@
import { AccountGroupObject } from '@metamask/account-tree-controller';
-import React from 'react';
+import React, { useCallback } from 'react';
import { TouchableOpacity, View } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
import { useStyles } from '../../../hooks';
import styleSheet from './AccountCell.styles';
import Text, { TextColor, TextVariant } from '../../../components/Texts/Text';
@@ -12,14 +13,22 @@ import {
} from '../../../../components/UI/Box/box.types';
import Icon, { IconName, IconSize } from '../../../components/Icons/Icon';
import { AccountCellIds } from '../../../../../e2e/selectors/MultichainAccounts/AccountCell.selectors';
+import Routes from '../../../../constants/navigation/Routes';
interface AccountCellProps {
accountGroup: AccountGroupObject;
isSelected: boolean;
}
-export const AccountCell = ({ accountGroup, isSelected }: AccountCellProps) => {
+const AccountCell = ({ accountGroup, isSelected }: AccountCellProps) => {
const { styles } = useStyles(styleSheet, { isSelected });
+ const { navigate } = useNavigation();
+
+ const handleMenuPress = useCallback(() => {
+ navigate(Routes.MULTICHAIN_ACCOUNTS.ACCOUNT_CELL_ACTIONS, {
+ accountGroup,
+ });
+ }, [navigate, accountGroup]);
return (
{
{
);
};
+
+export default React.memo(AccountCell);
diff --git a/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts b/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts
index 97146ae05a7..7cfd34a2193 100644
--- a/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts
+++ b/app/component-library/components-temp/MultichainAccounts/AccountCell/index.ts
@@ -1 +1 @@
-export { AccountCell as default } from './AccountCell';
+export { default } from './AccountCell';
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx
index 20c9b038586..759caf7927f 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListCell/AccountListCell.test.tsx
@@ -3,6 +3,13 @@ import { render, fireEvent } from '@testing-library/react-native';
import AccountListCell from './AccountListCell';
import { createMockAccountGroup } from '../../test-utils';
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => ({
+ ...jest.requireActual('@react-navigation/native'),
+ useNavigation: () => ({ navigate: mockNavigate }),
+}));
+
const mockAccountGroup = createMockAccountGroup(
'keyring:test-group/ethereum',
'Test Account',
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx
index 7fe910435d0..21f81d1a373 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx
@@ -19,6 +19,13 @@ import {
createMockInternalAccountsWithAddresses,
} from '../test-utils';
+const mockNavigate = jest.fn();
+
+jest.mock('@react-navigation/native', () => ({
+ ...jest.requireActual('@react-navigation/native'),
+ useNavigation: () => ({ navigate: mockNavigate }),
+}));
+
describe('MultichainAccountSelectorList', () => {
const mockOnSelectAccount = jest.fn();
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts
index 40922b15afc..56c56c19e76 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.constants.ts
@@ -1,10 +1,5 @@
import { MultichainAddressRowProps } from './MultichainAddressRow.types';
-
-export const SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS: MultichainAddressRowProps = {
- chainId: 'eip155:1',
- networkName: 'Ethereum Mainnet',
- address: '0x1234567890123456789012345678901234567890',
-};
+import { IconName } from '../../../components/Icons/Icon';
export const MULTICHAIN_ADDRESS_ROW_TEST_ID = 'multichain-address-row';
export const MULTICHAIN_ADDRESS_ROW_NETWORK_ICON_TEST_ID =
@@ -17,3 +12,27 @@ export const MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID =
'multichain-address-row-copy-button';
export const MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID =
'multichain-address-row-qr-button';
+
+export const SAMPLE_ICONS = [
+ {
+ name: IconName.Copy,
+ callback: () => {
+ // Do nothing
+ },
+ testId: MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID,
+ },
+ {
+ name: IconName.QrCode,
+ callback: () => {
+ // Do nothing
+ },
+ testId: MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID,
+ },
+];
+
+export const SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS: MultichainAddressRowProps = {
+ chainId: 'eip155:1',
+ networkName: 'Ethereum Mainnet',
+ address: '0x1234567890123456789012345678901234567890',
+ icons: SAMPLE_ICONS,
+};
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx
index 998c175bc59..12e8b1a6af5 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.stories.tsx
@@ -4,7 +4,11 @@ import { View } from 'react-native';
import { CaipChainId } from '@metamask/utils';
import { mockTheme } from '../../../../util/theme';
import { default as MultichainAddressRowComponent } from './MultichainAddressRow';
-import { SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS } from './MultichainAddressRow.constants';
+import {
+ SAMPLE_ICONS,
+ SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS,
+} from './MultichainAddressRow.constants';
+import { Icon } from './MultichainAddressRow.types';
const MultichainAddressRowMeta = {
title: 'Component Library / MultichainAccounts',
@@ -24,6 +28,7 @@ const MultichainAddressRowMeta = {
},
},
};
+
export default MultichainAddressRowMeta;
export const MultichainAddressRow = {
@@ -31,6 +36,7 @@ export const MultichainAddressRow = {
chainId: CaipChainId;
networkName: string;
address: string;
+ icons: Icon[];
}) => (
),
@@ -63,6 +70,7 @@ export const WithLongNetworkName = {
chainId={args.chainId || '0x1'}
networkName="Very Long Network Name That Might Wrap"
address={args.address}
+ icons={SAMPLE_ICONS}
/>
),
@@ -82,6 +90,7 @@ export const WithCustomNetwork = {
chainId="eip155:137"
networkName="Polygon Mainnet"
address="0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
+ icons={SAMPLE_ICONS}
/>
),
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx
index 7f98362b98f..edafdc8069c 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React from 'react';
import { View } from 'react-native';
import Avatar, {
@@ -9,7 +9,7 @@ import ButtonIcon, {
ButtonIconSizes,
} from '../../../components/Buttons/ButtonIcon';
import Text, { TextVariant, TextColor } from '../../../components/Texts/Text';
-import { IconName, IconColor } from '../../../components/Icons/Icon';
+import { IconColor } from '../../../components/Icons/Icon';
import { useStyles } from '../../../hooks';
import { formatAddress } from '../../../../util/address';
import { getNetworkImageSource } from '../../../../util/networks';
@@ -21,34 +21,22 @@ import {
MULTICHAIN_ADDRESS_ROW_NETWORK_ICON_TEST_ID,
MULTICHAIN_ADDRESS_ROW_NETWORK_NAME_TEST_ID,
MULTICHAIN_ADDRESS_ROW_ADDRESS_TEST_ID,
- MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID,
- MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID,
} from './MultichainAddressRow.constants';
-import useCopyClipboard from '../../../../components/Views/Notifications/Details/hooks/useCopyClipboard';
const MultichainAddressRow = ({
chainId,
networkName,
address,
+ icons,
style,
testID = MULTICHAIN_ADDRESS_ROW_TEST_ID,
...props
}: MultichainAddressRowProps) => {
const { styles } = useStyles(styleSheet, { style });
- const copyToClipboard = useCopyClipboard();
const networkImageSource = getNetworkImageSource({ chainId });
const truncatedAddress = formatAddress(address, 'short');
- const handleCopyClick = useCallback(() => {
- copyToClipboard(address);
- }, [copyToClipboard, address]);
-
- const handleQrClick = useCallback(() => {
- // TODO: Implement QR code functionality
- // QR code clicked for address: address
- }, []);
-
return (
-
-
-
+ {icons.map((icon, index) => (
+
+ ))}
);
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts
index e19ae421a37..7c55be07dfa 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/MultichainAddressRow.types.ts
@@ -1,6 +1,25 @@
import { StyleProp, ViewStyle } from 'react-native';
import { CaipChainId } from '@metamask/utils';
+import { IconName } from '../../../components/Icons/Icon';
+
+export interface Icon {
+ /**
+ * Icon name to display
+ */
+ name: IconName;
+ /**
+ * Callback function to execute when the icon is pressed
+ * This can be used for actions like copying the address or navigating to a different screen
+ */
+ callback: () => void;
+ /**
+ * Test ID for the icon, useful for testing purposes
+ * Should be unique to each icon in the row
+ */
+ testId: string;
+}
+
export interface MultichainAddressRowProps {
/**
* Chain ID to identify the network
@@ -14,6 +33,13 @@ export interface MultichainAddressRowProps {
* Address string to display (will be truncated)
*/
address: string;
+ /**
+ * Object containing icons to display in the row.
+ * Each icon should have a name, a callback function, and test ID.
+ * The callback will be executed when the icon is pressed.
+ * Icons are displayed in the order they are provided.
+ */
+ icons: Icon[];
/**
* Optional style object for the container
*/
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts
index 29ab96ce96a..cdfb3bc054a 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRow/index.ts
@@ -11,4 +11,5 @@ export {
MULTICHAIN_ADDRESS_ROW_COPY_BUTTON_TEST_ID,
MULTICHAIN_ADDRESS_ROW_QR_BUTTON_TEST_ID,
SAMPLE_MULTICHAIN_ADDRESS_ROW_PROPS,
+ SAMPLE_ICONS,
} from './MultichainAddressRow.constants';
diff --git a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx
index 5168a9af976..bc5acab86dd 100644
--- a/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx
+++ b/app/component-library/components-temp/MultichainAccounts/MultichainAddressRowsList/MultichainAddressRowsList.tsx
@@ -10,7 +10,7 @@ import Text, { TextVariant, TextColor } from '../../../components/Texts/Text';
import TextFieldSearch from '../../../components/Form/TextFieldSearch';
import { TextFieldSize } from '../../../components/Form/TextField/TextField.types';
import { strings } from '../../../../../locales/i18n';
-import MultichainAddressRow from '../MultichainAddressRow';
+import MultichainAddressRow, { SAMPLE_ICONS } from '../MultichainAddressRow';
import { selectEvmNetworkConfigurationsByChainId } from '../../../../selectors/networkController';
import { selectNonEvmNetworkConfigurationsByChainId } from '../../../../selectors/multichainNetworkController';
import {
@@ -109,6 +109,7 @@ const MultichainAddressRowsList: React.FC = ({
chainId={item.chainId}
networkName={item.networkName}
address={item.address}
+ icons={SAMPLE_ICONS}
/>
),
[],
diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx
index 4e8a816b8b9..666439a3648 100644
--- a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx
+++ b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.tsx
@@ -5,9 +5,6 @@ import { createStore } from 'redux';
import initialBackgroundState from '../../../../util/test/initial-background-state.json';
import { AggregatedPercentageProps } from './AggregatedPercentage.types';
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState: initialBackgroundState,
},
diff --git a/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx
index 9a802d49789..d25c7093201 100644
--- a/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx
+++ b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.tsx
@@ -5,9 +5,6 @@ import { createStore } from 'redux';
import initialBackgroundState from '../../../../util/test/initial-background-state.json';
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState: initialBackgroundState,
},
diff --git a/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx b/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx
index 55874e85b0a..5a4cf0c2da4 100644
--- a/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx
+++ b/app/component-library/components/Navigation/TabBar/TabBar.stories.tsx
@@ -14,9 +14,6 @@ import { default as TabBarComponent } from './TabBar';
import { TabBarIconKey } from './TabBar.types';
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState,
},
diff --git a/app/component-library/components/Navigation/TabBar/TabBar.test.tsx b/app/component-library/components/Navigation/TabBar/TabBar.test.tsx
index 99df17d1191..5e7bb4e04ff 100644
--- a/app/component-library/components/Navigation/TabBar/TabBar.test.tsx
+++ b/app/component-library/components/Navigation/TabBar/TabBar.test.tsx
@@ -18,9 +18,6 @@ const navigation = {
};
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState,
},
diff --git a/app/component-library/components/Navigation/TabBar/TabBar.tsx b/app/component-library/components/Navigation/TabBar/TabBar.tsx
index b852e349c3b..11123e04f70 100644
--- a/app/component-library/components/Navigation/TabBar/TabBar.tsx
+++ b/app/component-library/components/Navigation/TabBar/TabBar.tsx
@@ -26,7 +26,6 @@ import {
ICON_BY_TAB_BAR_ICON_KEY,
LABEL_BY_TAB_BAR_ICON_KEY,
} from './TabBar.constants';
-import OnboardingWizard from '../../../../components/UI/OnboardingWizard';
import { selectChainId } from '../../../../selectors/networkController';
const TabBar = ({ state, descriptors, navigation }: TabBarProps) => {
@@ -35,22 +34,6 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => {
const chainId = useSelector(selectChainId);
const tabBarRef = useRef(null);
const tw = useTailwind();
- /**
- * Current onboarding wizard step
- */
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const wizardStep = useSelector((reduxState: any) => reduxState.wizard.step);
- /**
- * Return current step of onboarding wizard if not step 5 nor 0
- */
- const renderOnboardingWizard = useCallback(
- () =>
- [4, 5, 6].includes(wizardStep) && (
-
- ),
- [navigation, wizardStep],
- );
const renderTabBarItem = useCallback(
(route: { name: string; key: string }, index: number) => {
@@ -144,7 +127,6 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => {
>
{renderTabBarItems()}
- {renderOnboardingWizard()}
);
};
diff --git a/app/components/Nav/App/App.test.tsx b/app/components/Nav/App/App.test.tsx
index d01115d893c..441f7e58a02 100644
--- a/app/components/Nav/App/App.test.tsx
+++ b/app/components/Nav/App/App.test.tsx
@@ -12,7 +12,6 @@ import Routes from '../../../constants/navigation/Routes';
import {
OPTIN_META_METRICS_UI_SEEN,
EXISTING_USER,
- ONBOARDING_WIZARD,
} from '../../../constants/storage';
import { strings } from '../../../../locales/i18n';
import {
@@ -50,6 +49,14 @@ jest.mock('react-native/Libraries/Linking/Linking', () => ({
removeEventListener: jest.fn(),
}));
+jest.mock('expo-sensors', () => ({
+ Accelerometer: {
+ setUpdateInterval: jest.fn(),
+ addListener: jest.fn(),
+ removeAllListeners: jest.fn(),
+ },
+}));
+
jest.mock('../../../core/DeeplinkManager/SharedDeeplinkManager', () => ({
init: jest.fn(),
parse: jest.fn(),
@@ -210,6 +217,7 @@ jest.mock('react-native-branch', () => ({
jest.mock('react-native-device-info', () => ({
getVersion: jest.fn().mockReturnValue('1.0.0'),
+ getBundleId: jest.fn().mockReturnValue('io.metamask'),
}));
jest.mock('../../../selectors/accountsController', () => ({
@@ -661,9 +669,7 @@ describe('App', () => {
if (key === OPTIN_META_METRICS_UI_SEEN) {
return true; // OptinMetrics UI has been seen
}
- if (key === ONBOARDING_WIZARD) {
- return true;
- }
+
return null; // Default for other keys
});
diff --git a/app/components/Nav/App/App.tsx b/app/components/Nav/App/App.tsx
index 7cf7b1f7e3a..d18dc48d7f9 100644
--- a/app/components/Nav/App/App.tsx
+++ b/app/components/Nav/App/App.tsx
@@ -48,6 +48,7 @@ import Toast, {
} from '../../../component-library/components/Toast';
import AccountSelector from '../../../components/Views/AccountSelector';
import { TokenSortBottomSheet } from '../../../components/UI/Tokens/TokensBottomSheet/TokenSortBottomSheet';
+import ProfilerManager from '../../../components/UI/ProfilerManager';
import { TokenFilterBottomSheet } from '../../../components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet';
import NetworkManager from '../../../components/UI/NetworkManager';
import AccountConnect from '../../../components/Views/AccountConnect';
@@ -156,6 +157,7 @@ import SolanaNewFeatureContent from '../../UI/SolanaNewFeatureContent';
import { DeepLinkModal } from '../../UI/DeepLinkModal';
import { checkForDeeplink } from '../../../actions/user';
import { WalletDetails } from '../../Views/MultichainAccounts/WalletDetails/WalletDetails';
+import MultichainAccountActions from '../../Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions';
import useInterval from '../../hooks/useInterval';
import { Duration } from '@metamask/utils';
import { selectSeedlessOnboardingLoginFlow } from '../../../selectors/seedlessOnboardingController';
@@ -835,6 +837,10 @@ const AppFlow = () => {
name={Routes.MULTICHAIN_ACCOUNTS.ACCOUNT_DETAILS}
component={MultichainAccountDetails}
/>
+
{
+
>
);
};
diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js
index c64c25d0ba3..91bc8793d59 100644
--- a/app/components/UI/AccountOverview/index.js
+++ b/app/components/UI/AccountOverview/index.js
@@ -164,10 +164,6 @@ class AccountOverview extends PureComponent {
/* Triggers global alert
*/
showAlert: PropTypes.func,
- /**
- * whether component is being rendered from onboarding wizard
- */
- onboardingWizard: PropTypes.bool,
/**
* Used to get child ref
*/
@@ -207,9 +203,8 @@ class AccountOverview extends PureComponent {
mainView = React.createRef();
openAccountSelector = () => {
- const { onboardingWizard, navigation } = this.props;
- !onboardingWizard &&
- navigation.navigate(...createAccountSelectorNavDetails({}));
+ const { navigation } = this.props;
+ navigation.navigate(...createAccountSelectorNavDetails({}));
};
isAccountLabelDefined = (accountLabel) =>
@@ -337,7 +332,6 @@ class AccountOverview extends PureComponent {
render() {
const {
account: { address, name },
- onboardingWizard,
} = this.props;
const colors = this.context.colors || mockTheme.colors;
const themeAppearance = this.context.themeAppearance || 'light';
@@ -359,15 +353,10 @@ class AccountOverview extends PureComponent {
-
+
-
-
-
-
-
-
-
-
-
- title
-
-
-
-
-
- content
-
-
-
- 1
- /6
-
-
-
-
- Got it
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Coachmark/index.js b/app/components/UI/OnboardingWizard/Coachmark/index.js
deleted file mode 100644
index 16fb6dad3ce..00000000000
--- a/app/components/UI/OnboardingWizard/Coachmark/index.js
+++ /dev/null
@@ -1,439 +0,0 @@
-import React, { PureComponent } from 'react';
-import PropTypes from 'prop-types';
-import { Animated, StyleSheet, Text, View } from 'react-native';
-import {
- colors as importedColors,
- fontStyles,
-} from '../../../../styles/common';
-import StyledButton from '../../StyledButton';
-import { strings } from '../../../../../locales/i18n';
-import { mockTheme, ThemeContext } from '../../../../util/theme';
-import ButtonIcon, {
- ButtonIconSizes,
-} from '../../../../component-library/components/Buttons/ButtonIcon';
-import {
- IconName,
- IconColor,
-} from '../../../../component-library/components/Icons/Icon';
-import { typography } from '@metamask/design-tokens';
-import {
- ButtonSize,
- ButtonVariants,
- ButtonWidthTypes,
-} from '../../../../component-library/components/Buttons/Button';
-import Button from '../../../../component-library/components/Buttons/Button/Button';
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-import {
- getFontFamily,
- TextVariant,
-} from '../../../../component-library/components/Texts/Text';
-
-const createStyles = (colors) =>
- StyleSheet.create({
- coachmark: {
- backgroundColor: colors.primary.default,
- borderRadius: 8,
- padding: 20,
- },
- progress: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- },
- actions: {
- flexDirection: 'row',
- },
- actionButtonPrimary: {
- flex: 0.5,
- borderWidth: 1,
- borderColor: colors.primary.inverse,
- marginRight: 4,
- },
- actionButtonSecondary: {
- flex: 0.5,
- backgroundColor: colors.primary.inverse,
- marginLeft: 4,
- },
- title: {
- ...fontStyles.bold,
- color: colors.primary.inverse,
- fontSize: 18,
- alignSelf: 'center',
- },
- triangle: {
- width: 0,
- height: 0,
- backgroundColor: importedColors.transparent,
- borderStyle: 'solid',
- borderLeftWidth: 15,
- borderRightWidth: 15,
- borderBottomWidth: 12,
- borderLeftColor: importedColors.transparent,
- borderRightColor: importedColors.transparent,
- borderBottomColor: colors.primary.default,
- position: 'absolute',
- },
- triangleDown: {
- width: 0,
- height: 0,
- backgroundColor: importedColors.transparent,
- borderStyle: 'solid',
- borderLeftWidth: 15,
- borderRightWidth: 15,
- borderTopWidth: 12,
- borderLeftColor: importedColors.transparent,
- borderRightColor: importedColors.transparent,
- borderTopColor: colors.primary.default,
- position: 'absolute',
- },
- progressButton: {
- width: 75,
- height: 45,
- padding: 5,
- },
- leftProgessButton: {
- left: 0,
- },
- rightProgessButton: {
- right: 0,
- },
- topCenter: {
- marginBottom: 10,
- bottom: -2,
- alignItems: 'center',
- },
- topLeft: {
- marginBottom: 10,
- bottom: -2,
- alignItems: 'flex-start',
- marginLeft: 30,
- },
- topRight: {
- marginBottom: 10,
- bottom: -2,
- alignItems: 'flex-end',
- marginRight: 38,
- },
- topLeftCorner: {
- marginBottom: 10,
- bottom: -2,
- alignItems: 'flex-start',
- marginLeft: 12,
- },
- topRightCorner: {
- marginBottom: 10,
- bottom: -2,
- alignItems: 'flex-end',
- marginRight: 12,
- },
- bottomCenter: {
- marginBottom: 10,
- top: -2,
- alignItems: 'center',
- },
- bottomLeft: {
- marginBottom: 10,
- top: -2,
- alignItems: 'flex-start',
- marginLeft: 60,
- },
- bottomLeftCorner: {
- marginBottom: 10,
- top: -2,
- alignItems: 'flex-start',
- marginLeft: 30,
- },
- bottomRight: {
- marginBottom: 10,
- top: -2,
- alignItems: 'flex-end',
- marginRight: 90,
- },
- circle: {
- width: 6,
- height: 6,
- borderRadius: 6 / 2,
- backgroundColor: colors.primary.inverse,
- opacity: 0.4,
- margin: 3,
- },
- solidCircle: {
- opacity: 1,
- },
- progessContainer: {
- flexDirection: 'row',
- alignSelf: 'center',
- },
- stepCounter: {
- ...typography.BodyMD,
- fontFamily: getFontFamily(TextVariant.BodyMD),
- color: colors.info.inverse,
- },
- titleContainer: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- },
- });
-
-export default class Coachmark extends PureComponent {
- static propTypes = {
- /**
- * Custom coachmark style to apply
- */
- coachmarkStyle: PropTypes.object,
- /**
- * Custom animated view style to apply
- */
- style: PropTypes.object,
- /**
- * Content object
- */
- content: PropTypes.object,
- /**
- * Title text
- */
- title: PropTypes.string,
- /**
- * Current onboarding wizard step
- */
- currentStep: PropTypes.number,
- /**
- * Callback to be called when next is pressed
- */
- onNext: PropTypes.func,
- /**
- * Callback to be called when back is pressed
- */
- onBack: PropTypes.func,
- /**
- * Whether action buttons have to be rendered
- */
- action: PropTypes.bool,
- /**
- * Top indicator position
- */
- topIndicatorPosition: PropTypes.oneOf([
- false,
- 'topCenter',
- 'topLeft',
- 'topLeftCorner',
- 'topRight',
- 'topRightCorner',
- ]),
- /**
- * Bottom indicator position
- */
- bottomIndicatorPosition: PropTypes.oneOf([
- false,
- 'bottomCenter',
- 'bottomLeft',
- 'bottomLeftCorner',
- 'bottomRight',
- ]),
- /**
- * Callback called when closing on boarding wizard
- */
- onClose: PropTypes.func,
- };
-
- state = {
- ready: false,
- };
-
- opacity = new Animated.Value(0);
-
- componentDidMount = () => {
- Animated.timing(this.opacity, {
- toValue: 1,
- duration: 500,
- useNativeDriver: true,
- isInteraction: false,
- }).start();
- };
-
- componentWillUnmount = () => {
- Animated.timing(this.opacity, {
- toValue: 0,
- duration: 500,
- useNativeDriver: true,
- isInteraction: false,
- }).start();
- };
-
- /**
- * Calls props onNext
- */
- onNext = () => {
- const { onNext } = this.props;
- onNext && onNext();
- };
-
- /**
- * Calls props onBack
- */
- onBack = () => {
- const { onBack } = this.props;
- onBack && onBack();
- };
-
- getStyles = () => {
- const colors = this.context.colors || mockTheme.colors;
- return createStyles(colors);
- };
-
- /**
- * Gets top indicator style according to 'topIndicatorPosition'
- *
- * @param {string} topIndicatorPosition - Indicator position
- * @returns {Object} - Corresponding style object
- */
- getIndicatorStyle = (topIndicatorPosition) => {
- const styles = this.getStyles();
-
- const positions = {
- topCenter: styles.topCenter,
- topLeft: styles.topLeft,
- topRight: styles.topRight,
- topLeftCorner: styles.topLeftCorner,
- topRightCorner: styles.topRightCorner,
- [undefined]: styles.topCenter,
- };
- return positions[topIndicatorPosition];
- };
-
- /**
- * Gets top indicator style according to 'bottomIndicatorPosition'
- *
- * @param {string} bottomIndicatorPosition - Indicator position
- * @returns {Object} - Corresponding style object
- */
- getBotttomIndicatorStyle = (bottomIndicatorPosition) => {
- const styles = this.getStyles();
-
- const positions = {
- bottomCenter: styles.bottomCenter,
- bottomLeft: styles.bottomLeft,
- bottomLeftCorner: styles.bottomLeftCorner,
- bottomRight: styles.bottomRight,
- [undefined]: styles.bottomCenter,
- };
- return positions[bottomIndicatorPosition];
- };
-
- /**
- * Returns progress bar, back and next buttons. According to currentStep
- *
- * @returns {Object} - Corresponding view object
- */
- renderProgressButtons = () => {
- const { currentStep } = this.props;
- const styles = this.getStyles();
- return (
-
-
- {currentStep !== 0 && (
- {currentStep}/6
- )}
-
-
-
- {strings('onboarding_wizard_new.coachmark.progress_next')}
-
-
- );
- };
-
- /**
- * Returns horizontal action buttons
- *
- * @returns {Object} - Corresponding view object
- */
- renderActionButtons = () => {
- const styles = this.getStyles();
-
- return (
-
-
-
-
-
- );
- };
-
- render() {
- const {
- content,
- title,
- topIndicatorPosition,
- bottomIndicatorPosition,
- action,
- currentStep,
- onClose,
- } = this.props;
- const style = this.props.style || {};
- const coachmarkStyle = this.props.coachmarkStyle || {};
- const styles = this.getStyles();
-
- return (
-
- {topIndicatorPosition && (
-
-
-
- )}
-
-
- {currentStep ? (
-
- ) : (
-
- )}
- {title}
-
-
- {content}
- {action ? this.renderActionButtons() : this.renderProgressButtons()}
-
- {bottomIndicatorPosition && (
-
-
-
- )}
-
- );
- }
-}
-
-Coachmark.contextType = ThemeContext;
diff --git a/app/components/UI/OnboardingWizard/Coachmark/index.test.tsx b/app/components/UI/OnboardingWizard/Coachmark/index.test.tsx
deleted file mode 100644
index a5c3b6de0c0..00000000000
--- a/app/components/UI/OnboardingWizard/Coachmark/index.test.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-import Coachmark from './';
-jest.useFakeTimers();
-
-describe('Coachmark', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step1/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Step1/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 97633906599..00000000000
--- a/app/components/UI/OnboardingWizard/Step1/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,245 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Step1 should render correctly 1`] = `
-
-
-
-
-
-
-
- Welcome to your wallet!
-
-
-
-
-
-
-
- On the blockchain, you need a wallet (and maybe some ETH). So let’s take a look at how to use your MetaMask wallet.
-
-
-
-
-
- No thanks
-
-
-
-
- Take the tour
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Step1/index.test.tsx b/app/components/UI/OnboardingWizard/Step1/index.test.tsx
deleted file mode 100644
index 2e4b74fcbe2..00000000000
--- a/app/components/UI/OnboardingWizard/Step1/index.test.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-import Step1 from './';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-
-const closeOnboardingWizard = jest.fn();
-
-describe('Step1', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step1/index.tsx b/app/components/UI/OnboardingWizard/Step1/index.tsx
deleted file mode 100644
index 34c99d7c5b8..00000000000
--- a/app/components/UI/OnboardingWizard/Step1/index.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import React, { useCallback, useContext } from 'react';
-import { View, Text, StyleSheet } from 'react-native';
-import { useDispatch } from 'react-redux';
-
-import Coachmark from '../Coachmark';
-import Device from '../../../../util/device';
-import setOnboardingWizardStep from '../../../../actions/wizard';
-import { strings } from '../../../../../locales/i18n';
-import onboardingStyles from '../styles';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../../core/Analytics';
-import { ThemeContext, mockTheme } from '../../../../util/theme';
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-import { useMetrics } from '../../../../components/hooks/useMetrics';
-
-const styles = StyleSheet.create({
- main: {
- flex: 1,
- },
- coachmark: {
- marginHorizontal: 16,
- },
- coachmarkContainer: {
- flex: 1,
- position: 'absolute',
- left: 0,
- right: 0,
- bottom: Device.isIphoneX() ? 80 : Device.isIos() ? 40 : 64,
- },
-});
-interface Step1Props {
- onClose: () => Promise;
-}
-
-const Step1 = ({ onClose }: Step1Props) => {
- const theme = useContext(ThemeContext) || mockTheme;
- const dynamicOnboardingStyles = onboardingStyles(theme.colors);
- const dispatch = useDispatch();
- const { trackEvent, createEventBuilder } = useMetrics();
-
- const content = useCallback(
- () => (
-
-
- {strings('onboarding_wizard_new.step1.content1')}
-
-
- ),
- [dynamicOnboardingStyles],
- );
-
- const onNext = useCallback(() => {
- dispatch(setOnboardingWizardStep?.(2));
-
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STARTED)
- .addProperties({
- tutorial_step_count: 1,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[1],
- })
- .build(),
- );
- }, [dispatch, trackEvent, createEventBuilder]);
-
- return (
-
-
-
-
-
- );
-};
-
-export default Step1;
diff --git a/app/components/UI/OnboardingWizard/Step2/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Step2/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 89324869a17..00000000000
--- a/app/components/UI/OnboardingWizard/Step2/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,278 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Step2 should render correctly 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
- Your accounts
-
-
-
-
-
-
-
- This is your account and unique public address (0x...). Create more accounts by tapping the arrow icon.
-
-
-
-
-
- 1
- /6
-
-
-
-
- Got it
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Step2/index.test.tsx b/app/components/UI/OnboardingWizard/Step2/index.test.tsx
deleted file mode 100644
index c22e76eb6b0..00000000000
--- a/app/components/UI/OnboardingWizard/Step2/index.test.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-import Step2 from './';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-
-const coachmarkRef = {
- yourAccountRef: {
- current: {
- measure: jest.fn(),
- },
- },
-};
-const closeOnboardingWizard = jest.fn();
-describe('Step2', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step2/index.tsx b/app/components/UI/OnboardingWizard/Step2/index.tsx
deleted file mode 100644
index 38d980f3207..00000000000
--- a/app/components/UI/OnboardingWizard/Step2/index.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import React, { useCallback, useEffect, useState } from 'react';
-import { useDispatch } from 'react-redux';
-import { StyleSheet, Text, View } from 'react-native';
-import setOnboardingWizardStep from '../../../../actions/wizard';
-import { strings } from '../../../../../locales/i18n';
-import Coachmark from '../Coachmark';
-
-import onboardingStyles from '../styles';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../../core/Analytics';
-import { useTheme } from '../../../../util/theme';
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-import { useMetrics } from '../../../hooks/useMetrics';
-import useHandleLayout from '../useHandleLayout';
-
-const styles = StyleSheet.create({
- main: {
- flex: 1,
- },
- coachmarkContainer: {
- position: 'absolute',
- left: 0,
- right: 0,
- marginHorizontal: 16,
- },
-});
-
-interface Step2Props {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- coachmarkRef: any;
- onClose: () => Promise;
-}
-
-const Step2 = ({ coachmarkRef, onClose }: Step2Props) => {
- const { colors } = useTheme();
- const { trackEvent, createEventBuilder } = useMetrics();
- const dispatch = useDispatch();
- const { coachmarkTop } = useHandleLayout(coachmarkRef);
-
- const onNext = () => {
- dispatch(setOnboardingWizardStep?.(3));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED)
- .addProperties({
- tutorial_step_count: 2,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2],
- })
- .build(),
- );
- };
-
- const onBack = () => {
- dispatch(setOnboardingWizardStep?.(1));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED)
- .addProperties({
- tutorial_step_count: 2,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2],
- })
- .build(),
- );
- };
-
- const getOnboardingStyles = () => onboardingStyles(colors);
-
- const content = () => {
- const dynamicOnboardingStyles = getOnboardingStyles();
-
- return (
-
-
- {strings('onboarding_wizard_new.step2.content1')}
-
-
- );
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default Step2;
diff --git a/app/components/UI/OnboardingWizard/Step3/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Step3/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index bf1ea7ce48c..00000000000
--- a/app/components/UI/OnboardingWizard/Step3/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,279 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Step3 should render correctly 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
- Managing your account
-
-
-
-
-
-
-
- Edit your account name, view transactions on Etherscan, share your public key, and see your private key by tapping this icon
-
-
-
-
-
- 2
- /6
-
-
-
-
- Got it
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Step3/index.test.tsx b/app/components/UI/OnboardingWizard/Step3/index.test.tsx
deleted file mode 100644
index d7fe82145e5..00000000000
--- a/app/components/UI/OnboardingWizard/Step3/index.test.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-import Step3 from './';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-
-const coachmarkRef = {
- yourAccountRef: {
- current: {
- measure: jest.fn(),
- },
- },
-};
-const closeOnboardingWizard = jest.fn();
-describe('Step3', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step3/index.tsx b/app/components/UI/OnboardingWizard/Step3/index.tsx
deleted file mode 100644
index 254884a0d92..00000000000
--- a/app/components/UI/OnboardingWizard/Step3/index.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import React, { useCallback, useEffect, useState } from 'react';
-import { useDispatch } from 'react-redux';
-import { StyleSheet, Text, View } from 'react-native';
-import Coachmark from '../Coachmark';
-import setOnboardingWizardStep from '../../../../actions/wizard';
-import { strings } from '../../../../../locales/i18n';
-import onboardingStyles from '../styles';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../../core/Analytics';
-import { useTheme } from '../../../../util/theme';
-import { useMetrics } from '../../../hooks/useMetrics';
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-import useHandleLayout from '../useHandleLayout';
-
-const styles = StyleSheet.create({
- main: {
- flex: 1,
- },
- coachmarkContainer: {
- position: 'absolute',
- left: 0,
- right: 0,
- marginHorizontal: 16,
- },
-});
-
-interface Step3Props {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- coachmarkRef: any;
- onClose: () => Promise;
-}
-
-const Step3 = ({ coachmarkRef, onClose }: Step3Props) => {
- const { colors } = useTheme();
- const { trackEvent, createEventBuilder } = useMetrics();
- const dispatch = useDispatch();
- const { coachmarkTop } = useHandleLayout(coachmarkRef);
-
- const onNext = () => {
- dispatch(setOnboardingWizardStep?.(4));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED)
- .addProperties({
- tutorial_step_count: 3,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3],
- })
- .build(),
- );
- };
-
- const onBack = () => {
- dispatch(setOnboardingWizardStep?.(2));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED)
- .addProperties({
- tutorial_step_count: 3,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[3],
- })
- .build(),
- );
- };
-
- const getOnboardingStyles = () => onboardingStyles(colors);
-
- const content = () => {
- const dynamicOnboardingStyles = getOnboardingStyles();
-
- return (
-
-
- {strings('onboarding_wizard_new.step3.content1')}
-
-
- );
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default Step3;
diff --git a/app/components/UI/OnboardingWizard/Step4/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Step4/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 7ce434a09a3..00000000000
--- a/app/components/UI/OnboardingWizard/Step4/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,279 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Step4 should render correctly 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
- Notifications
-
-
-
-
-
-
-
- Stay in the loop on what's happening in your wallet
-
-
-
-
-
- 3
- /6
-
-
-
-
- Got it
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Step4/index.test.tsx b/app/components/UI/OnboardingWizard/Step4/index.test.tsx
deleted file mode 100644
index 9ca06cfc7e9..00000000000
--- a/app/components/UI/OnboardingWizard/Step4/index.test.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import Step4 from './';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-
-const closeOnboardingWizard = jest.fn();
-describe('Step4', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step4/index.tsx b/app/components/UI/OnboardingWizard/Step4/index.tsx
deleted file mode 100644
index 3b61ed84cc4..00000000000
--- a/app/components/UI/OnboardingWizard/Step4/index.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useDispatch } from 'react-redux';
-import { StyleSheet, Text, View } from 'react-native';
-import setOnboardingWizardStep from '../../../../actions/wizard';
-import { strings } from '../../../../../locales/i18n';
-import Coachmark from '../Coachmark';
-
-import Device from '../../../../util/device';
-
-import onboardingStyles from '../styles';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../../core/Analytics';
-import { useTheme } from '../../../../util/theme';
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-
-import { useMetrics } from '../../../hooks/useMetrics';
-
-const styles = StyleSheet.create({
- main: {
- flex: 1,
- },
- coachmarkContainer: {
- position: 'absolute',
- left: 0,
- right: 0,
- marginHorizontal: 16,
- },
-});
-
-interface Step4Props {
- onClose: () => Promise;
-}
-
-const Step4 = ({ onClose }: Step4Props) => {
- const { colors } = useTheme();
- const { trackEvent, createEventBuilder } = useMetrics();
- const dispatch = useDispatch();
- const [coachmarkTop, setCoachmarkTop] = useState(0);
-
- const handleLayout = useCallback(() => {
- const top = Device.isIphoneX() ? 82 : Device.isIos() ? 64 : 60;
- setCoachmarkTop(top);
- }, []);
-
- useEffect(() => {
- handleLayout();
- }, [handleLayout]);
-
- const onNext = () => {
- dispatch(setOnboardingWizardStep?.(5));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED)
- .addProperties({
- tutorial_step_count: 4,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4],
- })
- .build(),
- );
- };
-
- const onBack = () => {
- dispatch(setOnboardingWizardStep?.(3));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED)
- .addProperties({
- tutorial_step_count: 4,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[4],
- })
- .build(),
- );
- };
-
- const getOnboardingStyles = () => onboardingStyles(colors);
-
- const content = () => {
- const dynamicOnboardingStyles = getOnboardingStyles();
-
- return (
-
-
- {strings('onboarding_wizard_new.step4.content1')}
-
-
- );
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default Step4;
diff --git a/app/components/UI/OnboardingWizard/Step5/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Step5/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index f4830a964d7..00000000000
--- a/app/components/UI/OnboardingWizard/Step5/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,276 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Step5 should render correctly 1`] = `
-
-
-
-
-
-
-
-
-
- Using your wallet
-
-
-
-
-
-
-
- Buy, send, swap, and receive assets by tapping this icon
-
-
-
-
-
- 4
- /6
-
-
-
-
- Got it
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Step5/index.test.tsx b/app/components/UI/OnboardingWizard/Step5/index.test.tsx
deleted file mode 100644
index 862d1684371..00000000000
--- a/app/components/UI/OnboardingWizard/Step5/index.test.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import Step5 from './';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-
-const coachmarkRef = {
- yourAccountRef: {
- current: {
- measure: jest.fn(),
- },
- },
-};
-
-const closeOnboardingWizard = jest.fn();
-describe('Step5', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step5/index.tsx b/app/components/UI/OnboardingWizard/Step5/index.tsx
deleted file mode 100644
index df08abd3209..00000000000
--- a/app/components/UI/OnboardingWizard/Step5/index.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { useDispatch } from 'react-redux';
-import { StyleSheet, Text, View } from 'react-native';
-import Coachmark from '../Coachmark';
-import setOnboardingWizardStep from '../../../../actions/wizard';
-import { strings } from '../../../../../locales/i18n';
-import onboardingStyles from '../styles';
-import Device from '../../../../util/device';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../../core/Analytics';
-import { useTheme } from '../../../../util/theme';
-
-import { useMetrics } from '../../../hooks/useMetrics';
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-
-const styles = StyleSheet.create({
- main: {
- flex: 1,
- },
- coachmarkContainer: {
- position: 'absolute',
- left: 0,
- right: 0,
- marginHorizontal: 16,
- bottom: Device.isIphoneX() ? 80 : Device.isIos() ? 40 : 64,
- },
-});
-
-interface Step5Props {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- coachmarkRef: any;
- onClose: () => Promise;
-}
-
-const Step5 = ({ onClose }: Step5Props) => {
- const { trackEvent, createEventBuilder } = useMetrics();
- const { colors } = useTheme();
- const dispatch = useDispatch();
- const dynamicOnboardingStyles = onboardingStyles(colors);
-
- /**
- * Dispatches 'setOnboardingWizardStep' with next step
- */
- const onNext = () => {
- dispatch(setOnboardingWizardStep?.(6));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED)
- .addProperties({
- tutorial_step_count: 5,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5],
- })
- .build(),
- );
- };
-
- /**
- * Dispatches 'setOnboardingWizardStep' with back step
- */
- const onBack = () => {
- dispatch(setOnboardingWizardStep?.(4));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED)
- .addProperties({
- tutorial_step_count: 5,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[5],
- })
- .build(),
- );
- };
-
- /**
- * Returns content for this step
- */
- const content = () => (
-
-
- {strings('onboarding_wizard_new.step5.content1')}
-
-
- );
-
- return (
-
-
-
-
-
- );
-};
-
-export default Step5;
diff --git a/app/components/UI/OnboardingWizard/Step6/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Step6/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index b1d5a6b11f7..00000000000
--- a/app/components/UI/OnboardingWizard/Step6/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,276 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Step6 should render correctly 1`] = `
-
-
-
-
-
-
-
-
-
- Exploring web3
-
-
-
-
-
-
-
- Open the browser by tapping this icon
-
-
-
-
-
- 5
- /6
-
-
-
-
- Got it
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Step6/index.test.tsx b/app/components/UI/OnboardingWizard/Step6/index.test.tsx
deleted file mode 100644
index b3e50ccecb4..00000000000
--- a/app/components/UI/OnboardingWizard/Step6/index.test.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import Step6 from './';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-
-const navigationMock = {
- navigate: jest.fn(),
-};
-
-const closeOnboardingWizard = jest.fn();
-describe('Step6', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step6/index.tsx b/app/components/UI/OnboardingWizard/Step6/index.tsx
deleted file mode 100644
index 944dca5bc90..00000000000
--- a/app/components/UI/OnboardingWizard/Step6/index.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import React from 'react';
-import { useDispatch } from 'react-redux';
-import { StyleSheet, Text, View } from 'react-native';
-import Device from '../../../../util/device';
-import Coachmark from '../Coachmark';
-import setOnboardingWizardStep from '../../../../actions/wizard';
-import { strings } from '../../../../../locales/i18n';
-import { createBrowserNavDetails } from '../../../Views/Browser';
-
-import onboardingStyles from '../styles';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../../core/Analytics';
-import { useTheme } from '../../../../util/theme';
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-import { useMetrics } from '../../../hooks/useMetrics';
-
-const styles = StyleSheet.create({
- main: {
- flex: 1,
- },
- coachmarkContainer: {
- position: 'absolute',
- alignSelf: 'center',
- left: 0,
- right: 0,
- marginHorizontal: 16,
- bottom: Device.isIphoneX() ? 80 : Device.isIos() ? 40 : 64,
- },
-});
-
-interface Step6Props {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- navigation: any;
- onClose: () => Promise;
-}
-
-const Step6 = ({ onClose, navigation }: Step6Props) => {
- const { trackEvent, createEventBuilder } = useMetrics();
- const { colors } = useTheme();
- const dynamicOnboardingStyles = onboardingStyles(colors);
- const dispatch = useDispatch();
- /**
- * Dispatches 'setOnboardingWizardStep' with next step
- */
- const onNext = () => {
- dispatch(setOnboardingWizardStep?.(7));
- navigation?.navigate(...createBrowserNavDetails());
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED)
- .addProperties({
- tutorial_step_count: 6,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[6],
- })
- .build(),
- );
- };
-
- /**
- * Dispatches 'setOnboardingWizardStep' with next step
- */
- const onBack = () => {
- dispatch(setOnboardingWizardStep?.(5));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED)
- .addProperties({
- tutorial_step_count: 6,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[6],
- })
- .build(),
- );
- };
-
- /**
- * Returns content for this step
- */
- const content = () => (
-
-
- {strings('onboarding_wizard_new.step6.content1')}
-
-
- );
-
- return (
-
-
-
-
-
- );
-};
-
-export default Step6;
diff --git a/app/components/UI/OnboardingWizard/Step7/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/Step7/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 181370f3539..00000000000
--- a/app/components/UI/OnboardingWizard/Step7/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,278 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Step7 should render correctly 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
- Using the browser
-
-
-
-
-
-
-
- Search for sites by keyword or enter a URL. Have fun out there!
-
-
-
-
-
- 6
- /6
-
-
-
-
- Got it
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/Step7/index.test.tsx b/app/components/UI/OnboardingWizard/Step7/index.test.tsx
deleted file mode 100644
index b810d434c16..00000000000
--- a/app/components/UI/OnboardingWizard/Step7/index.test.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import Step7 from './';
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-
-const navigationMock = {
- navigate: jest.fn(),
-};
-
-const closeOnboardingWizard = jest.fn();
-describe('Step7', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/Step7/index.tsx b/app/components/UI/OnboardingWizard/Step7/index.tsx
deleted file mode 100644
index c71c6414ea8..00000000000
--- a/app/components/UI/OnboardingWizard/Step7/index.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { useDispatch } from 'react-redux';
-import { StyleSheet, Text, View } from 'react-native';
-import Coachmark from '../Coachmark';
-import setOnboardingWizardStep from '../../../../actions/wizard';
-import { strings } from '../../../../../locales/i18n';
-import onboardingStyles from '../styles';
-import Routes from '../../../../constants/navigation/Routes';
-
-import Device from '../../../../util/device';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../../core/Analytics';
-import { useTheme } from '../../../../util/theme';
-
-import { OnboardingWizardModalSelectorsIDs } from '../../../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-import { useMetrics } from '../../../hooks/useMetrics';
-
-const styles = StyleSheet.create({
- main: {
- flex: 1,
- },
- coachmarkContainer: {
- position: 'absolute',
- left: 0,
- right: 0,
- marginHorizontal: 16,
- },
-});
-
-interface Step7Props {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- navigation: any;
- onClose: (arg0: boolean) => void;
-}
-
-const Step7 = ({ navigation, onClose }: Step7Props) => {
- const { trackEvent, createEventBuilder } = useMetrics();
- const dispatch = useDispatch();
- const [ready, setReady] = useState(false);
- const [coachmarkTop, setCoachmarkTop] = useState(0);
- const { colors } = useTheme();
- const dynamicOnboardingStyles = onboardingStyles(colors);
-
- /**
- * If component ref defined, calculate its position and position coachmark accordingly
- */
- const getPosition = () => {
- const position = Device.isAndroid() ? 280 : Device.isIphoneX() ? 320 : 280;
- setCoachmarkTop(position);
- setReady(true);
- };
-
- useEffect(() => {
- getPosition();
- }, []);
-
- /**
- * Dispatches 'setOnboardingWizardStep' with back step
- */
- const onBack = () => {
- navigation?.navigate?.(Routes.WALLET.HOME);
- setTimeout(() => {
- dispatch(setOnboardingWizardStep?.(6));
- }, 1);
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED)
- .addProperties({
- tutorial_step_count: 7,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[7],
- })
- .build(),
- );
- };
-
- /**
- * Returns content for this step
- */
- const content = () => (
-
-
- {strings('onboarding_wizard_new.step7.content1')}
-
-
- );
-
- if (!ready) return null;
-
- return (
-
-
-
-
-
- );
-};
-
-export default Step7;
diff --git a/app/components/UI/OnboardingWizard/__snapshots__/index.test.tsx.snap b/app/components/UI/OnboardingWizard/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 4561f01d8f7..00000000000
--- a/app/components/UI/OnboardingWizard/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,120 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`OnboardingWizard should render correctly 1`] = `
-
-
-
-
-
-
-
-
-`;
diff --git a/app/components/UI/OnboardingWizard/index.test.tsx b/app/components/UI/OnboardingWizard/index.test.tsx
deleted file mode 100644
index c6c2dc98f82..00000000000
--- a/app/components/UI/OnboardingWizard/index.test.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-
-import OnboardingWizard from './';
-import renderWithProvider from '../../../util/test/renderWithProvider';
-
-const navigationMock = {
- navigate: jest.fn(),
-};
-
-const coachmarkRef = {
- current: {
- measure: jest.fn(),
- },
-};
-
-describe('OnboardingWizard', () => {
- it('should render correctly', () => {
- const { toJSON } = renderWithProvider(
- ,
- );
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/OnboardingWizard/index.tsx b/app/components/UI/OnboardingWizard/index.tsx
deleted file mode 100644
index 3de6481301b..00000000000
--- a/app/components/UI/OnboardingWizard/index.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import React from 'react';
-import { View, StyleSheet, TextStyle } from 'react-native';
-import { useDispatch, useSelector } from 'react-redux';
-import Modal from 'react-native-modal';
-import type { Theme } from '@metamask/design-tokens';
-import { colors as importedColors } from '../../../styles/common';
-import {
- getFontFamily,
- TextVariant,
-} from '../../../component-library/components/Texts/Text';
-
-import Step1 from './Step1';
-import Step2 from './Step2';
-import Step3 from './Step3';
-import Step4 from './Step4';
-import Step5 from './Step5';
-import Step6 from './Step6';
-import Step7 from './Step7';
-import setOnboardingWizardStep from '../../../actions/wizard';
-import Routes from '../../../constants/navigation/Routes';
-import { ONBOARDING_WIZARD, EXPLORED } from '../../../constants/storage';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
-} from '../../../core/Analytics';
-import { useTheme } from '../../../util/theme';
-import Device from '../../../util/device';
-import StorageWrapper from '../../../store/storage-wrapper';
-import { isTest } from '../../../util/test/utils';
-import { useMetrics } from '../../hooks/useMetrics';
-import { RootState } from '../../../reducers';
-
-const createStyles = ({ colors, typography }: Theme) =>
- StyleSheet.create({
- root: {
- top: 0,
- bottom: 0,
- left: 0,
- right: 0,
- flex: 1,
- margin: 0,
- position: 'absolute',
- backgroundColor: importedColors.transparent,
- },
- main: {
- flex: 1,
- backgroundColor: importedColors.transparent,
- },
- smallSkipWrapper: {
- alignItems: 'center',
- alignSelf: 'center',
- bottom: Device.isIos() ? 25 : 30,
- },
- largeSkipWrapper: {
- alignItems: 'center',
- alignSelf: 'center',
- bottom: Device.isIos() && Device.isIphoneX() ? 93 : 61,
- },
- skipButtonContainer: {
- height: 30,
- width: 120,
- borderRadius: 15,
- backgroundColor: colors.background.default,
- },
- skipButton: {
- backgroundColor: colors.background.default,
- alignItems: 'center',
- justifyContent: 'center',
- },
- skipText: {
- ...typography.sBodyMD,
- fontFamily: getFontFamily(TextVariant.BodyMD),
- color: colors.primary.default,
- } as TextStyle,
- });
-
-interface OnboardingWizardProps {
- // TODO: Replace "any" with type
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- navigation: any;
- coachmarkRef?: React.RefObject | null;
-}
-
-const OnboardingWizard = ({
- navigation,
- coachmarkRef,
-}: OnboardingWizardProps) => {
- const theme = useTheme();
- const dispatch = useDispatch();
- const { trackEvent, createEventBuilder } = useMetrics();
- const styles = createStyles(theme);
-
- const { step } = useSelector((state: RootState) => state.wizard);
-
- /**
- * Close onboarding wizard setting step to 0 and closing drawer
- */
- const closeOnboardingWizard = async () => {
- await StorageWrapper.setItem(ONBOARDING_WIZARD, EXPLORED);
- dispatch(setOnboardingWizardStep(0));
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_SKIPPED)
- .addProperties({
- tutorial_step_count: step,
- tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[step],
- })
- .build(),
- );
- trackEvent(
- createEventBuilder(MetaMetricsEvents.ONBOARDING_TOUR_COMPLETED).build(),
- );
- };
-
- // Since react-native-default-preference is not covered by the fixtures,
- // when isTest is `true`, if the ONBOARDING_WIZARD is marked as 'explored',
- // it indicates that it was provided by fixtures, triggering the call to closeOnboardingWizard().
- if (isTest && step === 1) {
- const inTestCloseOnboardingWizard = async () => {
- const wizardStep = await StorageWrapper.getItem(ONBOARDING_WIZARD);
- if (wizardStep === EXPLORED) {
- await closeOnboardingWizard();
- }
- };
- inTestCloseOnboardingWizard();
- }
-
- const onboardingWizardNavigator = (s: number) => {
- const steps: Record = {
- 1: ,
- 2: ,
- 3: ,
- 4: ,
- 5: ,
- 6: ,
- 7: ,
- };
- return steps[s];
- };
-
- const getBackButtonBehavior = () => {
- if (step === 1) {
- return closeOnboardingWizard();
- } else if (step === 6) {
- dispatch(setOnboardingWizardStep(5));
- navigation.navigate(Routes.WALLET.HOME);
- } else if (step === 7) {
- dispatch(setOnboardingWizardStep(6));
- }
- return setOnboardingWizardStep(step - 1);
- };
-
- return (
-
- {onboardingWizardNavigator(step)}
-
- );
-};
-
-export default OnboardingWizard;
diff --git a/app/components/UI/OnboardingWizard/styles.ts b/app/components/UI/OnboardingWizard/styles.ts
deleted file mode 100644
index df52b846a32..00000000000
--- a/app/components/UI/OnboardingWizard/styles.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { StyleSheet } from 'react-native';
-import { fontStyles } from '../../../styles/common';
-import Device from '../../../util/device';
-
-const SMALL_DEVICE = Device.isSmallDevice();
-
-// TODO: Replace "any" with type
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export default (colors: any) =>
- StyleSheet.create({
- container: {
- flex: 1,
- },
- welcome: {
- fontSize: 20,
- },
- content: {
- ...fontStyles.normal,
- color: colors.primary.inverse,
- fontSize: 14,
- textAlign: 'left',
- marginBottom: SMALL_DEVICE ? 5 : 20,
- },
- contentContainer: {
- marginTop: 20,
- },
- coachmarkLeft: {
- marginLeft: SMALL_DEVICE ? 5 : 10,
- marginRight: SMALL_DEVICE ? 45 : 85,
- },
- });
diff --git a/app/components/UI/OnboardingWizard/useHandleLayout.tsx b/app/components/UI/OnboardingWizard/useHandleLayout.tsx
deleted file mode 100644
index 688c0f5d7a1..00000000000
--- a/app/components/UI/OnboardingWizard/useHandleLayout.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useCallback, useEffect, useState } from 'react';
-
-// TODO: Replace "any" with type
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const useHandleLayout = (coachmarkRef: any) => {
- const [coachmarkTop, setCoachmarkTop] = useState(0);
-
- const handleLayout = useCallback(() => {
- const yourAccRef = coachmarkRef.yourAccountRef?.current;
- if (!yourAccRef) return;
-
- yourAccRef.measure(
- (
- _accActionsFx: number,
- _accActionsFy: number,
- _accActionsWidth: number,
- accActionsHeight: number,
- _accActionsPageX: number,
- accActionsPageY: number,
- ) => {
- const top = accActionsHeight + accActionsPageY;
- setCoachmarkTop(top);
- },
- );
- }, [coachmarkRef.yourAccountRef]);
-
- useEffect(() => {
- handleLayout();
- }, [handleLayout]);
-
- return {
- coachmarkTop,
- };
-};
-
-export default useHandleLayout;
diff --git a/app/components/UI/OptinMetrics/index.js b/app/components/UI/OptinMetrics/index.js
index 5912bfa2dc9..4201f72413d 100644
--- a/app/components/UI/OptinMetrics/index.js
+++ b/app/components/UI/OptinMetrics/index.js
@@ -13,15 +13,10 @@ import {
import PropTypes from 'prop-types';
import { baseStyles, fontStyles } from '../../../styles/common';
import { strings } from '../../../../locales/i18n';
-import setOnboardingWizardStep from '../../../actions/wizard';
import { connect } from 'react-redux';
import { clearOnboardingEvents } from '../../../actions/onboarding';
import { setDataCollectionForMarketing } from '../../../actions/security';
-import {
- ONBOARDING_WIZARD,
- OPTIN_META_METRICS_UI_SEEN,
- TRUE,
-} from '../../../constants/storage';
+import { OPTIN_META_METRICS_UI_SEEN, TRUE } from '../../../constants/storage';
import AppConstants from '../../../core/AppConstants';
import {
MetaMetricsEvents,
@@ -135,10 +130,6 @@ class OptinMetrics extends PureComponent {
/* navigation object required to push and pop other views
*/
navigation: PropTypes.object,
- /**
- * Action to set onboarding wizard step
- */
- setOnboardingWizardStep: PropTypes.func,
/**
* Onboarding events array created in previous onboarding views
*/
@@ -247,18 +238,9 @@ class OptinMetrics extends PureComponent {
return onContinue();
}
- // Get onboarding wizard state
- const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD);
- if (onboardingWizard) {
- this.props.navigation.reset({
- routes: [{ name: Routes.ONBOARDING.HOME_NAV }],
- });
- } else {
- this.props.setOnboardingWizardStep(1);
- this.props.navigation.reset({
- routes: [{ name: Routes.ONBOARDING.HOME_NAV }],
- });
- }
+ this.props.navigation.reset({
+ routes: [{ name: Routes.ONBOARDING.HOME_NAV }],
+ });
};
/**
@@ -680,7 +662,6 @@ const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
- setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)),
clearOnboardingEvents: () => dispatch(clearOnboardingEvents()),
setDataCollectionForMarketing: (value) =>
dispatch(setDataCollectionForMarketing(value)),
diff --git a/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx b/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx
index 05170c9afc3..c6966a1fac5 100644
--- a/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx
+++ b/app/components/UI/PermissionsSummary/PermissionsSummary.test.tsx
@@ -58,9 +58,6 @@ const DEFAULT_PERMISSIONS_SUMMARY_PROPS = {
};
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState: {
...backgroundState,
diff --git a/app/components/UI/ProfilerManager/ProfilerManager.test.tsx b/app/components/UI/ProfilerManager/ProfilerManager.test.tsx
new file mode 100644
index 00000000000..656d4178b6f
--- /dev/null
+++ b/app/components/UI/ProfilerManager/ProfilerManager.test.tsx
@@ -0,0 +1,159 @@
+import React from 'react';
+import { render, fireEvent, act } from '@testing-library/react-native';
+import { Platform, Share } from 'react-native';
+import RNFS from 'react-native-fs';
+import { startProfiling, stopProfiling } from 'react-native-release-profiler';
+import ShakeDetector from './ShakeDetector';
+import ProfilerManager from './ProfilerManager';
+
+jest.mock('react-native-device-info', () => ({
+ getBundleId: jest.fn(),
+ getVersion: jest.fn(),
+}));
+
+jest.mock('react-native-release-profiler', () => ({
+ startProfiling: jest.fn(),
+ stopProfiling: jest.fn(),
+}));
+
+jest.mock('./ShakeDetector', () => jest.fn(() => null));
+
+// Provide a local mock for RNFS so we can control paths and behaviors in tests
+jest.mock('react-native-fs', () => ({
+ DocumentDirectoryPath: '/documents',
+ exists: jest.fn(),
+ copyFile: jest.fn(),
+}));
+
+describe('ProfilerManager', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('environment-based enabling', () => {
+ it('enables profiler when enabled prop is true', () => {
+ render();
+ expect(ShakeDetector).toHaveBeenCalled();
+ });
+
+ it('disables profiler when enabled prop is false', () => {
+ const { toJSON } = render();
+ expect(toJSON()).toBeNull();
+ expect(ShakeDetector).not.toHaveBeenCalled();
+ });
+
+ it('disables profiler when enabled prop is undefined and no environment set', () => {
+ const { toJSON } = render();
+ expect(toJSON()).toBeNull();
+ expect(ShakeDetector).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('forced enabling via props', () => {
+ it('enables profiler when forced via props regardless of environment', () => {
+ render();
+ expect(ShakeDetector).toHaveBeenCalled();
+ });
+
+ it('disables profiler when explicitly disabled via props', () => {
+ const { toJSON } = render();
+ expect(toJSON()).toBeNull();
+ expect(ShakeDetector).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('UI interactions and profiling flow', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('toggles visibility on shake and can be closed via the close button on iOS', async () => {
+ Platform.OS = 'ios';
+ const { queryByText, getByText, getByTestId } = render(
+ ,
+ );
+ expect(queryByText('Performance Profiler')).toBeNull();
+ // Trigger shake
+ const firstCallProps = (ShakeDetector as jest.Mock).mock.calls[0][0];
+ act(() => {
+ firstCallProps.onShake();
+ });
+ expect(getByText('Performance Profiler')).toBeTruthy();
+ expect(getByText('Shake device to toggle this menu.')).toBeTruthy();
+ expect(
+ queryByText(
+ 'You can find the profiling file in the Android Downloads folder.',
+ ),
+ ).toBeFalsy();
+ expect(getByText('Start')).toBeTruthy();
+ expect(getByText('Export')).toBeTruthy();
+ // Close via "x"
+ fireEvent.press(getByTestId('close-profiler-button'));
+ expect(queryByText('Performance Profiler')).toBeNull();
+ });
+
+ it('toggles visibility on shake and calls exportTrace when Export is pressed', async () => {
+ Platform.OS = 'ios';
+ const { queryByText, getByText } = render();
+ expect(queryByText('Performance Profiler')).toBeNull();
+
+ const firstCallProps = (ShakeDetector as jest.Mock).mock.calls[0][0];
+ act(() => {
+ firstCallProps.onShake();
+ });
+
+ const mockProfilePath = '/tmp/mock-profile.cpuprofile';
+ (startProfiling as jest.Mock).mockResolvedValue(undefined);
+ (stopProfiling as jest.Mock).mockResolvedValue(mockProfilePath);
+ (RNFS.exists as jest.Mock).mockResolvedValue(true);
+ (RNFS.copyFile as jest.Mock).mockResolvedValue(undefined);
+ jest.spyOn(Share, 'share').mockResolvedValue({
+ action: Share.sharedAction,
+ });
+
+ await act(async () => {
+ fireEvent.press(getByText('Start'));
+ });
+ await act(async () => {
+ fireEvent.press(getByText('Stop'));
+ });
+
+ await act(async () => {
+ fireEvent.press(getByText('Export'));
+ });
+
+ expect(RNFS.exists).toHaveBeenCalledWith(mockProfilePath);
+ expect(RNFS.copyFile).toHaveBeenCalledWith(
+ mockProfilePath,
+ expect.stringMatching(/^\/documents\/.+\.cpuprofile$/),
+ );
+ expect(Share.share).toHaveBeenCalledWith({
+ url: expect.stringMatching(/^file:\/\/\/documents\/.+\.cpuprofile$/),
+ });
+ });
+
+ it('toggles visibility on shake and can be closed via the close button on Android', async () => {
+ Platform.OS = 'android';
+ const { queryByText, getByText, getByTestId } = render(
+ ,
+ );
+ expect(queryByText('Performance Profiler')).toBeNull();
+ // Trigger shake
+ const firstCallProps = (ShakeDetector as jest.Mock).mock.calls[0][0];
+ act(() => {
+ firstCallProps.onShake();
+ });
+ expect(getByText('Performance Profiler')).toBeTruthy();
+ expect(
+ getByText(
+ 'Shake device to toggle this menu. You can find the profiling file in the Android Downloads folder.',
+ ),
+ ).toBeTruthy();
+ expect(getByText('Start')).toBeTruthy();
+ expect(queryByText('Export')).toBeFalsy();
+ // Close via "x"
+ fireEvent.press(getByTestId('close-profiler-button'));
+ expect(queryByText('Performance Profiler')).toBeNull();
+ });
+ });
+});
diff --git a/app/components/UI/ProfilerManager/ProfilerManager.tsx b/app/components/UI/ProfilerManager/ProfilerManager.tsx
new file mode 100644
index 00000000000..2777001f9b4
--- /dev/null
+++ b/app/components/UI/ProfilerManager/ProfilerManager.tsx
@@ -0,0 +1,188 @@
+import React, { useState, useCallback } from 'react';
+import { Platform, Pressable, Share } from 'react-native';
+import { getBundleId, getVersion } from 'react-native-device-info';
+import ShakeDetector from './ShakeDetector';
+import { Box, Text, TextVariant } from '@metamask/design-system-react-native';
+import { useTailwind } from '@metamask/design-system-twrnc-preset';
+import { startProfiling, stopProfiling } from 'react-native-release-profiler';
+import RNFS from 'react-native-fs';
+import ButtonIcon from '../../../component-library/components/Buttons/ButtonIcon';
+import {
+ IconName,
+ IconColor,
+} from '../../../component-library/components/Icons/Icon';
+import { isRc } from '../../../util/test/utils';
+
+interface ProfilerManagerProps {
+ enabled?: boolean;
+}
+
+const ProfilerManager: React.FC = ({
+ enabled = isRc,
+}) => {
+ const [isVisible, setIsVisible] = useState(false);
+ const [isRecording, setIsRecording] = useState(false);
+ const [sessionId, setSessionId] = useState(null);
+ const [lastProfilePath, setLastProfilePath] = useState(null);
+ const appId = getBundleId();
+ const tw = useTailwind();
+
+ const handleShake = useCallback(() => {
+ setIsVisible((prev) => !prev);
+ }, []);
+
+ const startProfiler = useCallback(async () => {
+ try {
+ const appVersion = getVersion();
+ const timestamp = Date.now();
+ const newSessionId = `${appId}_v${appVersion}_${timestamp}`;
+
+ await startProfiling();
+ setIsRecording(true);
+ setSessionId(newSessionId);
+ } catch (error) {
+ // fail silently
+ }
+ }, [appId]);
+
+ const stopProfiler = useCallback(async () => {
+ if (!sessionId) return;
+
+ try {
+ const path = await stopProfiling(true);
+ if (typeof path === 'string' && path.length > 0) {
+ setLastProfilePath(path);
+ }
+ } catch (error) {
+ // fail silently
+ }
+ setIsRecording(false);
+ setSessionId(null);
+ }, [sessionId]);
+
+ // For iOS only. We can find the file in the Downloads folder on Android.
+ const exportTrace = useCallback(async () => {
+ if (!lastProfilePath) return;
+ try {
+ const exists = await RNFS.exists(lastProfilePath);
+ if (!exists) return;
+
+ const appVersion = getVersion();
+ const timestamp = Date.now();
+ const fileName = `${appId}_v${appVersion}_${timestamp}.cpuprofile`;
+ const destPath = `${RNFS.DocumentDirectoryPath}/${fileName}`;
+
+ await RNFS.copyFile(lastProfilePath, destPath);
+
+ await Share.share({ url: `file://${destPath}` });
+ } catch (e) {
+ // fail silently
+ }
+ }, [lastProfilePath, appId]);
+
+ const toggleProfiling = useCallback(() => {
+ if (isRecording) {
+ stopProfiler();
+ } else {
+ startProfiler();
+ }
+ }, [isRecording, startProfiler, stopProfiler]);
+
+ const hideProfiler = useCallback(() => {
+ setIsVisible(false);
+ }, []);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+ <>
+
+ {isVisible && (
+
+
+
+ Performance Profiler
+
+
+
+
+
+
+ {isRecording ? 'Recording...' : 'Stopped'}
+
+
+
+
+ Build Type: {process.env.METAMASK_BUILD_TYPE || 'undefined'}
+
+
+ Environment: {process.env.METAMASK_ENVIRONMENT || 'undefined'}
+
+
+
+
+
+ {isRecording ? 'Stop' : 'Start'}
+
+
+ {Platform.OS === 'ios' && (
+
+ tw.style(
+ 'flex-1 p-2 rounded-md items-center justify-center',
+ isRecording || !lastProfilePath
+ ? 'bg-muted'
+ : pressed
+ ? 'bg-pressed'
+ : 'bg-primary-default',
+ )
+ }
+ onPress={exportTrace}
+ >
+
+ Export
+
+
+ )}
+
+
+
+
+ Shake device to toggle this menu.{' '}
+ {Platform.OS === 'android' &&
+ 'You can find the profiling file in the Android Downloads folder.'}
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default ProfilerManager;
diff --git a/app/components/UI/ProfilerManager/ShakeDetector.test.tsx b/app/components/UI/ProfilerManager/ShakeDetector.test.tsx
new file mode 100644
index 00000000000..fc0b298a81d
--- /dev/null
+++ b/app/components/UI/ProfilerManager/ShakeDetector.test.tsx
@@ -0,0 +1,91 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import { Accelerometer, type AccelerometerMeasurement } from 'expo-sensors';
+import ShakeDetector from './ShakeDetector';
+
+jest.mock('expo-sensors', () => ({
+ Accelerometer: {
+ setUpdateInterval: jest.fn(),
+ addListener: jest.fn(() => ({ remove: jest.fn() })),
+ },
+}));
+
+const mockAccelerometer = Accelerometer as jest.Mocked;
+
+describe('ShakeDetector', () => {
+ const mockOnShake = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('renders without crashing', () => {
+ const { toJSON } = render(
+ ,
+ );
+ expect(toJSON()).toBeNull();
+ });
+
+ it('configures accelerometer with correct update interval', () => {
+ render();
+ expect(mockAccelerometer.setUpdateInterval).toHaveBeenCalledWith(150);
+ });
+
+ it('adds accelerometer listener on mount', () => {
+ render();
+ expect(mockAccelerometer.addListener).toHaveBeenCalledTimes(1);
+ expect(mockAccelerometer.addListener).toHaveBeenCalledWith(
+ expect.any(Function),
+ );
+ });
+
+ it('removes only its own accelerometer listener on unmount', () => {
+ const { unmount } = render(
+ ,
+ );
+ const subscription = (mockAccelerometer.addListener as jest.Mock).mock
+ .results[0].value;
+ unmount();
+ expect(subscription.remove).toHaveBeenCalledTimes(1);
+ });
+
+ describe('shake detection', () => {
+ let accelerometerCallback: (data: AccelerometerMeasurement) => void;
+
+ beforeEach(() => {
+ render();
+ const addListenerCall = mockAccelerometer.addListener.mock.calls[0];
+ accelerometerCallback = addListenerCall[0];
+ });
+
+ it('triggers shake when acceleration exceeds sensibility threshold', () => {
+ accelerometerCallback({ x: 2, y: 2, z: 2, timestamp: Date.now() });
+ expect(mockOnShake).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not trigger shake when acceleration is below threshold', () => {
+ accelerometerCallback({ x: 0.5, y: 0.5, z: 0.5, timestamp: Date.now() });
+ expect(mockOnShake).not.toHaveBeenCalled();
+ });
+
+ it('debounces shake events within 1 second', () => {
+ accelerometerCallback({ x: 3, y: 3, z: 3, timestamp: Date.now() });
+ expect(mockOnShake).toHaveBeenCalledTimes(1);
+ accelerometerCallback({ x: 3, y: 3, z: 3, timestamp: Date.now() });
+ expect(mockOnShake).toHaveBeenCalledTimes(1);
+ });
+
+ it('allows shake after debounce period expires', () => {
+ accelerometerCallback({ x: 3, y: 3, z: 3, timestamp: Date.now() });
+ expect(mockOnShake).toHaveBeenCalledTimes(1);
+ jest.advanceTimersByTime(1001);
+ accelerometerCallback({ x: 3, y: 3, z: 3, timestamp: Date.now() });
+ expect(mockOnShake).toHaveBeenCalledTimes(2);
+ });
+ });
+});
diff --git a/app/components/UI/ProfilerManager/ShakeDetector.tsx b/app/components/UI/ProfilerManager/ShakeDetector.tsx
new file mode 100644
index 00000000000..169f1f97bee
--- /dev/null
+++ b/app/components/UI/ProfilerManager/ShakeDetector.tsx
@@ -0,0 +1,39 @@
+import { useEffect, useRef } from 'react';
+import { Accelerometer } from 'expo-sensors';
+
+interface ShakeDetectorProps {
+ onShake: () => void;
+ sensibility?: number;
+}
+
+const ShakeDetector: React.FC = ({
+ onShake,
+ sensibility = 1.8,
+}) => {
+ const lastShakeTime = useRef(0);
+
+ useEffect(() => {
+ Accelerometer.setUpdateInterval(150);
+
+ const onUpdate = ({ x, y, z }: { x: number; y: number; z: number }) => {
+ const acceleration = Math.sqrt(x * x + y * y + z * z);
+ if (acceleration >= sensibility) {
+ const now = Date.now();
+ if (now - lastShakeTime.current > 1000) {
+ lastShakeTime.current = now;
+ onShake();
+ }
+ }
+ };
+
+ const subscription = Accelerometer.addListener(onUpdate);
+
+ return () => {
+ subscription?.remove();
+ };
+ }, [onShake, sensibility]);
+
+ return null;
+};
+
+export default ShakeDetector;
diff --git a/app/components/UI/ProfilerManager/index.ts b/app/components/UI/ProfilerManager/index.ts
new file mode 100644
index 00000000000..7419f5e33a0
--- /dev/null
+++ b/app/components/UI/ProfilerManager/index.ts
@@ -0,0 +1 @@
+export { default } from './ProfilerManager';
diff --git a/app/components/UI/ReusableModal/index.tsx b/app/components/UI/ReusableModal/index.tsx
index 9033c018cd1..05e7c4428cb 100644
--- a/app/components/UI/ReusableModal/index.tsx
+++ b/app/components/UI/ReusableModal/index.tsx
@@ -46,6 +46,14 @@ import {
// Export to make compatible with components that use this file.
export type { ReusableModalRef } from './ReusableModal.types';
+/**
+ * @deprecated The `` component has been deprecated in favor of the new `` component from the component-library.
+ * Please update your code to use the new `` component instead, which can be found at app/component-library/components/BottomSheets/BottomSheet/BottomSheet.tsx.
+ * You can find documentation for the new BottomSheet component in the README:
+ * {@link https://github.com/MetaMask/metamask-mobile/tree/main/app/component-library/components/BottomSheets/BottomSheet}
+ * If you would like to help with the replacement of the old ReusableModal component, please submit a pull request against this GitHub issue:
+ * {@link https://github.com/MetaMask/metamask-mobile/issues/12656}
+ */
const ReusableModal = forwardRef(
(
{
diff --git a/app/components/UI/SwitchCustomNetwork/index.test.tsx b/app/components/UI/SwitchCustomNetwork/index.test.tsx
index 48eb0566995..aef67a31b20 100644
--- a/app/components/UI/SwitchCustomNetwork/index.test.tsx
+++ b/app/components/UI/SwitchCustomNetwork/index.test.tsx
@@ -23,9 +23,6 @@ jest.mock('react-native-scrollable-tab-view', () => ({
}));
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState: {
...backgroundState,
diff --git a/app/components/UI/Tabs/index.test.tsx b/app/components/UI/Tabs/index.test.tsx
index 1cc6400179b..97e09ee0980 100644
--- a/app/components/UI/Tabs/index.test.tsx
+++ b/app/components/UI/Tabs/index.test.tsx
@@ -5,9 +5,6 @@ import { backgroundState } from '../../../util/test/initial-root-state';
import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../util/test/accountsControllerTestUtils';
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState: {
...backgroundState,
diff --git a/app/components/Views/AccountBackupStep1/index.js b/app/components/Views/AccountBackupStep1/index.js
index ed2809edf07..41d281e3c99 100644
--- a/app/components/Views/AccountBackupStep1/index.js
+++ b/app/components/Views/AccountBackupStep1/index.js
@@ -16,10 +16,8 @@ import AndroidBackHandler from '../AndroidBackHandler';
import Device from '../../../util/device';
import scaling from '../../../util/scaling';
import Engine from '../../../core/Engine';
-import { ONBOARDING_WIZARD } from '../../../constants/storage';
import { connect } from 'react-redux';
import { saveOnboardingEvent as saveEvent } from '../../../actions/onboarding';
-import setOnboardingWizardStep from '../../../actions/wizard';
import { MetaMetricsEvents } from '../../../core/Analytics';
import StorageWrapper from '../../../store/storage-wrapper';
import { useTheme } from '../../../util/theme';
@@ -148,10 +146,6 @@ const AccountBackupStep1 = (props) => {
const skip = async () => {
track(MetaMetricsEvents.WALLET_SECURITY_SKIP_CONFIRMED);
- // Get onboarding wizard state
- const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD);
- !onboardingWizard && props.setOnboardingWizardStep(1);
-
const resetAction = CommonActions.reset({
index: 1,
routes: [
@@ -293,10 +287,6 @@ AccountBackupStep1.propTypes = {
* Object that represents the current route info like params passed to it
*/
route: PropTypes.object,
- /**
- * Action to set onboarding wizard step
- */
- setOnboardingWizardStep: PropTypes.func,
/**
* Action to save onboarding event
*/
@@ -304,7 +294,6 @@ AccountBackupStep1.propTypes = {
};
const mapDispatchToProps = (dispatch) => ({
- setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)),
saveOnboardingEvent: (...eventArgs) => dispatch(saveEvent(eventArgs)),
});
diff --git a/app/components/Views/AccountBackupStep1/index.test.tsx b/app/components/Views/AccountBackupStep1/index.test.tsx
index 1b659baff6a..05422f74546 100644
--- a/app/components/Views/AccountBackupStep1/index.test.tsx
+++ b/app/components/Views/AccountBackupStep1/index.test.tsx
@@ -2,7 +2,6 @@ import React from 'react';
import AccountBackupStep1 from './';
import { backgroundState } from '../../../util/test/initial-root-state';
import renderWithProvider from '../../../util/test/renderWithProvider';
-import { CommonActions } from '@react-navigation/native';
import { strings } from '../../../../locales/i18n';
import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors';
import { fireEvent } from '@testing-library/react-native';
@@ -10,8 +9,6 @@ import AndroidBackHandler from '../AndroidBackHandler';
import Device from '../../../util/device';
import Engine from '../../../core/Engine';
import StorageWrapper from '../../../store/storage-wrapper';
-import { ONBOARDING_SUCCESS_FLOW } from '../../../constants/onboarding';
-import Routes from '../../../constants/navigation/Routes';
import { InteractionManager, Platform } from 'react-native';
// Use fake timers to resolve reanimated issues.
@@ -103,21 +100,6 @@ jest.doMock('react-native', () => {
};
});
-const mockResetActionOnboardingSuccessWizard = CommonActions.reset({
- index: 1,
- routes: [
- {
- name: Routes.ONBOARDING.SUCCESS_FLOW,
- params: {
- screen: Routes.ONBOARDING.SUCCESS,
- params: {
- successFlow: ONBOARDING_SUCCESS_FLOW.NO_BACKED_UP_SRP,
- },
- },
- },
- ],
-});
-
describe('AccountBackupStep1', () => {
afterEach(() => {
jest.useFakeTimers({ legacyFakeTimers: true });
@@ -320,41 +302,11 @@ describe('AccountBackupStep1', () => {
});
describe('skip functionality', () => {
- it('calls onConfirm when onboarding wizard exists', async () => {
- (Engine.hasFunds as jest.Mock).mockReturnValue(false);
- (StorageWrapper.getItem as jest.Mock).mockResolvedValue({
- someData: 'exists',
- });
-
- const { wrapper } = setupTest();
-
- // Find and press the "Remind me later" button
- const remindLaterButton = wrapper.getByText(
- strings('account_backup_step_1.remind_me_later'),
- );
- fireEvent.press(remindLaterButton);
-
- // Get the onConfirm function from the modal params
- const modalParams = mockNavigate.mock.calls.find(
- (call) =>
- call[0] === 'RootModalFlow' &&
- call[1].screen === 'SkipAccountSecurityModal',
- )[1].params;
-
- mockNavigate.mockClear();
- // Call the onConfirm function (skip)
- await modalParams.onConfirm();
-
- // Verify navigation to OnboardingSuccess
- expect(mockDispatch).toHaveBeenCalledWith(
- mockResetActionOnboardingSuccessWizard,
- );
- });
-
- it('navigates to OnboardingSuccess when onboarding wizard does not exist', async () => {
+ it('navigates to OnboardingSuccess when onboarding', async () => {
(Engine.hasFunds as jest.Mock).mockReturnValue(false);
(StorageWrapper.getItem as jest.Mock).mockResolvedValue(null);
+ mockNavigate.mockClear();
const { wrapper } = setupTest();
// Find and press the "Remind me later" button
@@ -363,21 +315,19 @@ describe('AccountBackupStep1', () => {
);
fireEvent.press(remindLaterButton);
+ expect(mockNavigate).toHaveBeenCalledWith('RootModalFlow', {
+ screen: 'SkipAccountSecurityModal',
+ params: {
+ onConfirm: expect.any(Function),
+ onCancel: expect.any(Function),
+ },
+ });
+
// Get the onConfirm function from the modal params
- const modalParams = mockNavigate.mock.calls.find(
- (call) =>
- call[0] === 'RootModalFlow' &&
- call[1].screen === 'SkipAccountSecurityModal',
- )[1].params;
+ const modalParams = mockNavigate.mock.calls[0][1].params;
- mockNavigate.mockClear();
// Call the onConfirm function (skip)
await modalParams.onConfirm();
-
- // Verify navigation to OnboardingSuccess
- expect(mockDispatch).toHaveBeenCalledWith(
- mockResetActionOnboardingSuccessWizard,
- );
});
it('handle skip when metrics is disabled', async () => {
@@ -415,11 +365,6 @@ describe('AccountBackupStep1', () => {
// Call the onContinue function
await modalParams2.onContinue();
-
- // Verify navigation to OnboardingSuccess
- expect(mockDispatch).toHaveBeenCalledWith(
- mockResetActionOnboardingSuccessWizard,
- );
});
it('handle secure now button to goNext step when metrics is disabled', async () => {
diff --git a/app/components/Views/ActivityView/index.test.tsx b/app/components/Views/ActivityView/index.test.tsx
index c129c91f1dc..184ce48fac3 100644
--- a/app/components/Views/ActivityView/index.test.tsx
+++ b/app/components/Views/ActivityView/index.test.tsx
@@ -81,9 +81,6 @@ const renderComponent = (state: any = {}) =>
);
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState,
},
diff --git a/app/components/Views/AssetDetails/AssetDetailsActions/AssetDetailsActions.stories.tsx b/app/components/Views/AssetDetails/AssetDetailsActions/AssetDetailsActions.stories.tsx
index 84d4338d4cc..41ce2b438f6 100644
--- a/app/components/Views/AssetDetails/AssetDetailsActions/AssetDetailsActions.stories.tsx
+++ b/app/components/Views/AssetDetails/AssetDetailsActions/AssetDetailsActions.stories.tsx
@@ -7,9 +7,6 @@ import AssetDetailsActions, {
import initialBackgroundState from '../../../../util/test/initial-background-state.json';
const mockInitialState = {
- wizard: {
- step: 1,
- },
engine: {
backgroundState: initialBackgroundState,
},
diff --git a/app/components/Views/Browser/index.test.tsx b/app/components/Views/Browser/index.test.tsx
index 26056a43726..e57ee5fc784 100644
--- a/app/components/Views/Browser/index.test.tsx
+++ b/app/components/Views/Browser/index.test.tsx
@@ -73,9 +73,6 @@ const mockInitialState = {
primaryCurrency: 'ETH',
basicFunctionalityEnabled: true,
},
- wizard: {
- step: 1,
- },
browser: {
tabs: mockTabs,
activeTab: 1,
diff --git a/app/components/Views/BrowserTab/BrowserTab.tsx b/app/components/Views/BrowserTab/BrowserTab.tsx
index f680fcf6171..eab245acb34 100644
--- a/app/components/Views/BrowserTab/BrowserTab.tsx
+++ b/app/components/Views/BrowserTab/BrowserTab.tsx
@@ -36,7 +36,6 @@ import { addToHistory, addToWhitelist } from '../../../actions/browser';
import Device from '../../../util/device';
import AppConstants from '../../../core/AppConstants';
import { MetaMetricsEvents } from '../../../core/Analytics';
-import OnboardingWizard from '../../UI/OnboardingWizard';
import DrawerStatusTracker from '../../../core/DrawerStatusTracker';
import EntryScriptWeb3 from '../../../core/EntryScriptWeb3';
import ErrorBoundary from '../ErrorBoundary';
@@ -129,7 +128,6 @@ export const BrowserTab: React.FC = React.memo(
showTabs,
linkType,
isInTabsView,
- wizardStep,
updateTabInfo,
addToBrowserHistory,
bookmarks,
@@ -189,7 +187,6 @@ export const BrowserTab: React.FC = React.memo(
onMessage: (message: Record) => void;
}>();
const fromHomepage = useRef(false);
- const wizardScrollAdjustedRef = useRef(false);
const searchEngine = useSelector(selectSearchEngine);
const permittedEvmAccountsList = useSelector((state: RootState) => {
@@ -997,8 +994,6 @@ export const BrowserTab: React.FC = React.memo(
// Show autocomplete
fromHomepage,
toggleUrlModal,
- // Wizard
- wizardScrollAdjusted: wizardScrollAdjustedRef,
tabId,
injectHomePageScripts,
// TODO: This properties were missing, and were not optional
@@ -1267,22 +1262,6 @@ export const BrowserTab: React.FC = React.memo(
triggerDappViewedEvent(resolvedUrlRef.current);
}, [onSubmitEditing, triggerDappViewedEvent]);
- /**
- * Render the onboarding wizard browser step
- */
- const renderOnboardingWizard = () => {
- if ([7].includes(wizardStep)) {
- if (!wizardScrollAdjustedRef.current) {
- setTimeout(() => {
- reload();
- }, 1);
- wizardScrollAdjustedRef.current = true;
- }
- return ;
- }
- return null;
- };
-
const handleOnFileDownload = useCallback(
async ({
nativeEvent: { downloadUrl },
@@ -1586,7 +1565,6 @@ export const BrowserTab: React.FC = React.memo(
)}
{renderBottomBar()}
- {isTabActive && renderOnboardingWizard()}
@@ -1599,7 +1577,6 @@ const mapStateToProps = (state: RootState) => ({
ipfsGateway: selectIpfsGateway(state),
selectedAddress: selectSelectedInternalAccountFormattedAddress(state),
isIpfsGatewayEnabled: selectIsIpfsGatewayEnabled(state),
- wizardStep: state.wizard.step,
activeChainId: selectEvmChainId(state),
});
diff --git a/app/components/Views/BrowserTab/index.test.tsx b/app/components/Views/BrowserTab/index.test.tsx
index 3886f2d3f04..208fee98885 100644
--- a/app/components/Views/BrowserTab/index.test.tsx
+++ b/app/components/Views/BrowserTab/index.test.tsx
@@ -61,8 +61,6 @@ const mockProps = {
addToWhitelist: jest.fn(),
updateTabInfo: jest.fn(),
showTabs: jest.fn(),
- setOnboardingWizardStep: jest.fn(),
- wizardStep: 1,
isIpfsGatewayEnabled: false,
chainId: '0x1',
isInTabsView: false,
diff --git a/app/components/Views/BrowserTab/types.ts b/app/components/Views/BrowserTab/types.ts
index fcb87d01eb6..265eb412721 100644
--- a/app/components/Views/BrowserTab/types.ts
+++ b/app/components/Views/BrowserTab/types.ts
@@ -94,10 +94,6 @@ export type BrowserTabProps = SharedTabProps & {
* Function to store the a website in the browser whitelist
*/
addToWhitelist: (url: string) => void;
- /**
- * Current onboarding wizard step
- */
- wizardStep: number;
/**
* the current version of the app
*/
diff --git a/app/components/Views/EditAccountName/EditAccountName.test.tsx b/app/components/Views/EditAccountName/EditAccountName.test.tsx
index 66f92b0ebf9..e174e0853bc 100644
--- a/app/components/Views/EditAccountName/EditAccountName.test.tsx
+++ b/app/components/Views/EditAccountName/EditAccountName.test.tsx
@@ -40,9 +40,6 @@ InteractionManager.runAfterInteractions = jest.fn(async (callback) =>
const mockInitialState = {
swaps: { '0x1': { isLive: true }, hasOnboarded: false, isLive: true },
- wizard: {
- step: 0,
- },
settings: {
primaryCurrency: 'usd',
},
diff --git a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js
index 6b19f3b0219..363d190c40e 100644
--- a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js
+++ b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js
@@ -39,13 +39,11 @@ import { saveOnboardingEvent as saveEvent } from '../../../actions/onboarding';
import { passwordSet, seedphraseBackedUp } from '../../../actions/user';
import { QRTabSwitcherScreens } from '../../../components/Views/QRTabSwitcher';
import { setLockTime } from '../../../actions/settings';
-import setOnboardingWizardStep from '../../../actions/wizard';
import { strings } from '../../../../locales/i18n';
import { getOnboardingNavbarOptions } from '../../UI/Navbar';
import { ScreenshotDeterrent } from '../../UI/ScreenshotDeterrent';
import {
BIOMETRY_CHOICE_DISABLED,
- ONBOARDING_WIZARD,
TRUE,
PASSCODE_DISABLED,
} from '../../../constants/storage';
@@ -123,7 +121,6 @@ const ImportFromSecretRecoveryPhrase = ({
setLockTime,
seedphraseBackedUp,
saveOnboardingEvent,
- setOnboardingWizardStep,
route,
}) => {
const { colors, themeAppearance } = useTheme();
@@ -616,10 +613,6 @@ const ImportFromSecretRecoveryPhrase = ({
if (Device.isIos && err.toString() === IOS_REJECTED_BIOMETRICS_ERROR)
await handleRejectedOsBiometricPrompt(parsedSeed);
}
- // Get onboarding wizard state
- const onboardingWizard = await StorageWrapper.getItem(
- ONBOARDING_WIZARD,
- );
setLoading(false);
passwordSet();
setLockTime(AppConstants.DEFAULT_LOCK_TIMEOUT);
@@ -632,7 +625,6 @@ const ImportFromSecretRecoveryPhrase = ({
new_wallet: false,
account_type: 'imported',
});
- !onboardingWizard && setOnboardingWizardStep(1);
fetchAccountsWithActivity();
const resetAction = CommonActions.reset({
@@ -1193,10 +1185,6 @@ ImportFromSecretRecoveryPhrase.propTypes = {
* Action to save onboarding event
*/
saveOnboardingEvent: PropTypes.func,
- /**
- * Action to set onboarding wizard step
- */
- setOnboardingWizardStep: PropTypes.func,
/**
* Object that represents the current route info like params passed to it
*/
@@ -1208,7 +1196,6 @@ ImportFromSecretRecoveryPhrase.propTypes = {
const mapDispatchToProps = (dispatch) => ({
setLockTime: (time) => dispatch(setLockTime(time)),
- setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)),
passwordSet: () => dispatch(passwordSet()),
seedphraseBackedUp: () => dispatch(seedphraseBackedUp()),
saveOnboardingEvent: (...eventArgs) => dispatch(saveEvent(eventArgs)),
diff --git a/app/components/Views/Login/index.test.tsx b/app/components/Views/Login/index.test.tsx
index 21f8f50e823..9c65d116fd0 100644
--- a/app/components/Views/Login/index.test.tsx
+++ b/app/components/Views/Login/index.test.tsx
@@ -21,7 +21,6 @@ import {
import { IUseMetricsHook } from '../../hooks/useMetrics/useMetrics.types';
import {
OPTIN_META_METRICS_UI_SEEN,
- ONBOARDING_WIZARD,
BIOMETRY_CHOICE_DISABLED,
TRUE,
} from '../../../constants/storage';
@@ -88,11 +87,6 @@ jest.mock('../../../store/storage-wrapper', () => ({
setItem: jest.fn(),
}));
-// Mock setOnboardingWizardStep action
-jest.mock('../../../actions/wizard', () => ({
- __esModule: true,
- default: jest.fn(() => ({ type: 'SET_ONBOARDING_WIZARD_STEP' })),
-}));
jest.mock('../../../core/Authentication', () => ({
getType: jest.fn().mockResolvedValue({
currentAuthType: 'device_passcode',
@@ -253,7 +247,6 @@ describe('Login', () => {
it('should call trace function for AuthenticateUser during non-OAuth login', async () => {
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN) return true;
- if (key === ONBOARDING_WIZARD) return true;
return null;
});
@@ -312,7 +305,6 @@ describe('Login', () => {
// Arrange
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN) return Promise.resolve(null);
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
@@ -368,7 +360,6 @@ describe('Login', () => {
// Arrange
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN) return Promise.resolve(null);
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
@@ -413,7 +404,6 @@ describe('Login', () => {
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN)
return Promise.resolve('true');
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
@@ -458,7 +448,6 @@ describe('Login', () => {
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN)
return Promise.resolve('true');
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
@@ -514,7 +503,6 @@ describe('Login', () => {
// Arrange
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN) return Promise.resolve(null);
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
@@ -569,7 +557,6 @@ describe('Login', () => {
// Arrange
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN) return Promise.resolve(null);
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
@@ -613,7 +600,6 @@ describe('Login', () => {
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN)
return Promise.resolve('true');
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
@@ -657,7 +643,6 @@ describe('Login', () => {
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
if (key === OPTIN_META_METRICS_UI_SEEN)
return Promise.resolve('true');
- if (key === ONBOARDING_WIZARD) return Promise.resolve(null);
return Promise.resolve(null);
});
diff --git a/app/components/Views/Login/index.tsx b/app/components/Views/Login/index.tsx
index 3cf409e3dfd..798a1c1c848 100644
--- a/app/components/Views/Login/index.tsx
+++ b/app/components/Views/Login/index.tsx
@@ -26,9 +26,8 @@ import {
OnboardingActionTypes,
saveOnboardingEvent as saveEvent,
} from '../../../actions/onboarding';
-import setOnboardingWizardStepUtil from '../../../actions/wizard';
import { setAllowLoginWithRememberMe as setAllowLoginWithRememberMeUtil } from '../../../actions/security';
-import { connect, useDispatch } from 'react-redux';
+import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import {
passcodeType,
@@ -38,7 +37,6 @@ import { BiometryButton } from '../../UI/BiometryButton';
import Logger from '../../../util/Logger';
import {
BIOMETRY_CHOICE_DISABLED,
- ONBOARDING_WIZARD,
TRUE,
PASSCODE_DISABLED,
OPTIN_META_METRICS_UI_SEEN,
@@ -157,9 +155,6 @@ const Login: React.FC = ({ saveOnboardingEvent }) => {
styles,
theme: { colors, themeAppearance },
} = useStyles(stylesheet, EmptyRecordConstant);
- const dispatch = useDispatch();
- const setOnboardingWizardStep = (step: number) =>
- dispatch(setOnboardingWizardStepUtil(step));
const setAllowLoginWithRememberMe = (enabled: boolean) =>
setAllowLoginWithRememberMeUtil(enabled);
const passwordLoginAttemptTraceCtxRef = useRef(null);
@@ -297,13 +292,7 @@ const Login: React.FC = ({ saveOnboardingEvent }) => {
};
const navigateToHome = async () => {
- const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD);
- if (onboardingWizard) {
- navigation.replace(Routes.ONBOARDING.HOME_NAV);
- } else {
- setOnboardingWizardStep(1);
- navigation.replace(Routes.ONBOARDING.HOME_NAV);
- }
+ navigation.replace(Routes.ONBOARDING.HOME_NAV);
};
const checkMetricsUISeen = async (): Promise => {
diff --git a/app/components/Views/Login/index2.test.tsx b/app/components/Views/Login/index2.test.tsx
index 559afda5130..4df28cf926b 100644
--- a/app/components/Views/Login/index2.test.tsx
+++ b/app/components/Views/Login/index2.test.tsx
@@ -23,10 +23,7 @@ import { strings } from '../../../../locales/i18n';
import Engine from '../../../core/Engine';
import OAuthService from '../../../core/OAuthService/OAuthService';
import StorageWrapper from '../../../store/storage-wrapper';
-import {
- ONBOARDING_WIZARD,
- OPTIN_META_METRICS_UI_SEEN,
-} from '../../../constants/storage';
+import { OPTIN_META_METRICS_UI_SEEN } from '../../../constants/storage';
import { EndTraceRequest, TraceName } from '../../../util/trace';
import ReduxService from '../../../core/redux/ReduxService';
import { RecursivePartial } from '../../../core/Authentication/Authentication.test';
@@ -679,7 +676,6 @@ describe('Login test suite 2', () => {
},
});
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
- if (key === ONBOARDING_WIZARD) return true;
if (key === OPTIN_META_METRICS_UI_SEEN) return true;
return null;
});
@@ -738,7 +734,6 @@ describe('Login test suite 2', () => {
},
});
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
- if (key === ONBOARDING_WIZARD) return true;
if (key === OPTIN_META_METRICS_UI_SEEN) return null; // Not seen
return null;
});
@@ -799,7 +794,7 @@ describe('Login test suite 2', () => {
mockIsEnabled.mockReturnValue(true);
});
- it('should replace navigation when non-OAuth login with existing onboarding wizard', async () => {
+ it('should replace navigation when non-OAuth login ', async () => {
mockRoute.mockReturnValue({
params: {
locked: false,
@@ -807,7 +802,6 @@ describe('Login test suite 2', () => {
},
});
(StorageWrapper.getItem as jest.Mock).mockImplementation((key) => {
- if (key === ONBOARDING_WIZARD) return true;
if (key === OPTIN_META_METRICS_UI_SEEN) return true;
return null;
});
diff --git a/app/components/Views/ManualBackupStep3/index.js b/app/components/Views/ManualBackupStep3/index.js
index d850619af35..06cb345a316 100644
--- a/app/components/Views/ManualBackupStep3/index.js
+++ b/app/components/Views/ManualBackupStep3/index.js
@@ -13,11 +13,7 @@ import Device from '../../../util/device';
import Confetti from '../../UI/Confetti';
import HintModal from '../../UI/HintModal';
import { getTransparentOnboardingNavbarOptions } from '../../UI/Navbar';
-import setOnboardingWizardStep from '../../../actions/wizard';
-import {
- ONBOARDING_WIZARD,
- SEED_PHRASE_HINTS,
-} from '../../../constants/storage';
+import { SEED_PHRASE_HINTS } from '../../../constants/storage';
import { MetaMetricsEvents } from '../../../core/Analytics';
import { ThemeContext, mockTheme } from '../../../util/theme';
import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding';
@@ -98,10 +94,6 @@ class ManualBackupStep3 extends PureComponent {
* Object that represents the current route info like params passed to it
*/
route: PropTypes.object,
- /**
- * Action to set onboarding wizard step
- */
- setOnboardingWizardStep: PropTypes.func,
/**
* Action to save onboarding event
*/
@@ -183,13 +175,7 @@ class ManualBackupStep3 extends PureComponent {
};
done = async () => {
- const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD);
- if (onboardingWizard) {
- this.props.navigation.reset({ routes: [{ name: 'HomeNav' }] });
- } else {
- this.props.setOnboardingWizardStep(1);
- this.props.navigation.reset({ routes: [{ name: 'HomeNav' }] });
- }
+ this.props.navigation.reset({ routes: [{ name: 'HomeNav' }] });
};
handleChangeText = (text) => this.setState({ hintText: text });
@@ -237,7 +223,6 @@ ManualBackupStep3.contextType = ThemeContext;
const mapDispatchToProps = (dispatch) => ({
showAlert: (config) => dispatch(showAlert(config)),
- setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)),
saveOnboardingEvent: (...eventArgs) => dispatch(saveEvent(eventArgs)),
});
diff --git a/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.styles.ts b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.styles.ts
new file mode 100644
index 00000000000..986dd7ca61a
--- /dev/null
+++ b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.styles.ts
@@ -0,0 +1,20 @@
+// Third party dependencies.
+import { StyleSheet } from 'react-native';
+
+/**
+ * Style sheet function for MultichainAccountActions component.
+ *
+ * @returns StyleSheet object.
+ */
+const styleSheet = () =>
+ StyleSheet.create({
+ container: {
+ paddingVertical: 32,
+ },
+ accountAction: {
+ paddingTop: 16,
+ paddingBottom: 16,
+ },
+ });
+
+export default styleSheet;
diff --git a/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.test.tsx b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.test.tsx
new file mode 100644
index 00000000000..6143bd0875f
--- /dev/null
+++ b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.test.tsx
@@ -0,0 +1,124 @@
+import React from 'react';
+import { AccountGroupObject } from '@metamask/account-tree-controller';
+import { AccountGroupType } from '@metamask/account-api';
+import { InternalAccount } from '@metamask/keyring-internal-api';
+import MultichainAccountActions from './MultichainAccountActions';
+import renderWithProvider from '../../../../../util/test/renderWithProvider';
+import Engine from '../../../../../core/Engine';
+import Routes from '../../../../../constants/navigation/Routes';
+import {
+ MULTICHAIN_ACCOUNT_ACTIONS_ACCOUNT_DETAILS,
+ MULTICHAIN_ACCOUNT_ACTIONS_ADDRESSES,
+} from './MultichainAccountActions.testIds';
+
+const mockAccountGroup: AccountGroupObject = {
+ type: AccountGroupType.SingleAccount,
+ id: 'keyring:test-group/ethereum' as const,
+ accounts: ['account-1'],
+ metadata: {
+ name: 'Test Account Group',
+ pinned: false,
+ hidden: false,
+ },
+};
+
+const mockInternalAccount: InternalAccount = {
+ id: 'account-1',
+ address: '0x1234567890123456789012345678901234567890',
+ type: 'eip155:eoa',
+ options: {},
+ metadata: {
+ name: 'Test Account',
+ importTime: 1234567890,
+ keyring: { type: 'HD Key Tree' },
+ lastSelected: 0,
+ },
+ scopes: [],
+ methods: [],
+};
+
+// Mock navigation
+const mockNavigate = jest.fn();
+const mockGoBack = jest.fn();
+
+jest.mock('@react-navigation/native', () => ({
+ ...jest.requireActual('@react-navigation/native'),
+ useNavigation: () => ({ navigate: mockNavigate, goBack: mockGoBack }),
+ useRoute: () => ({
+ params: {
+ accountGroup: mockAccountGroup,
+ },
+ }),
+}));
+
+// Mock Engine
+jest.mock('../../../../../core/Engine', () => ({
+ context: {
+ AccountsController: {
+ getAccount: jest.fn().mockReturnValue(mockInternalAccount),
+ listAccounts: jest.fn(),
+ listMultichainAccounts: jest.fn(),
+ getSelectedAccount: jest.fn(),
+ getAccountByAddress: jest.fn(),
+ },
+ },
+}));
+
+// Mock react-native-safe-area-context
+jest.mock('react-native-safe-area-context', () => {
+ const inset = { top: 0, right: 0, bottom: 0, left: 0 };
+ const frame = { width: 0, height: 0, x: 0, y: 0 };
+ return {
+ SafeAreaProvider: jest.fn().mockImplementation(({ children }) => children),
+ SafeAreaConsumer: jest
+ .fn()
+ .mockImplementation(({ children }) => children(inset)),
+ useSafeAreaInsets: jest.fn().mockImplementation(() => inset),
+ useSafeAreaFrame: jest.fn().mockImplementation(() => frame),
+ };
+});
+
+describe('MultichainAccountActions', () => {
+ const mockEngine = jest.mocked(Engine);
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockEngine.context.AccountsController.getAccount.mockReturnValue(
+ mockInternalAccount,
+ );
+ });
+
+ it('renders account actions menu with correct options', () => {
+ const { getByText } = renderWithProvider();
+
+ expect(getByText('Account Details')).toBeTruthy();
+ // expect(getByText('Rename account')).toBeTruthy(); // TODO: Uncomment when account group renaming is supported
+ expect(getByText('Addresses')).toBeTruthy();
+ });
+
+ it('renders action buttons with correct test IDs', () => {
+ const { getByTestId } = renderWithProvider();
+
+ expect(
+ getByTestId(MULTICHAIN_ACCOUNT_ACTIONS_ACCOUNT_DETAILS),
+ ).toBeTruthy();
+ // expect(getByTestId(MULTICHAIN_ACCOUNT_ACTIONS_EDIT_NAME)).toBeTruthy(); // TODO: Uncomment when account group renaming is supported
+ expect(getByTestId(MULTICHAIN_ACCOUNT_ACTIONS_ADDRESSES)).toBeTruthy();
+ });
+
+ it('navigates to account details when account details button is pressed', () => {
+ const { getByTestId } = renderWithProvider();
+
+ const accountDetailsButton = getByTestId(
+ MULTICHAIN_ACCOUNT_ACTIONS_ACCOUNT_DETAILS,
+ );
+ accountDetailsButton.props.onPress();
+
+ expect(mockNavigate).toHaveBeenCalledWith(
+ Routes.MULTICHAIN_ACCOUNTS.ACCOUNT_DETAILS,
+ {
+ account: mockInternalAccount,
+ },
+ );
+ });
+});
diff --git a/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.testIds.ts b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.testIds.ts
new file mode 100644
index 00000000000..e0ae8accd6b
--- /dev/null
+++ b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.testIds.ts
@@ -0,0 +1,8 @@
+export const MULTICHAIN_ACCOUNT_ACTIONS_ACCOUNT_DETAILS =
+ 'multichain-account-actions-account-details';
+
+export const MULTICHAIN_ACCOUNT_ACTIONS_EDIT_NAME =
+ 'multichain-account-actions-edit-name';
+
+export const MULTICHAIN_ACCOUNT_ACTIONS_ADDRESSES =
+ 'multichain-account-actions-addresses';
diff --git a/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.tsx b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.tsx
new file mode 100644
index 00000000000..c3eb6c0695e
--- /dev/null
+++ b/app/components/Views/MultichainAccounts/sheets/MultichainAccountActions/MultichainAccountActions.tsx
@@ -0,0 +1,94 @@
+import React, { useCallback, useMemo } from 'react';
+import { View } from 'react-native';
+import {
+ useNavigation,
+ RouteProp,
+ ParamListBase,
+ useRoute,
+} from '@react-navigation/native';
+
+import { AccountGroupObject } from '@metamask/account-tree-controller';
+import { InternalAccount } from '@metamask/keyring-internal-api';
+import BottomSheet, {
+ BottomSheetRef,
+} from '../../../../../component-library/components/BottomSheets/BottomSheet';
+import AccountAction from '../../../AccountAction/AccountAction';
+import { IconName } from '../../../../../component-library/components/Icons/Icon';
+import { useStyles } from '../../../../../component-library/hooks';
+
+import { strings } from '../../../../../../locales/i18n';
+import Routes from '../../../../../constants/navigation/Routes';
+import Engine from '../../../../../core/Engine';
+import styleSheet from './MultichainAccountActions.styles';
+import {
+ MULTICHAIN_ACCOUNT_ACTIONS_ACCOUNT_DETAILS,
+ // MULTICHAIN_ACCOUNT_ACTIONS_EDIT_NAME,
+ MULTICHAIN_ACCOUNT_ACTIONS_ADDRESSES,
+} from './MultichainAccountActions.testIds';
+
+interface MultichainAccountActionsParams {
+ accountGroup: AccountGroupObject;
+}
+
+const MultichainAccountActions = () => {
+ const route = useRoute>();
+ const { accountGroup } = route.params as MultichainAccountActionsParams;
+ const { styles } = useStyles(styleSheet, {});
+ const sheetRef = React.useRef(null);
+ const { navigate } = useNavigation();
+
+ // TODO: Update when Account Details for group is implemented. For the moment, we just get the first account from a group
+ const firstAccount = useMemo((): InternalAccount | null => {
+ const firstAccountId = accountGroup.accounts[0];
+ if (firstAccountId) {
+ const { AccountsController } = Engine.context;
+ return AccountsController.getAccount(firstAccountId) ?? null;
+ }
+ return null;
+ }, [accountGroup.accounts]);
+
+ const goToAccountDetails = useCallback(() => {
+ if (!firstAccount) return;
+
+ sheetRef.current?.onCloseBottomSheet(() => {
+ navigate(Routes.MULTICHAIN_ACCOUNTS.ACCOUNT_DETAILS, {
+ account: firstAccount,
+ });
+ });
+ }, [navigate, firstAccount]);
+
+ // const goToEditAccountName = useCallback(() => null, []); // TODO: To be implemented
+
+ const goToAddresses = useCallback(() => null, []); // TODO: To be implemented
+
+ return (
+
+
+
+ {/* TODO: Uncomment when account group renaming is supported
+ */}
+
+
+
+ );
+};
+
+export default React.memo(MultichainAccountActions);
diff --git a/app/components/Views/Root/index.test.tsx b/app/components/Views/Root/index.test.tsx
index eb01805cc48..3ef67bf6c25 100644
--- a/app/components/Views/Root/index.test.tsx
+++ b/app/components/Views/Root/index.test.tsx
@@ -26,6 +26,14 @@ jest.mock('../../../core/OAuthService/OAuthLoginHandlers', () => ({
createLoginHandler: jest.fn(),
}));
+jest.mock('expo-sensors', () => ({
+ Accelerometer: {
+ setUpdateInterval: jest.fn(),
+ addListener: jest.fn(),
+ removeAllListeners: jest.fn(),
+ },
+}));
+
describe('Root', () => {
it('should render correctly', () => {
const { toJSON } = render();
diff --git a/app/components/Views/Wallet/index.test.tsx b/app/components/Views/Wallet/index.test.tsx
index d2384a60c81..d4afa103548 100644
--- a/app/components/Views/Wallet/index.test.tsx
+++ b/app/components/Views/Wallet/index.test.tsx
@@ -187,9 +187,6 @@ const mockInitialState = {
hasOnboarded: false,
isLive: true,
},
- wizard: {
- step: 0,
- },
card: cardInitialState,
settings: {
primaryCurrency: 'usd',
diff --git a/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts b/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts
index e918ae7803e..79b943411da 100644
--- a/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts
+++ b/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts
@@ -33,6 +33,7 @@ const MOCK_STORE_STATE = {
chainId: '0x1',
ticker: 'ETH',
},
+ selectedNetworkClientId: 'mainnet',
},
CurrencyRateController: {
currentCurrency: 'USD',
@@ -41,6 +42,14 @@ const MOCK_STORE_STATE = {
PreferencesController: {
isTokenNetworkFilterEqualCurrentNetwork: true,
},
+ TokensController: {
+ allTokens: {},
+ allIgnoredTokens: {},
+ allDetectedTokens: {},
+ },
+ NetworkEnablementController: {
+ enabledNetworks: ['0x1', '0x89'],
+ },
},
},
} as unknown as RootState;
@@ -63,10 +72,16 @@ jest.mock('../useGetTotalFiatBalanceCrossChains', () => ({
useGetTotalFiatBalanceCrossChains: jest.fn().mockReturnValue({}),
}));
+jest.mock(
+ '../useIsOriginalNativeTokenSymbol/useIsOriginalNativeTokenSymbol',
+ () => jest.fn().mockReturnValue(true),
+);
+
jest.mock('../../../util/networks', () => ({
...jest.requireActual('../../../util/networks'),
isTestNet: jest.fn().mockReturnValue(false),
isPortfolioViewEnabled: jest.fn().mockReturnValue(false),
+ isRemoveGlobalNetworkSelectorEnabled: jest.fn().mockReturnValue(false),
}));
describe('useSelectedAccountMultichainBalances', () => {
diff --git a/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.ts b/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.ts
index c4b4eb3f0ac..b9c289fcaea 100644
--- a/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.ts
+++ b/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.ts
@@ -1,5 +1,8 @@
import { useSelector } from 'react-redux';
-import { isPortfolioViewEnabled } from '../../../util/networks';
+import {
+ isPortfolioViewEnabled,
+ isRemoveGlobalNetworkSelectorEnabled,
+} from '../../../util/networks';
import {
selectChainId,
selectIsPopularNetwork,
@@ -30,6 +33,7 @@ import {
getAggregatedBalance,
getShouldShowAggregatedPercentage,
} from './utils';
+import { selectEVMEnabledNetworks } from '../../../selectors/networkEnablementController';
import { SupportedCaipChainId } from '@metamask/multichain-network-controller';
/**
* Hook to manage portfolio balance data across chains.
@@ -43,6 +47,8 @@ const useSelectedAccountMultichainBalances =
const evmChainId = useSelector(selectEvmChainId);
const currentCurrency = useSelector(selectCurrentCurrency);
const allChainIDs = useSelector(getChainIdsToPoll);
+
+ const enabledChains = useSelector(selectEVMEnabledNetworks);
const isTokenNetworkFilterEqualCurrentNetwork = useSelector(
selectIsTokenNetworkFilterEqualCurrentNetwork,
);
@@ -50,10 +56,18 @@ const useSelectedAccountMultichainBalances =
const { type } = useSelector(selectProviderConfig);
const ticker = useSelector(selectEvmTicker);
+ const shouldAggregateAcrossChains = isRemoveGlobalNetworkSelectorEnabled()
+ ? true
+ : !isTokenNetworkFilterEqualCurrentNetwork && isPopularNetwork;
+
+ const chainsToAggregateAcross = isRemoveGlobalNetworkSelectorEnabled()
+ ? enabledChains
+ : allChainIDs;
+
const formattedTokensWithBalancesPerChain = useGetFormattedTokensPerChain(
[selectedInternalAccount as InternalAccount],
- !isTokenNetworkFilterEqualCurrentNetwork && isPopularNetwork,
- allChainIDs,
+ shouldAggregateAcrossChains,
+ chainsToAggregateAcross,
);
const totalFiatBalancesCrossEvmChain = useGetTotalFiatBalanceCrossChains(
diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts
index 5edd7a5b397..add20266fd3 100644
--- a/app/constants/navigation/Routes.ts
+++ b/app/constants/navigation/Routes.ts
@@ -279,6 +279,7 @@ const Routes = {
MULTICHAIN_ACCOUNTS: {
ACCOUNT_DETAILS: 'MultichainAccountDetails',
WALLET_DETAILS: 'MultichainWalletDetails',
+ ACCOUNT_CELL_ACTIONS: 'MultichainAccountActions',
},
SOLANA_NEW_FEATURE_CONTENT: 'SolanaNewFeatureContentView',
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
diff --git a/app/constants/storage.ts b/app/constants/storage.ts
index 007eb3147d1..bbb7a0c53c4 100644
--- a/app/constants/storage.ts
+++ b/app/constants/storage.ts
@@ -9,7 +9,6 @@ export const BIOMETRY_CHOICE_DISABLED = `${prefix}biometryChoiceDisabled`;
export const PASSCODE_CHOICE = `${prefix}passcodeChoice`;
export const PASSCODE_DISABLED = `${prefix}passcodeDisabled`;
-export const ONBOARDING_WIZARD = `${prefix}onboardingWizard`;
export const METRICS_OPT_IN = `${prefix}metricsOptIn`;
export const ANALYTICS_DATA_DELETION_TASK_ID = `${prefix}analyticsDataDeletionTaskId`;
export const ANALYTICS_DATA_DELETION_DATE = `${prefix}analyticsDataDeletionDate`;
diff --git a/app/core/Analytics/MetaMetrics.events.ts b/app/core/Analytics/MetaMetrics.events.ts
index b5eff233557..a4b5b49f268 100644
--- a/app/core/Analytics/MetaMetrics.events.ts
+++ b/app/core/Analytics/MetaMetrics.events.ts
@@ -22,16 +22,6 @@ export const generateOpt = (
return { category: name };
};
-const ONBOARDING_WIZARD_STEP_DESCRIPTION: { [key: number]: string } = {
- 1: 'Welcome',
- 2: 'Accounts',
- 3: 'Account Name',
- 4: 'Notifications',
- 5: 'Main Navigation',
- 6: 'Browser',
- 7: 'Search',
-};
-
/**
* Analytics Tracking Events
*/
@@ -1903,4 +1893,4 @@ const legacyMetaMetricsEvents = {
const MetaMetricsEvents = { ...events, ...legacyMetaMetricsEvents };
-export { MetaMetricsEvents, ONBOARDING_WIZARD_STEP_DESCRIPTION, EVENT_NAME };
+export { MetaMetricsEvents, EVENT_NAME };
diff --git a/app/core/Analytics/index.ts b/app/core/Analytics/index.ts
index fd382b27a4e..817a9f73e72 100644
--- a/app/core/Analytics/index.ts
+++ b/app/core/Analytics/index.ts
@@ -1,9 +1,5 @@
import MetaMetrics from './MetaMetrics';
-import {
- MetaMetricsEvents,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
- EVENT_NAME,
-} from './MetaMetrics.events';
+import { MetaMetricsEvents, EVENT_NAME } from './MetaMetrics.events';
import {
DataDeleteStatus,
DataDeleteResponseStatus,
@@ -15,7 +11,6 @@ export {
MetaMetricsEvents,
DataDeleteStatus,
DataDeleteResponseStatus,
- ONBOARDING_WIZARD_STEP_DESCRIPTION,
EVENT_NAME,
};
diff --git a/app/core/Engine/controllers/snaps/execution-service-init.ts b/app/core/Engine/controllers/snaps/execution-service-init.ts
index 856799caf5e..a8795d8f0fb 100644
--- a/app/core/Engine/controllers/snaps/execution-service-init.ts
+++ b/app/core/Engine/controllers/snaps/execution-service-init.ts
@@ -49,7 +49,6 @@ export const executionServiceInit: ControllerInitFunction<
isHomepage: () => false,
fromHomepage: { current: false },
toggleUrlModal: () => null,
- wizardScrollAdjusted: { current: false },
tabId: false,
isWalletConnect: true,
isMMSDK: false,
diff --git a/app/core/RPCMethods/RPCMethodMiddleware.test.ts b/app/core/RPCMethods/RPCMethodMiddleware.test.ts
index d4daed88b62..0e0c943eef2 100644
--- a/app/core/RPCMethods/RPCMethodMiddleware.test.ts
+++ b/app/core/RPCMethods/RPCMethodMiddleware.test.ts
@@ -182,8 +182,6 @@ function getMinimalOptions() {
// Show autocomplete
fromHomepage: { current: false },
toggleUrlModal: jest.fn(),
- // Wizard
- wizardScrollAdjusted: { current: false },
// For the browser
tabId: '' as const,
// For WalletConnect
diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts
index 9ff40c3c93b..53038b7ba1f 100644
--- a/app/core/RPCMethods/RPCMethodMiddleware.ts
+++ b/app/core/RPCMethods/RPCMethodMiddleware.ts
@@ -35,7 +35,6 @@ import { strings } from '../../../locales/i18n';
import { resemblesAddress, safeToChecksumAddress } from '../../util/address';
import { store } from '../../store';
import { removeBookmark } from '../../actions/bookmarks';
-import setOnboardingWizardStep from '../../actions/wizard';
import { v1 as random } from 'uuid';
import { getPermittedAccounts } from '../Permissions';
import AppConstants from '../AppConstants';
@@ -112,8 +111,6 @@ export interface RPCMethodsMiddleParameters {
// Show autocomplete
fromHomepage: { current: boolean };
toggleUrlModal: (shouldClearUrlInput: boolean) => void;
- // Wizard
- wizardScrollAdjusted: { current: boolean };
// For the browser
tabId: number | '' | false;
// For WalletConnect
@@ -390,8 +387,6 @@ export const getRpcMethodMiddleware = ({
// Show autocomplete
fromHomepage,
toggleUrlModal,
- // Wizard
- wizardScrollAdjusted,
// For the browser
tabId,
// For WalletConnect
@@ -960,20 +955,6 @@ export const getRpcMethodMiddleware = ({
});
},
- metamask_showTutorial: async () => {
- checkTabActive();
- if (!isHomepage()) {
- throw providerErrors.unauthorized('Forbidden.');
- }
- wizardScrollAdjusted.current = false;
-
- store.dispatch(setOnboardingWizardStep(1));
-
- navigation.navigate('WalletView');
-
- res.result = true;
- },
-
metamask_showAutocomplete: async () => {
checkTabActive();
if (!isHomepage()) {
diff --git a/app/core/SDKConnect/AndroidSDK/getDefaultBridgeParams.ts b/app/core/SDKConnect/AndroidSDK/getDefaultBridgeParams.ts
index e5ebb333137..284c6b7dc65 100644
--- a/app/core/SDKConnect/AndroidSDK/getDefaultBridgeParams.ts
+++ b/app/core/SDKConnect/AndroidSDK/getDefaultBridgeParams.ts
@@ -38,8 +38,6 @@ const getDefaultBridgeParams = (clientInfo: DappClient) => ({
isHomepage: () => false,
// Show autocomplete
fromHomepage: { current: false },
- // Wizard
- wizardScrollAdjusted: { current: false },
tabId: '',
isWalletConnect: false,
analytics: {
diff --git a/app/core/SDKConnect/handlers/setupBridge.ts b/app/core/SDKConnect/handlers/setupBridge.ts
index 07814268464..3604d5c20b1 100644
--- a/app/core/SDKConnect/handlers/setupBridge.ts
+++ b/app/core/SDKConnect/handlers/setupBridge.ts
@@ -65,8 +65,6 @@ export const setupBridge = ({
isHomepage: () => false,
// Show autocomplete
fromHomepage: { current: false },
- // Wizard
- wizardScrollAdjusted: { current: false },
tabId: '',
isWalletConnect: false,
analytics: {
diff --git a/app/core/WalletConnect/WalletConnect.js b/app/core/WalletConnect/WalletConnect.js
index 339302de910..c9b4ab6958b 100644
--- a/app/core/WalletConnect/WalletConnect.js
+++ b/app/core/WalletConnect/WalletConnect.js
@@ -362,8 +362,7 @@ class WalletConnect {
// Show autocomplete
fromHomepage: false,
toggleUrlModal: () => null,
- // Wizard
- wizardScrollAdjusted: () => null,
+
tabId: false,
isWalletConnect: true,
}),
diff --git a/app/core/WalletConnect/WalletConnect2Session.ts b/app/core/WalletConnect/WalletConnect2Session.ts
index 965c00c53db..a70bdcdfd64 100644
--- a/app/core/WalletConnect/WalletConnect2Session.ts
+++ b/app/core/WalletConnect/WalletConnect2Session.ts
@@ -144,7 +144,6 @@ class WalletConnect2Session {
title: { current: name },
icon: { current: icons?.[0] as ImageSourcePropType },
toggleUrlModal: () => null,
- wizardScrollAdjusted: { current: false },
tabId: '',
isWalletConnect: true,
}),
diff --git a/app/reducers/index.ts b/app/reducers/index.ts
index e6b8600f536..485e55f19b1 100644
--- a/app/reducers/index.ts
+++ b/app/reducers/index.ts
@@ -8,7 +8,6 @@ import alertReducer from './alert';
import transactionReducer from './transaction';
import legalNoticesReducer from './legalNotices';
import userReducer, { UserState } from './user';
-import wizardReducer from './wizard';
import onboardingReducer, { OnboardingState } from './onboarding';
import fiatOrders from './fiatOrders';
import swapsReducer from './swaps';
@@ -88,7 +87,6 @@ export interface RootState {
user: UserState;
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- wizard: any;
onboarding: OnboardingState;
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -145,7 +143,6 @@ const baseReducers = {
alert: alertReducer,
transaction: transactionReducer,
user: userReducer,
- wizard: wizardReducer,
onboarding: onboardingReducer,
notification: notificationReducer,
signatureRequest: signatureRequestReducer,
diff --git a/app/reducers/wizard/index.js b/app/reducers/wizard/index.js
deleted file mode 100644
index 3633351ca74..00000000000
--- a/app/reducers/wizard/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { REHYDRATE } from 'redux-persist';
-
-const initialState = {
- step: 0,
-};
-
-const onboardingWizardReducer = (state = initialState, action) => {
- switch (action.type) {
- case REHYDRATE:
- return {
- ...initialState,
- };
- case 'SET_ONBOARDING_WIZARD_STEP':
- return {
- ...state,
- step: action.step,
- };
- default:
- return state;
- }
-};
-export default onboardingWizardReducer;
diff --git a/app/util/sentry/__snapshots__/utils.test.ts.snap b/app/util/sentry/__snapshots__/utils.test.ts.snap
index d55bc068069..2dce1f863eb 100644
--- a/app/util/sentry/__snapshots__/utils.test.ts.snap
+++ b/app/util/sentry/__snapshots__/utils.test.ts.snap
@@ -264,8 +264,5 @@ exports[`captureSentryFeedback maskObject masks initial root state fixture 1`] =
"seedphraseBackedUp": true,
"userLoggedIn": true,
},
- "wizard": {
- "step": 1,
- },
}
`;
diff --git a/app/util/sentry/utils.js b/app/util/sentry/utils.js
index 45b04415d3f..ceefbfcf888 100644
--- a/app/util/sentry/utils.js
+++ b/app/util/sentry/utils.js
@@ -247,7 +247,6 @@ export const sentryStateMask = {
seedphraseBackedUp: true,
userLoggedIn: true,
},
- wizard: true,
};
const METAMASK_ENVIRONMENT = process.env['METAMASK_ENVIRONMENT'] || 'local'; // eslint-disable-line dot-notation
diff --git a/app/util/sentry/utils.test.ts b/app/util/sentry/utils.test.ts
index 308136f1d6e..ae4f4dbacd7 100644
--- a/app/util/sentry/utils.test.ts
+++ b/app/util/sentry/utils.test.ts
@@ -480,9 +480,6 @@ describe('captureSentryFeedback', () => {
seedphraseBackedUp: true,
userLoggedIn: true,
},
- wizard: {
- step: 1,
- },
onboarding: {
events: [],
},
@@ -601,7 +598,6 @@ describe('captureSentryFeedback', () => {
transaction: 'object',
confirmationMetrics: 'object',
user: 'object',
- wizard: 'object',
});
});
@@ -642,7 +638,6 @@ describe('captureSentryFeedback', () => {
transaction: 'object',
confirmationMetrics: 'object',
user: 'object',
- wizard: 'object',
});
});
@@ -679,7 +674,6 @@ describe('captureSentryFeedback', () => {
transaction: 'object',
confirmationMetrics: 'object',
user: 'object',
- wizard: 'object',
});
});
diff --git a/app/util/test/initial-root-state.ts b/app/util/test/initial-root-state.ts
index f46af9b9190..0336604537a 100644
--- a/app/util/test/initial-root-state.ts
+++ b/app/util/test/initial-root-state.ts
@@ -33,7 +33,6 @@ const initialRootState: RootState = {
alert: undefined,
transaction: undefined,
user: userInitialState,
- wizard: undefined,
onboarding: initialOnboardingState,
notification: undefined,
swaps: undefined,
diff --git a/app/util/test/utils.js b/app/util/test/utils.js
index c5e12a09c1e..c93656eed99 100644
--- a/app/util/test/utils.js
+++ b/app/util/test/utils.js
@@ -31,3 +31,5 @@ export const isE2E =
export const enableApiCallLogs = process.env.LOG_API_CALLS === 'true';
export const getFixturesServerPortInApp = () =>
testConfig.fixtureServerPort ?? FIXTURE_SERVER_PORT;
+
+export const isRc = process.env.METAMASK_ENVIRONMENT === 'rc';
diff --git a/bitrise.yml b/bitrise.yml
index bcc36dad236..9632907ddd5 100644
--- a/bitrise.yml
+++ b/bitrise.yml
@@ -44,6 +44,12 @@ pipelines:
- bump_version_stage: {}
- create_build_beta: {}
- deploy_build_release: {}
+ #Releases MetaMask RC apps for release buil profiling
+ release_rc_builds_to_store_pipeline:
+ stages:
+ - bump_version_stage: {}
+ - create_build_rc: {}
+ - deploy_build_release: {}
#Releases MetaMask apps and stores ipa into App(TestFlight) Store
release_ios_to_store_pipeline:
stages:
@@ -112,7 +118,7 @@ pipelines:
- set_main_target_stage: {}
- pr_cache_check_stage: {}
- build_smoke_e2e_ios_android_stage: {}
- - run_smoke_e2e_performance_ios_android_stage: {}
+ # - run_smoke_e2e_performance_ios_android_stage: {}
- notify: {}
#PR_e2e_verfication (build ios & android), run iOS (regression), emulator Android
@@ -208,6 +214,10 @@ stages:
- build_ios_main_prod: {}
- build_ios_main_beta: {}
- build_ios_main_rc: {}
+ create_build_rc:
+ workflows:
+ - build_android_main_rc: {}
+ - build_ios_main_rc: {}
deploy_android_release:
workflows:
- deploy_android_to_store: {}
@@ -294,8 +304,9 @@ stages:
workflows:
- build_ios_main_e2e:
run_if: '{{getenv "SKIP_IOS_BUILD" | eq "false"}}'
- - build_android_main_e2e:
- run_if: '{{getenv "SKIP_ANDROID_BUILD" | eq "false"}}'
+ # Disabling in CI to allow GHA runs
+ # - build_android_main_e2e:
+ # run_if: '{{getenv "SKIP_ANDROID_BUILD" | eq "false"}}'
build_multichain_permissions_e2e_ios_android_stage:
abort_on_fail: true
workflows:
@@ -333,29 +344,30 @@ stages:
workflows:
- run_ios_api_specs: {}
- run_trade_swimlane_ios_smoke: {}
- - run_trade_swimlane_android_smoke: {}
+ # - run_trade_swimlane_android_smoke: {}
- run_network_abstraction_swimlane_ios_smoke: {}
- - run_network_abstraction_swimlane_android_smoke: {}
+ # - run_network_abstraction_swimlane_android_smoke: {}
- run_network_expansion_swimlane_ios_smoke: {}
- - run_network_expansion_swimlane_android_smoke: {}
+ # - run_network_expansion_swimlane_android_smoke: {}
- run_wallet_platform_swimlane_ios_smoke: {}
- - run_wallet_platform_swimlane_android_smoke: {}
+ # - run_wallet_platform_swimlane_android_smoke: {}
- run_tag_smoke_confirmations_ios: {}
- - run_tag_smoke_confirmations_android: {}
+ # - run_tag_smoke_confirmations_android: {}
- run_tag_smoke_identity_ios: {}
- - run_tag_smoke_identity_android: {}
+ # - run_tag_smoke_identity_android: {}
- run_tag_smoke_confirmations_redesigned_ios: {}
- run_tag_smoke_multichain_api_ios: {}
- run_tag_smoke_accounts_ios: {}
- - run_tag_smoke_accounts_android: {}
+ # - run_tag_smoke_accounts_android: {}
# - run_tag_smoke_performance_ios: {}
- - run_tag_smoke_performance_android: {}
+ # - run_tag_smoke_performance_android: {}
- run_tag_smoke_card_ios: {}
- - run_tag_smoke_card_android: {}
- run_smoke_e2e_performance_ios_android_stage:
- workflows:
+ # - run_tag_smoke_card_android: {}
+ # The entire workflow is disabled as Android runs on GHA and iOS was already skipped due to a regression
+ # run_smoke_e2e_performance_ios_android_stage:
+ # workflows:
# - run_tag_smoke_performance_ios: {}
- - run_tag_smoke_performance_android: {}
+ #- run_tag_smoke_performance_android: {}
# TODO: This stage does the same thing as build_smoke_e2e_ios_android_stage
build_regression_e2e_ios_android_stage:
abort_on_fail: true
diff --git a/docs/readme/release-build-profiler.md b/docs/readme/release-build-profiler.md
new file mode 100644
index 00000000000..21fd97e9b9a
--- /dev/null
+++ b/docs/readme/release-build-profiler.md
@@ -0,0 +1,47 @@
+## Release Build Profiling with `react-native-release-profiler`
+
+This guide covers building an RC (Release Candidate) release, recording a Hermes CPU profile in production conditions, retrieving the trace on iOS and Android, and viewing it in Chrome's tracing UI.
+
+### 1) Build an RC release
+
+- Create a branch off the commit you want to profile and bump the version to `7.XX.99`.
+- In Bitrise, trigger `release_rc_builds_to_store_pipeline` for that branch.
+
+#### iOS
+
+- Once `deploy_ios_to_store` completes, install the RC build from TestFlight.
+
+#### Android
+
+- Download and install the release APK from the `build_android_main_rc` workflow (Artifacts tab).
+
+### 2) Record a profiling session in the app
+
+On a physical device, open the Profiler UI (shake gesture), then:
+
+- Tap Start → reproduce the journey you want to measure → tap Stop.
+- iOS: tap Export to send the file to yourself (AirDrop, Files, iCloud, etc.).
+- Android: the `.cpuprofile` is written to the device's Downloads folder automatically.
+
+Notes:
+
+- Each session writes a unique file; previous traces are not overwritten.
+- iOS ignores "save to downloads" and always writes to Caches; use Export to share it.
+- Android copies to Downloads when profiling stops.
+
+### 3) Convert and view in Chrome tracing
+
+Chrome's tracing UI expects a JSON trace. Convert the `.cpuprofile` first:
+
+```bash
+npx react-native-release-profiler --local /path/to/profile.cpuprofile
+```
+
+Then open Chrome and load the generated JSON:
+
+- Navigate to `chrome://tracing` → Load → select the JSON file.
+
+Other viewers:
+
+- SpeedScope: open the `.cpuprofile` directly at https://www.speedscope.app
+- Perfetto UI: open the converted JSON at https://ui.perfetto.dev
diff --git a/e2e/api-mocking/mock-responses/cardholder-mocks.ts b/e2e/api-mocking/mock-responses/cardholder-mocks.ts
index e4ff877e5b9..d55b7a51882 100644
--- a/e2e/api-mocking/mock-responses/cardholder-mocks.ts
+++ b/e2e/api-mocking/mock-responses/cardholder-mocks.ts
@@ -1,5 +1,7 @@
import { CaipAccountId } from '@metamask/utils';
-import { TestSpecificMock } from '../../framework/types';
+import { MockApiEndpoint } from '../../framework/types';
+import { DEFAULT_FIXTURE_ACCOUNT } from '../../framework/fixtures/FixtureBuilder';
+import { mockEvents } from '../mock-config/mock-events';
/**
* Mock responses for cardholder API calls
@@ -10,10 +12,10 @@ import { TestSpecificMock } from '../../framework/types';
* Get cardholder API mocks with realistic responses
* @returns {CardholderApiMocks} Object containing GET mocks for cardholder APIs
*/
-export const getCardholderApiMocks = (
+const getCardholderApiMocks = (
caipAccountAddresses: CaipAccountId[],
cardholderAddresses?: CaipAccountId[],
-): TestSpecificMock => {
+): MockApiEndpoint => {
const url = new URL('v1/metadata', 'https://accounts.api.cx.metamask.io');
url.searchParams.set(
'accountIds',
@@ -22,14 +24,138 @@ export const getCardholderApiMocks = (
url.searchParams.set('label', 'card_user');
return {
- GET: [
- {
- urlEndpoint: url.toString(),
- response: {
- is: cardholderAddresses || caipAccountAddresses,
+ urlEndpoint: url.toString(),
+ response: {
+ is: cardholderAddresses || caipAccountAddresses,
+ },
+ responseCode: 200,
+ };
+};
+
+const cardApiMocks = getCardholderApiMocks([
+ `eip155:0:${DEFAULT_FIXTURE_ACCOUNT.toLowerCase()}`,
+]);
+
+export const testSpecificMock = {
+ GET: [
+ cardApiMocks,
+ {
+ urlEndpoint: 'https://on-ramp.dev-api.cx.metamask.io/geolocation',
+ response: 'PT',
+ responseCode: 200,
+ },
+ {
+ urlEndpoint:
+ 'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=main&environment=dev',
+ response: [
+ {
+ depositConfig: {
+ active: true,
+ entrypoints: {
+ walletActions: true,
+ },
+ minimumVersion: '1.0.0',
+ providerApiKey: 'DUMMY_VALUE',
+ providerFrontendAuth: 'DUMMY_VALUE',
+ },
+ cardFeature: {
+ constants: {
+ accountsApiUrl: 'https://accounts.api.cx.metamask.io',
+ onRampApiUrl: 'https://on-ramp.uat-api.cx.metamask.io',
+ },
+ chains: {
+ 'eip155:59144': {
+ tokens: [
+ {
+ symbol: 'USDC',
+ address: '0x176211869cA2b568f2A7D4EE941E073a821EE1ff',
+ decimals: 6,
+ enabled: true,
+ name: 'USD Coin',
+ },
+ {
+ enabled: true,
+ name: 'Tether USD',
+ symbol: 'USDT',
+ address: '0xA219439258ca9da29E9Cc4cE5596924745e12B93',
+ decimals: 6,
+ },
+ {
+ address: '0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f',
+ decimals: 18,
+ enabled: true,
+ name: 'Wrapped Ether',
+ symbol: 'WETH',
+ },
+ {
+ decimals: 18,
+ enabled: true,
+ name: 'EURe',
+ symbol: 'EURe',
+ address: '0x3ff47c5Bf409C86533FE1f4907524d304062428D',
+ },
+ {
+ name: 'GBPe',
+ symbol: 'GBPe',
+ address: '0x3Bce82cf1A2bc357F956dd494713Fe11DC54780f',
+ decimals: 18,
+ enabled: true,
+ },
+ {
+ decimals: 6,
+ enabled: true,
+ name: 'Aave USDC',
+ symbol: 'aUSDC',
+ address: '0x374D7860c4f2f604De0191298dD393703Cce84f3',
+ },
+ ],
+ balanceScannerAddress:
+ '0xed9f04f2da1b42ae558d5e688fe2ef7080931c9a',
+ enabled: true,
+ foxConnectAddresses: {
+ us: '0xA90b298d05C2667dDC64e2A4e17111357c215dD2',
+ global: '0x9dd23A4a0845f10d65D293776B792af1131c7B30',
+ },
+ },
+ },
+ },
},
- responseCode: 200,
+ ],
+ responseCode: 200,
+ },
+ ],
+ POST: [
+ mockEvents.POST.segmentTrack,
+ {
+ urlEndpoint: 'https://linea-mainnet.infura.io/v3/',
+ requestBody: {
+ method: 'eth_chainId',
+ params: [],
},
- ],
- };
+ ignoreFields: ['id', 'jsonrpc'],
+ response: { jsonrpc: '2.0', id: 42, result: '0xe708' },
+ responseCode: 200,
+ },
+ {
+ urlEndpoint: 'https://linea-mainnet.infura.io/v3/',
+ requestBody: {
+ method: 'eth_call',
+ params: [
+ {
+ to: '0xed9f04f2da1b42ae558d5e688fe2ef7080931c9a',
+ data: '0xda89f7dd00000000000000000000000076cf1cdd1fcc252442b50d6e97207228aa4aefc3000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000006000000000000000000000000176211869ca2b568f2a7d4ee941e073a821ee1ff000000000000000000000000a219439258ca9da29e9cc4ce5596924745e12b93000000000000000000000000e5d7c2a44ffddf6b295a15c148167daaaf5cf34f0000000000000000000000003ff47c5bf409c86533fe1f4907524d304062428d0000000000000000000000003bce82cf1a2bc357f956dd494713fe11dc54780f000000000000000000000000374d7860c4f2f604de0191298dd393703cce84f3000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009dd23a4a0845f10d65d293776b792af1131c7b30000000000000000000000000a90b298d05c2667ddc64e2a4e17111357c215dd200000000000000000000000000000000000000000000000000000000000000020000000000000000000000009dd23a4a0845f10d65d293776b792af1131c7b30000000000000000000000000a90b298d05c2667ddc64e2a4e17111357c215dd200000000000000000000000000000000000000000000000000000000000000020000000000000000000000009dd23a4a0845f10d65d293776b792af1131c7b30000000000000000000000000a90b298d05c2667ddc64e2a4e17111357c215dd200000000000000000000000000000000000000000000000000000000000000020000000000000000000000009dd23a4a0845f10d65d293776b792af1131c7b30000000000000000000000000a90b298d05c2667ddc64e2a4e17111357c215dd200000000000000000000000000000000000000000000000000000000000000020000000000000000000000009dd23a4a0845f10d65d293776b792af1131c7b30000000000000000000000000a90b298d05c2667ddc64e2a4e17111357c215dd200000000000000000000000000000000000000000000000000000000000000020000000000000000000000009dd23a4a0845f10d65d293776b792af1131c7b30000000000000000000000000a90b298d05c2667ddc64e2a4e17111357c215dd2',
+ },
+ 'latest',
+ ],
+ },
+ ignoreFields: ['id', 'jsonrpc'],
+ response: {
+ jsonrpc: '2.0',
+ id: 44,
+ result:
+ '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000',
+ },
+ responseCode: 200,
+ },
+ ],
};
diff --git a/e2e/api-mocking/mock-server.js b/e2e/api-mocking/mock-server.js
index 624432f2d1d..2f07178398b 100644
--- a/e2e/api-mocking/mock-server.js
+++ b/e2e/api-mocking/mock-server.js
@@ -104,24 +104,64 @@ export const startMockServer = async (events, port) => {
.thenCallback(async (request) => {
const urlEndpoint = new URL(request.url).searchParams.get('url');
const method = request.method;
+ // Read the body ONCE for POST requests to avoid stream exhaustion
+ let requestBodyText;
+ let requestBodyJson;
+ if (method === 'POST') {
+ try {
+ requestBodyText = await request.body.getText();
+ try {
+ requestBodyJson = JSON.parse(requestBodyText);
+ } catch (e) {
+ requestBodyJson = undefined;
+ }
+ } catch (e) {
+ requestBodyText = undefined;
+ }
+ }
// Find matching mock event
const methodEvents = events[method] || [];
- const matchingEvent = methodEvents.find(
- (event) => event.urlEndpoint === urlEndpoint,
- );
+ const candidateEvents = methodEvents.filter((event) => {
+ const eventUrl = event.urlEndpoint;
+ if (!eventUrl || !urlEndpoint) return false;
+ // Support exact match and prefix (partial) match to avoid leaking keys in tests
+ return urlEndpoint === eventUrl || urlEndpoint.startsWith(eventUrl);
+ });
+ let matchingEvent;
- if (matchingEvent) {
- console.log(`Mocking ${method} request to: ${urlEndpoint}`);
- console.log(`Response status: ${matchingEvent.responseCode}`);
- console.log('Response:', matchingEvent.response);
+ if (candidateEvents.length > 0) {
+ if (method === 'POST') {
+ // Prefer events whose requestBody matches (respecting ignoreFields)
+ matchingEvent = candidateEvents.find((event) => {
+ if (!event.requestBody || !requestBodyJson) return false;
+ const requestToCheck = _.cloneDeep(requestBodyJson);
+ const expectedRequest = _.cloneDeep(event.requestBody);
+ const ignoreFields = event.ignoreFields || [];
+ ignoreFields.forEach((field) => {
+ _.unset(requestToCheck, field);
+ _.unset(expectedRequest, field);
+ });
+ return _.isMatch(requestToCheck, expectedRequest);
+ });
+ // Fallback to an event without a requestBody matcher
+ if (!matchingEvent) {
+ matchingEvent = candidateEvents.find((event) => !event.requestBody);
+ }
+ } else {
+ // Non-POST requests: first candidate by URL
+ matchingEvent = candidateEvents[0];
+ }
+ }
+
+ if (matchingEvent) {
// For POST requests, verify the request body if specified
if (method === 'POST' && matchingEvent.requestBody) {
- const requestBodyJson = await request.body.getJson();
+ const parsedRequestBodyJson = requestBodyJson;
// Ensure both objects exist before comparison
- if (!requestBodyJson || !matchingEvent.requestBody) {
+ if (!parsedRequestBodyJson || !matchingEvent.requestBody) {
console.log('Request body validation failed: Missing request body');
return {
statusCode: 400,
@@ -130,7 +170,7 @@ export const startMockServer = async (events, port) => {
}
// Clone objects to avoid mutations
- const requestToCheck = _.cloneDeep(requestBodyJson);
+ const requestToCheck = _.cloneDeep(parsedRequestBodyJson);
const expectedRequest = _.cloneDeep(matchingEvent.requestBody);
const ignoreFields = matchingEvent.ignoreFields || [];
@@ -149,12 +189,15 @@ export const startMockServer = async (events, port) => {
'Expected:',
JSON.stringify(matchingEvent.requestBody, null, 2),
);
- console.log('Received:', JSON.stringify(requestBodyJson, null, 2));
+ console.log(
+ 'Received:',
+ JSON.stringify(parsedRequestBodyJson, null, 2),
+ );
console.log(
'Differences:',
JSON.stringify(
_.differenceWith(
- [requestBodyJson],
+ [parsedRequestBodyJson],
[matchingEvent.requestBody],
_.isEqual,
),
@@ -168,7 +211,7 @@ export const startMockServer = async (events, port) => {
body: JSON.stringify({
error: 'Request body validation failed',
expected: matchingEvent.requestBody,
- received: requestBodyJson,
+ received: parsedRequestBodyJson,
}),
};
}
@@ -191,13 +234,19 @@ export const startMockServer = async (events, port) => {
const errorMessage = `Request going to live server: ${updatedUrl}`;
logger.warn(errorMessage);
global.liveServerRequest = new Error(errorMessage);
+ } else if (ALLOWLISTED_URLS.includes(updatedUrl)) {
+ // Explicit debug to help with debugging in CI
+ console.warn(`Allowed URL: ${updatedUrl}`);
+ if (method === 'POST') {
+ console.warn(`Request Body: ${requestBodyText}`);
+ }
}
return handleDirectFetch(
updatedUrl,
method,
request.headers,
- method === 'POST' ? await request.body.getText() : undefined,
+ method === 'POST' ? requestBodyText : undefined,
);
});
@@ -208,6 +257,12 @@ export const startMockServer = async (events, port) => {
const errorMessage = `Request going to live server: ${request.url}`;
logger.warn(errorMessage);
global.liveServerRequest = new Error(errorMessage);
+ } else if (ALLOWLISTED_URLS.includes(request.url)) {
+ // Explicit debug to help with debugging in CI
+ console.warn(`Allowed URL: ${request.url}`);
+ if (request.method === 'POST') {
+ console.warn(`Request Body: ${await request.body.getText()}`);
+ }
}
return handleDirectFetch(
diff --git a/e2e/framework/fixtures/FixtureBuilder.ts b/e2e/framework/fixtures/FixtureBuilder.ts
index f922c9d3a1d..dc5514d8741 100644
--- a/e2e/framework/fixtures/FixtureBuilder.ts
+++ b/e2e/framework/fixtures/FixtureBuilder.ts
@@ -100,7 +100,6 @@ class FixtureBuilder {
this.fixture.asyncState = {
'@MetaMask:existingUser': 'true',
'@MetaMask:OptinMetaMetricsUISeen': 'true',
- '@MetaMask:onboardingWizard': 'explored',
'@MetaMask:UserTermsAcceptedv1.0': 'true',
'@MetaMask:WhatsNewAppVersionSeen': '7.24.3',
'@MetaMask:solanaFeatureModalShownV2': 'false',
@@ -559,9 +558,6 @@ class FixtureBuilder {
appTheme: 'os',
existingUser: true,
},
- wizard: {
- step: 0,
- },
onboarding: {
events: [],
},
@@ -733,7 +729,6 @@ class FixtureBuilder {
asyncState: {
'@MetaMask:existingUser': 'true',
'@MetaMask:OptinMetaMetricsUISeen': 'true',
- '@MetaMask:onboardingWizard': 'explored',
'@MetaMask:UserTermsAcceptedv1.0': 'true',
'@MetaMask:WhatsNewAppVersionSeen': '7.24.3',
'@MetaMask:solanaFeatureModalShownV2': 'true',
diff --git a/e2e/framework/types.ts b/e2e/framework/types.ts
index caed04b9fc6..869673cc75f 100644
--- a/e2e/framework/types.ts
+++ b/e2e/framework/types.ts
@@ -166,6 +166,8 @@ export interface TestSpecificMock {
export interface MockApiEndpoint {
urlEndpoint: string;
+ requestBody?: unknown;
+ ignoreFields?: string[];
response: unknown;
responseCode: number;
}
diff --git a/e2e/pages/Onboarding/OnboardingWizardModal.ts b/e2e/pages/Onboarding/OnboardingWizardModal.ts
deleted file mode 100644
index e3e094f646b..00000000000
--- a/e2e/pages/Onboarding/OnboardingWizardModal.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { OnboardingWizardModalSelectorsIDs } from '../../selectors/Onboarding/OnboardingWizardModal.selectors';
-import Matchers from '../../framework/Matchers';
-import Gestures from '../../framework/Gestures';
-
-class OnboardingWizardModal {
- get stepOneContainer(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.STEP_ONE_CONTAINER,
- );
- }
-
- get stepTwoContainer(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.STEP_TWO_CONTAINER,
- );
- }
-
- get stepThreeContainer(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.STEP_THREE_CONTAINER,
- );
- }
-
- get stepFourContainer(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.STEP_FOUR_CONTAINER,
- );
- }
-
- get stepFiveContainer(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.STEP_FIVE_CONTAINER,
- );
- }
-
- get stepSixContainer(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.STEP_SIX_CONTAINER,
- );
- }
-
- get stepSevenContainer(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.STEP_SEVENTH_CONTAINER,
- );
- }
-
- get noThanksButton(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.NO_THANKS_BUTTON,
- );
- }
-
- get takeTourButton(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.TAKE_TOUR_BUTTON,
- );
- }
-
- get gotItButton(): DetoxElement {
- return device.getPlatform() === 'ios'
- ? Matchers.getElementByID(OnboardingWizardModalSelectorsIDs.GOT_IT_BUTTON)
- : Matchers.getElementByLabel(
- OnboardingWizardModalSelectorsIDs.GOT_IT_BUTTON,
- );
- }
-
- get backButton(): DetoxElement {
- return Matchers.getElementByID(
- OnboardingWizardModalSelectorsIDs.BACK_BUTTON,
- );
- }
-
- async tapNoThanksButton(): Promise {
- await Gestures.waitAndTap(this.noThanksButton, {
- elemDescription: 'No Thanks Button in Onboarding Wizard Modal',
- });
- }
-
- async tapTakeTourButton(): Promise {
- await Gestures.waitAndTap(this.takeTourButton, {
- elemDescription: 'Take Tour Button in Onboarding Wizard Modal',
- });
- }
-
- async tapGotItButton(): Promise {
- await Gestures.waitAndTap(this.gotItButton, {
- elemDescription: 'Got It Button in Onboarding Wizard Modal',
- });
- }
-
- async tapBackButton(): Promise {
- await Gestures.waitAndTap(this.backButton, {
- elemDescription: 'Back Button in Onboarding Wizard Modal',
- });
- }
-}
-
-export default new OnboardingWizardModal();
diff --git a/e2e/pages/Ramps/BuildQuoteView.ts b/e2e/pages/Ramps/BuildQuoteView.ts
index 6769421ee76..09fb55cecbe 100644
--- a/e2e/pages/Ramps/BuildQuoteView.ts
+++ b/e2e/pages/Ramps/BuildQuoteView.ts
@@ -144,13 +144,33 @@ class BuildQuoteView {
});
}
- async tapPaymentMethodDropdown(paymentMethod: string): Promise {
+ async tapPaymentMethodDropdown(
+ paymentMethod: string | RegExp,
+ ): Promise {
const paymentMethodOption = Matchers.getElementByText(paymentMethod);
await Gestures.waitAndTap(paymentMethodOption, {
elemDescription: `Payment Method Dropdown (${paymentMethod}) in Build Quote View`,
});
}
+ async getPaymentMethodDropdownText(
+ regex: string | RegExp,
+ ): Promise {
+ try {
+ const elem = await Matchers.getElementByText(regex);
+ const attributes = await (
+ elem as unknown as IndexableNativeElement
+ ).getAttributes();
+ return (
+ (attributes as { text?: string; label?: string }).text ??
+ (attributes as { text?: string; label?: string }).label
+ );
+ } catch (error) {
+ // Purposefully returning undefined to use in an assertion
+ return undefined;
+ }
+ }
+
async tapRegionSelector() {
await Gestures.waitAndTap(this.regionDropdown, {
elemDescription: 'Region Dropdown in Build Quote View',
diff --git a/e2e/selectors/Onboarding/OnboardingWizardModal.selectors.js b/e2e/selectors/Onboarding/OnboardingWizardModal.selectors.js
deleted file mode 100644
index 8d077c8544b..00000000000
--- a/e2e/selectors/Onboarding/OnboardingWizardModal.selectors.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export const OnboardingWizardModalSelectorsIDs = {
- STEP_ONE_CONTAINER: 'onboarding-wizard-step1-container-id',
- STEP_TWO_CONTAINER: 'onboarding-wizard-step2-content',
- STEP_THREE_CONTAINER: 'onboarding-wizard-step3-content',
- STEP_FOUR_CONTAINER: 'onboarding-wizard-step4-content',
- STEP_FIVE_CONTAINER: 'onboarding-wizard-step5-content',
- STEP_SIX_CONTAINER: 'onboarding-wizard-step6-content',
- STEP_SEVENTH_CONTAINER: 'onboarding-wizard-step7-content',
- NO_THANKS_BUTTON: 'onboarding-wizard-no-thanks-button',
- TAKE_TOUR_BUTTON: 'onboarding-wizard-next-button',
- GOT_IT_BUTTON: 'onboarding-wizard-got-it-button',
- BACK_BUTTON: 'onboarding-wizard-back-button',
-};
diff --git a/e2e/specs/card/card-button.spec.ts b/e2e/specs/card/card-button.spec.ts
index e8e98d1256d..0787a32f225 100644
--- a/e2e/specs/card/card-button.spec.ts
+++ b/e2e/specs/card/card-button.spec.ts
@@ -3,26 +3,15 @@ import { SmokeCard } from '../../tags';
import Assertions from '../../framework/Assertions';
import { loginToApp } from '../../viewHelper';
import { withFixtures } from '../../framework/fixtures/FixtureHelper';
-import FixtureBuilder, {
- DEFAULT_FIXTURE_ACCOUNT,
-} from '../../framework/fixtures/FixtureBuilder';
-import { getCardholderApiMocks } from '../../api-mocking/mock-responses/cardholder-mocks';
+import FixtureBuilder from '../../framework/fixtures/FixtureBuilder';
+import { testSpecificMock } from '../../api-mocking/mock-responses/cardholder-mocks';
import { EventPayload, getEventsPayloads } from '../analytics/helpers';
-import { mockEvents } from '../../api-mocking/mock-config/mock-events';
import CardHomeView from '../../pages/Card/CardHomeView';
import SoftAssert from '../../utils/SoftAssert';
import { CustomNetworks } from '../../resources/networks.e2e';
-const cardApiMocks = getCardholderApiMocks([
- `eip155:0:${DEFAULT_FIXTURE_ACCOUNT.toLowerCase()}`,
-]);
-
describe(SmokeCard('Card NavBar Button'), () => {
const eventsToCheck: EventPayload[] = [];
- const cardholderApiWithSegmentMock = {
- ...cardApiMocks,
- POST: [mockEvents.POST.segmentTrack],
- };
const setupCardTest = async (testFunction: () => Promise) => {
await withFixtures(
@@ -32,7 +21,7 @@ describe(SmokeCard('Card NavBar Button'), () => {
.withNetworkController(CustomNetworks.Tenderly.Linea)
.build(),
restartDevice: true,
- testSpecificMock: cardholderApiWithSegmentMock,
+ testSpecificMock,
endTestfn: async ({ mockServer: mockServerInstance }) => {
const events = await getEventsPayloads(mockServerInstance);
eventsToCheck.push(...events);
diff --git a/e2e/specs/card/card-home-add-funds.spec.ts b/e2e/specs/card/card-home-add-funds.spec.ts
index acbc8b368b9..d5b1faec07e 100644
--- a/e2e/specs/card/card-home-add-funds.spec.ts
+++ b/e2e/specs/card/card-home-add-funds.spec.ts
@@ -3,26 +3,15 @@ import { SmokeCard } from '../../tags';
import Assertions from '../../framework/Assertions';
import { loginToApp } from '../../viewHelper';
import { withFixtures } from '../../framework/fixtures/FixtureHelper';
-import FixtureBuilder, {
- DEFAULT_FIXTURE_ACCOUNT,
-} from '../../framework/fixtures/FixtureBuilder';
-import { getCardholderApiMocks } from '../../api-mocking/mock-responses/cardholder-mocks';
+import FixtureBuilder from '../../framework/fixtures/FixtureBuilder';
+import { testSpecificMock } from '../../api-mocking/mock-responses/cardholder-mocks';
import { EventPayload, getEventsPayloads } from '../analytics/helpers';
-import { mockEvents } from '../../api-mocking/mock-config/mock-events';
import CardHomeView from '../../pages/Card/CardHomeView';
import SoftAssert from '../../utils/SoftAssert';
import { CustomNetworks } from '../../resources/networks.e2e';
-const cardApiMocks = getCardholderApiMocks([
- `eip155:0:${DEFAULT_FIXTURE_ACCOUNT.toLowerCase()}`,
-]);
-
describe(SmokeCard('CardHome - Add Funds'), () => {
const eventsToCheck: EventPayload[] = [];
- const cardholderApiWithSegmentMock = {
- ...cardApiMocks,
- POST: [mockEvents.POST.segmentTrack],
- };
const setupCardTest = async (testFunction: () => Promise) => {
await withFixtures(
@@ -32,7 +21,7 @@ describe(SmokeCard('CardHome - Add Funds'), () => {
.withNetworkController(CustomNetworks.Tenderly.Linea)
.build(),
restartDevice: true,
- testSpecificMock: cardholderApiWithSegmentMock,
+ testSpecificMock,
endTestfn: async ({ mockServer: mockServerInstance }) => {
const events = await getEventsPayloads(mockServerInstance);
eventsToCheck.push(...events);
diff --git a/e2e/specs/card/card-home-manage-card.spec.ts b/e2e/specs/card/card-home-manage-card.spec.ts
index 2e037475514..d411f36bc0e 100644
--- a/e2e/specs/card/card-home-manage-card.spec.ts
+++ b/e2e/specs/card/card-home-manage-card.spec.ts
@@ -3,26 +3,15 @@ import { SmokeCard } from '../../tags';
import Assertions from '../../framework/Assertions';
import { loginToApp } from '../../viewHelper';
import { withFixtures } from '../../framework/fixtures/FixtureHelper';
-import FixtureBuilder, {
- DEFAULT_FIXTURE_ACCOUNT,
-} from '../../framework/fixtures/FixtureBuilder';
-import { getCardholderApiMocks } from '../../api-mocking/mock-responses/cardholder-mocks';
+import FixtureBuilder from '../../framework/fixtures/FixtureBuilder';
+import { testSpecificMock } from '../../api-mocking/mock-responses/cardholder-mocks';
import { EventPayload, getEventsPayloads } from '../analytics/helpers';
-import { mockEvents } from '../../api-mocking/mock-config/mock-events';
import CardHomeView from '../../pages/Card/CardHomeView';
import SoftAssert from '../../utils/SoftAssert';
import { CustomNetworks } from '../../resources/networks.e2e';
-const cardApiMocks = getCardholderApiMocks([
- `eip155:0:${DEFAULT_FIXTURE_ACCOUNT.toLowerCase()}`,
-]);
-
describe(SmokeCard('CardHome - Manage Card'), () => {
const eventsToCheck: EventPayload[] = [];
- const cardholderApiWithSegmentMock = {
- ...cardApiMocks,
- POST: [mockEvents.POST.segmentTrack],
- };
const setupCardTest = async (testFunction: () => Promise) => {
await withFixtures(
@@ -32,7 +21,7 @@ describe(SmokeCard('CardHome - Manage Card'), () => {
.withNetworkController(CustomNetworks.Tenderly.Linea)
.build(),
restartDevice: true,
- testSpecificMock: cardholderApiWithSegmentMock,
+ testSpecificMock,
endTestfn: async ({ mockServer: mockServerInstance }) => {
const events = await getEventsPayloads(mockServerInstance);
eventsToCheck.push(...events);
diff --git a/e2e/specs/onboarding/onboarding-wizard-opt-in.spec.ts b/e2e/specs/onboarding/onboarding-wizard-opt-in.spec.ts
index 8a7053ecfa5..0dfbe171520 100644
--- a/e2e/specs/onboarding/onboarding-wizard-opt-in.spec.ts
+++ b/e2e/specs/onboarding/onboarding-wizard-opt-in.spec.ts
@@ -28,9 +28,7 @@ const testSpecificMock = {
};
describe(
- Regression(
- 'Onboarding wizard opt-in, metametrics opt out from settings WITH ANALYTICS',
- ),
+ Regression('Regression - metametrics opt out from settings WITH ANALYTICS'),
() => {
let mockServer: MockttpServer;
let eventsBeforeDisablingAnalytics: EventPayload[];
diff --git a/e2e/specs/ramps/onramp-parameters.spec.ts b/e2e/specs/ramps/onramp-parameters.spec.ts
index 9e53beba71e..00e7f60e6a4 100644
--- a/e2e/specs/ramps/onramp-parameters.spec.ts
+++ b/e2e/specs/ramps/onramp-parameters.spec.ts
@@ -19,6 +19,7 @@ import { EventPayload, getEventsPayloads } from '../analytics/helpers';
import SoftAssert from '../../utils/SoftAssert';
import { RampsRegions, RampsRegionsEnum } from '../../framework/Constants';
import { Mockttp } from 'mockttp';
+import Matchers from '../../framework/Matchers';
let mockServer: Mockttp;
let mockServerPort: number;
@@ -98,10 +99,14 @@ describe(SmokeTrade('On-Ramp Parameters'), () => {
it('should select payment method and verify display', async () => {
await setupOnRampTest(async () => {
const paymentMethod =
- device.getPlatform() === 'ios' ? 'Apple Pay' : 'Google Pay';
+ device.getPlatform() === 'ios'
+ ? 'Apple Pay'
+ : /^(?:Google|Revolut)\s+Pay$/i;
await BuildQuoteView.tapPaymentMethodDropdown(paymentMethod);
await SelectPaymentMethodView.tapPaymentMethodOption('Debit or Credit');
- await Assertions.expectTextNotDisplayed(paymentMethod);
+ await Assertions.expectElementToNotBeVisible(
+ Matchers.getElementByText(paymentMethod),
+ );
await Assertions.expectTextDisplayed('Debit or Credit');
});
});
diff --git a/e2e/specs/solana/solana-send-flow.spec.ts b/e2e/specs/solana/solana-send-flow.spec.ts
index 026dc9ee861..f8291aa44bf 100644
--- a/e2e/specs/solana/solana-send-flow.spec.ts
+++ b/e2e/specs/solana/solana-send-flow.spec.ts
@@ -54,7 +54,8 @@ describe(SmokeNetworkExpansion('Solana Token Transfer Functionality'), () => {
await SnapSendActionSheet.tapCancelButton();
});
- it('should successfully transfer SOL to a valid recipient address', async () => {
+ // Skipped due to the test being targetting a live network. This should be re-enabled once we have local Solana node
+ it.skip('should successfully transfer SOL to a valid recipient address', async () => {
await WalletView.tapWalletSendButton();
await SnapSendActionSheet.sendActionInputAddress(RECIPIENT_ADDRESS);
await SnapSendActionSheet.sendActionInputAmount(TRANSFER_AMOUNT);
@@ -67,7 +68,8 @@ describe(SmokeNetworkExpansion('Solana Token Transfer Functionality'), () => {
await Assertions.expectTextDisplayed(EXPECTED_CONFIRMATION);
});
- it('should verify that transaction is sent successfully', async () => {
+ // Skipped due to the test being targetting a live network. This should be re-enabled once we have local Solana node
+ it.skip('should verify that transaction is sent successfully', async () => {
await SnapSendActionSheet.tapCloseButton();
await TabBarComponent.tapActivity();
await ActivitiesView.tapOnTransactionItem(RECENT_TRANSACTION_INDEX);
diff --git a/e2e/viewHelper.ts b/e2e/viewHelper.ts
index 26b0a1258b0..40a1872c77f 100644
--- a/e2e/viewHelper.ts
+++ b/e2e/viewHelper.ts
@@ -216,7 +216,6 @@ export const importWalletWithRecoveryPhrase = async ({
//'Should dismiss Enable device Notifications checks alert'
// await skipNotificationsDeviceSettings();
- // should dismiss the onboarding wizard
// dealing with flakiness on bitrise.
await closeOnboardingModals(fromResetWallet);
};
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index e6482750124..cd802ad71fe 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -260,6 +260,8 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - ExpoSensors (14.0.2):
+ - ExpoModulesCore
- ExpoWebBrowser (14.0.2):
- ExpoModulesCore
- EXUpdatesInterface (1.0.0):
@@ -1850,6 +1852,27 @@ PODS:
- Yoga
- react-native-randombytes (3.6.1):
- React-Core
+ - react-native-release-profiler (0.4.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.10.14.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- react-native-render-html (6.3.4):
- React-Core
- react-native-safe-area-context (5.4.0):
@@ -2777,6 +2800,7 @@ DEPENDENCIES:
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLinking (from `../node_modules/expo-linking/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
+ - ExpoSensors (from `../node_modules/expo-sensors/ios`)
- ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`)
- EXUpdatesInterface (from `../node_modules/expo-updates-interface/ios`)
- fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
@@ -2843,6 +2867,7 @@ DEPENDENCIES:
- react-native-quick-base64 (from `../node_modules/react-native-quick-base64`)
- react-native-quick-crypto (from `../node_modules/react-native-quick-crypto`)
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
+ - react-native-release-profiler (from `../node_modules/react-native-release-profiler`)
- react-native-render-html (from `../node_modules/react-native-render-html`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
@@ -2975,6 +3000,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-linking/ios"
ExpoModulesCore:
:path: "../node_modules/expo-modules-core"
+ ExpoSensors:
+ :path: "../node_modules/expo-sensors/ios"
ExpoWebBrowser:
:path: "../node_modules/expo-web-browser/ios"
EXUpdatesInterface:
@@ -3096,6 +3123,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-quick-crypto"
react-native-randombytes:
:path: "../node_modules/react-native-randombytes"
+ react-native-release-profiler:
+ :path: "../node_modules/react-native-release-profiler"
react-native-render-html:
:path: "../node_modules/react-native-render-html"
react-native-safe-area-context:
@@ -3250,6 +3279,7 @@ SPEC CHECKSUMS:
ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680
ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402
ExpoModulesCore: c25d77625038b1968ea1afefc719862c0d8dd993
+ ExpoSensors: 02a52ddab1e3a8a1438258c3d87d1ee5f721743a
ExpoWebBrowser: a212e6b480d8857d3e441fba51e0c968333803b3
EXUpdatesInterface: 7c977640bdd8b85833c19e3959ba46145c5719db
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
@@ -3326,6 +3356,7 @@ SPEC CHECKSUMS:
react-native-quick-base64: 17dc4b8daee50e680d5f57cc3ee6773b6ee0110a
react-native-quick-crypto: 7a9ba486072f5081c908d487d520aba218409f76
react-native-randombytes: 3c8f3e89d12487fd03a2f966c288d495415fc116
+ react-native-release-profiler: fdca7c73a6e6a03fa2a343f5088fce4787e8d4ee
react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd
react-native-safe-area-context: c68127652d8b9a26a28ac9597167a3ad90bcd713
react-native-slider: e4b7f9d0616032ec2909ba073731eabcde242256
diff --git a/locales/languages/en.json b/locales/languages/en.json
index 3ce5b3cb175..921ee11fcb6 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -4568,6 +4568,8 @@
"add_bitcoin_account": "Bitcoin account",
"add_solana_account": "Solana account",
"switch_to_smart_account": "Switch to Smart account",
+ "rename_account": "Rename account",
+ "addresses": "Addresses",
"headers": {
"bitcoin": "Bitcoin",
"solana": "Solana"
diff --git a/package.json b/package.json
index c9aa6a96b78..462419a5720 100644
--- a/package.json
+++ b/package.json
@@ -360,6 +360,7 @@
"expo-build-properties": "~0.13.2",
"expo-dev-client": "~5.0.18",
"expo-file-system": "~18.0.7",
+ "expo-sensors": "~14.0.2",
"fast-equals": "^5.2.2",
"fuse.js": "3.4.4",
"he": "^1.2.0",
@@ -427,6 +428,7 @@
"react-native-quick-crypto": "^0.7.15",
"react-native-randombytes": "^3.5.3",
"react-native-reanimated": "^3.17.2",
+ "react-native-release-profiler": "^0.4.0",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "3.37.0",
diff --git a/wdio/features/Wallet/ExploringWizard.feature b/wdio/features/Wallet/ExploringWizard.feature
deleted file mode 100644
index 86ef77daed7..00000000000
--- a/wdio/features/Wallet/ExploringWizard.feature
+++ /dev/null
@@ -1,50 +0,0 @@
-@androidApp
-@regression
-@wallet
-Feature: Exploring wizard
-
- Scenario: A user should be able to go through the start exploring tutorial
- Given the app displayed the splash animation
- And I have imported my wallet
- And I tap No Thanks on the Enable security check screen
- And the onboarding wizard is visible on wallet view
- When On the onboarding wizard I tap on "Take a Tour" button
- Then the tutorial modal heading should read "Your accounts"
- And there should be an explanation of the accounts functionality.
- When On the onboarding wizard I tap on "Got it" button
- Then the tutorial modal heading should read "Managing your account"
- And there should be an explanation about adding a nickname to your account.
- When On the onboarding wizard I tap on "Got it" button
- Then the tutorial modal heading should read "Using your wallet"
- And there should be an explanation of the what exists within the main menu.
- When On the onboarding wizard I tap on "Got it" button
- Then the tutorial modal heading should read "Exploring web3"
- And there should be an explanation of the what the purpose of the browser.
- When On the onboarding wizard I tap on "Back" button
- Then the tutorial modal heading should read "Using your wallet"
- And there should be an explanation of the what exists within the main menu.
- When On the onboarding wizard I tap on "Got it" button
- Then the tutorial modal heading should read "Exploring web3"
- And there should be an explanation of the what the purpose of the browser.
- When On the onboarding wizard I tap on "Got it" button
- Then the tutorial modal heading should read "Using the browser"
- And there should be an explanation of the what the purpose of the search input box.
- When On the onboarding wizard I tap on "Got it" button
- Then the onboarding wizard is no longer visible
-
-
-# Scenario: A user should be able to tap the Skip button
-# Given the app displayed the splash animation
-# And I have imported my wallet
-# And I tap No Thanks on the Enable security check screen
-# And the onboarding wizard is visible on wallet view
-# When On the onboarding wizard I tap on "Take a Tour" button
-# Then the tutorial modal heading should read "Your Accounts"
-# And there should be an explanation of the accounts functionality.
-# When On the onboarding wizard I tap on "Got it" button
-# Then the tutorial modal heading should read "Edit Account Name"
-# And there should be an explanation about adding a nickname to your account.
-# When On the onboarding wizard I tap on "Skip" button
-# Then the onboarding wizard is no longer visible
-# And the "Skip" button is no longer visible
-#
diff --git a/wdio/screen-objects/Modals/OnboardingWizardModal.js b/wdio/screen-objects/Modals/OnboardingWizardModal.js
deleted file mode 100644
index 58c9ed18343..00000000000
--- a/wdio/screen-objects/Modals/OnboardingWizardModal.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { OnboardingWizardModalSelectorsIDs } from '../../../e2e/selectors/Onboarding/OnboardingWizardModal.selectors';
-import Selectors from '../../helpers/Selectors';
-import Gestures from '../../helpers/Gestures';
-
-class OnboardingWizardModal {
- get container() {
- return Selectors.getXpathElementByResourceId(
- OnboardingWizardModalSelectorsIDs.STEP_ONE_CONTAINER,
- );
- }
-
- get noThanksButton() {
- return Selectors.getXpathElementByResourceId(
- OnboardingWizardModalSelectorsIDs.NO_THANKS_BUTTON,
- );
- }
-
- get backButton() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.BACK_BUTTON
- );
- }
-
- get takeTourButton() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.TAKE_TOUR_BUTTON,
- );
- }
-
- get step2Description() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.STEP_TWO_CONTAINER,
- );
- }
-
- get step3Description() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.STEP_THREE_CONTAINER,
- );
- }
-
- get step4Description() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.STEP_FOUR_CONTAINER,
- );
- }
-
- get step5Description() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.STEP_FIVE_CONTAINER,
- );
- }
-
- get step6Description() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.STEP_SIX_CONTAINER,
- );
- }
-
- get onBoardingWizardGotItButton() {
- return Selectors.getElementByPlatform(
- OnboardingWizardModalSelectorsIDs.GOT_IT_BUTTON
- );
- }
-
- async isVisible() {
- await expect(this.container).toBeDisplayed();
- }
-
- async isNotVisible() {
- await expect(this.container).not.toBeDisplayed();
- }
-
- async tapNoThanksButton() {
- await Gestures.waitAndTap(this.noThanksButton);
- }
-
- async tapTakeTourButton() {
- await Gestures.waitAndTap(this.takeTourButton);
- }
-
- async isHeaderDisplayedByXPath(text) {
- await expect(await Selectors.getXpathElementByText(text)).toBeDisplayed();
- }
-
- async isStep2ContentDisplayed() {
- await expect(await this.step2Description).toBeDisplayed();
- }
-
- async isStep3ContentDisplayed() {
- await expect(await this.step3Description).toBeDisplayed();
- }
-
- async isStep4ContentDisplayed() {
- await expect(await this.step4Description).toBeDisplayed();
- }
-
- async isStep5ContentDisplayed() {
- await expect(await this.step5Description).toBeDisplayed();
- }
-
- async isStep6ContentDisplayed() {
- await expect(await this.step6Description).toBeDisplayed();
- }
-
- async tapGotItButton() {
- await Gestures.tap(this.onBoardingWizardGotItButton);
- }
-
- async isGotItButtonNotDisplayed() {
- await expect(this.onBoardingWizardGotItButton).not.toBeExisting();
- }
-
- async tapBackButton() {
- await Gestures.waitAndTap(this.backButton);
- }
-}
-
-export default new OnboardingWizardModal();
diff --git a/wdio/step-definitions/common-steps.js b/wdio/step-definitions/common-steps.js
index f4dcf38ce16..e548d78b60e 100644
--- a/wdio/step-definitions/common-steps.js
+++ b/wdio/step-definitions/common-steps.js
@@ -9,7 +9,6 @@ import CreateNewWalletScreen from '../screen-objects/Onboarding/CreateNewWalletS
import WalletMainScreen from '../screen-objects/WalletMainScreen';
import CommonScreen from '../screen-objects/CommonScreen';
import SkipAccountSecurityModal from '../screen-objects/Modals/SkipAccountSecurityModal.js';
-import OnboardingWizardModal from '../screen-objects/Modals/OnboardingWizardModal.js';
import LoginScreen from '../screen-objects/LoginScreen';
import TermOfUseScreen from '../screen-objects/Modals/TermOfUseScreen';
import WhatsNewModal from '../screen-objects/Modals/WhatsNewModal';
@@ -126,10 +125,8 @@ Given(/^I import wallet using seed phrase "([^"]*)?"/, async (phrase) => {
});
Given(/^I tap No thanks on the onboarding welcome tutorial/, async () => {
- await OnboardingWizardModal.isVisible();
const setTimeout = 1500;
await driver.pause(setTimeout);
- await OnboardingWizardModal.tapNoThanksButton();
});
Then(/^"([^"]*)?" is visible/, async (text) => {
@@ -321,5 +318,4 @@ Then(/^I use the back button on Android$/, async () => {
Given(/^I dismiss the Solana New Feature Sheet$/, async () => {
await SolanaNewFeatureSheet.isVisible();
await SolanaNewFeatureSheet.tapNotNowButton();
- await OnboardingWizardModal.isNotVisible();
});
diff --git a/wdio/step-definitions/onboarding.steps.js b/wdio/step-definitions/onboarding.steps.js
index 1ac53d8190e..6c9327ad947 100644
--- a/wdio/step-definitions/onboarding.steps.js
+++ b/wdio/step-definitions/onboarding.steps.js
@@ -6,7 +6,6 @@ import OnboardingScreen from '../screen-objects/Onboarding/OnboardingScreen.js';
import WelcomeScreen from '../screen-objects/Onboarding/OnboardingCarousel.js';
import SkipAccountSecurityModal from '../screen-objects/Modals/SkipAccountSecurityModal.js';
-import OnboardingWizardModal from '../screen-objects/Modals/OnboardingWizardModal.js';
import AddressBarScreen from '../screen-objects/BrowserObject/AddressBarScreen';
import CreatePasswordScreen from '../screen-objects/Onboarding/CreatePasswordScreen.js';
import OnboardingSucessScreen from '../screen-objects/OnboardingSucessScreen.js';
@@ -61,9 +60,6 @@ When(/^I tap "([^"]*)"/, async (text) => {
case 'Create Password':
await CreatePasswordScreen.tapCreatePasswordButton();
break;
- case 'No, Thanks':
- await OnboardingWizardModal.tapNoThanksButton();
- break;
case 'https://uniswap.exchange':
await AddressBarScreen.tapUniswapSuggestionButton();
break;
diff --git a/wdio/step-definitions/start-exploring.steps.js b/wdio/step-definitions/start-exploring.steps.js
deleted file mode 100644
index d8ec1286836..00000000000
--- a/wdio/step-definitions/start-exploring.steps.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Given, Then, When } from '@wdio/cucumber-framework';
-import OnboardingWizardModal from '../screen-objects/Modals/OnboardingWizardModal.js';
-import WalletAccountModal from '../screen-objects/Modals/WalletAccountModal.js';
-
-Given(/^the onboarding wizard is visible on wallet view$/, async () => {
- await OnboardingWizardModal.isVisible();
-});
-
-When(/^On the onboarding wizard I tap on "([^"]*)" button$/, async (text) => {
- switch (text) {
- case 'Take a Tour':
- await OnboardingWizardModal.tapTakeTourButton();
- break;
- case 'Got it':
- await OnboardingWizardModal.tapGotItButton();
- break;
- case 'Back':
- await OnboardingWizardModal.tapBackButton();
- break;
- default:
- throw new Error('Button not found');
- }
-});
-
-When(/^I tap and hold on the account Name$/, async () => {
- await WalletAccountModal.longPressAccountNameLabel();
-});
-
-When(/^I enter "([^"]*)" for account name$/, async (text) => {
- await WalletAccountModal.editAccountNameLabel(text);
-});
-
-Then(/^the tutorial modal heading should read "([^"]*)"$/, async (text) => {
- await OnboardingWizardModal.isHeaderDisplayedByXPath(text);
-});
-
-Then(
- /^there should be an explanation of the accounts functionality.$/,
- async () => {
- await OnboardingWizardModal.isStep2ContentDisplayed();
- },
-);
-
-Then(
- /^there should be an explanation about adding a nickname to your account.$/,
- async () => {
- await OnboardingWizardModal.isStep3ContentDisplayed();
- },
-);
-
-Then(/^I should be able to edit the account Name$/, async () => {
- await WalletAccountModal.isAccountNameLabelEditable();
-});
-
-Then(/^the account nickname should read "([^"]*)"$/, async (text) => {
- await WalletAccountModal.isAccountInputLabelEqualTo(text);
-});
-
-Then(
- /^there should be an explanation of the what exists within the main menu.$/,
- async () => {
- await OnboardingWizardModal.isStep4ContentDisplayed();
- },
-);
-
-Then(
- /^there should be an explanation of the what the purpose of the browser.$/,
- async () => {
- await OnboardingWizardModal.isStep5ContentDisplayed();
- },
-);
-
-Then(
- /^there should be an explanation of the what the purpose of the search input box.$/,
- async () => {
- await OnboardingWizardModal.isStep6ContentDisplayed();
- },
-);
-
-Then(/^the onboarding wizard is no longer visible$/, async () => {
- await OnboardingWizardModal.isGotItButtonNotDisplayed();
-});
diff --git a/yarn.lock b/yarn.lock
index c06d32b9366..191b37475e2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4744,6 +4744,13 @@
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.8.0.tgz#fc32e07746689459c4b049dc581d1dbda5545686"
integrity sha512-+70fkgjhVJeJ+nJqnburIM3UAsfvxat1Low9HMPobLbv64FIdB4Nzu5ct3qojNQ58r5sK01tg5UoFIJYslaVrg==
+"@margelo/hermes-profile-transformer@^0.0.10":
+ version "0.0.10"
+ resolved "https://registry.yarnpkg.com/@margelo/hermes-profile-transformer/-/hermes-profile-transformer-0.0.10.tgz#2694437e056bfcef0e46fd2373f3cda92c1b115c"
+ integrity sha512-mQp4pMbdBRUPNdgXEt0H+VlQWdEOpvaImP5HBgwY0dsFLxN/qnCGZoERe5kyN5q5x5GT5bj9kmZPt5V7EMhfRA==
+ dependencies:
+ source-map "^0.7.3"
+
"@metamask/abi-utils@^2.0.3":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@metamask/abi-utils/-/abi-utils-2.0.4.tgz#20908c1d910f7a17a89fdf5778a5c59d5cb8b8be"
@@ -16762,6 +16769,11 @@ commander@^10.0.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
+commander@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906"
+ integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
+
commander@^12.0.0, commander@^12.1.0:
version "12.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
@@ -19910,6 +19922,13 @@ expo-modules-core@2.2.3:
dependencies:
invariant "^2.2.4"
+expo-sensors@~14.0.2:
+ version "14.0.2"
+ resolved "https://registry.yarnpkg.com/expo-sensors/-/expo-sensors-14.0.2.tgz#bf2cfadcb17f396e0447aaa5405d1d90d6e46e95"
+ integrity sha512-nCb1Q3ctb0oVTZ9p6eFmQ2fINa6KoxXXIhagPpdN0qR82p00YosP27IuyxjVB3fnCJFeC4TffNxNjBxwAUk+nA==
+ dependencies:
+ invariant "^2.2.4"
+
expo-updates-interface@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-1.0.0.tgz#b98c66b800d29561c62409556948b2af3d5316e5"
@@ -27787,6 +27806,14 @@ react-native-redash@^18.1.3:
normalize-svg-path "^1.0.1"
parse-svg-path "^0.1.2"
+react-native-release-profiler@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/react-native-release-profiler/-/react-native-release-profiler-0.4.0.tgz#43c315754fa69fa4d9dedf6cd6922474e7a0ee16"
+ integrity sha512-CqtqI2Gyuu6HpQTDpsJEKQjvsyPUjNuG267EKZNCObTsNbTIeKrEPX+/Z86tmPN3qYTfIsqgcT4e06mR8zVo9A==
+ dependencies:
+ "@margelo/hermes-profile-transformer" "^0.0.10"
+ commander "^11.1.0"
+
react-native-render-html@^6.3.4:
version "6.3.4"
resolved "https://registry.yarnpkg.com/react-native-render-html/-/react-native-render-html-6.3.4.tgz#01684897bed2de84829e540a1dbb3a7bdf9d0e57"
@@ -29656,6 +29683,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+source-map@^0.7.3:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02"
+ integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==
+
spawn-wrap@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e"