From 887491a60f23fc8489b21ff7f2ad7aed540e0389 Mon Sep 17 00:00:00 2001 From: LukasFridmansky Date: Fri, 18 Aug 2023 17:15:02 +0200 Subject: [PATCH] test: full coverage --- .gitignore | 3 +- jest.setup.js | 32 +- package-lock.json | 6 +- src/components/Avatar/index.tsx | 24 +- src/components/Header/index.tsx | 6 +- src/components/Image/index.tsx | 72 ++-- src/components/InstagramStories/index.tsx | 40 ++- src/components/Loader/index.tsx | 16 +- src/components/Modal/index.tsx | 46 +-- src/core/dto/componentsDTO.ts | 6 +- tests/index.test.js | 420 ++++++++++++++++++++++ 11 files changed, 554 insertions(+), 117 deletions(-) diff --git a/.gitignore b/.gitignore index b1bc6fe..2a97119 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules -/coverage \ No newline at end of file +/coverage +/dist \ No newline at end of file diff --git a/jest.setup.js b/jest.setup.js index b8a8f86..5f367b0 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,6 +1,4 @@ -import { BackHandler, Dimensions } from 'react-native'; - jest.mock('react-native-reanimated', () => { const View = require('react-native').View; @@ -24,10 +22,10 @@ jest.mock('react-native-reanimated', () => { useSharedValue: jest.fn(), useDerivedValue: (a) => ({ value: a() }), useAnimatedScrollHandler: () => () => {}, - useAnimatedGestureHandler: () => () => {}, + useAnimatedGestureHandler: ({onStart, onActive, onFinish}) => ({onStart, onActive, onFinish}), useAnimatedStyle: (cb) => cb(), useAnimatedRef: () => ({ current: null }), - useAnimatedReaction: (value, cb) => cb(value(), ''), + useAnimatedReaction: jest.fn(), useAnimatedProps: (cb) => cb(), withTiming: (toValue, _, cb) => { cb && cb(true); @@ -48,7 +46,7 @@ jest.mock('react-native-reanimated', () => { return 0; }, withRepeat: (animation, _, __, cb) => { - cb(); + cb?.(); return animation; }, cancelAnimation: () => {}, @@ -92,20 +90,30 @@ jest.mock('react-native-reanimated', () => { jest.mock('react-native-gesture-handler', () => { const View = require('react-native').View; - const ScrollView = require('react-native').ScrollView; return { - GestureDetector: ({gesture, children}) => ( + PanGestureHandler: ({onGestureEvent, children}) => ( {children} ), - ScrollView + gestureHandlerRootHOC: (Component) => Component, }; -}); \ No newline at end of file +}); + +jest.mock('./src/core/helpers/image', () => ({ + loadImage: (url) => url, + preloadStories: () => [], +})); + +jest.mock('./src/core/helpers/storage', () => ({ + clearProgressStorage: () => {}, + getProgressStorage: jest.fn(), + setProgressStorage: jest.fn(), +})); diff --git a/package-lock.json b/package-lock.json index d43b57f..41ee2ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14807,9 +14807,9 @@ "peer": true }, "node_modules/react-native-svg": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.11.0.tgz", - "integrity": "sha512-ApMp0t7NQh85BNq66nfN1/l8pwkj1Lx8Y+ErmBmZ0Oado3DSd+o/Fx/6Jm3GmBkmXl62t6Et0FHj1NYxNQw64w==", + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.10.0.tgz", + "integrity": "sha512-D/oYTmUi5nsA/2Nw4WYlF1UUi3vZqhpESpiEhpYCIFB/EMd6vz4A/uq3tIzZFcfa5z2oAdGSxRU1TaYr8IcPlQ==", "peer": true, "dependencies": { "css-select": "^5.1.0", diff --git a/src/components/Avatar/index.tsx b/src/components/Avatar/index.tsx index 0a79476..e3d62c8 100644 --- a/src/components/Avatar/index.tsx +++ b/src/components/Avatar/index.tsx @@ -1,15 +1,14 @@ import React, { FC, memo } from 'react'; -import { View, Image, Text } from 'react-native'; +import { + View, Image, Text, TouchableOpacity, +} from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, useDerivedValue, withTiming, } from 'react-native-reanimated'; -import { TouchableOpacity } from 'react-native-gesture-handler'; import { StoryAvatarProps } from '../../core/dto/componentsDTO'; import AvatarStyles from './Avatar.styles'; import Loader from '../Loader'; -import { - AVATAR_OFFSET, AVATAR_SIZE, DEFAULT_COLORS, SEEN_LOADER_COLORS, -} from '../../core/constants'; +import { AVATAR_OFFSET, AVATAR_SIZE } from '../../core/constants'; const AnimatedImage = Animated.createAnimatedComponent( Image ); @@ -21,15 +20,15 @@ const StoryAvatar: FC = ( { loadingStory, seenStories, onPress, - colors = DEFAULT_COLORS, - seenColors = SEEN_LOADER_COLORS, - size = AVATAR_SIZE, - showName = false, + colors, + seenColors, + size, + showName, nameTextStyle, } ) => { const loaded = useSharedValue( false ); - const isLoading = useDerivedValue( () => loadingStory.value === name || !loaded.value ); + const isLoading = useDerivedValue( () => loadingStory.value === id || !loaded.value ); const loaderColor = useDerivedValue( () => ( seenStories.value[id] === stories[stories.length - 1]?.id ? seenColors @@ -49,7 +48,7 @@ const StoryAvatar: FC = ( { return ( - + = ( { imageAnimatedStyles, { width: AVATAR_SIZE, height: AVATAR_SIZE, borderRadius: AVATAR_SIZE / 2 }, ]} + testID="storyAvatarImage" onLoad={onLoad} /> - {showName && {name}} + {Boolean( showName ) && {name}} ); diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 3e31ef7..74341c7 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -1,6 +1,7 @@ import React, { FC, memo } from 'react'; -import { View, Text, Image } from 'react-native'; -import { TouchableOpacity } from 'react-native-gesture-handler'; +import { + View, Text, Image, TouchableOpacity, +} from 'react-native'; import { PROGRESS_COLOR, WIDTH } from '../../core/constants'; import HeaderStyles from './Header.styles'; import { StoryHeaderProps } from '../../core/dto/componentsDTO'; @@ -24,6 +25,7 @@ const StoryHeader: FC = ( { { buttonHandled.value = true; diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index 350d29b..6d0f7f4 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -1,6 +1,6 @@ import { Image, View } from 'react-native'; import React, { - FC, memo, useState, useEffect, + FC, memo, useState, useEffect, useRef, } from 'react'; import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; import { StoryImageProps } from '../../core/dto/componentsDTO'; @@ -18,6 +18,8 @@ const StoryImage: FC = ( { const loading = useSharedValue( true ); const color = useSharedValue( LOADER_COLORS ); + const storyImgUrl = useRef( '' ); + const onImageChange = async () => { if ( !active.value ) { @@ -26,32 +28,28 @@ const StoryImage: FC = ( { } - const story = stories.find( ( item ) => item.id === activeStory.value ); + const story = stories.find( ( item ) => item.id === activeStory.value )!; loading.value = true; - if ( story ) { - - setUri( undefined ); - - if ( preloadImages ) { + if ( preloadImages ) { - const image = await loadImage( story.imgUrl ); - setUri( image ); + const image = await loadImage( story?.imgUrl ); - const nextStory = stories[stories.indexOf( story ) ?? 0 + 1]; + setUri( image ); - if ( nextStory ) { + storyImgUrl.current = story?.imgUrl!; + const nextStory = stories[stories.indexOf( story! ) + 1]; - loadImage( nextStory.imgUrl ); + if ( nextStory ) { - } + loadImage( nextStory.imgUrl ); - } else { + } - setUri( story.imgUrl ); + } else if ( story?.imgUrl !== uri ) { - } + setUri( story?.imgUrl ); } @@ -59,18 +57,15 @@ const StoryImage: FC = ( { const setDefaultImage = async () => { - if ( !active.value ) { - - if ( preloadImages ) { + if ( preloadImages ) { - const image = await loadImage( defaultImage ); - setUri( image ); + const image = await loadImage( defaultImage ); + setUri( image ); + storyImgUrl.current = defaultImage; - } else { + } else { - setUri( defaultImage ); - - } + setUri( defaultImage ); } @@ -99,20 +94,19 @@ const StoryImage: FC = ( { - {uri && ( - onImageLayout( Math.min( HEIGHT, e.nativeEvent.layout.height ) )} - onLoad={() => { - - loading.value = false; - onLoad(); - - }} - /> - )} + onImageLayout( Math.min( HEIGHT, e.nativeEvent.layout.height ) )} + onLoad={() => { + + loading.value = false; + onLoad(); + + }} + /> ); diff --git a/src/components/InstagramStories/index.tsx b/src/components/InstagramStories/index.tsx index 9bd9bf7..1ccb0a2 100644 --- a/src/components/InstagramStories/index.tsx +++ b/src/components/InstagramStories/index.tsx @@ -2,7 +2,7 @@ import React, { forwardRef, useImperativeHandle, useState, useEffect, useRef, memo, } from 'react'; import { useSharedValue } from 'react-native-reanimated'; -import { ScrollView } from 'react-native-gesture-handler'; +import { ScrollView } from 'react-native'; import StoryAvatar from '../Avatar'; import { clearProgressStorage, getProgressStorage, setProgressStorage } from '../../core/helpers/storage'; import { InstagramStoriesProps, InstagramStoriesPublicMethods } from '../../core/dto/instagramStoriesDTO'; @@ -97,10 +97,10 @@ const InstagramStories = forwardRef story.id === user ); const oldIndex = userData?.stories.findIndex( ( story ) => story.id === seenStories.value[user], - ) ?? 0; - const newIndex = userData?.stories.findIndex( ( story ) => story.id === value ) ?? 0; + ); + const newIndex = userData?.stories.findIndex( ( story ) => story.id === value ); - if ( oldIndex > newIndex ) { + if ( oldIndex! > newIndex! ) { return; @@ -132,22 +132,28 @@ const InstagramStories = forwardRef { - const userIndex = data.findIndex( ( story ) => story.id === user ); + const userData = data.find( ( story ) => story.id === user ); - if ( !userIndex || !data[userIndex] ) { + if ( !userData ) { return; } - const newData = { - ...data[userIndex], - stories: index === undefined - ? [ ...data[userIndex].stories, ...newStories ] - : data[userIndex].stories.splice( index, 0, ...newStories ), - }; + const newData = index === undefined + ? [ ...userData.stories, ...newStories ] + : [ ...userData.stories ]; - setData( data.map( ( value, i ) => ( i === userIndex ? newData : value ) ) ); + if ( index !== undefined ) { + + newData.splice( index, 0, ...newStories ); + + } + + setData( data.map( ( value ) => ( value.id === user ? { + ...value, + stories: newData, + } : value ) ) ); }, setStories: ( newStories ) => { @@ -167,9 +173,15 @@ const InstagramStories = forwardRef { + + setData( stories ); + + }, [ stories ] ); + return ( <> - + {data.map( ( story ) => ( = ( { }; + const onColorChange = ( newColors: string[] ) => { + + 'worklet'; + + if ( JSON.stringify( colors ) === JSON.stringify( newColors ) ) { + + return; + + } + + runOnJS( setColors )( newColors ); + + }; + useAnimatedReaction( () => loading.value, ( res ) => ( res ? startAnimation() : stopAnimation() ), @@ -67,7 +81,7 @@ const Loader: FC = ( { ); useAnimatedReaction( () => color.value, - ( res ) => runOnJS( setColors )( res ), + ( res ) => onColorChange( res ), [ color.value ], ); diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 5959343..8861615 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -25,11 +25,11 @@ const StoryModal = forwardRef( ( { const x = useSharedValue( 0 ); const y = useSharedValue( HEIGHT ); const animation = useSharedValue( 0 ); - const currentStory = useSharedValue( stories[0].stories[0]?.id ); + const currentStory = useSharedValue( stories[0]?.stories[0]?.id ); const buttonHandled = useSharedValue( false ); const userIndex = useDerivedValue( () => Math.round( x.value / WIDTH ) ); - const storyIndex = useDerivedValue( () => stories[userIndex.value].stories.findIndex( + const storyIndex = useDerivedValue( () => stories[userIndex.value]?.stories.findIndex( ( story ) => story.id === currentStory.value, ) ); const userId = useDerivedValue( () => stories[userIndex.value]?.id ); @@ -60,18 +60,12 @@ const StoryModal = forwardRef( ( { }; - const stopAnimation = ( pause = false ) => { + const stopAnimation = () => { 'worklet'; cancelAnimation( animation ); - if ( !pause ) { - - animation.value = 0; - - } - }; const startAnimation = ( resume = false ) => { @@ -102,27 +96,27 @@ const StoryModal = forwardRef( ( { const newUserIndex = stories.findIndex( ( story ) => story.id === id ); const newX = newUserIndex * WIDTH; - if ( !stories[newUserIndex] || newX === x.value ) { - - return; - - } - x.value = animated ? withTiming( newX, ANIMATION_CONFIG ) : newX; - const newStoryIndex = stories[newUserIndex].stories.findIndex( + const newStoryIndex = stories[newUserIndex]?.stories.findIndex( ( story ) => story.id === seenStories.value[id], ); - const userStories = stories[newUserIndex].stories; + const userStories = stories[newUserIndex]?.stories; currentStory.value = userStories[newStoryIndex + 1]?.id ?? userStories[0]?.id; startAnimation(); }; - const toNextStory = () => { + const toNextStory = ( value = true ) => { 'worklet'; + if ( !value ) { + + return; + + } + if ( !nextStory.value ) { if ( nextUserId.value ) { @@ -178,7 +172,7 @@ const StoryModal = forwardRef( ( { ctx.x = x.value; ctx.pressedX = e.x; ctx.pressedAt = Date.now(); - stopAnimation( true ); + stopAnimation(); }, onActive: ( e, ctx ) => { @@ -277,22 +271,14 @@ const StoryModal = forwardRef( ( { useAnimatedReaction( () => animation.value, - ( res, prev ) => { - - if ( res !== prev && res === 1 ) { - - toNextStory(); - - } - - }, + ( res, prev ) => res !== prev && toNextStory( res === 1 ), [ animation.value ], ); return ( - + - + ; seenStories: SharedValue; onPress: () => void; - colors?: string[]; - seenColors?: string[]; - size?: number; + colors: string[]; + seenColors: string[]; + size: number; showName?: boolean; nameTextStyle?: TextStyle; } diff --git a/tests/index.test.js b/tests/index.test.js index e69de29..76b46dc 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -0,0 +1,420 @@ +import { createRef } from 'react'; +import { render, fireEvent, act } from '@testing-library/react-native'; +import * as Reanimated from 'react-native-reanimated'; +import { Platform, View } from 'react-native'; +import InstagramStories from '../src'; +import { WIDTH } from '../src/core/constants'; +import StoryAvatar from '../src/components/Avatar'; +import Loader from '../src/components/Loader'; +import * as Storage from '../src/core/helpers/storage'; + +const sleep = async () => new Promise( ( resolve ) => setTimeout( resolve, 250 ) ); +jest.spyOn( Reanimated, 'useAnimatedReaction' ).mockImplementation( ( value, cb ) => cb( value(), '' ) ); +jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value } ) ); +jest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( {} ) ); +jest.spyOn( Storage, 'setProgressStorage' ).mockImplementation( () => ( {} ) ); + +const stories = [ { + id: '1', + name: 'John Doe', + imgUrl: 'https://picsum.photos/200/300', + stories: [ { + id: '1', + imgUrl: 'https://picsum.photos/200/300', + renderContent: () => , + } ], +} ]; + +const stories2 = [ { + id: '1', + name: 'John Doe', + imgUrl: 'https://picsum.photos/200/300', + stories: [ { + id: '1', + imgUrl: 'https://picsum.photos/200/300', + } ], +}, { + id: '2', + name: 'John Doe 2', + imgUrl: 'https://picsum.photos/200/300', + stories: [ { + id: '1', + imgUrl: 'https://picsum.photos/200/300', + } ], +} ]; + +const stories3 = [ { + id: '1', + name: 'John Doe', + imgUrl: 'https://picsum.photos/200/300', + stories: [ { + id: '1', + imgUrl: 'https://picsum.photos/200/300', + }, { + id: '2', + imgUrl: 'https://picsum.photos/200/300', + } ], +} ]; + +describe( 'Instagram Stories test', () => { + + it( 'Should render the stories list', () => { + + const { getByTestId } = render( ); + + expect( getByTestId( 'storiesList' ) ).toBeTruthy(); + + } ); + + it( 'Should open story on press', async () => { + + const { getByTestId } = render( ); + + const story = getByTestId( '1StoryAvatar1Story' ); + expect( story ).toBeTruthy(); + + await act( async () => { + + fireEvent( story, 'click' ); + await sleep(); + expect( getByTestId( 'storyModal' ) ).toBeTruthy(); + + } ); + + } ); + + it( 'Should work with preloadImages & saveProgress', async () => { + + jest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( { 1: '1' } ) ); + const { getByTestId, getAllByTestId } = render( + , + ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' ); + await sleep(); + + getAllByTestId( 'storyAvatarImage' ).forEach( ( element ) => { + + element.props.onLoad(); + + } ); + getAllByTestId( 'storyImageComponent' ).forEach( ( element ) => { + + element.props.onLoad(); + + } ); + getAllByTestId( 'storyImageComponent' ).forEach( ( element ) => { + + element.props.onLayout( { nativeEvent: { layout: { height: 100 } } } ); + + } ); + getAllByTestId( 'storyCloseButton' ).forEach( ( element ) => { + + fireEvent( element.parent, 'onPressIn' ); + + } ); + + } ); + + } ); + + it( 'Should work if new seen story is older than saved', async () => { + + jest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( { 1: '2' } ) ); + const { getByTestId } = render( + , + ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar2Story' ), 'click' ); + await sleep(); + + } ); + + } ); + + it( 'Should work with public methods', async () => { + + const ref = createRef(); + + const { getByTestId, queryByTestId } = render( + , + ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' ); + await sleep(); + expect( getByTestId( 'storyModal' ) ).toBeTruthy(); + + } ); + + await act( async () => { + + ref.current.clearProgressStorage(); + + ref.current.hide(); + + await sleep(); + expect( queryByTestId( 'storyModal' ) ).toBeFalsy(); + + ref.current.spliceStories( [ { + id: '2', + name: 'John Doe 2', + imgUrl: 'https://picsum.photos/200/300', + stories: [ { + id: '1', + imgUrl: 'https://picsum.photos/200/300', + } ], + } ] ); + + await sleep(); + + ref.current.spliceStories( [ { + id: '3', + name: 'John Doe 3', + imgUrl: 'https://picsum.photos/200/300', + stories: [ { + id: '1', + imgUrl: 'https://picsum.photos/200/300', + } ], + } ], -1 ); + + await sleep(); + + ref.current.spliceUserStories( [ { + id: '2', + imgUrl: 'https://picsum.photos/200/300', + } ], '1' ); + + await sleep(); + + ref.current.spliceUserStories( [ { + id: '2', + imgUrl: 'https://picsum.photos/200/300', + } ], '2', 2 ); + + ref.current.spliceUserStories( [ { + id: '2', + imgUrl: 'https://picsum.photos/200/300', + } ], '20', 2 ); + + await sleep(); + + expect( getByTestId( '1StoryAvatar2Story' ) ).toBeTruthy(); + expect( getByTestId( '2StoryAvatar2Story' ) ).toBeTruthy(); + expect( getByTestId( '3StoryAvatar1Story' ) ).toBeTruthy(); + + ref.current.setStories( stories ); + + await sleep(); + + expect( getByTestId( '1StoryAvatar1Story' ) ).toBeTruthy(); + + } ); + + } ); + + it( 'Should work animations', async () => { + + const { getByTestId, queryByTestId } = render( ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderStart', { x: 0 }, {} ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderMove', { + x: 0, velocityX: 0, velocityY: 10, translationY: 10, + }, { x: 0 } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderMove', { + x: 0, velocityX: 10, velocityY: 0, translationX: 10, + }, { x: 0 } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', { translationY: 10 }, { vertical: true } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { moving: true, x: 10 } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { moving: true, x: 300 } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { moving: true, x: -300 } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { pressedX: 0 } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { pressedAt: 0 } ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, {} ); + await sleep(); + + expect( queryByTestId( 'gestureContainer' ) ).toBeFalsy(); + + } ); + + } ); + + it( 'Should not continue if button pressed', async () => { + + jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === false ? true : value } ) ); + const { getByTestId } = render( ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, {} ); + await sleep(); + + } ); + + } ); + + it( 'Should close with swipe down', async () => { + + jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value } ) ); + const { getByTestId, queryByTestId } = render( ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', { translationY: 200 }, { vertical: true } ); + await sleep(); + + expect( queryByTestId( 'gestureContainer' ) ).toBeFalsy(); + + } ); + + } ); + + it( 'Should go to next story', async () => { + + const { getByTestId } = render( ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar2Story' ), 'click' ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, {} ); + await sleep(); + + } ); + + } ); + + it( 'Should go to next user', async () => { + + const { getByTestId } = render( ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, {} ); + await sleep(); + + } ); + + } ); + + it( 'Should go to previous story', async () => { + + jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === '1' ? '2' : value } ) ); + + const { getByTestId } = render( ); + + await act( async () => { + + fireEvent( getByTestId( '1StoryAvatar2Story' ), 'click' ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { pressedX: 0, pressedAt: Date.now() } ); + await sleep(); + + } ); + + } ); + + it( 'Should go to previous user + work for android', async () => { + + Platform.OS = 'android'; + jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === 0 ? 500 : value } ) ); + jest.spyOn( Reanimated, 'interpolate' ).mockImplementation( () => WIDTH ); + + const { getByTestId } = render( ); + + await act( async () => { + + fireEvent( getByTestId( '2StoryAvatar1Story' ), 'click' ); + await sleep(); + + fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { pressedX: 0, pressedAt: Date.now() } ); + await sleep(); + + } ); + + } ); + +} ); + +describe( 'StoryAvatar test', () => { + + it( 'Should work with seenStories & call onPress', () => { + + jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === false ? true : value } ) ); + const callback = jest.fn(); + const { getByTestId } = render( ); + + expect( getByTestId( '1StoryAvatar1Story' ) ).toBeTruthy(); + + act( () => { + + fireEvent.press( getByTestId( '1StoryAvatar1Story' ) ); + expect( callback ).toHaveBeenCalled(); + + } ); + + } ); + +} ); + +describe( 'Loader test', () => { + + it( 'Should work with empty stories', () => { + + jest.spyOn( Reanimated, 'useAnimatedReaction' ).mockImplementation( ( value, cb ) => cb( typeof value() !== 'boolean' ? [ '#AAA' ] : value() ) ); + render( ); + + } ); + +} );