Skip to content

Commit 35a6250

Browse files
committed
fix(MUSD-747): no mount animation on education-seen path
Conditionally seed footerTranslateY and isStepperVisibleRef from isStepperRendered. Previously both were unconditionally initialized (translateY=240, ref=true), so on mount when education is already seen the visibility-recompute effect saw next=false (no stepper) vs ref.current=true and triggered a 300ms slide-in animation every time the screen mounted post-education. Lock the invariant via a withTiming spy: the education-seen path must not call withTiming on mount or on the post-mount scroll events. Tests previously missed this because withTiming is mocked to resolve synchronously. Addresses bugbot finding r3194824789.
1 parent c90b11d commit 35a6250

2 files changed

Lines changed: 22 additions & 7 deletions

File tree

app/components/UI/Money/Views/MoneyHomeView/MoneyHomeView.test.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,8 @@ describe('MoneyHomeView', () => {
985985
});
986986

987987
it('always renders the footer and never the stepper when education has been seen', () => {
988+
mockWithTiming.mockClear();
989+
988990
const { queryByTestId, getByTestId } = renderWithProvider(
989991
<MoneyHomeView />,
990992
{ state: educationSeenState },
@@ -997,15 +999,21 @@ describe('MoneyHomeView', () => {
997999
expect(
9981000
queryByTestId(MoneyOnboardingCardTestIds.CONTAINER),
9991001
).not.toBeOnTheScreen();
1002+
// The footer must render in its final visible position with NO
1003+
// mount animation. If withTiming fires here, the user sees an
1004+
// unwanted 300ms slide-in every time the screen mounts post-education.
1005+
expect(mockWithTiming).not.toHaveBeenCalled();
10001006

1001-
// Scroll events with the stepper absent must keep the footer visible.
1007+
// Scroll events with the stepper absent must keep the footer visible
1008+
// and must not trigger any animation (visibility ref starts in sync).
10021009
const scrollView = getByTestId(MoneyHomeViewTestIds.SCROLL_VIEW);
10031010
act(() => {
10041011
fireScrollViewLayout(scrollView);
10051012
fireScroll(scrollView, 0);
10061013
});
10071014

10081015
expect(queryByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen();
1016+
expect(mockWithTiming).not.toHaveBeenCalled();
10091017
});
10101018

10111019
it('keeps the footer translated off-screen when the stepper is below the viewport (off-screen, not yet scrolled to)', () => {

app/components/UI/Money/Views/MoneyHomeView/MoneyHomeView.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,13 @@ const MoneyHomeView = () => {
231231
const [footerHeight, setFooterHeight] = useState(0);
232232

233233
const isStepperRendered = !hasSeenMusdConversionEducation;
234-
const footerTranslateY = useSharedValue(FOOTER_HIDDEN_OFFSET);
234+
// Conditionally seed both shared value and visibility ref so the
235+
// education-seen path renders the footer in its final position with NO
236+
// mount animation. Otherwise the on-mount visibility-recompute effect
237+
// would slide the footer in from FOOTER_HIDDEN_OFFSET → 0 every time.
238+
const footerTranslateY = useSharedValue(
239+
isStepperRendered ? FOOTER_HIDDEN_OFFSET : 0,
240+
);
235241
const footerAnimatedStyle = useAnimatedStyle(() => ({
236242
transform: [{ translateY: footerTranslateY.value }],
237243
}));
@@ -240,11 +246,12 @@ const MoneyHomeView = () => {
240246
[footerHeight],
241247
);
242248

243-
// Default to "stepper visible" until layouts settle so the footer stays
244-
// hidden in the initial paint and we avoid a brief flash of "Add money".
245-
// Mirrored in a ref so the scroll-driven flip can compare without stale
246-
// state and trigger the animation outside any setState updater.
247-
const isStepperVisibleRef = useRef(true);
249+
// Seed visibility tracking based on whether the stepper is rendered at all.
250+
// - Stepper rendered: default to "visible" until layouts settle so the
251+
// footer stays hidden on initial paint and we avoid a flash of "Add money".
252+
// - Stepper not rendered (education seen): default to "not visible" so the
253+
// on-mount visibility-recompute matches and produces no animation.
254+
const isStepperVisibleRef = useRef(isStepperRendered);
248255

249256
const computeStepperVisibility = useCallback(() => {
250257
// When the stepper isn't rendered, it can't obstruct the footer.

0 commit comments

Comments
 (0)