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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .cursor/rules/component-view-testing.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
description: Mobile component-view testing rules (project-wide). Engine-only mocks, state-driven tests, presets/renderers, deterministicFiat, AAA, execution.
globs:
- "**/*.view.test.{ts,tsx,js,jsx}"
- "app/util/test/component-view/**"
alwaysApply: false
---

## Component-view Testing (Project-wide)

Follow these rules for all component-view tests. Nested, component-specific rules may also apply.

- **Mock policy**
- Only mock `../../../core/Engine`, `../../../core/Engine/Engine`, and `react-native-device-info`.
- No mocking of hooks or selectors; drive behavior via Redux state.
- Enforced by runtime guard in `app/util/test/testSetup.js` and ESLint override in `.eslintrc.js`.

- **Framework usage (REQUIRED)**
- Use presets: `initialStateBridge`, `initialStateWallet`.
- Use renderers: `renderBridgeView`, `renderWalletView`.
- Use `stateFixture.ts` helpers for overrides (e.g., quotes, feature flags).
- Prefer `deterministicFiat: true` for exact fiat assertions.

- **Navigation tests**
- Use `renderScreenWithRoutes` and build `state` via the preset + minimal overrides.
- Assert using route names from `app/constants/navigation/Routes.ts`.

- **Test structure**
- AAA pattern with blank lines between sections.
- Action-oriented names (no “should”).
- One behavior per test; robust assertions over brittle formatting.

- **Execution**
- Prefer `--coverage=false` during iteration for speed.
- Use yarn only (no npm/npx).

- **References**
- Component-view framework guide: `app/util/test/component-view/README.md`
- Detailed rules: `app/util/test/component-view/COMPONENT_VIEW_TEST_RULES.md`
- Cursor authoring rules: [Cursor Context Rules](https://cursor.com/docs/context/rules)

@app/util/test/component-view/README.md
@app/util/test/component-view/COMPONENT_VIEW_TEST_RULES.md
2 changes: 1 addition & 1 deletion .cursor/rules/pr-creation-guidelines.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Use `.github/pull-request-template.md` - fill ALL sections:
- Labels applied
- Target `main` branch
- Keep focused (single feature/fix)
- Include tests (unit/integration/e2e)
- Include tests (unit/Component-view/e2e)
- Update TSDoc when relevant

**⚠️ Force Push Policy**:
Expand Down
14 changes: 14 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ module.exports = {
},
},
},
{
files: ['**/*.view.test.{js,ts,tsx,jsx}'],
rules: {
'no-restricted-syntax': [
'error',
{
selector:
"CallExpression[callee.object.name='jest'][callee.property.name='mock'][arguments.0.type='Literal'][arguments.0.value!='../../../core/Engine'][arguments.0.value!='../../../core/Engine/Engine'][arguments.0.value!='react-native-device-info']",
message:
'Only Engine and react-native-device-info can be mocked in component-view tests.',
},
],
},
},
],

globals: {
Expand Down
26 changes: 22 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,28 @@ jobs:
else
echo "No changes detected"
fi

needs_e2e_build:
uses: ./.github/workflows/needs-e2e-build.yml

uses: ./.github/workflows/needs-e2e-build.yml
component-view-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
- name: Install Yarn dependencies with retry
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: yarn install --immutable
- name: Clean state and following up dependencies installation
run: yarn setup:github-ci --node
- run: yarn test:view --coverage=false --forceExit --silent
env:
NODE_OPTIONS: --max_old_space_size=20480
smart-e2e-selection:
name: 'Smart E2E Selection'
runs-on: ubuntu-latest
Expand Down Expand Up @@ -366,7 +384,7 @@ jobs:
- name: Generate iOS bundle
run: yarn gen-bundle:ios
env:
NODE_OPTIONS: --max_old_space_size=8192
NODE_OPTIONS: --max_old_space_size=12288

- name: Check bundle size
run: ./scripts/js-bundle-stats.sh ios/main.jsbundle 52
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/merge-approved-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Merge Approved PR

on:
# Trigger the workflow when a comment is created on an issue or pull request
issue_comment:
types: [created]

jobs:
merge-pr:
# Only run if the comment is on a PR and the comment body matches exactly "Merge my PR"
if: >-
github.event.issue.pull_request &&
github.event.comment.body == 'Merge my PR' &&
(
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'OWNER'
)
uses: MetaMask/github-tools/.github/workflows/merge-approved-pr.yml@30baa9da0b80d94dd8bdf0b6a932c46506664857
with:
pr-number: ${{ github.event.issue.number }}
secrets:
github-token: ${{ secrets.METAMASK_MOBILE_BRANCH_SYNC_TOKEN }}
6 changes: 3 additions & 3 deletions app/components/Base/RemoteImage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const RemoteImage: React.FC<RemoteImageProps> = (props) => {

const uri =
resolvedIpfsUrl ||
(source.uri === undefined || source.uri?.startsWith('ipfs')
(!source || source.uri === undefined || source.uri?.startsWith('ipfs')
? ''
: source.uri);

Expand All @@ -71,7 +71,7 @@ const RemoteImage: React.FC<RemoteImageProps> = (props) => {
useEffect(() => {
async function resolveIpfsUrl() {
try {
if (!source.uri) {
if (!source?.uri) {
setResolvedIpfsUrl(false);
return;
}
Expand All @@ -88,7 +88,7 @@ const RemoteImage: React.FC<RemoteImageProps> = (props) => {
}
}
resolveIpfsUrl();
}, [source.uri, ipfsGateway]);
}, [source?.uri, ipfsGateway]);

const calculateImageDimensions = useCallback(
(imageWidth: number, imageHeight: number) => {
Expand Down
Loading
Loading