Skip to content

Commit b14cdcc

Browse files
committed
merge
2 parents f9e7b89 + 4e37e58 commit b14cdcc

File tree

344 files changed

+42258
-16529
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

344 files changed

+42258
-16529
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
---
2+
description: Project Guidelines for Unit Testing
3+
globs: *.test.*
4+
alwaysApply: false
5+
---
6+
- Use (the unit testing guidelines)[https://github.com/MetaMask/contributor-docs/blob/main/docs/testing/unit-testing.md]
7+
8+
# Developer Best Practices
9+
10+
- Use meaningful test names that describe the purpose, not implementation details.
11+
```ts
12+
it('displays an error when input is invalid', () => { ... });
13+
```
14+
- Structure tests using the AAA pattern (Arrange, Act, Assert) for readability and maintainability.
15+
```ts
16+
// Arrange
17+
const input = 'bad';
18+
// Act
19+
const result = validate(input);
20+
// Assert
21+
expect(result).toBe(false);
22+
```
23+
- Each test must cover one behavior and be isolated from others.
24+
```ts
25+
it('returns true for valid email', () => { expect(isEmail('a@b.com')).toBe(true); });
26+
```
27+
- Favor focused assertions and clear structure.
28+
```ts
29+
expect(screen.getByText('Welcome')).toBeOnTheScreen();
30+
```
31+
- Avoid duplicated or polluted tests.
32+
```ts
33+
// Only define shared mocks once, not in every test
34+
beforeEach(() => mockReset());
35+
```
36+
- Use mocks only when necessary.
37+
```ts
38+
jest.mock('api'); // mock only when calling real API is not feasible
39+
```
40+
- Parameterize tests to cover all values (e.g., enums) with type-safe iteration.
41+
```ts
42+
it.each(['small', 'medium', 'large'] as const)('renders %s size', (size) => {
43+
expect(renderComponent(size)).toBeOnTheScreen();
44+
});
45+
```
46+
- Prefer comments like "Given / When / Then" for test case clarity.
47+
```ts
48+
// Given a logged out user
49+
// When they visit the dashboard
50+
// Then they should be redirected to login
51+
```
52+
53+
# Test Determinism & Brittleness
54+
55+
- Avoid brittle tests: do not test internal state or UI snapshots for logic.
56+
- Only test public behavior, not implementation details.
57+
- Mock time, randomness, and external systems to ensure consistent results.
58+
```ts
59+
jest.useFakeTimers();
60+
jest.setSystemTime(new Date('2024-01-01'));
61+
```
62+
- Avoid relying on global state or hardcoded values (e.g., dates) or mock it.
63+
64+
# Reviewer Responsibilities
65+
66+
- Validate that tests fail when the code is broken (test the test).
67+
```ts
68+
// Break the SuT and make sure this test fails
69+
expect(result).toBe(false);
70+
```
71+
- Ensure tests use proper matchers (`toBeOnTheScreen` vs `toBeDefined`).
72+
- Do not approve PRs without reviewing snapshot diffs.
73+
- Reject tests with complex names combining multiple logical conditions (AND/OR).
74+
```ts
75+
// OK
76+
it('renders button when enabled')
77+
78+
// NOT OK
79+
it('renders and disables button when input is empty or invalid')
80+
```
81+
82+
# Refactoring Support
83+
84+
- Ensure tests provide safety nets during refactors and logic changes. Run the tests before pushing commits!
85+
- Encourage small, testable components.
86+
- Unit tests must act as documentation for feature expectations.
87+
88+
# Anti-patterns to Avoid
89+
90+
- ❌ Do not consider snapshot coverage as functional coverage.
91+
```ts
92+
expect(component).toMatchSnapshot(); // 🚫 not behavior validation
93+
```
94+
- ❌ Do not rely on code coverage percentage without real assertions.
95+
```ts
96+
// 100% lines executed, but no assertions
97+
```
98+
- ❌ Do not use weak matchers like `toBeDefined` or `toBeTruthy` to assert element presence.
99+
```ts
100+
expect(queryByText('Item')).toBeOnTheScreen(); // ✅ use strong matchers
101+
```
102+
103+
# Unit tests developement workflow
104+
105+
- Always run unit tests after making code changes.
106+
```shell
107+
yarn test:unit
108+
```
109+
- Confirm all tests are passing before commit.
110+
- When a snapshot update is detected, confirm the changes are expected.
111+
- Do not blindly update snapshots without understanding the differences.
112+
113+
# Reference Code Examples
114+
115+
## ✅ Proper Test Structure (AAA)
116+
```ts
117+
it('indicates expired milk when past due date', () => {
118+
// Arrange
119+
const today = new Date('2025-06-01');
120+
const milk = { expiration: new Date('2025-05-30') };
121+
122+
// Act
123+
const result = isMilkGood(today, milk);
124+
125+
// Assert
126+
expect(result).toBe(false);
127+
});
128+
```
129+
130+
## ❌ Brittle Snapshot
131+
```ts
132+
it('renders the button', () => {
133+
const { container } = render(<MyButton />);
134+
expect(container).toMatchSnapshot(); // 🚫 fails on minor style changes
135+
});
136+
```
137+
138+
## ✅ Robust UI Assertion
139+
```ts
140+
it('displays error message when API fails', async () => {
141+
mockApi.failOnce();
142+
const { findByText } = render(<MyComponent />);
143+
expect(await findByText('Something went wrong')).toBeOnTheScreen();
144+
});
145+
```
146+
147+
## ✅ Test the Test
148+
```ts
149+
it('hides selector when disabled', () => {
150+
const { queryByTestId } = render(<Selector enabled={false} />);
151+
expect(queryByTestId('IPFS_GATEWAY_SELECTED')).toBeNull();
152+
153+
// Intentionally break: render with enabled=true and see if test fails
154+
});
155+
```
156+
157+
# Resources
158+
159+
- Contributor docs: https://github.com/MetaMask/contributor-docs/blob/main/docs/testing/unit-testing.md
160+
- Jest Matchers: https://jestjs.io/docs/using-matchers
161+
- React Native Testing Library: https://testing-library.com/docs/react-native-testing-library/intro/

.github/CODEOWNERS

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,6 @@ app/core/Engine/controllers/seedless-onboarding-controller @MetaMask/web3auth
190190
app/components/Views/Onboarding @MetaMask/web3auth
191191
app/reducers/onboarding @MetaMask/web3auth
192192

193-
# QA Team
194-
e2e/** @MetaMask/qa
195-
wdio/** @MetaMask/qa
196-
197-
198-
199193
# Snapshots – no code owners assigned
200194
# This allows anyone with write access to approve changes to any *.snap files.
201195
# ⚠️ Note: Leaving this rule unassigned disables Code Owner review enforcement for snapshot files.

.js.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,7 @@ export RAMP_INTERNAL_BUILD="true"
133133

134134

135135
# set seedless onboarding environment
136-
export WEB3AUTH_NETWORK="sapphire_devnet"
136+
export WEB3AUTH_NETWORK="sapphire_devnet"
137+
138+
# Enable 7702 locally
139+
export MM_SMART_ACCOUNT_UI_ENABLED="true"

.storybook/main.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ module.exports = {
22
stories: [
33
'../app/component-library/components/**/*.stories.?(ts|tsx|js|jsx)',
44
'../app/component-library/base-components/**/*.stories.?(ts|tsx|js|jsx)',
5-
'../app/component-library/components-temp/TagColored/**/*.stories.?(ts|tsx|js|jsx)',
6-
'../app/component-library/components-temp/KeyValueRow/**/*.stories.?(ts|tsx|js|jsx)',
7-
'../app/component-library/components-temp/Buttons/ButtonToggle/**/*.stories.?(ts|tsx|js|jsx)',
8-
'../app/component-library/components-temp/SegmentedControl/**/*.stories.?(ts|tsx|js|jsx)',
5+
'../app/component-library/components-temp/**/*.stories.?(ts|tsx|js|jsx)',
96
],
107
addons: ['@storybook/addon-ondevice-controls'],
118
framework: '@storybook/react-native',

.storybook/storybook.requires.js

Lines changed: 9 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- feat(bridge): add "hardware wallets not supported" error when attempting Solana swap/bridge ([#15743](https://github.com/MetaMask/metamask-mobile/pull/15743))
1111
- feat(bridge): fetch all tokens for bridge input([#15993](https://github.com/MetaMask/metamask-mobile/pull/15993))
1212
- fix(bridge): don't show currency value unless amount is above zero ([#15798](https://github.com/MetaMask/metamask-mobile/pull/15798))
13+
- fix(bridge): prevent crash when viewing Solana asset details ([#16770](https://github.com/MetaMask/metamask-mobile/pull/16770))
1314

1415
## [7.47.1]
1516

app/component-library/components-temp/ListItemMultiSelectButton/ListItemMultiSelectButton.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const ListItemWithButtonSelect = {
6565
<Icon name={IconName.Clock} />
6666
</ListItemColumn>
6767
<ListItemColumn widthType={WidthType.Fill}>
68-
<Text numberOfLines={1} variant={TextVariant.HeadingSMRegular}>
68+
<Text numberOfLines={1} variant={TextVariant.BodyMD}>
6969
{'Sample Title'}
7070
</Text>
7171
<Text variant={TextVariant.BodyMD}>{'Sample Description'}</Text>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* eslint-disable import/prefer-default-export */
2+
3+
// Test IDs
4+
export const MAINACTIONBUTTON_TEST_ID = 'main-action-button';
5+
export const MAINACTIONBUTTON_ICON_TEST_ID = 'main-action-button-icon';
6+
export const MAINACTIONBUTTON_LABEL_TEST_ID = 'main-action-button-label';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// External dependencies.
2+
import { IconName } from '../../components/Icons/Icon';
3+
4+
// Internal dependencies.
5+
import { default as MainActionButtonComponent } from './MainActionButton';
6+
7+
const MainActionButtonMeta = {
8+
title: 'Components Temp / Main Action Button',
9+
component: MainActionButtonComponent,
10+
argTypes: {
11+
iconName: {
12+
options: IconName,
13+
control: {
14+
type: 'select',
15+
},
16+
},
17+
label: {
18+
control: { type: 'text' },
19+
},
20+
isDisabled: {
21+
control: { type: 'boolean' },
22+
},
23+
},
24+
};
25+
export default MainActionButtonMeta;
26+
27+
export const MainActionButton = {
28+
args: {
29+
iconName: IconName.Add,
30+
label: 'Add',
31+
isDisabled: false,
32+
},
33+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Third party dependencies.
2+
import { StyleSheet } from 'react-native';
3+
4+
// External dependencies.
5+
import { Theme } from '../../../util/theme/models';
6+
7+
// Internal dependencies.
8+
import { MainActionButtonStyleSheetVars } from './MainActionButton.types';
9+
10+
/**
11+
* Style sheet function for MainActionButton component.
12+
*
13+
* @param params Style sheet params.
14+
* @param params.theme App theme from ThemeContext.
15+
* @param params.vars Inputs that the style sheet depends on.
16+
* @returns StyleSheet object.
17+
*/
18+
const styleSheet = (params: {
19+
theme: Theme;
20+
vars: MainActionButtonStyleSheetVars;
21+
}) => {
22+
const { theme, vars } = params;
23+
const { style, isDisabled } = vars;
24+
25+
let backgroundColor = theme.colors.background.muted;
26+
27+
if (isDisabled) {
28+
backgroundColor = theme.colors.background.muted;
29+
}
30+
31+
return StyleSheet.create({
32+
base: Object.assign(
33+
{
34+
backgroundColor,
35+
borderRadius: 12,
36+
padding: 8,
37+
minWidth: 68,
38+
minHeight: 68,
39+
justifyContent: 'center',
40+
alignItems: 'center',
41+
opacity: isDisabled ? 0.5 : 1,
42+
} as const,
43+
style,
44+
),
45+
pressed: {
46+
backgroundColor: theme.colors.background.pressed,
47+
},
48+
container: {
49+
alignItems: 'center',
50+
justifyContent: 'center',
51+
},
52+
label: {
53+
textAlign: 'center',
54+
marginTop: 2,
55+
},
56+
});
57+
};
58+
59+
export default styleSheet;

0 commit comments

Comments
 (0)