Skip to content

Commit 39afe73

Browse files
authored
Merge branch 'main' into feat/infra-3527-bitrise-ios-runners-poc
2 parents 87ea48d + 271cdc0 commit 39afe73

35 files changed

Lines changed: 1405 additions & 61 deletions
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import React from 'react';
2+
import { render, fireEvent, act } from '@testing-library/react-native';
3+
import PredictSportLineSelector from './PredictSportLineSelector';
4+
import { PREDICT_SPORT_LINE_SELECTOR_TEST_IDS } from './PredictSportLineSelector.testIds';
5+
6+
const mockWithTiming = jest.fn((v: number) => v);
7+
8+
jest.mock('react-native-reanimated', () => {
9+
const { View } = jest.requireActual('react-native');
10+
const { useRef } = jest.requireActual('react');
11+
return {
12+
__esModule: true,
13+
default: { View },
14+
useSharedValue: (v: number) => {
15+
const ref = useRef({ value: v });
16+
return ref.current;
17+
},
18+
useAnimatedStyle: (fn: () => object) => fn(),
19+
withTiming: mockWithTiming,
20+
Easing: { inOut: (fn: unknown) => fn, ease: jest.fn() },
21+
};
22+
});
23+
24+
jest.mock('@react-native-masked-view/masked-view', () =>
25+
jest.fn(
26+
({
27+
children,
28+
}: {
29+
children?: React.ReactNode;
30+
maskElement?: React.ReactNode;
31+
}) => children,
32+
),
33+
);
34+
35+
jest.mock('react-native-linear-gradient', () => 'LinearGradient');
36+
37+
jest.mock('expo-haptics', () => ({
38+
impactAsync: jest.fn(),
39+
ImpactFeedbackStyle: { Light: 'light' },
40+
}));
41+
42+
const TEST_ID = 'line-selector';
43+
const IDS = PREDICT_SPORT_LINE_SELECTOR_TEST_IDS;
44+
45+
const arrowLeftId = `${TEST_ID}-${IDS.ARROW_LEFT}`;
46+
const arrowRightId = `${TEST_ID}-${IDS.ARROW_RIGHT}`;
47+
const lineId = (value: number) => `${TEST_ID}-${IDS.LINE_PREFIX}${value}`;
48+
49+
describe('PredictSportLineSelector', () => {
50+
const defaultProps = {
51+
lines: [4, 4.5, 5, 5.5, 6],
52+
selectedLine: 5,
53+
onSelectLine: jest.fn(),
54+
testID: TEST_ID,
55+
};
56+
57+
beforeEach(() => {
58+
jest.clearAllMocks();
59+
});
60+
61+
it('renders all lines', () => {
62+
const { getByText } = render(
63+
<PredictSportLineSelector {...defaultProps} />,
64+
);
65+
66+
defaultProps.lines.forEach((line) => {
67+
expect(getByText(String(line))).toBeOnTheScreen();
68+
});
69+
});
70+
71+
it('applies different color to selected vs unselected lines', () => {
72+
const { getByText } = render(
73+
<PredictSportLineSelector {...defaultProps} />,
74+
);
75+
76+
const selectedStyle = getByText('5').props.style;
77+
const unselectedStyle = getByText('4').props.style;
78+
79+
expect(selectedStyle).not.toEqual(unselectedStyle);
80+
});
81+
82+
it('calls onSelectLine with correct value when a line is tapped', () => {
83+
const { getByText } = render(
84+
<PredictSportLineSelector {...defaultProps} />,
85+
);
86+
87+
fireEvent.press(getByText('4.5'));
88+
89+
expect(defaultProps.onSelectLine).toHaveBeenCalledWith(4.5);
90+
});
91+
92+
it('calls onSelectLine with previous line when left arrow is tapped', () => {
93+
const { getByTestId } = render(
94+
<PredictSportLineSelector {...defaultProps} />,
95+
);
96+
97+
fireEvent.press(getByTestId(arrowLeftId));
98+
99+
expect(defaultProps.onSelectLine).toHaveBeenCalledWith(4.5);
100+
});
101+
102+
it('calls onSelectLine with next line when right arrow is tapped', () => {
103+
const { getByTestId } = render(
104+
<PredictSportLineSelector {...defaultProps} />,
105+
);
106+
107+
fireEvent.press(getByTestId(arrowRightId));
108+
109+
expect(defaultProps.onSelectLine).toHaveBeenCalledWith(5.5);
110+
});
111+
112+
it('disables left arrow when first line is selected', () => {
113+
const { getByTestId } = render(
114+
<PredictSportLineSelector {...defaultProps} selectedLine={4} />,
115+
);
116+
117+
const leftArrow = getByTestId(arrowLeftId);
118+
expect(leftArrow.props.accessibilityState.disabled).toBe(true);
119+
120+
fireEvent.press(leftArrow);
121+
expect(defaultProps.onSelectLine).not.toHaveBeenCalled();
122+
});
123+
124+
it('disables right arrow when last line is selected', () => {
125+
const { getByTestId } = render(
126+
<PredictSportLineSelector {...defaultProps} selectedLine={6} />,
127+
);
128+
129+
const rightArrow = getByTestId(arrowRightId);
130+
expect(rightArrow.props.accessibilityState.disabled).toBe(true);
131+
132+
fireEvent.press(rightArrow);
133+
expect(defaultProps.onSelectLine).not.toHaveBeenCalled();
134+
});
135+
136+
it('applies test IDs correctly', () => {
137+
const { getByTestId } = render(
138+
<PredictSportLineSelector {...defaultProps} />,
139+
);
140+
141+
expect(getByTestId(TEST_ID)).toBeOnTheScreen();
142+
expect(getByTestId(arrowLeftId)).toBeOnTheScreen();
143+
expect(getByTestId(arrowRightId)).toBeOnTheScreen();
144+
145+
defaultProps.lines.forEach((line) => {
146+
expect(getByTestId(lineId(line))).toBeOnTheScreen();
147+
});
148+
});
149+
150+
it('uses fallback test IDs when testID prop is not provided', () => {
151+
const { getByTestId } = render(
152+
<PredictSportLineSelector
153+
lines={[1, 2, 3]}
154+
selectedLine={2}
155+
onSelectLine={jest.fn()}
156+
/>,
157+
);
158+
159+
expect(getByTestId(IDS.CONTAINER)).toBeOnTheScreen();
160+
expect(getByTestId(`${IDS.CONTAINER}-${IDS.ARROW_LEFT}`)).toBeOnTheScreen();
161+
expect(
162+
getByTestId(`${IDS.CONTAINER}-${IDS.ARROW_RIGHT}`),
163+
).toBeOnTheScreen();
164+
});
165+
166+
it('displays numbers as-is without formatting', () => {
167+
const { getByText } = render(
168+
<PredictSportLineSelector
169+
{...defaultProps}
170+
lines={[-2.5, 0, 4.5]}
171+
selectedLine={0}
172+
/>,
173+
);
174+
175+
expect(getByText('-2.5')).toBeOnTheScreen();
176+
expect(getByText('0')).toBeOnTheScreen();
177+
expect(getByText('4.5')).toBeOnTheScreen();
178+
});
179+
180+
it('invokes onLayout handler without error', () => {
181+
const { UNSAFE_getAllByType } = render(
182+
<PredictSportLineSelector {...defaultProps} />,
183+
);
184+
185+
const Box = jest.requireActual('@metamask/design-system-react-native').Box;
186+
const layoutBox = UNSAFE_getAllByType(Box).find(
187+
(b: { props: { onLayout?: unknown } }) => b.props.onLayout,
188+
);
189+
190+
expect(() => {
191+
act(() => {
192+
layoutBox?.props.onLayout({
193+
nativeEvent: { layout: { width: 300 } },
194+
});
195+
});
196+
}).not.toThrow();
197+
});
198+
199+
it('re-renders with a different selectedLine without error', () => {
200+
const { rerender } = render(<PredictSportLineSelector {...defaultProps} />);
201+
202+
expect(() => {
203+
rerender(
204+
<PredictSportLineSelector {...defaultProps} selectedLine={5.5} />,
205+
);
206+
}).not.toThrow();
207+
});
208+
209+
it('fires haptic feedback on line tap', () => {
210+
const { impactAsync } = jest.requireMock('expo-haptics') as {
211+
impactAsync: jest.Mock;
212+
};
213+
const { getByText } = render(
214+
<PredictSportLineSelector {...defaultProps} />,
215+
);
216+
217+
fireEvent.press(getByText('4.5'));
218+
219+
expect(impactAsync).toHaveBeenCalledWith('light');
220+
});
221+
222+
it('fires haptic feedback on arrow tap', () => {
223+
const { impactAsync } = jest.requireMock('expo-haptics') as {
224+
impactAsync: jest.Mock;
225+
};
226+
const { getByTestId } = render(
227+
<PredictSportLineSelector {...defaultProps} />,
228+
);
229+
230+
fireEvent.press(getByTestId(arrowRightId));
231+
232+
expect(impactAsync).toHaveBeenCalledWith('light');
233+
});
234+
235+
it('does not call onSelectLine when selectedLine is not in lines', () => {
236+
const onSelectLine = jest.fn();
237+
const { getByTestId } = render(
238+
<PredictSportLineSelector
239+
lines={[4, 4.5, 5]}
240+
selectedLine={999}
241+
onSelectLine={onSelectLine}
242+
testID={TEST_ID}
243+
/>,
244+
);
245+
246+
const leftArrow = getByTestId(arrowLeftId);
247+
expect(leftArrow.props.accessibilityState.disabled).toBe(true);
248+
249+
fireEvent.press(leftArrow);
250+
251+
expect(onSelectLine).not.toHaveBeenCalled();
252+
});
253+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const PREDICT_SPORT_LINE_SELECTOR_TEST_IDS = {
2+
CONTAINER: 'predict-sport-line-selector',
3+
ARROW_LEFT: 'arrow-left',
4+
ARROW_RIGHT: 'arrow-right',
5+
LINE_PREFIX: 'line-',
6+
} as const;

0 commit comments

Comments
 (0)