From 3e16f7d5b3744d9ce2741e1d8ec8e21e374e2cb9 Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Wed, 7 Jan 2026 09:04:13 +0300 Subject: [PATCH 1/4] fix: Skip passed e2e tests on re-run to reduce CI time (#24263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Similar what we've done in the extension [here](https://github.com/MetaMask/metamask-extension/pull/38932), this PR implements same approach into mobile repository. For more information please see thread [here](https://consensys.slack.com/archives/C1L7H42BT/p1765974245415709). When an E2E test fails in CI and developer triggers a re-run, all tests in that chunk are executed again, not just the failed ones. This leads to: - Wasted CI time: A chunk with 20 tests where only 1 failed will re-run all 20 tests - Increased costs: Unnecessary compute resources consumed on re-runs - Slower feedback loop: Developers wait longer to verify if a flaky test passes on retry - Inefficient debugging: When investigating flaky tests, having to wait for unrelated tests adds friction For example, with 20 parallel chunks each running ~15 tests, a single flaky test failure triggers a re-run of the entire chunk (~15 tests) instead of just the 1 failed test. For solution; implement selective test re-runs that automatically detect when a job is being re-run and skip tests that already passed. - First run (run_attempt = 1): Tests run normally, results are uploaded as artifacts - Re-run (run_attempt > 1): - Download the previous run's test result artifacts - Parse XML results to extract passed test file paths - Run all tests in the chunk EXCEPT those that passed - Skip execution entirely if all tests in the chunk passed previously --- ### Validation of PR: Intentionally broke tests here: https://github.com/MetaMask/metamask-mobile/pull/24263/commits/c8f029f28700d51e5842d43b1d0db1487b8f70e0 On attempt1 it failed but other tests passed: https://github.com/MetaMask/metamask-mobile/actions/runs/20751876785/attempts/1?pr=24263 On attempt2 it only run failed test and skipped already passed tests: https://github.com/MetaMask/metamask-mobile/actions/runs/20751876785/attempts/2?pr=24263 Logs from attempt 2: ``` Found 8 executed test files Found 2 failed test files Found 6 fully passed test files Failed tests: e2e/specs/confirmations-redesigned/transactions/send-max-transfer.spec.ts, e2e/specs/confirmations-redesigned/transactions/send-max-transfer-retry-1.spec.ts Previous run results: 6 passed, 2 failed This chunk: 1 failed, 0 not executed ``` ## **Changelog** CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [X] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- > [!NOTE] > Reduces CI time by re-running only failed or not-executed E2E specs on job re-runs and preserving pass/fail history. > > - New scripts: `e2e-extract-test-results.mjs` (parses jest-junit XML to identify passed/failed/executed specs) and `e2e-merge-test-results.mjs` (merges prior XML results for specs not executed this attempt) > - Updates `e2e-split-tags-shards.mjs` to: on re-runs, filter shard files using extracted results; skip if all passed; limit flakiness duplication to first attempt > - Modifies `run-e2e-workflow.yml` to: download previous results on re-runs, pass `RUN_ATTEMPT`/`PREVIOUS_RESULTS_PATH` envs, add debug listing, and merge previous results into current `e2e/reports` before uploading artifacts > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e5cd5c77d6b6940199494fde424af646c73e620a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .github/scripts/e2e-extract-test-results.mjs | 209 +++++++++++++++++++ .github/scripts/e2e-merge-test-results.mjs | 182 ++++++++++++++++ .github/scripts/e2e-split-tags-shards.mjs | 63 +++++- .github/workflows/run-e2e-workflow.yml | 47 +++++ 4 files changed, 494 insertions(+), 7 deletions(-) create mode 100644 .github/scripts/e2e-extract-test-results.mjs create mode 100644 .github/scripts/e2e-merge-test-results.mjs 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 From bbef7bff914513b74d79a3228318c2b561648e79 Mon Sep 17 00:00:00 2001 From: Brian August Nguyen Date: Wed, 7 Jan 2026 01:34:33 -0800 Subject: [PATCH 2/4] chore: add subtitle to Headers and increase height to 56px (#24268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR enhances the `HeaderCenter` and `HeaderWithTitleLeftScrollable` components with additional functionality: 1. **Added subtitle support** - Both components now support an optional `subtitle` prop that renders below the title with center alignment, along with `subtitleProps` for customization. 2. **Extended HeaderBase props** - Removed the `Omit` constraints from both components: - `HeaderCenter` now extends `HeaderBaseProps` directly (previously omitted `startButtonIconProps`) - `HeaderWithTitleLeftScrollable` now extends `HeaderBaseProps` directly (previously omitted `children`) 3. **Added `titleProps` to HeaderWithTitleLeftScrollable** - Allows customization of the compact header title. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Header subtitle support Scenario: user views header with subtitle Given user is on a screen using HeaderCenter or HeaderWithTitleLeftScrollable When the header is configured with title and subtitle props Then the title displays in bold And the subtitle displays below the title in a smaller, alternative color text Scenario: user scrolls on HeaderWithTitleLeftScrollable page Given user is on a screen using HeaderWithTitleLeftScrollable with subtitle When user scrolls down Then the header collapses to show compact title with subtitle And the subtitle remains visible below the compact title ``` ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/ecc857b5-7902-4adf-b25c-51281de8966f Simulator Screenshot - iPhone 15
Pro Max - 2026-01-06 at 08 55 39 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- > [!NOTE] > Introduces subtitle support and raises the compact header height across header components. > > - `HeaderCenter`: adds `subtitle` and `subtitleProps`; stacks title + subtitle centered; story `WithSubtitle` and tests for rendering/props > - `HeaderWithTitleLeftScrollable`: adds `titleProps`, `subtitle`, `subtitleProps`, and `children` handling in compact header; new tests for subtitle and children precedence; stories updated (`WithSubtitle`, `OnClose`, `BackAndClose`) > - Increases compact height from 48 to 56 via `HeaderBase` (`h-12` → `h-14`) and `HeaderWithTitleLeftScrollable` `DEFAULT_COLLAPSED_HEIGHT` to `56`; updates numerous snapshots accordingly > - Both headers now pass through `HeaderBaseProps` and resolve start/end buttons (back/close) consistently > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit dd0c82601d799c82aabfabb5bdb93d3663a4786e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../HeaderCenter/HeaderCenter.stories.tsx | 13 +++ .../HeaderCenter/HeaderCenter.test.tsx | 35 +++++++ .../HeaderCenter/HeaderCenter.tsx | 31 ++++-- .../HeaderCenter/HeaderCenter.types.ts | 10 ++ .../HeaderWithTitleLeft.stories.tsx | 27 +++++ .../HeaderWithTitleLeftScrollable.stories.tsx | 99 ++++++++++++------- .../HeaderWithTitleLeftScrollable.test.tsx | 88 +++++++++++++++++ .../HeaderWithTitleLeftScrollable.tsx | 46 +++++++-- .../HeaderWithTitleLeftScrollable.types.ts | 23 ++++- .../BottomSheetHeader.test.tsx.snap | 4 +- .../components/HeaderBase/HeaderBase.tsx | 2 +- .../__snapshots__/form.test.ts.snap | 4 +- .../BridgeDestNetworkSelector.test.tsx.snap | 2 +- .../BridgeDestTokenSelector.test.tsx.snap | 2 +- .../BridgeSourceNetworkSelector.test.tsx.snap | 2 +- .../BridgeSourceTokenSelector.test.tsx.snap | 2 +- .../QuoteExpiredModal.test.tsx.snap | 2 +- .../__snapshots__/SlippageModal.test.tsx.snap | 2 +- .../AddFundsBottomSheet.test.tsx.snap | 8 +- .../LendingLearnMoreModal.test.tsx.snap | 2 +- .../__snapshots__/EarnTokenList.test.tsx.snap | 2 +- .../__snapshots__/MaxInputModal.test.tsx.snap | 2 +- .../LendingMaxWithdrawalModal.test.tsx.snap | 2 +- .../__snapshots__/index.test.tsx.snap | 2 +- .../NetworkVerificationInfo.test.tsx.snap | 4 +- .../PerpsBottomSheetTooltip.test.tsx.snap | 2 +- .../__snapshots__/Checkout.test.tsx.snap | 20 ++-- .../__snapshots__/SettingsModal.test.tsx.snap | 2 +- .../Quotes/__snapshots__/Quotes.test.tsx.snap | 6 +- .../FiatSelectorModal.test.tsx.snap | 8 +- ...ncompatibleAccountTokenModal.test.tsx.snap | 2 +- .../PaymentMethodSelectorModal.test.tsx.snap | 6 +- .../RegionSelectorModal.test.tsx.snap | 22 ++--- .../TokenSelectModal.test.tsx.snap | 2 +- .../UnsupportedRegionModal.test.tsx.snap | 4 +- .../ConfigurationModal.test.tsx.snap | 2 +- .../ErrorDetailsModal.test.tsx.snap | 2 +- ...ncompatibleAccountTokenModal.test.tsx.snap | 2 +- .../PaymentMethodSelectorModal.test.tsx.snap | 2 +- .../RegionSelectorModal.test.tsx.snap | 16 +-- .../__snapshots__/SsnInfoModal.test.tsx.snap | 2 +- .../StateSelectorModal.test.tsx.snap | 12 +-- .../TokenSelectorModal.test.tsx.snap | 6 +- .../UnsupportedRegionModal.test.tsx.snap | 4 +- .../UnsupportedStateModal.test.tsx.snap | 2 +- .../__snapshots__/WebviewModal.test.tsx.snap | 4 +- .../EligibilityFailedModal.test.tsx.snap | 2 +- .../RampUnsupportedModal.test.tsx.snap | 2 +- .../UnsupportedTokenModal.test.tsx.snap | 2 +- .../GasImpactModal.test.tsx.snap | 2 +- .../PoolStakingLearnMoreModal.test.tsx.snap | 2 +- ...tPermissionsConfirmRevokeAll.test.tsx.snap | 2 +- .../ConnectionDetails.test.tsx.snap | 2 +- .../PermittedNetworksInfoSheet.test.tsx.snap | 2 +- .../AddressSelector.test.tsx.snap | 2 +- .../RpcSelectionModal.test.tsx.snap | 2 +- .../hooks/send/useSendNavbar.test.tsx | 4 +- 57 files changed, 416 insertions(+), 152 deletions(-) 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/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' }, }; }); From 76a0e9ea2b7f05654a7d7e1c735d92de93110869 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Wed, 7 Jan 2026 10:05:59 +0000 Subject: [PATCH 3/4] chore: bump @metamask/tron-wallet-snap to 1.17.0 (#24185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Bump the Tron snap to the latest version ## **Changelog** CHANGELOG entry: Bump @metamask/tron-wallet-snap to 1.17.0 ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- > [!NOTE] > Update `@metamask/tron-wallet-snap` from `^1.16.1` to `^1.17.0` and refresh `yarn.lock`. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f7272b19407e79f7d4427c41329b6e46da3d1d77. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) 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" From 2bee8b46ebe085ff115295a90774ab5aa259446a Mon Sep 17 00:00:00 2001 From: George Gkasdrogkas Date: Wed, 7 Jan 2026 13:05:16 +0200 Subject: [PATCH 4/4] refactor: remove unused testnet config for swaps (#24244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** In MM mobile, we define an array of allowed testnets for swaps. This is consumed only on DEV env and is used by some utilities like isSwapsAllowed. To complete the legacy migration we will have to take care of those utilities (either remove or migrate) but before that, this PR removes the dependency to testnets and reduce the tech debt of the swaps module. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/SWAPS-3594 ## **Manual testing steps** ```gherkin This PR does not introduce any business logic change, just run a basic regression test to ensure swaps functionality is not affected. ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- > [!NOTE] > Removes testnet-specific swaps configuration and dev-only paths, simplifying chain gating and feature flag handling for Swaps. > > - Drops `SWAPS_TESTNET_CHAIN_ID` and `allowedTestnetChainIds`; removes `__DEV__` and `ONLY_MAINNET` branching in `isSwapsAllowed` > - Updates allowed chain lists in `UI/Swaps/utils` and `Engine/constants` to include only supported mainnets and multichain scopes > - Simplifies `getFeatureFlagChainId` to identity and adjusts liveness/feature flag mapping in `reducers/swaps` > - Cleans up imports and removes unused constants > - Updates unit tests to reflect removal of testnet logic and identity `getFeatureFlagChainId` > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit fba3425fb8fe201e63d9279f64a8b44bca9eaf2f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- app/components/UI/Swaps/utils/index.js | 18 ------ app/components/UI/Swaps/utils/index.test.js | 21 ------- app/constants/bridge.ts | 6 -- app/core/Engine/constants.ts | 2 - app/reducers/swaps/index.js | 23 ++------ app/reducers/swaps/swaps.test.ts | 62 +++------------------ 6 files changed, 13 insertions(+), 119 deletions(-) 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/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'); }); });