diff --git a/.github/scripts/e2e-extract-test-results.mjs b/.github/scripts/e2e-extract-test-results.mjs new file mode 100644 index 000000000000..cdaa551f0d80 --- /dev/null +++ b/.github/scripts/e2e-extract-test-results.mjs @@ -0,0 +1,209 @@ +#!/usr/bin/env node + +/** + * Extracts the paths of test files that passed and failed in a previous run. + * On re-run, we only run failed tests - passed tests are skipped and + * tests that were never executed stay in the queue. + * + * A test file is only considered "passed" if ALL of its test cases pass. + * This handles files with multiple test cases where some pass and some fail. + * + * Note: jest-junit is configured with classNameTemplate: '{filepath}' + * which puts the file path in each testcase's classname attribute. + */ + +import fs from 'node:fs'; +import path from 'node:path'; +import xml2js from 'xml2js'; + +const xmlParser = new xml2js.Parser(); + +/** + * Normalize a test path to be relative and consistent + * @param {string} filePath - The path to normalize + * @returns {string} Normalized path starting with e2e/ + */ +function normalizeTestPath(filePath) { + const normalized = filePath.replace(/\\/g, '/'); + // Find the e2e/ part of the path and return from there + const e2eIndex = normalized.indexOf('e2e/'); + if (e2eIndex !== -1) { + return normalized.slice(e2eIndex); + } + return normalized; +} + +/** + * Parse XML content using xml2js + * @param {string} content - XML content to parse + * @returns {Promise} Parsed XML data + */ +async function parseXml(content) { + return xmlParser.parseStringPromise(content); +} + +/** + * Extracts test results from JUnit XML files in the results directory. + * + * jest-junit outputs XML with structure: + * + * + * + * ... + * + * + * + * + * @param {string} resultsDir - Directory containing XML test result files + * @returns {Promise<{passed: string[], failed: string[], executed: string[]}>} + */ +export async function extractTestResults(resultsDir) { + const emptyResult = { + passed: [], + failed: [], + executed: [], + }; + + if (!fs.existsSync(resultsDir)) { + console.log(`Results directory does not exist: ${resultsDir}`); + return emptyResult; + } + + const files = fs.readdirSync(resultsDir).filter((f) => f.endsWith('.xml')); + + if (files.length === 0) { + console.log(`No XML files found in: ${resultsDir}`); + // List all files in directory to help debug + try { + const allFiles = fs.readdirSync(resultsDir); + console.log(` Directory contents: ${allFiles.join(', ') || '(empty)'}`); + } catch (e) { + console.log(` Could not list directory: ${e.message}`); + } + return emptyResult; + } + + console.log(`Found ${files.length} XML file(s) to parse: ${files.join(', ')}`); + + // Track all executed test files and which ones have failures + // Key: normalized file path, Value: { executed: true, failed: boolean } + const testFileResults = new Map(); + + for (const file of files) { + try { + const content = fs.readFileSync(path.join(resultsDir, file), 'utf-8'); + const result = await parseXml(content); + + // Handle both single testsuite and testsuites wrapper + let testsuites = []; + if (result.testsuites?.testsuite) { + testsuites = Array.isArray(result.testsuites.testsuite) + ? result.testsuites.testsuite + : [result.testsuites.testsuite]; + } else if (result.testsuite) { + testsuites = Array.isArray(result.testsuite) + ? result.testsuite + : [result.testsuite]; + } + + console.log(` Parsing ${file}: found ${testsuites.length} testsuite(s)`); + + for (const suite of testsuites) { + // Get testcases from the suite + let testcases = []; + if (suite.testcase) { + testcases = Array.isArray(suite.testcase) + ? suite.testcase + : [suite.testcase]; + } + + console.log(` Suite "${suite.$?.name || 'unnamed'}": ${testcases.length} testcase(s)`); + + // Debug: show first testcase structure + if (testcases.length > 0 && testcases[0].$) { + console.log(` First testcase classname: "${testcases[0].$.classname || 'N/A'}"`); + } + + for (const testcase of testcases) { + if (!testcase.$ || !testcase.$.classname) { + continue; + } + + // jest-junit puts the file path in classname when using {filepath} template + const classname = testcase.$.classname; + + // Only process if it looks like a spec file path + if (!classname.includes('.spec.')) { + console.log(` Skipping classname (no .spec.): "${classname}"`); + continue; + } + + const testPath = normalizeTestPath(classname); + + // Check if this testcase has failures or errors + const hasFailure = !!testcase.failure; + const hasError = !!testcase.error; + const testFailed = hasFailure || hasError; + + // Get or create entry for this file + if (!testFileResults.has(testPath)) { + testFileResults.set(testPath, { executed: true, failed: false }); + } + + // If any test in the file fails, mark the whole file as failed + if (testFailed) { + testFileResults.get(testPath).failed = true; + } + } + } + } catch (error) { + console.warn(`Failed to parse XML file ${file}:`, error?.message || error); + } + } + + // Build result arrays + const executedTests = []; + const passedTests = []; + const failedTests = []; + + for (const [testPath, result] of testFileResults) { + executedTests.push(testPath); + if (result.failed) { + failedTests.push(testPath); + } else { + passedTests.push(testPath); + } + } + + console.log(`Found ${executedTests.length} executed test files`); + console.log(`Found ${failedTests.length} failed test files`); + console.log(`Found ${passedTests.length} fully passed test files`); + + if (failedTests.length > 0) { + console.log(`Failed tests: ${failedTests.join(', ')}`); + } + + return { + passed: passedTests, + failed: failedTests, + executed: executedTests, + }; +} + +/** + * CLI entry point for testing the script directly + */ +if (process.argv[1].endsWith('e2e-extract-test-results.mjs')) { + const resultsDir = process.argv[2] || './previous-test-results'; + + extractTestResults(resultsDir) + .then((results) => { + console.log('\nPassed tests:', results.passed); + console.log('\nFailed tests:', results.failed); + console.log('\nExecuted tests:', results.executed); + }) + .catch((error) => { + console.error('Error:', error); + process.exit(1); + }); +} diff --git a/.github/scripts/e2e-merge-test-results.mjs b/.github/scripts/e2e-merge-test-results.mjs new file mode 100644 index 000000000000..0269ee368c6f --- /dev/null +++ b/.github/scripts/e2e-merge-test-results.mjs @@ -0,0 +1,182 @@ +#!/usr/bin/env node + +/** + * Merges test results from a previous run into the current results directory. + * Only copies results for tests that were NOT executed in the current run. + * This preserves results for tests that were skipped (passed in previous runs). + * + * This is needed for re-runs (attempt > 2) where: + * - Attempt 1: All tests run, results uploaded + * - Attempt 2: Only failed tests re-run, but artifact only contains those results + * - Attempt 3+: Would lose information about tests that passed in attempt 1 + * + * By merging, we ensure all historical pass/fail information is preserved. + * + * Note: jest-junit is configured with classNameTemplate: '{filepath}' + * which puts the file path in each testcase's classname attribute. + */ + +import fs from 'node:fs'; +import path from 'node:path'; +import xml2js from 'xml2js'; + +const xmlParser = new xml2js.Parser(); + +/** + * Normalize a test path to be relative and consistent + * @param {string} filePath - The path to normalize + * @returns {string} Normalized path starting with e2e/ + */ +function normalizeTestPath(filePath) { + const normalized = filePath.replace(/\\/g, '/'); + const e2eIndex = normalized.indexOf('e2e/'); + if (e2eIndex !== -1) { + return normalized.slice(e2eIndex); + } + return normalized; +} + +/** + * Parse XML content using xml2js + * @param {string} content - XML content to parse + * @returns {Promise} Parsed XML data + */ +async function parseXml(content) { + return xmlParser.parseStringPromise(content); +} + +/** + * Extracts test file paths from an XML result file + * @param {string} xmlPath - Path to the XML file + * @returns {Promise} Array of test file paths + */ +async function getTestFilesFromXml(xmlPath) { + try { + const content = fs.readFileSync(xmlPath, 'utf-8'); + const result = await parseXml(content); + const files = new Set(); + + // Handle both single testsuite and testsuites wrapper + let testsuites = []; + if (result.testsuites?.testsuite) { + testsuites = Array.isArray(result.testsuites.testsuite) + ? result.testsuites.testsuite + : [result.testsuites.testsuite]; + } else if (result.testsuite) { + testsuites = Array.isArray(result.testsuite) + ? result.testsuite + : [result.testsuite]; + } + + for (const suite of testsuites) { + // Get testcases from the suite + let testcases = []; + if (suite.testcase) { + testcases = Array.isArray(suite.testcase) + ? suite.testcase + : [suite.testcase]; + } + + for (const testcase of testcases) { + if (!testcase.$ || !testcase.$.classname) { + continue; + } + + // jest-junit puts the file path in classname when using {filepath} template + const classname = testcase.$.classname; + + // Only process if it looks like a spec file path + if (classname.includes('.spec.')) { + files.add(normalizeTestPath(classname)); + } + } + } + + return [...files]; + } catch (error) { + console.warn(`Failed to parse ${xmlPath}:`, error?.message || error); + return []; + } +} + +/** + * Gets all test file paths from XML results in a directory + * @param {string} resultsDir - Directory containing XML files + * @returns {Promise>} Set of test file paths + */ +async function getTestFilesFromResults(resultsDir) { + const testFiles = new Set(); + + if (!fs.existsSync(resultsDir)) { + return testFiles; + } + + const xmlFiles = fs.readdirSync(resultsDir).filter((f) => f.endsWith('.xml')); + + for (const file of xmlFiles) { + const files = await getTestFilesFromXml(path.join(resultsDir, file)); + files.forEach((f) => testFiles.add(f)); + } + + return testFiles; +} + +/** + * Merges test results from a previous run into the current results directory. + * @param {string} previousResultsDir - Directory with previous run's XML results + * @param {string} currentResultsDir - Directory with current run's XML results + */ +export async function mergeTestResults(previousResultsDir, currentResultsDir) { + // Ensure current results directory exists + fs.mkdirSync(currentResultsDir, { recursive: true }); + + if (!fs.existsSync(previousResultsDir)) { + console.log(`Previous results directory does not exist: ${previousResultsDir}`); + return; + } + + // Get test files that have results in current run + const currentTestFiles = await getTestFilesFromResults(currentResultsDir); + console.log(`Found ${currentTestFiles.size} test files in current results`); + + // Get XML files from previous results and copy if test not in current + const previousXmls = fs.readdirSync(previousResultsDir).filter((f) => + f.endsWith('.xml'), + ); + let copiedCount = 0; + + for (const xmlFile of previousXmls) { + const previousXmlPath = path.join(previousResultsDir, xmlFile); + const testFiles = await getTestFilesFromXml(previousXmlPath); + + // Check if ANY test in this XML was executed in current run + const alreadyExecuted = testFiles.some((tf) => currentTestFiles.has(tf)); + + if (!alreadyExecuted && testFiles.length > 0) { + // Copy the XML file - preserves result for skipped test + const destPath = path.join(currentResultsDir, xmlFile); + if (!fs.existsSync(destPath)) { + fs.copyFileSync(previousXmlPath, destPath); + copiedCount++; + console.log(`Copied result for skipped test: ${testFiles[0]}`); + } + } + } + + console.log(`Merged ${copiedCount} test result files from previous run`); +} + +/** + * CLI entry point + */ +if (process.argv[1].endsWith('e2e-merge-test-results.mjs')) { + const previousDir = process.argv[2] || './previous-test-results'; + const currentDir = process.argv[3] || './e2e/reports'; + + mergeTestResults(previousDir, currentDir) + .then(() => console.log('Merge complete')) + .catch((error) => { + console.error('Error:', error); + process.exit(1); + }); +} diff --git a/.github/scripts/e2e-split-tags-shards.mjs b/.github/scripts/e2e-split-tags-shards.mjs index 971df95e038a..8aaf40e7769e 100644 --- a/.github/scripts/e2e-split-tags-shards.mjs +++ b/.github/scripts/e2e-split-tags-shards.mjs @@ -1,11 +1,13 @@ import fs from 'node:fs'; import path from 'node:path'; import { spawn } from 'node:child_process'; +import { extractTestResults } from './e2e-extract-test-results.mjs'; // 1) Find all specs files that include the given E2E tags // 2) Compute sharding split (evenly across runners) -// 3) Flaky test detector mechanism in PRs (test retries) -// 4) Log and run the selected specs for the given shard split +// 3) On re-runs, skip passed tests and only run failed/not-executed tests +// 4) Flaky test detector mechanism in PRs (test retries) +// 5) Log and run the selected specs for the given shard split const env = { TEST_SUITE_TAG: process.env.TEST_SUITE_TAG, @@ -18,6 +20,8 @@ const env = { REPOSITORY: process.env.REPOSITORY || 'MetaMask/metamask-mobile', GITHUB_TOKEN: process.env.GITHUB_TOKEN || '', CHANGED_FILES: process.env.CHANGED_FILES || '', + RUN_ATTEMPT: Number(process.env.RUN_ATTEMPT || '1'), + PREVIOUS_RESULTS_PATH: process.env.PREVIOUS_RESULTS_PATH || '', }; // Example of format of CHANGED_FILES: .github/scripts/e2e-check-build-needed.mjs .github/scripts/needs-e2e-builds.mjs @@ -327,6 +331,7 @@ function applyFlakinessDetection(splitFiles) { async function main() { console.log("๐Ÿš€ Starting E2E tests..."); + console.log(`GitHub Actions: attempt ${env.RUN_ATTEMPT}`); // 1) Find all specs files that include the given E2E tags console.log(`Searching for E2E test files with tags: ${env.TEST_SUITE_TAG}`); @@ -344,15 +349,59 @@ async function main() { process.exit(0); } - // 3) Flaky test detector mechanism in PRs (test retries) + // 3) On re-runs, skip passed tests and only run failed/not-executed tests + // This avoids running all tests over and over again on re-runs + if (env.RUN_ATTEMPT > 1 && env.PREVIOUS_RESULTS_PATH) { + console.log(`\n๐Ÿ”„ Re-run detected (attempt ${env.RUN_ATTEMPT}), checking for failed tests to re-run...`); + + const { passed, failed, executed } = await extractTestResults(env.PREVIOUS_RESULTS_PATH); + + // If no tests were executed in previous run, something is wrong - run all tests + if (executed.length === 0) { + console.log('โš ๏ธ No test results found from previous run, running all tests in chunk.'); + } else { + // Re-run tests that failed OR were never executed in previous run + // Only skip tests that explicitly passed - this ensures: + // 1. Failed tests get re-run + // 2. Tests that never executed (due to crash/cancel) also get run + // 3. Only confirmed passing tests are skipped + const passedSet = new Set(passed.map(normalizePathForCompare)); + const testsToRerun = splitFiles.filter( + (testPath) => !passedSet.has(normalizePathForCompare(testPath)) + ); + + const failedInChunk = testsToRerun.filter((t) => + failed.map(normalizePathForCompare).includes(normalizePathForCompare(t)) + ).length; + const notExecutedInChunk = testsToRerun.length - failedInChunk; + + console.log(`Previous run results: ${passed.length} passed, ${failed.length} failed`); + console.log(`This chunk: ${failedInChunk} failed, ${notExecutedInChunk} not executed`); + + if (testsToRerun.length > 0) { + console.log(`\n๐Ÿ” Re-running ${testsToRerun.length} tests (${failedInChunk} failed, ${notExecutedInChunk} not executed):`); + testsToRerun.forEach((t) => console.log(` - ${t}`)); + runFiles = testsToRerun; + } else { + // No tests to re-run - all tests in this chunk passed + console.log('โœ… All tests in this chunk passed, skipping.'); + process.exit(0); + } + } + } + + // 4) Flaky test detector mechanism in PRs (test retries) // - Only duplicates changed files that are in this shard's split // - Creates base + retry files for flakiness detection - const shouldSkipFlakinessGate = await shouldSkipFlakinessDetection(); - if (!shouldSkipFlakinessGate) { - runFiles = applyFlakinessDetection(splitFiles); + // - Skip flakiness detection on re-runs since we're already re-running failed tests + if (env.RUN_ATTEMPT === 1) { + const shouldSkipFlakinessGate = await shouldSkipFlakinessDetection(); + if (!shouldSkipFlakinessGate) { + runFiles = applyFlakinessDetection(runFiles); + } } - // 4) Log and run the selected specs for the given shard split + // 5) Log and run the selected specs for the given shard split console.log(`\n๐Ÿงช Running ${runFiles.length} spec files for this given shard split (${env.SPLIT_NUMBER}/${env.TOTAL_SPLITS}):`); for (const f of runFiles) { console.log(` - ${f}`); diff --git a/.github/workflows/run-e2e-workflow.yml b/.github/workflows/run-e2e-workflow.yml index ecae27319637..63e95f0c4a9e 100644 --- a/.github/workflows/run-e2e-workflow.yml +++ b/.github/workflows/run-e2e-workflow.yml @@ -178,12 +178,32 @@ jobs: with: path: artifacts/ + # On re-run (run_attempt > 1), download previous test results to identify failed tests + - name: Download previous test results (on re-run) + if: ${{ github.run_attempt > 1 }} + id: download-previous-results + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: test-e2e-${{ inputs.test-suite-name }}-junit-results + path: ./previous-test-results/ + + - name: Debug - List downloaded test results (on re-run) + if: ${{ github.run_attempt > 1 && steps.download-previous-results.outcome == 'success' }} + run: | + echo "๐Ÿ“‚ Contents of ./previous-test-results/:" + find ./previous-test-results -type f -name "*.xml" | head -20 + echo "" + echo "๐Ÿ“„ First XML file preview (first 50 lines):" + find ./previous-test-results -type f -name "*.xml" | head -1 | xargs head -50 || echo "No XML files found" + - name: ๐Ÿงช Run E2E tests timeout-minutes: ${{ inputs.test-timeout-minutes }} run: | echo "Running ${{ inputs.test-suite-name }} tests on ${{ inputs.platform }}" echo "Running split ${{ inputs.split_number }} of ${{ inputs.total_splits }}" echo "Using TEST_SUITE_TAG: ${{ inputs.test_suite_tag }}" + echo "Run attempt: ${{ github.run_attempt }}" # Always use the splitting script (handles both split and non-split cases) node .github/scripts/e2e-split-tags-shards.mjs @@ -191,6 +211,9 @@ jobs: JOB_NAME: ${{ inputs.test-suite-name }} RUN_ID: ${{ github.run_id }} PR_NUMBER: ${{ github.event.pull_request.number || '' }} + RUN_ATTEMPT: ${{ github.run_attempt }} + # On re-run, pass the path to previous results to run only failed tests + PREVIOUS_RESULTS_PATH: ${{ steps.download-previous-results.outcome == 'success' && './previous-test-results' || '' }} - name: Merge Detox JUnit reports if: always() @@ -213,6 +236,30 @@ jobs: # Clean up temporary node_modules rm -rf node_modules temp-deps + # Merge previous test results with current results to preserve pass/fail history across re-runs. + # Without this, tests that passed in attempt N but were skipped in attempt N+1 would have no + # results in the artifact, causing them to be re-run in attempt N+2. + - name: Merge previous test results (on re-run) + if: ${{ !cancelled() && github.run_attempt > 1 && steps.download-previous-results.outcome == 'success' }} + continue-on-error: true + run: | + echo "๐Ÿ“Š Merging previous test results to preserve history..." + + # Install xml2js for XML parsing (lightweight, no full yarn install needed) + mkdir -p temp-deps && cd temp-deps + npm init -y > /dev/null 2>&1 + npm install xml2js@0.6.2 --no-audit --no-fund --silent + + # Copy node_modules to workspace for script usage + cp -r node_modules ${{ github.workspace }}/ + cd ${{ github.workspace }} + + # Run merge script to combine previous results with current + node .github/scripts/e2e-merge-test-results.mjs ./previous-test-results ./e2e/reports + + # Clean up temporary node_modules + rm -rf node_modules temp-deps + - name: Upload JUnit XML results if: always() uses: actions/upload-artifact@v4 diff --git a/app/component-library/components-temp/HeaderCenter/HeaderCenter.stories.tsx b/app/component-library/components-temp/HeaderCenter/HeaderCenter.stories.tsx index 0b4c8ef8d820..fc213795a898 100644 --- a/app/component-library/components-temp/HeaderCenter/HeaderCenter.stories.tsx +++ b/app/component-library/components-temp/HeaderCenter/HeaderCenter.stories.tsx @@ -17,6 +17,9 @@ const HeaderCenterMeta = { title: { control: 'text', }, + subtitle: { + control: 'text', + }, twClassName: { control: 'text', }, @@ -56,6 +59,16 @@ export const BackAndClose = { ), }; +export const WithSubtitle = { + render: () => ( + console.log('Back pressed')} + /> + ), +}; + export const MultipleEndButtons = { render: () => ( { expect(getByText('Children Text')).toBeOnTheScreen(); expect(queryByText('Title Text')).not.toBeOnTheScreen(); }); + + it('renders subtitle when provided', () => { + const { getByText } = render( + , + ); + + expect(getByText('Test Subtitle')).toBeOnTheScreen(); + }); + + it('does not render subtitle when not provided', () => { + const { queryByText } = render(); + + expect(queryByText('Test Subtitle')).not.toBeOnTheScreen(); + }); + + it('renders subtitle with testID when provided via subtitleProps', () => { + const { getByTestId } = render( + , + ); + + expect(getByTestId('subtitle-test-id')).toBeOnTheScreen(); + }); + + it('renders both title and subtitle together', () => { + const { getByText } = render( + , + ); + + expect(getByText('Main Title')).toBeOnTheScreen(); + expect(getByText('Supporting Text')).toBeOnTheScreen(); + }); }); describe('back button', () => { diff --git a/app/component-library/components-temp/HeaderCenter/HeaderCenter.tsx b/app/component-library/components-temp/HeaderCenter/HeaderCenter.tsx index ac03d38e1851..190a148abc79 100644 --- a/app/component-library/components-temp/HeaderCenter/HeaderCenter.tsx +++ b/app/component-library/components-temp/HeaderCenter/HeaderCenter.tsx @@ -3,8 +3,11 @@ import React, { useMemo } from 'react'; // External dependencies. import { + Box, + BoxAlignItems, Text, TextVariant, + TextColor, FontWeight, IconName, ButtonIconProps, @@ -37,6 +40,8 @@ import { HeaderCenterProps } from './HeaderCenter.types'; const HeaderCenter: React.FC = ({ title, titleProps, + subtitle, + subtitleProps, children, onBack, backButtonProps, @@ -91,13 +96,25 @@ const HeaderCenter: React.FC = ({ } if (title) { return ( - - {title} - + + + {title} + + {subtitle && ( + + {subtitle} + + )} + ); } return null; diff --git a/app/component-library/components-temp/HeaderCenter/HeaderCenter.types.ts b/app/component-library/components-temp/HeaderCenter/HeaderCenter.types.ts index 6edd2cebca7d..4653a9253ad3 100644 --- a/app/component-library/components-temp/HeaderCenter/HeaderCenter.types.ts +++ b/app/component-library/components-temp/HeaderCenter/HeaderCenter.types.ts @@ -22,6 +22,16 @@ export interface HeaderCenterProps extends HeaderBaseProps { * Props are spread to the Text component and can override default values. */ titleProps?: Partial; + /** + * Subtitle text to display below the title. + * Rendered with TextVariant.BodySm and TextColor.TextAlternative by default. + */ + subtitle?: string; + /** + * Additional props to pass to the subtitle Text component. + * Props are spread to the Text component and can override default values. + */ + subtitleProps?: Partial; /** * Callback when the back button is pressed. * If provided, a back button will be rendered as startButtonIconProps. diff --git a/app/component-library/components-temp/HeaderWithTitleLeft/HeaderWithTitleLeft.stories.tsx b/app/component-library/components-temp/HeaderWithTitleLeft/HeaderWithTitleLeft.stories.tsx index 1c9b6419bfe0..2a2c5e27c72b 100644 --- a/app/component-library/components-temp/HeaderWithTitleLeft/HeaderWithTitleLeft.stories.tsx +++ b/app/component-library/components-temp/HeaderWithTitleLeft/HeaderWithTitleLeft.stories.tsx @@ -64,6 +64,33 @@ export const WithBottomLabel = { ), }; +export const OnClose = { + render: () => ( + console.log('Close pressed')} + titleLeftProps={{ + topLabel: 'Send', + title: '$4.42', + endAccessory: , + }} + /> + ), +}; + +export const BackAndClose = { + render: () => ( + console.log('Back pressed')} + onClose={() => console.log('Close pressed')} + titleLeftProps={{ + topLabel: 'Send', + title: '$4.42', + endAccessory: , + }} + /> + ), +}; + export const EndButtonIconProps = { render: () => ( ( {({ scrollYValue, setExpandedHeight }) => ( console.log('Back pressed')} - endButtonIconProps={[ - { - iconName: IconName.Close, - onPress: () => console.log('Close pressed'), - }, - ]} titleLeftProps={{ topLabel: 'Send', title: '$4.42', @@ -170,18 +168,17 @@ export const EndButtonIconProps = { ), }; -export const BackButtonProps = { +export const OnClose = { render: () => ( {({ scrollYValue, setExpandedHeight }) => ( console.log('Custom back pressed'), - }} + title="Send" + onClose={() => console.log('Close pressed')} titleLeftProps={{ - topLabel: 'Receive', - title: '$1,234.56', + topLabel: 'Send', + title: '$4.42', + endAccessory: , }} scrollY={scrollYValue} onExpandedHeightChange={setExpandedHeight} @@ -191,21 +188,19 @@ export const BackButtonProps = { ), }; -export const TitleLeft = { +export const BackAndClose = { render: () => ( {({ scrollYValue, setExpandedHeight }) => ( console.log('Back pressed')} - titleLeft={ - - Custom Title Section - - This is a completely custom title section - - - } + onClose={() => console.log('Close pressed')} + titleLeftProps={{ + topLabel: 'Send', + title: '$4.42', + endAccessory: , + }} scrollY={scrollYValue} onExpandedHeightChange={setExpandedHeight} /> @@ -214,16 +209,23 @@ export const TitleLeft = { ), }; -export const NoBackButton = { +export const EndButtonIconProps = { render: () => ( {({ scrollYValue, setExpandedHeight }) => ( console.log('Back pressed')} + endButtonIconProps={[ + { + iconName: IconName.Close, + onPress: () => console.log('Close pressed'), + }, + ]} titleLeftProps={{ - topLabel: 'Account Balance', - title: '$12,345.67', - bottomLabel: '+$123.45 (1.2%)', + topLabel: 'Send', + title: '$4.42', + endAccessory: , }} scrollY={scrollYValue} onExpandedHeightChange={setExpandedHeight} @@ -233,20 +235,43 @@ export const NoBackButton = { ), }; -export const FastCollapse = { +export const BackButtonProps = { render: () => ( - + {({ scrollYValue, setExpandedHeight }) => ( console.log('Back pressed')} + title="Receive" + backButtonProps={{ + onPress: () => console.log('Custom back pressed'), + }} titleLeftProps={{ - topLabel: 'Send', - title: '$4.42', - endAccessory: , + topLabel: 'Receive', + title: '$1,234.56', }} scrollY={scrollYValue} - scrollTriggerPosition={60} + onExpandedHeightChange={setExpandedHeight} + /> + )} + + ), +}; + +export const TitleLeft = { + render: () => ( + + {({ scrollYValue, setExpandedHeight }) => ( + console.log('Back pressed')} + titleLeft={ + + Custom Title Section + + This is a completely custom title section + + + } + scrollY={scrollYValue} onExpandedHeightChange={setExpandedHeight} /> )} diff --git a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.test.tsx b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.test.tsx index ac49a77d9d19..64c614dcb2cb 100644 --- a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.test.tsx +++ b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.test.tsx @@ -257,6 +257,94 @@ describe('HeaderWithTitleLeftScrollable', () => { expect(queryByText('Props Title')).toBeNull(); }); }); + + describe('subtitle', () => { + it('renders subtitle in compact header when provided', () => { + const { getByText } = render( + + {(scrollYValue) => ( + + )} + , + ); + + expect(getByText('Test Subtitle')).toBeOnTheScreen(); + }); + + it('does not render subtitle when not provided', () => { + const { queryByText } = render( + + {(scrollYValue) => ( + + )} + , + ); + + expect(queryByText('Test Subtitle')).toBeNull(); + }); + + it('renders subtitle with testID when provided via subtitleProps', () => { + const { getByTestId } = render( + + {(scrollYValue) => ( + + )} + , + ); + + expect(getByTestId('subtitle-test-id')).toBeOnTheScreen(); + }); + }); + + describe('children', () => { + it('renders custom children in compact header section', () => { + const { getByText } = render( + + {(scrollYValue) => ( + + Custom Compact Content + + )} + , + ); + + expect(getByText('Custom Compact Content')).toBeOnTheScreen(); + }); + + it('does not render subtitle when children provided', () => { + const { getByText, queryByText } = render( + + {(scrollYValue) => ( + + Custom Content + + )} + , + ); + + expect(getByText('Custom Content')).toBeOnTheScreen(); + expect(queryByText('Test Subtitle')).toBeNull(); + }); + }); }); describe('useHeaderWithTitleLeftScrollable', () => { diff --git a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.tsx b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.tsx index 80e8b8fd4bec..dc978e1c3997 100644 --- a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.tsx +++ b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.tsx @@ -13,8 +13,10 @@ import Animated, { // External dependencies. import { Box, + BoxAlignItems, Text, TextVariant, + TextColor, FontWeight, IconName, ButtonIconProps, @@ -28,7 +30,7 @@ import TitleLeft from '../TitleLeft'; import { HeaderWithTitleLeftScrollableProps } from './HeaderWithTitleLeftScrollable.types'; const DEFAULT_EXPANDED_HEIGHT = 140; -const DEFAULT_COLLAPSED_HEIGHT = 48; +const DEFAULT_COLLAPSED_HEIGHT = 56; /** * HeaderWithTitleLeftScrollable is a collapsing header component that transitions @@ -68,6 +70,10 @@ const HeaderWithTitleLeftScrollable: React.FC< HeaderWithTitleLeftScrollableProps > = ({ title, + titleProps, + subtitle, + subtitleProps, + children, onBack, backButtonProps, onClose, @@ -207,6 +213,36 @@ const HeaderWithTitleLeftScrollable: React.FC< return ; }; + // Render compact title content + // If children is provided, use it; otherwise render default title + subtitle + const renderCompactContent = () => { + if (children) { + return children; + } + return ( + + + {title} + + {subtitle && ( + + {subtitle} + + )} + + ); + }; const containerStyle = useMemo( () => [ tw.style('absolute left-0 right-0 z-10'), @@ -231,13 +267,7 @@ const HeaderWithTitleLeftScrollable: React.FC< > {/* Compact title - fades in when collapsed */} - - {title} - + {renderCompactContent()} diff --git a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.types.ts b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.types.ts index 1b1a3d486bc2..256827a1c5bb 100644 --- a/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.types.ts +++ b/app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.types.ts @@ -3,7 +3,10 @@ import { ReactNode } from 'react'; import { SharedValue } from 'react-native-reanimated'; // External dependencies. -import { ButtonIconProps } from '@metamask/design-system-react-native'; +import { + ButtonIconProps, + TextProps, +} from '@metamask/design-system-react-native'; // Internal dependencies. import { HeaderBaseProps } from '../../components/HeaderBase'; @@ -12,13 +15,27 @@ import { TitleLeftProps } from '../TitleLeft/TitleLeft.types'; /** * Props for the HeaderWithTitleLeftScrollable component. */ -export interface HeaderWithTitleLeftScrollableProps - extends Omit { +export interface HeaderWithTitleLeftScrollableProps extends HeaderBaseProps { /** * Title text displayed in the compact header state. * Also used as default title for TitleLeft if titleLeftProps.title is not provided. */ title: string; + /** + * Additional props to pass to the title Text component in the compact header. + * Props are spread to the Text component and can override default values. + */ + titleProps?: Partial; + /** + * Subtitle text to display below the title in the compact header. + * Rendered with TextVariant.BodySm and TextColor.TextAlternative by default. + */ + subtitle?: string; + /** + * Additional props to pass to the subtitle Text component. + * Props are spread to the Text component and can override default values. + */ + subtitleProps?: Partial; /** * Callback when the back button is pressed. * If provided, a back button will be rendered as startAccessory. diff --git a/app/component-library/components/BottomSheets/BottomSheetHeader/__snapshots__/BottomSheetHeader.test.tsx.snap b/app/component-library/components/BottomSheets/BottomSheetHeader/__snapshots__/BottomSheetHeader.test.tsx.snap index 067c03d79a7c..44de92ca58d7 100644 --- a/app/component-library/components/BottomSheets/BottomSheetHeader/__snapshots__/BottomSheetHeader.test.tsx.snap +++ b/app/component-library/components/BottomSheets/BottomSheetHeader/__snapshots__/BottomSheetHeader.test.tsx.snap @@ -8,7 +8,7 @@ exports[`BottomSheetHeader renders snapshot correctly with Compact variant 1`] = "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -119,7 +119,7 @@ exports[`BottomSheetHeader should render snapshot correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/component-library/components/HeaderBase/HeaderBase.tsx b/app/component-library/components/HeaderBase/HeaderBase.tsx index 26fddffe589e..8d168b0d9ebb 100644 --- a/app/component-library/components/HeaderBase/HeaderBase.tsx +++ b/app/component-library/components/HeaderBase/HeaderBase.tsx @@ -120,7 +120,7 @@ const HeaderBase: React.FC = ({ // Compact: fixed height, Display: content-based with no default styles const baseStyles = isLeftAligned ? 'flex-row items-center gap-4' - : 'flex-row items-center gap-4 h-12'; + : 'flex-row items-center gap-4 h-14'; const resolvedTwClassName = twClassName ? `${baseStyles} ${twClassName}` : baseStyles; diff --git a/app/components/Snaps/SnapUIRenderer/components/__snapshots__/form.test.ts.snap b/app/components/Snaps/SnapUIRenderer/components/__snapshots__/form.test.ts.snap index bee6873a3f97..6fb75492f3c7 100644 --- a/app/components/Snaps/SnapUIRenderer/components/__snapshots__/form.test.ts.snap +++ b/app/components/Snaps/SnapUIRenderer/components/__snapshots__/form.test.ts.snap @@ -943,7 +943,7 @@ exports[`SnapUIForm will render with fields 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1676,7 +1676,7 @@ exports[`SnapUIForm will render with fields 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap index 734c18146643..69c9624069b4 100644 --- a/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap +++ b/app/components/UI/Bridge/components/BridgeDestNetworkSelector/__snapshots__/BridgeDestNetworkSelector.test.tsx.snap @@ -437,7 +437,7 @@ exports[`BridgeDestNetworkSelector renders with initial state and displays netwo "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap index c0a828d7722e..6b528cfce444 100644 --- a/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap +++ b/app/components/UI/Bridge/components/BridgeDestTokenSelector/__snapshots__/BridgeDestTokenSelector.test.tsx.snap @@ -437,7 +437,7 @@ exports[`BridgeDestTokenSelector renders with initial state and displays tokens "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap index a2d02860f232..0648c2bc5ef0 100644 --- a/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap +++ b/app/components/UI/Bridge/components/BridgeSourceNetworkSelector/__snapshots__/BridgeSourceNetworkSelector.test.tsx.snap @@ -437,7 +437,7 @@ exports[`BridgeSourceNetworkSelector renders with initial state and displays net "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap b/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap index 25b488a5ee87..7318e30d7fb3 100644 --- a/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap +++ b/app/components/UI/Bridge/components/BridgeSourceTokenSelector/__snapshots__/BridgeSourceTokenSelector.test.tsx.snap @@ -437,7 +437,7 @@ exports[`BridgeSourceTokenSelector renders with initial state and displays token "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Bridge/components/QuoteExpiredModal/__snapshots__/QuoteExpiredModal.test.tsx.snap b/app/components/UI/Bridge/components/QuoteExpiredModal/__snapshots__/QuoteExpiredModal.test.tsx.snap index 12ba9cc2b912..1f1b11008505 100644 --- a/app/components/UI/Bridge/components/QuoteExpiredModal/__snapshots__/QuoteExpiredModal.test.tsx.snap +++ b/app/components/UI/Bridge/components/QuoteExpiredModal/__snapshots__/QuoteExpiredModal.test.tsx.snap @@ -136,7 +136,7 @@ exports[`QuoteExpiredModal renders correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Bridge/components/SlippageModal/__snapshots__/SlippageModal.test.tsx.snap b/app/components/UI/Bridge/components/SlippageModal/__snapshots__/SlippageModal.test.tsx.snap index 28f194e85c9e..ddf3cca72176 100644 --- a/app/components/UI/Bridge/components/SlippageModal/__snapshots__/SlippageModal.test.tsx.snap +++ b/app/components/UI/Bridge/components/SlippageModal/__snapshots__/SlippageModal.test.tsx.snap @@ -136,7 +136,7 @@ exports[`SlippageModal renders all UI elements with the proper slippage options "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Card/components/AddFundsBottomSheet/__snapshots__/AddFundsBottomSheet.test.tsx.snap b/app/components/UI/Card/components/AddFundsBottomSheet/__snapshots__/AddFundsBottomSheet.test.tsx.snap index d2ae5513c526..1f63319489fd 100644 --- a/app/components/UI/Card/components/AddFundsBottomSheet/__snapshots__/AddFundsBottomSheet.test.tsx.snap +++ b/app/components/UI/Card/components/AddFundsBottomSheet/__snapshots__/AddFundsBottomSheet.test.tsx.snap @@ -437,7 +437,7 @@ exports[`AddFundsBottomSheet renders with both options enabled and matches snaps "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1245,7 +1245,7 @@ exports[`AddFundsBottomSheet renders with no options when both are disabled and "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1837,7 +1837,7 @@ exports[`AddFundsBottomSheet renders with only deposit option when swaps are not "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2537,7 +2537,7 @@ exports[`AddFundsBottomSheet renders with only swap option when deposit is disab "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Earn/LendingLearnMoreModal/__snapshots__/LendingLearnMoreModal.test.tsx.snap b/app/components/UI/Earn/LendingLearnMoreModal/__snapshots__/LendingLearnMoreModal.test.tsx.snap index 4c0093d25f1c..5b9b37dec43a 100644 --- a/app/components/UI/Earn/LendingLearnMoreModal/__snapshots__/LendingLearnMoreModal.test.tsx.snap +++ b/app/components/UI/Earn/LendingLearnMoreModal/__snapshots__/LendingLearnMoreModal.test.tsx.snap @@ -116,7 +116,7 @@ exports[`LendingLearnMoreModal render lending history apy chart 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Earn/components/EarnTokenList/__snapshots__/EarnTokenList.test.tsx.snap b/app/components/UI/Earn/components/EarnTokenList/__snapshots__/EarnTokenList.test.tsx.snap index 156502aea8a0..e7a04ec7a4bb 100644 --- a/app/components/UI/Earn/components/EarnTokenList/__snapshots__/EarnTokenList.test.tsx.snap +++ b/app/components/UI/Earn/components/EarnTokenList/__snapshots__/EarnTokenList.test.tsx.snap @@ -136,7 +136,7 @@ exports[`EarnTokenList render matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Earn/components/MaxInputModal/__snapshots__/MaxInputModal.test.tsx.snap b/app/components/UI/Earn/components/MaxInputModal/__snapshots__/MaxInputModal.test.tsx.snap index d0b093ebbcb0..67fe00e2c69e 100644 --- a/app/components/UI/Earn/components/MaxInputModal/__snapshots__/MaxInputModal.test.tsx.snap +++ b/app/components/UI/Earn/components/MaxInputModal/__snapshots__/MaxInputModal.test.tsx.snap @@ -443,7 +443,7 @@ exports[`MaxInputModal render matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Earn/modals/LendingMaxWithdrawalModal/__snapshots__/LendingMaxWithdrawalModal.test.tsx.snap b/app/components/UI/Earn/modals/LendingMaxWithdrawalModal/__snapshots__/LendingMaxWithdrawalModal.test.tsx.snap index 97ba5af0c472..251d35d0512e 100644 --- a/app/components/UI/Earn/modals/LendingMaxWithdrawalModal/__snapshots__/LendingMaxWithdrawalModal.test.tsx.snap +++ b/app/components/UI/Earn/modals/LendingMaxWithdrawalModal/__snapshots__/LendingMaxWithdrawalModal.test.tsx.snap @@ -9,7 +9,7 @@ exports[`LendingMaxWithdrawalModal should render correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap index 4deb03fec81c..e8f1953b54c4 100644 --- a/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap @@ -153,7 +153,7 @@ exports[`NetworkDetails renders correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap b/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap index bb33dc389802..dbf873da33d9 100644 --- a/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap +++ b/app/components/UI/NetworkVerificationInfo/__snapshots__/NetworkVerificationInfo.test.tsx.snap @@ -11,7 +11,7 @@ exports[`NetworkVerificationInfo renders correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -550,7 +550,7 @@ exports[`NetworkVerificationInfo renders updated details when isNetworkRpcUpdate "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Perps/components/PerpsBottomSheetTooltip/__snapshots__/PerpsBottomSheetTooltip.test.tsx.snap b/app/components/UI/Perps/components/PerpsBottomSheetTooltip/__snapshots__/PerpsBottomSheetTooltip.test.tsx.snap index 377fad8ff764..46355f240c17 100644 --- a/app/components/UI/Perps/components/PerpsBottomSheetTooltip/__snapshots__/PerpsBottomSheetTooltip.test.tsx.snap +++ b/app/components/UI/Perps/components/PerpsBottomSheetTooltip/__snapshots__/PerpsBottomSheetTooltip.test.tsx.snap @@ -137,7 +137,7 @@ exports[`PerpsBottomSheetTooltip renders correctly when visible 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/Views/Checkout/__snapshots__/Checkout.test.tsx.snap b/app/components/UI/Ramp/Aggregator/Views/Checkout/__snapshots__/Checkout.test.tsx.snap index 3cf9833f1ae9..9cbffedb1bed 100644 --- a/app/components/UI/Ramp/Aggregator/Views/Checkout/__snapshots__/Checkout.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/Views/Checkout/__snapshots__/Checkout.test.tsx.snap @@ -437,7 +437,7 @@ exports[`Checkout displays WebView when url is present and no errors 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -978,7 +978,7 @@ exports[`Checkout displays and tracks error if no url or errors 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1672,7 +1672,7 @@ exports[`Checkout displays sdkError when present 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2410,7 +2410,7 @@ exports[`Checkout displays sell WebView when url is present and no errors 1`] = "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2951,7 +2951,7 @@ exports[`Checkout handles get order error gracefully 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -3689,7 +3689,7 @@ exports[`Checkout handles undefined order gracefully 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -4427,7 +4427,7 @@ exports[`Checkout ignores irrelevant error on http error in WebView for callback "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -4968,7 +4968,7 @@ exports[`Checkout sets and displays error on http error in WebView 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -5706,7 +5706,7 @@ exports[`Checkout sets and displays error on http error in WebView for callback "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -6444,7 +6444,7 @@ exports[`Checkout sets error when handling url navigation state change and selec "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/Views/Modals/Settings/__snapshots__/SettingsModal.test.tsx.snap b/app/components/UI/Ramp/Aggregator/Views/Modals/Settings/__snapshots__/SettingsModal.test.tsx.snap index eda961d9b7ad..72ee480a47bf 100644 --- a/app/components/UI/Ramp/Aggregator/Views/Modals/Settings/__snapshots__/SettingsModal.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/Views/Modals/Settings/__snapshots__/SettingsModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`SettingsModal renders snapshot correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/Views/Quotes/__snapshots__/Quotes.test.tsx.snap b/app/components/UI/Ramp/Aggregator/Views/Quotes/__snapshots__/Quotes.test.tsx.snap index 4f543cfe6394..481601023f5c 100644 --- a/app/components/UI/Ramp/Aggregator/Views/Quotes/__snapshots__/Quotes.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/Views/Quotes/__snapshots__/Quotes.test.tsx.snap @@ -1162,7 +1162,7 @@ exports[`Quotes custom action renders correctly after animation with the recomme "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -3445,7 +3445,7 @@ exports[`Quotes renders correctly after animation with expanded quotes 2`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -5332,7 +5332,7 @@ exports[`Quotes renders correctly after animation with the recommended quote 1`] "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/components/FiatSelectorModal/__snapshots__/FiatSelectorModal.test.tsx.snap b/app/components/UI/Ramp/Aggregator/components/FiatSelectorModal/__snapshots__/FiatSelectorModal.test.tsx.snap index 84b05179ba28..72d056eb5252 100644 --- a/app/components/UI/Ramp/Aggregator/components/FiatSelectorModal/__snapshots__/FiatSelectorModal.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/components/FiatSelectorModal/__snapshots__/FiatSelectorModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`FiatSelectorModal renders the modal with currency list 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1372,7 +1372,7 @@ exports[`FiatSelectorModal search displays filtered currencies when search strin "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2308,7 +2308,7 @@ exports[`FiatSelectorModal search displays filtered currencies when search strin "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -3244,7 +3244,7 @@ exports[`FiatSelectorModal search displays max 20 results 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/components/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap b/app/components/UI/Ramp/Aggregator/components/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap index b0bd1ad19db7..c14ece625154 100644 --- a/app/components/UI/Ramp/Aggregator/components/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/components/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`IncompatibleAccountTokenModal renders the modal with the correct title "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/components/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap b/app/components/UI/Ramp/Aggregator/components/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap index ab444e7c59e6..d0ca3b5f95a6 100644 --- a/app/components/UI/Ramp/Aggregator/components/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/components/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`PaymentMethodSelectorModal renders correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1582,7 +1582,7 @@ exports[`PaymentMethodSelectorModal renders for sell flow 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2728,7 +2728,7 @@ exports[`PaymentMethodSelectorModal renders without disclaimer when selected pay "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/components/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap b/app/components/UI/Ramp/Aggregator/components/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap index bc46e5a84138..8c91f81991f7 100644 --- a/app/components/UI/Ramp/Aggregator/components/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/components/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`RegionSelectorModal clears search when clear button is pressed 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1296,7 +1296,7 @@ exports[`RegionSelectorModal clears search when clear button is pressed 2`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2494,7 +2494,7 @@ exports[`RegionSelectorModal filters regions based on search input 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -3354,7 +3354,7 @@ exports[`RegionSelectorModal handles empty regions list gracefully 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -4082,7 +4082,7 @@ exports[`RegionSelectorModal handles undefined regions gracefully 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -4810,7 +4810,7 @@ exports[`RegionSelectorModal navigates back to country view when back button is "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -5660,7 +5660,7 @@ exports[`RegionSelectorModal navigates back to country view when back button is "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -6858,7 +6858,7 @@ exports[`RegionSelectorModal renders the modal with region list 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -8056,7 +8056,7 @@ exports[`RegionSelectorModal renders the modal with selected region in list 1`] "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -9270,7 +9270,7 @@ exports[`RegionSelectorModal renders the modal with selected state in list 1`] = "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -10484,7 +10484,7 @@ exports[`RegionSelectorModal shows empty state when search returns no results 1` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/components/TokenSelectModal/__snapshots__/TokenSelectModal.test.tsx.snap b/app/components/UI/Ramp/Aggregator/components/TokenSelectModal/__snapshots__/TokenSelectModal.test.tsx.snap index 54a6a1e19d45..47e6ce065e9e 100644 --- a/app/components/UI/Ramp/Aggregator/components/TokenSelectModal/__snapshots__/TokenSelectModal.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/components/TokenSelectModal/__snapshots__/TokenSelectModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`TokenSelectModal renders the modal with token list 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Aggregator/components/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap b/app/components/UI/Ramp/Aggregator/components/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap index 62f93cab0052..76f0c1d478f8 100644 --- a/app/components/UI/Ramp/Aggregator/components/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/components/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap @@ -416,7 +416,7 @@ exports[`UnsupportedRegionModal renders correctly for buy flow 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1012,7 +1012,7 @@ exports[`UnsupportedRegionModal renders correctly for sell flow 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/ConfigurationModal/__snapshots__/ConfigurationModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/ConfigurationModal/__snapshots__/ConfigurationModal.test.tsx.snap index 55ca6ecf5ef1..f8c6f989e6c9 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/ConfigurationModal/__snapshots__/ConfigurationModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/ConfigurationModal/__snapshots__/ConfigurationModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`ConfigurationModal render matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/ErrorDetailsModal/__snapshots__/ErrorDetailsModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/ErrorDetailsModal/__snapshots__/ErrorDetailsModal.test.tsx.snap index 00c63c8bae9c..aed5df02790c 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/ErrorDetailsModal/__snapshots__/ErrorDetailsModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/ErrorDetailsModal/__snapshots__/ErrorDetailsModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`ErrorDetailsModal renders correctly and matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap index 594744e20ccd..ec8dac5ee7f4 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/IncompatibleAccountTokenModal/__snapshots__/IncompatibleAccountTokenModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`IncompatibleAccountTokenModal renders the modal with the correct title "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap index a467c45a8548..9a5d6723a0da 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/PaymentMethodSelectorModal/__snapshots__/PaymentMethodSelectorModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`PaymentMethodSelectorModal Component renders correctly and matches snap "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap index d9132f344f5c..7105dd6a693b 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/RegionSelectorModal/__snapshots__/RegionSelectorModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`RegionSelectorModal Component handles empty regions array from navigati "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1148,7 +1148,7 @@ exports[`RegionSelectorModal Component receives and uses regions from navigation "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2056,7 +2056,7 @@ exports[`RegionSelectorModal Component render matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -3196,7 +3196,7 @@ exports[`RegionSelectorModal Component render matches snapshot when search has n "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -3949,7 +3949,7 @@ exports[`RegionSelectorModal Component render matches snapshot when searching fo "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -4790,7 +4790,7 @@ exports[`RegionSelectorModal Component render matches snapshot with allRegionsSe "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -5930,7 +5930,7 @@ exports[`RegionSelectorModal Component render matches snapshot with custom selec "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -7070,7 +7070,7 @@ exports[`RegionSelectorModal Component sorts recommended regions to the top when "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/SsnInfoModal/__snapshots__/SsnInfoModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/SsnInfoModal/__snapshots__/SsnInfoModal.test.tsx.snap index 73f26d669221..caa9f023a8f6 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/SsnInfoModal/__snapshots__/SsnInfoModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/SsnInfoModal/__snapshots__/SsnInfoModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`SsnInfoModal Component renders correctly and matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/StateSelectorModal/__snapshots__/StateSelectorModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/StateSelectorModal/__snapshots__/StateSelectorModal.test.tsx.snap index f9954f6b1f25..f9267c7bc40e 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/StateSelectorModal/__snapshots__/StateSelectorModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/StateSelectorModal/__snapshots__/StateSelectorModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`StateSelectorModal Component Snapshot Tests renders cleared search stat "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1237,7 +1237,7 @@ exports[`StateSelectorModal Component Snapshot Tests renders empty state when no "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1982,7 +1982,7 @@ exports[`StateSelectorModal Component Snapshot Tests renders filtered state when "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2783,7 +2783,7 @@ exports[`StateSelectorModal Component Snapshot Tests renders filtered state when "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -3584,7 +3584,7 @@ exports[`StateSelectorModal Component Snapshot Tests renders initial state corre "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -4648,7 +4648,7 @@ exports[`StateSelectorModal Component Snapshot Tests renders partial search resu "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/TokenSelectorModal/__snapshots__/TokenSelectorModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/TokenSelectorModal/__snapshots__/TokenSelectorModal.test.tsx.snap index d27ae7b3425a..b2264aa33654 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/TokenSelectorModal/__snapshots__/TokenSelectorModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/TokenSelectorModal/__snapshots__/TokenSelectorModal.test.tsx.snap @@ -436,7 +436,7 @@ exports[`TokenSelectorModal Component displays empty state when no tokens match "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1390,7 +1390,7 @@ exports[`TokenSelectorModal Component displays network filter selector when pres "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -2544,7 +2544,7 @@ exports[`TokenSelectorModal Component renders correctly and matches snapshot 1`] "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap index 043cf8e9d89a..8217691be47d 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedRegionModal/__snapshots__/UnsupportedRegionModal.test.tsx.snap @@ -416,7 +416,7 @@ exports[`UnsupportedRegionModal handles missing region gracefully 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -1086,7 +1086,7 @@ exports[`UnsupportedRegionModal render match snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedStateModal/__snapshots__/UnsupportedStateModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedStateModal/__snapshots__/UnsupportedStateModal.test.tsx.snap index 949d0b30ef3a..d09b5f6d6668 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedStateModal/__snapshots__/UnsupportedStateModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/UnsupportedStateModal/__snapshots__/UnsupportedStateModal.test.tsx.snap @@ -416,7 +416,7 @@ exports[`UnsupportedStateModal render match snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/Deposit/Views/Modals/WebviewModal/__snapshots__/WebviewModal.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/Modals/WebviewModal/__snapshots__/WebviewModal.test.tsx.snap index 475315dd436b..a588cca49bbf 100644 --- a/app/components/UI/Ramp/Deposit/Views/Modals/WebviewModal/__snapshots__/WebviewModal.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/Modals/WebviewModal/__snapshots__/WebviewModal.test.tsx.snap @@ -437,7 +437,7 @@ exports[`WebviewModal Component renders correctly and matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { @@ -998,7 +998,7 @@ exports[`WebviewModal Component should display error view when webview HTTP erro "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/components/EligibilityFailedModal/__snapshots__/EligibilityFailedModal.test.tsx.snap b/app/components/UI/Ramp/components/EligibilityFailedModal/__snapshots__/EligibilityFailedModal.test.tsx.snap index f6b0efddc296..6443200d71b9 100644 --- a/app/components/UI/Ramp/components/EligibilityFailedModal/__snapshots__/EligibilityFailedModal.test.tsx.snap +++ b/app/components/UI/Ramp/components/EligibilityFailedModal/__snapshots__/EligibilityFailedModal.test.tsx.snap @@ -319,7 +319,7 @@ exports[`EligibilityFailedModal renders modal with title and description 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/components/RampUnsupportedModal/__snapshots__/RampUnsupportedModal.test.tsx.snap b/app/components/UI/Ramp/components/RampUnsupportedModal/__snapshots__/RampUnsupportedModal.test.tsx.snap index 1fc8f3e6b01d..225a0083f744 100644 --- a/app/components/UI/Ramp/components/RampUnsupportedModal/__snapshots__/RampUnsupportedModal.test.tsx.snap +++ b/app/components/UI/Ramp/components/RampUnsupportedModal/__snapshots__/RampUnsupportedModal.test.tsx.snap @@ -319,7 +319,7 @@ exports[`RampUnsupportedModal renders modal with title and description 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Ramp/components/UnsupportedTokenModal/__snapshots__/UnsupportedTokenModal.test.tsx.snap b/app/components/UI/Ramp/components/UnsupportedTokenModal/__snapshots__/UnsupportedTokenModal.test.tsx.snap index 116f7de1903d..af715d6ba6ab 100644 --- a/app/components/UI/Ramp/components/UnsupportedTokenModal/__snapshots__/UnsupportedTokenModal.test.tsx.snap +++ b/app/components/UI/Ramp/components/UnsupportedTokenModal/__snapshots__/UnsupportedTokenModal.test.tsx.snap @@ -319,7 +319,7 @@ exports[`UnsupportedTokenModal renders the modal with correct title and descript "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Stake/components/GasImpactModal/__snapshots__/GasImpactModal.test.tsx.snap b/app/components/UI/Stake/components/GasImpactModal/__snapshots__/GasImpactModal.test.tsx.snap index a4242ac69a8b..19f4aa5aa569 100644 --- a/app/components/UI/Stake/components/GasImpactModal/__snapshots__/GasImpactModal.test.tsx.snap +++ b/app/components/UI/Stake/components/GasImpactModal/__snapshots__/GasImpactModal.test.tsx.snap @@ -143,7 +143,7 @@ exports[`GasImpactModal render matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Stake/components/PoolStakingLearnMoreModal/__snapshots__/PoolStakingLearnMoreModal.test.tsx.snap b/app/components/UI/Stake/components/PoolStakingLearnMoreModal/__snapshots__/PoolStakingLearnMoreModal.test.tsx.snap index cd5b2f94647a..46b55d043696 100644 --- a/app/components/UI/Stake/components/PoolStakingLearnMoreModal/__snapshots__/PoolStakingLearnMoreModal.test.tsx.snap +++ b/app/components/UI/Stake/components/PoolStakingLearnMoreModal/__snapshots__/PoolStakingLearnMoreModal.test.tsx.snap @@ -120,7 +120,7 @@ exports[`PoolStakingLearnMoreModal render matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/UI/Swaps/utils/index.js b/app/components/UI/Swaps/utils/index.js index 3c7240db1be6..84e9337b2943 100644 --- a/app/components/UI/Swaps/utils/index.js +++ b/app/components/UI/Swaps/utils/index.js @@ -3,13 +3,8 @@ import BigNumber from 'bignumber.js'; import { swapsUtils } from '@metamask/swaps-controller'; import { strings } from '../../../../../locales/i18n'; import AppConstants from '../../../../core/AppConstants'; -import { NETWORKS_CHAIN_ID } from '../../../../constants/network'; import { SolScope, BtcScope, TrxScope } from '@metamask/keyring-api'; import { CHAIN_IDS } from '@metamask/transaction-controller'; -import { - NATIVE_SWAPS_TOKEN_ADDRESS, - SWAPS_TESTNET_CHAIN_ID, -} from '../../../../constants/bridge'; import { isSwapsNativeAsset } from '../../../../util/bridge'; const allowedChainIds = [ @@ -24,25 +19,12 @@ const allowedChainIds = [ CHAIN_IDS.BASE, CHAIN_IDS.SEI, CHAIN_IDS.MONAD, - SWAPS_TESTNET_CHAIN_ID, ]; -export const allowedTestnetChainIds = [ - NETWORKS_CHAIN_ID.GOERLI, - NETWORKS_CHAIN_ID.SEPOLIA, -]; - -if (__DEV__) { - allowedChainIds.push(...allowedTestnetChainIds); -} - export function isSwapsAllowed(chainId) { if (!AppConstants.SWAPS.ACTIVE) { return false; } - if (!AppConstants.SWAPS.ONLY_MAINNET) { - allowedChainIds.push(SWAPS_TESTNET_CHAIN_ID); - } if ( chainId === SolScope.Mainnet || diff --git a/app/components/UI/Swaps/utils/index.test.js b/app/components/UI/Swaps/utils/index.test.js index c1d9023f3b33..c4bb5cbe9040 100644 --- a/app/components/UI/Swaps/utils/index.test.js +++ b/app/components/UI/Swaps/utils/index.test.js @@ -4,7 +4,6 @@ import { isSwapsAllowed, } from './index'; import { CHAIN_IDS } from '@metamask/transaction-controller'; -import { SWAPS_TESTNET_CHAIN_ID } from '../../../../constants/bridge'; // Mock AppConstants const mockSwapsConstantsGetter = jest.fn(() => ({ @@ -221,24 +220,4 @@ describe('isSwapsAllowed', () => { const unsupportedChainId = '0x9999'; expect(isSwapsAllowed(unsupportedChainId)).toBe(false); }); - - describe('testnet chain IDs', () => { - it('should return true for testnet chain IDs in development when ONLY_MAINNET is true', () => { - global.__DEV__ = true; - mockSwapsConstantsGetter.mockReturnValue({ - ...mockSwapsConstantsGetter(), - ONLY_MAINNET: true, - }); - expect(isSwapsAllowed(SWAPS_TESTNET_CHAIN_ID)).toBe(true); - }); - - it('should return true for testnet chain IDs when ONLY_MAINNET is false', () => { - global.__DEV__ = false; - mockSwapsConstantsGetter.mockReturnValue({ - ...mockSwapsConstantsGetter(), - ONLY_MAINNET: false, - }); - expect(isSwapsAllowed(SWAPS_TESTNET_CHAIN_ID)).toBe(true); - }); - }); }); diff --git a/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/__snapshots__/AccountPermissionsConfirmRevokeAll.test.tsx.snap b/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/__snapshots__/AccountPermissionsConfirmRevokeAll.test.tsx.snap index 0fbd8538f687..633bd28e6536 100644 --- a/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/__snapshots__/AccountPermissionsConfirmRevokeAll.test.tsx.snap +++ b/app/components/Views/AccountPermissions/AccountPermissionsConfirmRevokeAll/__snapshots__/AccountPermissionsConfirmRevokeAll.test.tsx.snap @@ -133,7 +133,7 @@ exports[`AccountPermissionsConfirmRevokeAll renders correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/Views/AccountPermissions/ConnectionDetails/__snapshots__/ConnectionDetails.test.tsx.snap b/app/components/Views/AccountPermissions/ConnectionDetails/__snapshots__/ConnectionDetails.test.tsx.snap index 63bff6f6812a..51d7cb56766f 100644 --- a/app/components/Views/AccountPermissions/ConnectionDetails/__snapshots__/ConnectionDetails.test.tsx.snap +++ b/app/components/Views/AccountPermissions/ConnectionDetails/__snapshots__/ConnectionDetails.test.tsx.snap @@ -16,7 +16,7 @@ exports[`ConnectionDetails renders correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/Views/AccountPermissions/PermittedNetworksInfoSheet/__snapshots__/PermittedNetworksInfoSheet.test.tsx.snap b/app/components/Views/AccountPermissions/PermittedNetworksInfoSheet/__snapshots__/PermittedNetworksInfoSheet.test.tsx.snap index 6c57312572b0..f092974a3e36 100644 --- a/app/components/Views/AccountPermissions/PermittedNetworksInfoSheet/__snapshots__/PermittedNetworksInfoSheet.test.tsx.snap +++ b/app/components/Views/AccountPermissions/PermittedNetworksInfoSheet/__snapshots__/PermittedNetworksInfoSheet.test.tsx.snap @@ -17,7 +17,7 @@ exports[`PermittedNetworksInfoSheet should render correctly 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/Views/AddressSelector/__snapshots__/AddressSelector.test.tsx.snap b/app/components/Views/AddressSelector/__snapshots__/AddressSelector.test.tsx.snap index 1b7fcea163aa..b33bdd5f3d3b 100644 --- a/app/components/Views/AddressSelector/__snapshots__/AddressSelector.test.tsx.snap +++ b/app/components/Views/AddressSelector/__snapshots__/AddressSelector.test.tsx.snap @@ -437,7 +437,7 @@ exports[`AccountSelector renders correctly and matches snapshot 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/Views/NetworkSelector/RpcSelectionModal/__snapshots__/RpcSelectionModal.test.tsx.snap b/app/components/Views/NetworkSelector/RpcSelectionModal/__snapshots__/RpcSelectionModal.test.tsx.snap index 800e1cd7cf40..2c619f2f8330 100644 --- a/app/components/Views/NetworkSelector/RpcSelectionModal/__snapshots__/RpcSelectionModal.test.tsx.snap +++ b/app/components/Views/NetworkSelector/RpcSelectionModal/__snapshots__/RpcSelectionModal.test.tsx.snap @@ -125,7 +125,7 @@ exports[`RpcSelectionModal should render correctly when visible 1`] = ` "alignItems": "center", "flexDirection": "row", "gap": 16, - "height": 48, + "height": 56, }, false, { diff --git a/app/components/Views/confirmations/hooks/send/useSendNavbar.test.tsx b/app/components/Views/confirmations/hooks/send/useSendNavbar.test.tsx index e19e4be1145d..3f396041d33e 100644 --- a/app/components/Views/confirmations/hooks/send/useSendNavbar.test.tsx +++ b/app/components/Views/confirmations/hooks/send/useSendNavbar.test.tsx @@ -56,8 +56,10 @@ jest.mock('@metamask/design-system-react-native', () => { ), ButtonIconSize: { Md: 'md' }, IconName: { Close: 'Close', ArrowLeft: 'ArrowLeft' }, - TextVariant: { BodyMd: 'body-md' }, + TextVariant: { BodyMd: 'body-md', BodySm: 'body-sm' }, + TextColor: { TextAlternative: 'text-alternative' }, FontWeight: { Bold: 'bold' }, + BoxAlignItems: { Center: 'center' }, }; }); diff --git a/app/constants/bridge.ts b/app/constants/bridge.ts index 6a09a1b9bdac..56bab4400230 100644 --- a/app/constants/bridge.ts +++ b/app/constants/bridge.ts @@ -6,12 +6,6 @@ import { BRIDGE_PROD_API_BASE_URL, } from '@metamask/bridge-controller'; -/** - * Swaps testnet chain ID (1337 in decimal) - * Used for testing swaps functionality on local/test networks - */ -export const SWAPS_TESTNET_CHAIN_ID: Hex = '0x539'; - /** * Native token address (zero address) * Used to represent native tokens (ETH, BNB, MATIC, etc.) across all EVM chains diff --git a/app/core/Engine/constants.ts b/app/core/Engine/constants.ts index 73f3b437b4e7..7a92acdd4699 100644 --- a/app/core/Engine/constants.ts +++ b/app/core/Engine/constants.ts @@ -1,5 +1,4 @@ import { CHAIN_IDS } from '@metamask/transaction-controller'; -import { SWAPS_TESTNET_CHAIN_ID } from '../../constants/bridge'; /** * Messageable modules that are part of the Engine's context, but are not defined with state. @@ -88,7 +87,6 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [ export const swapsSupportedChainIds = [ CHAIN_IDS.MAINNET, CHAIN_IDS.BSC, - SWAPS_TESTNET_CHAIN_ID, CHAIN_IDS.POLYGON, CHAIN_IDS.AVALANCHE, CHAIN_IDS.ARBITRUM, diff --git a/app/reducers/swaps/index.js b/app/reducers/swaps/index.js index 06c51c6b5bda..fecee02ce9b8 100644 --- a/app/reducers/swaps/index.js +++ b/app/reducers/swaps/index.js @@ -2,34 +2,23 @@ import { createSelector } from 'reselect'; import { isMainnetByChainId } from '../../util/networks'; import { safeToChecksumAddress, areAddressesEqual } from '../../util/address'; import { lte } from '../../util/lodash'; -import { - selectChainId, - selectEvmChainId, -} from '../../selectors/networkController'; +import { selectEvmChainId } from '../../selectors/networkController'; import { selectAllTokens, selectTokens, } from '../../selectors/tokensController'; import { selectTokenList } from '../../selectors/tokenListController'; import { selectContractBalances } from '../../selectors/tokenBalancesController'; -import { getChainFeatureFlags, getSwapsLiveness } from './utils'; -import { allowedTestnetChainIds } from '../../components/UI/Swaps/utils'; -import { NETWORKS_CHAIN_ID } from '../../constants/network'; +import { getSwapsLiveness } from './utils'; import { selectSelectedInternalAccountAddress } from '../../selectors/accountsController'; import { CHAIN_ID_TO_NAME_MAP } from '@metamask/swaps-controller/dist/constants'; import { invert, omit } from 'lodash'; import { createDeepEqualSelector } from '../../selectors/util'; import { toHex } from '@metamask/controller-utils'; -import { SolScope } from '@metamask/keyring-api'; - -// If we are in dev and on a testnet, just use mainnet feature flags, -// since we don't have feature flags for testnets in the API -export const getFeatureFlagChainId = (chainId) => - typeof __DEV__ !== 'undefined' && - __DEV__ && - allowedTestnetChainIds.includes(chainId) - ? NETWORKS_CHAIN_ID.MAINNET - : chainId; + +// Identity function, will be removed when legacy swaps is removed, +// but keep it for now to keep changes atomic. +export const getFeatureFlagChainId = (chainId) => chainId; // * Constants export const SWAPS_SET_LIVENESS = 'SWAPS_SET_LIVENESS'; diff --git a/app/reducers/swaps/swaps.test.ts b/app/reducers/swaps/swaps.test.ts index 08810ce2c395..c760b6c9930d 100644 --- a/app/reducers/swaps/swaps.test.ts +++ b/app/reducers/swaps/swaps.test.ts @@ -4,7 +4,6 @@ import Device from '../../util/device'; import { NetworkClientType } from '@metamask/network-controller'; // eslint-disable-next-line import/no-namespace import * as tokensControllerSelectors from '../../selectors/tokensController'; -import { NETWORKS_CHAIN_ID } from '../../constants/network'; import { FeatureFlags } from '@metamask/swaps-controller/dist/types'; // Type definitions for the swaps reducer @@ -42,15 +41,11 @@ interface SwapsState { } jest.mock('../../selectors/tokensController'); -jest.mock('../../components/UI/Swaps/utils', () => ({ - allowedTestnetChainIds: ['0xaa36a7'], // Sepolia testnet -})); jest.mock('@metamask/swaps-controller/dist/constants', () => ({ CHAIN_ID_TO_NAME_MAP: { '0x1': 'ethereum', '0x38': 'bsc', '0x89': 'polygon', - '0xaa36a7': 'sepolia', }, })); @@ -111,16 +106,6 @@ const DEFAULT_FEATURE_FLAGS = { } as unknown as FeatureFlags; describe('swaps reducer', () => { - const withGlobalDev = (devValue: boolean, testFn: () => void) => { - const originalDev = (global as { __DEV__?: boolean }).__DEV__; - (global as { __DEV__?: boolean }).__DEV__ = devValue; - try { - testFn(); - } finally { - (global as { __DEV__?: boolean }).__DEV__ = originalDev; - } - }; - it('should return initial state', () => { const state = reducer(undefined, emptyAction); expect(state).toEqual(initialState); @@ -299,30 +284,6 @@ describe('swaps reducer', () => { ).toEqual(DEFAULT_FEATURE_FLAGS.bsc); }); - it('should handle testnet chain IDs in dev mode', () => { - withGlobalDev(true, () => { - const initalState = reducer(undefined, emptyAction); - const action: SetLivenessAction = { - type: SWAPS_SET_LIVENESS, - payload: { - featureFlags: DEFAULT_FEATURE_FLAGS, - chainId: '0xaa36a7', // Sepolia testnet - }, - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const liveState = reducer(initalState as any, action) as SwapsState; - - // Should use mainnet feature flags for testnet - expect(liveState['0x1'].featureFlags).toEqual( - DEFAULT_FEATURE_FLAGS.ethereum, - ); - // Should also set the testnet chain with mainnet flags - expect( - (liveState['0xaa36a7'] as { featureFlags?: unknown }).featureFlags, - ).toEqual(DEFAULT_FEATURE_FLAGS.ethereum); - }); - }); - it('should preserve existing state when updating feature flags', () => { const existingState = { ...initialState, @@ -404,25 +365,16 @@ describe('swaps reducer', () => { }); describe('getFeatureFlagChainId', () => { - it('should return mainnet chain ID for testnets in dev mode', () => { - withGlobalDev(true, () => { - const result = getFeatureFlagChainId('0xaa36a7'); // Sepolia - expect(result).toBe(NETWORKS_CHAIN_ID.MAINNET); - }); - }); + it('returns the same chain ID without modification', () => { + const result = getFeatureFlagChainId('0x1'); - it('should return original chain ID for non-testnets', () => { - withGlobalDev(true, () => { - const result = getFeatureFlagChainId('0x38'); // BSC - expect(result).toBe('0x38'); - }); + expect(result).toBe('0x1'); }); - it('should return original chain ID when not in dev mode', () => { - withGlobalDev(false, () => { - const result = getFeatureFlagChainId('0xaa36a7'); // Sepolia - expect(result).toBe('0xaa36a7'); - }); + it('returns the same chain ID for any input', () => { + const result = getFeatureFlagChainId('0x38'); + + expect(result).toBe('0x38'); }); }); diff --git a/package.json b/package.json index b1aeb2fccdaa..02556e8766ff 100644 --- a/package.json +++ b/package.json @@ -292,7 +292,7 @@ "@metamask/token-search-discovery-controller": "^4.0.0", "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.7.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch", "@metamask/transaction-pay-controller": "^10.5.0", - "@metamask/tron-wallet-snap": "^1.16.1", + "@metamask/tron-wallet-snap": "^1.17.0", "@metamask/utils": "^11.8.1", "@ngraveio/bc-ur": "^1.1.6", "@nktkas/hyperliquid": "^0.27.1", diff --git a/yarn.lock b/yarn.lock index 545edfd896f2..bc55a7a53734 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9601,10 +9601,10 @@ __metadata: languageName: node linkType: hard -"@metamask/tron-wallet-snap@npm:^1.16.1": - version: 1.16.1 - resolution: "@metamask/tron-wallet-snap@npm:1.16.1" - checksum: 10/f6e871c911edc22af6955676e190a7479446a1663ebecdbd69ee2cf31611a3721a162ed8d6a9d8857f44809a6c4973dd1b9c33c067a5168297c62a21f7cfa44c +"@metamask/tron-wallet-snap@npm:^1.17.0": + version: 1.17.0 + resolution: "@metamask/tron-wallet-snap@npm:1.17.0" + checksum: 10/b7ec689ed01d55342a12dc25cb9da7bd03e2d8df65a52e15c11a2e69842339d57cf7ea30d66ebff1e902d6dd43dff5cf7b559a4b6a5c3d1b910fb1f64192b549 languageName: node linkType: hard @@ -34257,7 +34257,7 @@ __metadata: "@metamask/token-search-discovery-controller": "npm:^4.0.0" "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.7.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch" "@metamask/transaction-pay-controller": "npm:^10.5.0" - "@metamask/tron-wallet-snap": "npm:^1.16.1" + "@metamask/tron-wallet-snap": "npm:^1.17.0" "@metamask/utils": "npm:^11.8.1" "@ngraveio/bc-ur": "npm:^1.1.6" "@nktkas/hyperliquid": "npm:^0.27.1"