Skip to content

Commit 4427372

Browse files
fix: stable button widtah and disable wrap (#29021)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** https://github.com/user-attachments/assets/fa95c546-2050-4019-a190-7e2edb141e8c <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Fixed a layout shift that happened when following/unfollowing traders with high PNL ## **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** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a summary for commit 8a1961d. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 72b66ad commit 4427372

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

app/components/Views/Homepage/Sections/TopTraders/components/TraderRow.test.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
2+
import { StyleSheet } from 'react-native';
23
import { screen, fireEvent } from '@testing-library/react-native';
4+
import type { ReactTestInstance } from 'react-test-renderer';
35
import renderWithProvider from '../../../../../../util/test/renderWithProvider';
46
import TraderRow from './TraderRow';
57
import type { TopTrader } from '../types';
@@ -114,4 +116,91 @@ describe('TraderRow', () => {
114116
);
115117
expect(screen.getByTestId('custom-test-id')).toBeOnTheScreen();
116118
});
119+
120+
describe('layout stability (prevents Follow/Following toggle shift)', () => {
121+
const findAncestor = (
122+
start: ReactTestInstance | null,
123+
predicate: (node: ReactTestInstance) => boolean,
124+
): ReactTestInstance | null => {
125+
let node: ReactTestInstance | null = start;
126+
while (node) {
127+
if (predicate(node)) return node;
128+
node = node.parent;
129+
}
130+
return null;
131+
};
132+
133+
const resolveMinWidth = (node: ReactTestInstance): number | undefined => {
134+
const flat = StyleSheet.flatten(node.props.style) as
135+
| { minWidth?: number }
136+
| undefined;
137+
return flat?.minWidth;
138+
};
139+
140+
it('renders the stats line with numberOfLines=1 so it does not wrap when the button grows', () => {
141+
const trader: TopTrader = {
142+
...baseTrader,
143+
percentageChange: 28233,
144+
pnlValue: 407000,
145+
};
146+
renderWithProvider(
147+
<TraderRow trader={trader} onFollowPress={mockOnFollowPress} />,
148+
);
149+
150+
const roiSegment = screen.getByText('+28233.0%');
151+
const statsText = findAncestor(
152+
roiSegment,
153+
(node) => node.props?.numberOfLines === 1,
154+
);
155+
156+
expect(statsText).not.toBeNull();
157+
});
158+
159+
it('renders the Follow button with a minimum width', () => {
160+
renderWithProvider(
161+
<TraderRow trader={baseTrader} onFollowPress={mockOnFollowPress} />,
162+
);
163+
164+
const followLabel = screen.getByText('Follow');
165+
const buttonWithMinWidth = findAncestor(
166+
followLabel,
167+
(node) => resolveMinWidth(node) !== undefined,
168+
);
169+
170+
expect(buttonWithMinWidth).not.toBeNull();
171+
expect(resolveMinWidth(buttonWithMinWidth as ReactTestInstance)).toBe(96);
172+
});
173+
174+
it('keeps the same minimum width when toggling between Follow and Following', () => {
175+
const { rerender } = renderWithProvider(
176+
<TraderRow trader={baseTrader} onFollowPress={mockOnFollowPress} />,
177+
);
178+
179+
const followLabel = screen.getByText('Follow');
180+
const followButton = findAncestor(
181+
followLabel,
182+
(node) => resolveMinWidth(node) !== undefined,
183+
);
184+
const followMinWidth = resolveMinWidth(followButton as ReactTestInstance);
185+
186+
rerender(
187+
<TraderRow
188+
trader={{ ...baseTrader, isFollowing: true }}
189+
onFollowPress={mockOnFollowPress}
190+
/>,
191+
);
192+
193+
const followingLabel = screen.getByText('Following');
194+
const followingButton = findAncestor(
195+
followingLabel,
196+
(node) => resolveMinWidth(node) !== undefined,
197+
);
198+
const followingMinWidth = resolveMinWidth(
199+
followingButton as ReactTestInstance,
200+
);
201+
202+
expect(followMinWidth).toBeDefined();
203+
expect(followingMinWidth).toBe(followMinWidth);
204+
});
205+
});
117206
});

app/components/Views/Homepage/Sections/TopTraders/components/TraderRow.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ const TraderRow: React.FC<TraderRowProps> = ({
105105
>
106106
{trader.username}
107107
</Text>
108-
<Text variant={TextVariant.BodySm} fontWeight={FontWeight.Medium}>
108+
<Text
109+
variant={TextVariant.BodySm}
110+
fontWeight={FontWeight.Medium}
111+
numberOfLines={1}
112+
>
109113
<Text
110114
variant={TextVariant.BodySm}
111115
fontWeight={FontWeight.Medium}
@@ -150,6 +154,7 @@ const TraderRow: React.FC<TraderRowProps> = ({
150154
}
151155
size={ButtonSize.Sm}
152156
onPress={() => onFollowPress(trader.id)}
157+
twClassName="min-w-[96px]"
153158
>
154159
{trader.isFollowing
155160
? strings('social_leaderboard.following')

0 commit comments

Comments
 (0)