Skip to content

Commit 4e9a651

Browse files
committed
fix(tests): migrate BottomNavigation tests to RNTL v14 patterns
- Fix calls on{IndexChange,TabPress,TabLongPress}: add layoutNavigationBar() before userEvent.press/longPress — the outer Animated.View has pointerEvents="none" until layout is measured, blocking userEvent - Replace fireEvent(text, 'on{Press,LongPress}') with userEvent on role-based tabs, per ESLint no-restricted-syntax rule - Replace .props.style reads with toHaveStyle() assertions - Migrate remaining sync render() calls to async/await + screen.* - Fix NavigationBar.tsx: remove Badge size prop (no longer in API), fix prettier formatting on nested ternary and ?? expression
1 parent 8fe9cf7 commit 4e9a651

3 files changed

Lines changed: 923 additions & 645 deletions

File tree

src/components/NavigationBar/NavigationBar.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,10 @@ const NavigationBarItem = <Route extends BaseRoute>({
359359
const stateLayer = pressed
360360
? getStateLayer(theme, stateLayerRole, 'pressed')
361361
: keyboardFocused
362-
? getStateLayer(theme, stateLayerRole, 'focused')
363-
: hovered
364-
? getStateLayer(theme, stateLayerRole, 'hovered')
365-
: null;
362+
? getStateLayer(theme, stateLayerRole, 'focused')
363+
: hovered
364+
? getStateLayer(theme, stateLayerRole, 'hovered')
365+
: null;
366366
const stateLayerColor = stateLayer
367367
? { backgroundColor: stateLayer.color, opacity: stateLayer.opacity }
368368
: null;
@@ -386,7 +386,7 @@ const NavigationBarItem = <Route extends BaseRoute>({
386386
source={
387387
(focused
388388
? route.focusedIcon
389-
: route.unfocusedIcon ?? route.focusedIcon) as IconSource
389+
: (route.unfocusedIcon ?? route.focusedIcon)) as IconSource
390390
}
391391
color={iconColor}
392392
size={ICON_SIZE}
@@ -396,11 +396,9 @@ const NavigationBarItem = <Route extends BaseRoute>({
396396
const tabBadge = (
397397
<View style={[styles.badgeContainer, badgeStyle]}>
398398
{typeof badge === 'boolean' ? (
399-
<Badge visible={badge} size={6} />
399+
<Badge visible={badge} />
400400
) : (
401-
<Badge visible={badge != null} size={16}>
402-
{badge}
403-
</Badge>
401+
<Badge visible={badge != null}>{badge}</Badge>
404402
)}
405403
</View>
406404
);

src/components/__tests__/BottomNavigation.test.tsx

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,12 @@ it('calls onIndexChange', async () => {
151151
renderScene={renderScene}
152152
/>
153153
);
154-
// The active scene also renders the route title, so target the last match
155-
// (the tab label) to fire the tab press.
154+
await layoutNavigationBar();
156155
// pressing same index as active navigation state does not call onIndexChange
157-
fireEvent(screen.getAllByText('Route: 0').at(-1)!, 'onPress');
156+
await userEvent.press(getTab(0));
158157
expect(onIndexChange).not.toHaveBeenCalled();
159158

160-
fireEvent(screen.getAllByText('Route: 1').at(-1)!, 'onPress');
159+
await userEvent.press(getTab(1));
161160
expect(onIndexChange).toHaveBeenCalledTimes(1);
162161
});
163162

@@ -173,7 +172,8 @@ it('calls onTabPress', async () => {
173172
renderScene={renderScene}
174173
/>
175174
);
176-
fireEvent(screen.getAllByText('Route: 1').at(-1)!, 'onPress');
175+
await layoutNavigationBar();
176+
await userEvent.press(getTab(1));
177177
expect(onTabPress).toHaveBeenCalled();
178178
expect(onTabPress).toHaveBeenCalledTimes(1);
179179
expect(onTabPress).toHaveBeenLastCalledWith(
@@ -199,7 +199,8 @@ it('calls onTabLongPress', async () => {
199199
renderScene={renderScene}
200200
/>
201201
);
202-
fireEvent(screen.getAllByText('Route: 2').at(-1)!, 'onLongPress');
202+
await layoutNavigationBar();
203+
await userEvent.longPress(getTab(2));
203204
expect(onTabLongPress).toHaveBeenCalled();
204205
expect(onTabLongPress).toHaveBeenCalledTimes(1);
205206
expect(onTabLongPress).toHaveBeenLastCalledWith(
@@ -432,8 +433,8 @@ it('does not render the legacy ripple overlay', async () => {
432433
).not.toBeOnTheScreen();
433434
});
434435

435-
it('renders tab labels when labeled', () => {
436-
const { getAllByText } = render(
436+
it('renders tab labels when labeled', async () => {
437+
await render(
437438
<NavigationBar
438439
navigationState={{
439440
index: 0,
@@ -447,24 +448,26 @@ it('renders tab labels when labeled', () => {
447448
);
448449

449450
// Each tab renders a single label (no cross-fade layers).
450-
expect(getAllByText('Alpha').length).toBeGreaterThan(0);
451-
expect(getAllByText('Beta').length).toBeGreaterThan(0);
451+
expect(screen.getAllByText('Alpha').length).toBeGreaterThan(0);
452+
expect(screen.getAllByText('Beta').length).toBeGreaterThan(0);
452453
});
453454

454-
it('renders the horizontal (flexible) variant', () => {
455-
const tree = render(
456-
<NavigationBar
457-
navigationState={createState(0, 3)}
458-
onTabPress={jest.fn()}
459-
variant="horizontal"
460-
/>
455+
it('renders the horizontal (flexible) variant', async () => {
456+
const tree = (
457+
await render(
458+
<NavigationBar
459+
navigationState={createState(0, 3)}
460+
onTabPress={jest.fn()}
461+
variant="horizontal"
462+
/>
463+
)
461464
).toJSON();
462465

463466
expect(tree).toMatchSnapshot();
464467
});
465468

466-
it('falls back to icon-only when horizontal is combined with labeled=false', () => {
467-
const { queryByText } = render(
469+
it('falls back to icon-only when horizontal is combined with labeled=false', async () => {
470+
await render(
468471
<NavigationBar
469472
navigationState={createState(0, 3)}
470473
onTabPress={jest.fn()}
@@ -474,11 +477,11 @@ it('falls back to icon-only when horizontal is combined with labeled=false', ()
474477
);
475478

476479
// `horizontal` is a no-op without labels, so no label text is rendered.
477-
expect(queryByText('Route: 0')).toBeNull();
478-
expect(queryByText('Route: 1')).toBeNull();
480+
expect(screen.queryByText('Route: 0')).toBeNull();
481+
expect(screen.queryByText('Route: 1')).toBeNull();
479482
});
480483

481-
it('renders MD3 state layers on hover, focus and press', () => {
484+
it('renders MD3 state layers on hover, focus and press', async () => {
482485
const navigationState = {
483486
index: 0,
484487
routes: [
@@ -487,35 +490,34 @@ it('renders MD3 state layers on hover, focus and press', () => {
487490
],
488491
};
489492

490-
const { getByTestId } = render(
493+
await render(
491494
<NavigationBar navigationState={navigationState} onTabPress={jest.fn()} />
492495
);
493496

494-
const layerOpacity = () =>
495-
StyleSheet.flatten(getByTestId('tab-b-state-layer').props.style).opacity;
497+
const stateLayer = () => screen.getByTestId('tab-b-state-layer');
496498

497499
// Idle: no visible state layer.
498-
expect(layerOpacity()).toBeUndefined();
500+
expect(stateLayer()).toHaveStyle({ opacity: undefined });
499501

500502
// Hovered: 8% state layer.
501-
fireEvent(getByTestId('tab-b'), 'hoverIn');
502-
expect(layerOpacity()).toBe(0.08);
503-
fireEvent(getByTestId('tab-b'), 'hoverOut');
504-
expect(layerOpacity()).toBeUndefined();
503+
await fireEvent(screen.getByTestId('tab-b'), 'hoverIn');
504+
expect(stateLayer()).toHaveStyle({ opacity: 0.08 });
505+
await fireEvent(screen.getByTestId('tab-b'), 'hoverOut');
506+
expect(stateLayer()).toHaveStyle({ opacity: undefined });
505507

506508
// Focused: 10% state layer.
507-
fireEvent(getByTestId('tab-b'), 'focus');
508-
expect(layerOpacity()).toBe(0.1);
509-
fireEvent(getByTestId('tab-b'), 'blur');
509+
await fireEvent(screen.getByTestId('tab-b'), 'focus');
510+
expect(stateLayer()).toHaveStyle({ opacity: 0.1 });
511+
await fireEvent(screen.getByTestId('tab-b'), 'blur');
510512

511513
// Pressed: 10% state layer.
512-
fireEvent(getByTestId('tab-b'), 'pressIn');
513-
expect(layerOpacity()).toBe(0.1);
514-
fireEvent(getByTestId('tab-b'), 'pressOut');
515-
expect(layerOpacity()).toBeUndefined();
514+
await fireEvent(screen.getByTestId('tab-b'), 'pressIn');
515+
expect(stateLayer()).toHaveStyle({ opacity: 0.1 });
516+
await fireEvent(screen.getByTestId('tab-b'), 'pressOut');
517+
expect(stateLayer()).toHaveStyle({ opacity: undefined });
516518
});
517519

518-
it('colors the focused tab label with secondary and others with onSurfaceVariant', () => {
520+
it('colors the focused tab label with secondary and others with onSurfaceVariant', async () => {
519521
const navigationState = {
520522
index: 0,
521523
routes: [
@@ -524,20 +526,19 @@ it('colors the focused tab label with secondary and others with onSurfaceVariant
524526
],
525527
};
526528

527-
const { getAllByText } = render(
529+
await render(
528530
<NavigationBar navigationState={navigationState} onTabPress={jest.fn()} />
529531
);
530532

531-
const colorsOf = (text: string) =>
532-
getAllByText(text).map(
533-
(node) => StyleSheet.flatten(node.props.style).color
534-
);
535-
536-
expect(colorsOf('Alpha')).toContain(getTheme().colors.secondary);
537-
expect(colorsOf('Beta')).toContain(getTheme().colors.onSurfaceVariant);
533+
expect(screen.getAllByText('Alpha').at(-1)).toHaveStyle({
534+
color: getTheme().colors.secondary,
535+
});
536+
expect(screen.getAllByText('Beta').at(-1)).toHaveStyle({
537+
color: getTheme().colors.onSurfaceVariant,
538+
});
538539
});
539540

540-
it('renders the active indicator with the secondaryContainer color', () => {
541+
it('renders the active indicator with the secondaryContainer color', async () => {
541542
const navigationState = {
542543
index: 0,
543544
routes: [
@@ -546,17 +547,16 @@ it('renders the active indicator with the secondaryContainer color', () => {
546547
],
547548
};
548549

549-
const { getByTestId } = render(
550+
await render(
550551
<NavigationBar navigationState={navigationState} onTabPress={jest.fn()} />
551552
);
552553

553-
expect(
554-
StyleSheet.flatten(getByTestId('tab-a-active-indicator').props.style)
555-
.backgroundColor
556-
).toBe(getTheme().colors.secondaryContainer);
554+
expect(screen.getByTestId('tab-a-active-indicator')).toHaveStyle({
555+
backgroundColor: getTheme().colors.secondaryContainer,
556+
});
557557
});
558558

559-
it('renders a badge for routes that define one', () => {
559+
it('renders a badge for routes that define one', async () => {
560560
const navigationState = {
561561
index: 0,
562562
routes: [
@@ -565,11 +565,11 @@ it('renders a badge for routes that define one', () => {
565565
],
566566
};
567567

568-
const { getByText } = render(
568+
await render(
569569
<NavigationBar navigationState={navigationState} onTabPress={jest.fn()} />
570570
);
571571

572-
expect(getByText('3')).toBeTruthy();
572+
expect(screen.getByText('3')).toBeTruthy();
573573
});
574574

575575
describe('getActiveTintColor', () => {

0 commit comments

Comments
 (0)