Skip to content

Commit 4dea65a

Browse files
committed
Carousel - render tests - first tests
1 parent 71d6cc3 commit 4dea65a

File tree

2 files changed

+151
-46
lines changed

2 files changed

+151
-46
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {map} from 'lodash';
2+
import React from 'react';
3+
import {render, fireEvent} from '@testing-library/react-native';
4+
import {Text, View} from 'react-native';
5+
import {Constants} from '../../../helpers';
6+
import Carousel from '../index';
7+
8+
9+
const numberOfPagesShown = 5;
10+
const eventData = {
11+
nativeEvent: {
12+
contentOffset: {
13+
x: Constants.screenWidth
14+
}
15+
}
16+
};
17+
const onChangePageMock = jest.fn();
18+
const onScrollMock = jest.fn();
19+
const props = {
20+
testID: 'carousel',
21+
initialPage: 0,
22+
pagingEnabled: true,
23+
autoplay: false,
24+
autoplayInterval: 4000,
25+
horizontal: true,
26+
onChangePage: onChangePageMock,
27+
onScroll: onScrollMock
28+
//animatedScrollOffset: // set to check Animated
29+
};
30+
31+
const Page = ({children, ...others}) => {
32+
return (
33+
<View {...others} style={{flex: 1}}>
34+
{children}
35+
</View>
36+
);
37+
};
38+
39+
describe('Carousel tests', () => {
40+
describe('default setup', () => {
41+
it('should be set to default', () => {
42+
const component = render(<Carousel {...props}>
43+
{map([...Array(numberOfPagesShown)], (_, index) => (
44+
<Page key={index}>
45+
<Text testID={`page-${index}`}>Page #{index}</Text>
46+
</Page>
47+
))}
48+
</Carousel>);
49+
component.getByText('Page #0'); // Validates that the text is there
50+
});
51+
52+
it('should trigger onScroll from the second scroll', () => {
53+
const component = render(<Carousel {...props}>
54+
{map([...Array(numberOfPagesShown)], (_, index) => (
55+
<Page key={index}>
56+
<Text>Page #{index}</Text>
57+
</Page>
58+
))}
59+
</Carousel>);
60+
const scrollView = component.getByTestId('carousel.scrollView');
61+
62+
fireEvent.scroll(scrollView, eventData); //NOTE: first scroll will no fire onScroll
63+
expect(onScrollMock).not.toHaveBeenCalled();
64+
65+
fireEvent.scroll(scrollView, eventData);
66+
expect(onScrollMock).toHaveBeenCalled();
67+
});
68+
69+
it('should trigger onChangePage with current page', async () => {
70+
const component = render(<Carousel {...props}>
71+
{map([...Array(numberOfPagesShown)], (_, index) => (
72+
<Page key={index}>
73+
<Text>Page #{index}</Text>
74+
</Page>
75+
))}
76+
</Carousel>);
77+
const scrollView = component.getByTestId('carousel.scrollView');
78+
79+
fireEvent.scroll(scrollView, eventData); //NOTE: first scroll will no fire onScroll
80+
fireEvent.scroll(scrollView, eventData);
81+
expect(onChangePageMock).not.toHaveBeenCalled();
82+
83+
await new Promise((r) => setTimeout(r, 2000));
84+
fireEvent(scrollView, 'onMomentumScrollEnd', eventData);
85+
expect(onChangePageMock).toHaveBeenCalled();
86+
expect(onChangePageMock).toHaveBeenCalledWith(1, 0, {isAutoScrolled: false});
87+
});
88+
});
89+
});

src/components/carousel/index.tsx

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class Carousel extends Component<CarouselProps, CarouselState> {
5353
const defaultPageWidth = props.loop || !props.pageWidth ? Constants.screenWidth : props.pageWidth;
5454
const pageHeight = props.pageHeight ?? Constants.screenHeight;
5555
this.isAutoScrolled = false;
56+
5657
this.state = {
5758
containerWidth: undefined,
5859
// @ts-ignore (defaultProps)
@@ -198,6 +199,27 @@ class Carousel extends Component<CarouselProps, CarouselState> {
198199
this.setState({currentPage: this.getCalcIndex(pageIndex)}, () => this.updateOffset(animated));
199200
}
200201

202+
goToNextPage() {
203+
const {currentPage} = this.state;
204+
const pagesCount = presenter.getChildrenLength(this.props);
205+
const {loop} = this.props;
206+
let nextPageIndex;
207+
208+
if (loop) {
209+
nextPageIndex = currentPage + 1;
210+
} else {
211+
nextPageIndex = Math.min(pagesCount - 1, currentPage + 1);
212+
}
213+
214+
this.goToPage(nextPageIndex, true);
215+
216+
// in case of a loop, after we advanced right to the cloned first page,
217+
// we return silently to the real first page
218+
if (loop && currentPage === pagesCount) {
219+
this.goToPage(0, false);
220+
}
221+
}
222+
201223
getCalcIndex(index: number): number {
202224
// to handle scrollView index issue in Android's RTL layout
203225
if (Constants.isRTL && Constants.isAndroid) {
@@ -217,8 +239,10 @@ class Carousel extends Component<CarouselProps, CarouselState> {
217239
if (containerWidth) {
218240
const spacings = pageWidth === containerWidth ? 0 : this.getItemSpacings(this.props);
219241
const initialBreak = pageWidth - (containerWidth - pageWidth - spacings) / 2;
220-
const snapToOffsets = _.times(presenter.getChildrenLength(this.props),
221-
index => initialBreak + index * pageWidth + this.getContainerMarginHorizontal());
242+
const snapToOffsets = _.times(
243+
presenter.getChildrenLength(this.props),
244+
index => initialBreak + index * pageWidth + this.getContainerMarginHorizontal()
245+
);
222246
return snapToOffsets;
223247
}
224248
};
@@ -233,6 +257,18 @@ class Carousel extends Component<CarouselProps, CarouselState> {
233257
return horizontal ? pagingEnabled && !this.shouldUsePageWidth() : true;
234258
}
235259

260+
shouldAllowAccessibilityLayout() {
261+
const {allowAccessibleLayout} = this.props;
262+
return allowAccessibleLayout && Constants.accessibility.isScreenReaderEnabled;
263+
}
264+
265+
onContentSizeChange = () => {
266+
// this is to handle initial scroll position (content offset)
267+
if (Constants.isAndroid) {
268+
this.updateOffset();
269+
}
270+
};
271+
236272
onContainerLayout = ({
237273
nativeEvent: {
238274
layout: {width: containerWidth, height: containerHeight}
@@ -249,54 +285,22 @@ class Carousel extends Component<CarouselProps, CarouselState> {
249285
this.setState({containerWidth, pageWidth, pageHeight, initialOffset});
250286
};
251287

252-
shouldAllowAccessibilityLayout() {
253-
const {allowAccessibleLayout} = this.props;
254-
return allowAccessibleLayout && Constants.accessibility.isScreenReaderEnabled;
255-
}
256-
257-
onContentSizeChange = () => {
258-
// this is to handle initial scroll position (content offset)
259-
if (Constants.isAndroid) {
260-
this.updateOffset();
261-
}
262-
};
263-
264288
onMomentumScrollEnd = () => {
265289
// finished full page scroll
266290
const {currentStandingPage, currentPage} = this.state;
267291
const index = this.getCalcIndex(currentPage);
268-
269292
const pagesCount = presenter.getChildrenLength(this.props);
293+
270294
if (index < pagesCount) {
271295
this.setState({currentStandingPage: index});
296+
272297
if (currentStandingPage !== index) {
273298
this.props.onChangePage?.(index, currentStandingPage, {isAutoScrolled: this.isAutoScrolled});
274299
this.isAutoScrolled = false;
275300
}
276301
}
277302
};
278303

279-
goToNextPage() {
280-
const {currentPage} = this.state;
281-
const pagesCount = presenter.getChildrenLength(this.props);
282-
const {loop} = this.props;
283-
284-
let nextPageIndex;
285-
if (loop) {
286-
nextPageIndex = currentPage + 1;
287-
} else {
288-
nextPageIndex = Math.min(pagesCount - 1, currentPage + 1);
289-
}
290-
291-
this.goToPage(nextPageIndex, true);
292-
293-
// in case of a loop, after we advanced right to the cloned first page,
294-
// we return silently to the real first page
295-
if (loop && currentPage === pagesCount) {
296-
this.goToPage(0, false);
297-
}
298-
}
299-
300304
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
301305
if (!this.skippedInitialScroll) {
302306
this.skippedInitialScroll = true;
@@ -307,7 +311,6 @@ class Carousel extends Component<CarouselProps, CarouselState> {
307311
const {pageWidth, pageHeight} = this.state;
308312
const offsetX = event.nativeEvent.contentOffset.x;
309313
const offsetY = event.nativeEvent.contentOffset.y;
310-
311314
const offset = horizontal ? offsetX : offsetY;
312315
const pageSize = horizontal ? pageWidth : pageHeight;
313316

@@ -333,11 +336,17 @@ class Carousel extends Component<CarouselProps, CarouselState> {
333336
};
334337

335338
// @ts-ignore
336-
onScrollEvent = Animated.event([{nativeEvent: {contentOffset: {y: this.props?.animatedScrollOffset?.y, x: this.props?.animatedScrollOffset?.x}}}],
337-
{
338-
useNativeDriver: true,
339-
listener: this.onScroll
340-
});
339+
onScrollEvent = Animated.event([
340+
{nativeEvent:
341+
{contentOffset:
342+
{y: this.props?.animatedScrollOffset?.y, x: this.props?.animatedScrollOffset?.x}
343+
}
344+
}
345+
],
346+
{
347+
useNativeDriver: true,
348+
listener: this.onScroll
349+
});
341350

342351
renderChild = (child: ReactNode, key: Key): JSX.Element | undefined => {
343352
if (child) {
@@ -436,11 +445,12 @@ class Carousel extends Component<CarouselProps, CarouselState> {
436445
}
437446

438447
renderAccessibleLayout() {
439-
const {containerStyle, children} = this.props;
448+
const {containerStyle, children, testID} = this.props;
440449

441450
return (
442451
<View style={containerStyle} onLayout={this.onContainerLayout}>
443452
<ScrollView
453+
testID={`${testID}.scrollView`}
444454
ref={this.carousel}
445455
showsVerticalScrollIndicator={false}
446456
pagingEnabled
@@ -457,7 +467,7 @@ class Carousel extends Component<CarouselProps, CarouselState> {
457467
}
458468

459469
renderCarousel() {
460-
const {containerStyle, animated, horizontal, animatedScrollOffset, ...others} = this.props;
470+
const {containerStyle, animated, horizontal, animatedScrollOffset, testID, ...others} = this.props;
461471
const {initialOffset} = this.state;
462472
const scrollContainerStyle = this.shouldUsePageWidth()
463473
? {paddingRight: this.getItemSpacings(this.props)}
@@ -466,8 +476,14 @@ class Carousel extends Component<CarouselProps, CarouselState> {
466476
const marginBottom = Math.max(0, this.getContainerPaddingVertical() - 16);
467477
const ScrollContainer = animatedScrollOffset ? Animated.ScrollView : ScrollView;
468478
return (
469-
<View animated={animated} style={[{marginBottom}, containerStyle]} onLayout={this.onContainerLayout}>
479+
<View
480+
animated={animated}
481+
style={[{marginBottom}, containerStyle]}
482+
onLayout={this.onContainerLayout}
483+
testID={testID}
484+
>
470485
<ScrollContainer
486+
testID={`${testID}.scrollView`}
471487
showsHorizontalScrollIndicator={false}
472488
showsVerticalScrollIndicator={false}
473489
decelerationRate="fast"
@@ -502,7 +518,7 @@ export default asBaseComponent<CarouselProps, Carousel & {pageControlPositions:
502518
const styles = StyleSheet.create({
503519
counter: {
504520
paddingHorizontal: 8,
505-
paddingVertical: 3, // height: 24,
521+
paddingVertical: 3,
506522
borderRadius: 20,
507523
backgroundColor: Colors.rgba(Colors.black, 0.6),
508524
position: 'absolute',

0 commit comments

Comments
 (0)