UNPKG

@birdwingo/react-native-instagram-stories

Version:

A versatile and captivating React Native component that empowers developers to seamlessly integrate Instagram-style stories into their mobile applications, fostering an engaging and interactive user experience.

547 lines (363 loc) 14.1 kB
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 StoryImage from '../src/components/Image'; import Loader from '../src/components/Loader'; import * as Storage from '../src/core/helpers/storage'; const reactions = new Map(); const sleep = async ( ms ) => new Promise( ( resolve ) => setTimeout( resolve, ms ?? 250 ) ); jest.spyOn( Reanimated, 'useAnimatedReaction' ).mockImplementation( ( value, cb ) => { if ( reactions.has( `${cb}` ) && reactions.get( `${cb}` ) === value() ) { return; } reactions.set( `${cb}`, value() ); 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', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, renderContent: () => <View />, } ], } ]; const stories2 = [ { id: '1', name: 'John Doe', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, } ], }, { id: '2', name: 'John Doe 2', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, } ], } ]; const stories3 = [ { id: '1', name: 'John Doe', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, }, { id: '2', source: { uri: 'https://picsum.photos/200/300' }, } ], } ]; const stories4 = [ { id: '1', name: 'John Doe', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, mediaType: 'video', } ], }, { id: '2', name: 'John Doe 2', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, mediaType: 'video', } ], } ]; describe( 'Instagram Stories test', () => { beforeEach( () => { reactions.clear(); } ); it( 'Should render the stories list', () => { const { getByTestId } = render( <InstagramStories stories={stories} /> ); expect( getByTestId( 'storiesList' ) ).toBeTruthy(); } ); it( 'Should open story on press', async () => { const { getByTestId } = render( <InstagramStories stories={stories} showName /> ); const story = getByTestId( '1StoryAvatar1Story' ); expect( story ).toBeTruthy(); await act( async () => { fireEvent( story, 'click' ); await sleep(); expect( getByTestId( 'storyModal' ) ).toBeTruthy(); } ); } ); it( 'Should work with saveProgress', async () => { jest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( { 1: '1' } ) ); const { getByTestId, getAllByTestId } = render( <InstagramStories stories={stories2} saveProgress />, ); 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', { nativeEvent: { locationX: 0, locationY: 0 } } ); } ); } ); } ); it( 'Should work if new seen story is older than saved', async () => { jest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( { 1: '2' } ) ); const { getByTestId } = render( <InstagramStories stories={stories3} saveProgress />, ); await act( async () => { fireEvent( getByTestId( '1StoryAvatar2Story' ), 'click' ); await sleep(); } ); } ); it( 'Should work with public methods', async () => { const ref = createRef(); const { getByTestId, queryByTestId } = render( <InstagramStories stories={stories} ref={ref} saveProgress />, ); 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', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, } ], } ] ); await sleep(); ref.current.spliceStories( [ { id: '3', name: 'John Doe 3', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [ { id: '1', source: { uri: 'https://picsum.photos/200/300' }, } ], } ], -1 ); await sleep(); ref.current.spliceUserStories( [ { id: '2', source: { uri: 'https://picsum.photos/200/300' }, } ], '1' ); await sleep(); ref.current.spliceUserStories( [ { id: '2', source: { uri: 'https://picsum.photos/200/300' }, } ], '2', 2 ); ref.current.spliceUserStories( [ { id: '2', source: { uri: '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(); ref.current.show(); ref.current.show('1'); } ); } ); it( 'Should not open if empty array', async () => { const ref = createRef(); const { queryByTestId } = render( <InstagramStories stories={[]} ref={ref} saveProgress />, ); await act( async () => { ref.current.show(); await sleep(); expect( queryByTestId( 'storyModal' ) ).toBeFalsy(); } ); } ); it( 'Should work animations', async () => { const { getByTestId, queryByTestId } = render( <InstagramStories stories={stories} /> ); 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', { translationY: 200 }, { vertical: true } ); 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( <InstagramStories stories={stories} /> ); 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( <InstagramStories stories={stories} /> ); 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( <InstagramStories stories={stories3} /> ); 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( <InstagramStories stories={stories2} /> ); 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( <InstagramStories stories={stories3} /> ); 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( <InstagramStories stories={stories2} /> ); await act( async () => { fireEvent( getByTestId( '2StoryAvatar1Story' ), 'click' ); await sleep(); fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { pressedX: 0, pressedAt: Date.now() } ); await sleep(); } ); } ); it( 'Should work with video', async () => { const { getByTestId } = render( <InstagramStories stories={stories4} /> ); await act( async () => { fireEvent( getByTestId( '2StoryAvatar1Story' ), 'click' ); await sleep(); } ); } ); it( 'Should work with empty array', async () => { render( <InstagramStories stories={[ { id: '1', name: 'John Doe', avatarSource: { uri: 'https://picsum.photos/200/300' }, stories: [], } ]} /> ); } ); it( 'Should work with video & default duration', async () => { const { getByTestId } = render( <InstagramStories stories={stories4} videoAnimationMaxDuration={1000} /> ); await act( async () => { fireEvent( getByTestId( '2StoryAvatar1Story' ), 'click' ); 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( <StoryAvatar {...stories[0]} seenStories={{ value: { 1: '1' } }} loadingStory={{ value: '2' }} onPress={callback} colors={[ '#FFF' ]} seenColors={[ '#FFF' ]} size={50} /> ); 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( <Loader loading={{ value: false }} color={{ value: [ '#FFF' ] }} /> ); } ); } ); describe( 'Story Image test', () => { it( 'Should work with wrong story', () => { render( <StoryImage stories={stories[0].stories} active={{ value: true }} activeStory={{ value: '2' }} defaultImage="url" paused={{ value: true }} isActive={{ value: true }} /> ); } ); it( 'Should work if story already loaded', async () => { jest.spyOn(Reanimated, 'useSharedValue').mockImplementation((value) => ({ value: value === true ? false : value })); const onLoad = jest.fn(); render( <StoryImage stories={[ { id: '1', source: { uri: '' } } ]} active={{ value: true }} activeStory={{ value: '1' }} defaultImage="url" onLoad={onLoad} paused={{ value: true }} isActive={{ value: true }} /> ); expect( onLoad ).toHaveBeenCalled(); } ); } );