Unit Testing (#35)

* add testing lib

* remove coverage folder from git

* finished basic test setup

* fix tests typescript and import paths

* add first snapshot

* testing utils

* rename test files; update script flags; ++tests

* testing utils functions

* testing downloadAndResize wip

* remove download test

* specify unwanted coverage paths;
remove update snapshots flag

* fix strings tests

* testing downloadAndResize method

* increasing testing

* fixing snapshots wip

* fixed shell mobile snapshot

* adding snapshots for the screens

* fix onboard snapshot

* fix typescript issues

* fix TabsSelector snapshot

* Account for testing device's locale in ago() tests

* Remove platform detection on regex

* mocking store state wip

* mocking store state

* increasing test coverage

* increasing test coverage

* increasing test coverage on src/screens

* src/screens (except for profile) above 80% cov

* testing profile screen wip

* increase coverage on Menu and TabsSelector

* mocking profile ui state wip

* mocking profile ui state wip

* fixing mobileshell tests wip

* snapshots using testing-library

* fixing profile tests wip

* removing mobile shell tests

* src/view/com tests wip

* remove unnecessary patch-package

* fixed profile test error

* clear mocks after every test

* fix base mocked store values (getters)

* fix base mocked store values
(hasLoaded, nonReplyFeed)

* profile screen above 80% coverage

* testing custom hooks

* improving composer coverage

* fix tests after merge

* finishing composer coverage

* improving src/com/discover coverage

* improve src/view/com/login coverage
fix SuggestedFollows tests
adding some comments

* fix SuggestedFollows tests

* improve src/view/com/profile coverage
extra minor fixes

* improve src/view/com/notifications coverage

* update coverage ignore patterns

* rename errorMessageTryAgainButton
increase SuggestedFollows converage

* improve src/view/com/posts coverage

* improve src/view/com/onboard coverage

* update snapshot

* improve src/view/com/post coverage

* improve src/view/com/post-thread coverage
rename ErrorMessage tests
test Debug and Log components

* init testing state

* testing root-store

* updating comments

* small fixes

* removed extra console logs

* improve src/state/models coverage
refactor rootStore
rename some spies

* adding cleanup method after tests

* improve src/state/models coverage

* improve src/state/models coverage

* improve src/state/models coverage

* improve src/state/models coverage

* test setInterval in setupState

* Clean up tests and update Home screen state management

* Remove some tests we dont need

* Remove snapshot tests

* Remove any tests that dont demonstrate clear value

* Cleanup

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
João Ferreiro 2023-01-17 16:06:00 +00:00 committed by GitHub
parent 11c861d2d3
commit 5abcc8e336
95 changed files with 2852 additions and 9936 deletions

View file

@ -0,0 +1,43 @@
import React from 'react'
import {Autocomplete} from '../../../../src/view/com/composer/Autocomplete'
import {cleanup, fireEvent, render} from '../../../../jest/test-utils'
describe('Autocomplete', () => {
const onSelectMock = jest.fn()
const mockedProps = {
active: true,
items: [
{
handle: 'handle.test',
displayName: 'Test Display',
},
{
handle: 'handle2.test',
displayName: 'Test Display 2',
},
],
onSelect: onSelectMock,
}
afterAll(() => {
jest.clearAllMocks()
cleanup()
})
it('renders a button for each user', async () => {
const {findAllByTestId} = render(<Autocomplete {...mockedProps} />)
const autocompleteButton = await findAllByTestId('autocompleteButton')
expect(autocompleteButton.length).toBe(2)
})
it('triggers onSelect by pressing the button', async () => {
const {findAllByTestId} = render(<Autocomplete {...mockedProps} />)
const autocompleteButton = await findAllByTestId('autocompleteButton')
fireEvent.press(autocompleteButton[0])
expect(onSelectMock).toHaveBeenCalledWith('handle.test')
fireEvent.press(autocompleteButton[1])
expect(onSelectMock).toHaveBeenCalledWith('handle2.test')
})
})

View file

@ -0,0 +1,117 @@
import React from 'react'
import {ComposePost} from '../../../../src/view/com/composer/ComposePost'
import {cleanup, fireEvent, render, waitFor} from '../../../../jest/test-utils'
import * as apilib from '../../../../src/state/lib/api'
import {
mockedAutocompleteViewStore,
mockedRootStore,
} from '../../../../__mocks__/state-mock'
import Toast from 'react-native-root-toast'
describe('ComposePost', () => {
const mockedProps = {
replyTo: {
uri: 'testUri',
cid: 'testCid',
text: 'testText',
author: {
handle: 'test.handle',
displayName: 'test name',
avatar: '',
},
},
onPost: jest.fn(),
onClose: jest.fn(),
}
afterAll(() => {
jest.clearAllMocks()
cleanup()
})
it('renders post composer', async () => {
const {findByTestId} = render(<ComposePost {...mockedProps} />)
const composePostView = await findByTestId('composePostView')
expect(composePostView).toBeTruthy()
})
it('closes composer', async () => {
const {findByTestId} = render(<ComposePost {...mockedProps} />)
const composerCancelButton = await findByTestId('composerCancelButton')
fireEvent.press(composerCancelButton)
expect(mockedProps.onClose).toHaveBeenCalled()
})
it('changes text and publishes post', async () => {
const postSpy = jest.spyOn(apilib, 'post').mockResolvedValue({
uri: '',
cid: '',
})
const toastSpy = jest.spyOn(Toast, 'show')
const wrapper = render(<ComposePost {...mockedProps} />)
const composerTextInput = await wrapper.findByTestId('composerTextInput')
fireEvent.changeText(composerTextInput, 'testing publish')
const composerPublishButton = await wrapper.findByTestId(
'composerPublishButton',
)
fireEvent.press(composerPublishButton)
expect(postSpy).toHaveBeenCalledWith(
mockedRootStore,
'testing publish',
'testUri',
[],
new Set<string>(),
expect.anything(),
)
// Waits for request to be resolved
await waitFor(() => {
expect(mockedProps.onPost).toHaveBeenCalled()
expect(mockedProps.onClose).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith('Your reply has been published', {
animation: true,
duration: 3500,
hideOnPress: true,
position: 50,
shadow: true,
})
})
})
it('selects autocomplete item', async () => {
jest
.spyOn(React, 'useMemo')
.mockReturnValueOnce(mockedAutocompleteViewStore)
const {findAllByTestId} = render(<ComposePost {...mockedProps} />)
const autocompleteButton = await findAllByTestId('autocompleteButton')
fireEvent.press(autocompleteButton[0])
expect(mockedAutocompleteViewStore.setActive).toHaveBeenCalledWith(false)
})
it('selects photos', async () => {
const {findByTestId, queryByTestId} = render(
<ComposePost {...mockedProps} />,
)
let photoCarouselPickerView = queryByTestId('photoCarouselPickerView')
expect(photoCarouselPickerView).toBeFalsy()
const composerSelectPhotosButton = await findByTestId(
'composerSelectPhotosButton',
)
fireEvent.press(composerSelectPhotosButton)
photoCarouselPickerView = await findByTestId('photoCarouselPickerView')
expect(photoCarouselPickerView).toBeTruthy()
fireEvent.press(composerSelectPhotosButton)
photoCarouselPickerView = queryByTestId('photoCarouselPickerView')
expect(photoCarouselPickerView).toBeFalsy()
})
})

View file

@ -0,0 +1,92 @@
import React from 'react'
import {PhotoCarouselPicker} from '../../../../src/view/com/composer/PhotoCarouselPicker'
import {cleanup, fireEvent, render} from '../../../../jest/test-utils'
import {
openCamera,
openCropper,
openPicker,
} from 'react-native-image-crop-picker'
describe('PhotoCarouselPicker', () => {
const mockedProps = {
selectedPhotos: ['mock-uri', 'mock-uri-2'],
onSelectPhotos: jest.fn(),
localPhotos: {
photos: [
{
node: {
image: {
uri: 'mock-uri',
},
},
},
],
},
}
afterAll(() => {
jest.clearAllMocks()
cleanup()
})
it('renders carousel picker', async () => {
const {findByTestId} = render(<PhotoCarouselPicker {...mockedProps} />)
const photoCarouselPickerView = await findByTestId(
'photoCarouselPickerView',
)
expect(photoCarouselPickerView).toBeTruthy()
})
it('triggers openCamera', async () => {
const {findByTestId} = render(<PhotoCarouselPicker {...mockedProps} />)
const openCameraButton = await findByTestId('openCameraButton')
fireEvent.press(openCameraButton)
expect(openCamera).toHaveBeenCalledWith({
compressImageQuality: 1,
cropping: true,
forceJpg: true,
freeStyleCropEnabled: true,
height: 1000,
mediaType: 'photo',
width: 1000,
})
})
it('triggers openCropper', async () => {
const {findByTestId} = render(<PhotoCarouselPicker {...mockedProps} />)
const openSelectPhotoButton = await findByTestId('openSelectPhotoButton')
fireEvent.press(openSelectPhotoButton)
expect(openCropper).toHaveBeenCalledWith({
compressImageQuality: 1,
forceJpg: true,
freeStyleCropEnabled: true,
height: 1000,
mediaType: 'photo',
path: 'mock-uri',
width: 1000,
})
})
it('triggers openPicker', async () => {
const {findByTestId} = render(<PhotoCarouselPicker {...mockedProps} />)
const openGalleryButton = await findByTestId('openGalleryButton')
fireEvent.press(openGalleryButton)
expect(openPicker).toHaveBeenCalledWith({
maxFiles: 2,
mediaType: 'photo',
multiple: true,
})
expect(openCropper).toHaveBeenCalledWith({
compressImageQuality: 1,
forceJpg: true,
freeStyleCropEnabled: true,
height: 1000,
mediaType: 'photo',
path: 'mock-uri',
width: 1000,
})
})
})

View file

@ -0,0 +1,70 @@
import React from 'react'
import {SelectedPhoto} from '../../../../src/view/com/composer/SelectedPhoto'
import {cleanup, fireEvent, render} from '../../../../jest/test-utils'
describe('SelectedPhoto', () => {
const mockedProps = {
selectedPhotos: ['mock-uri', 'mock-uri-2'],
onSelectPhotos: jest.fn(),
}
afterAll(() => {
jest.clearAllMocks()
cleanup()
})
it('has no photos to render', () => {
const {queryByTestId} = render(
<SelectedPhoto selectedPhotos={[]} onSelectPhotos={jest.fn()} />,
)
const selectedPhotosView = queryByTestId('selectedPhotosView')
expect(selectedPhotosView).toBeNull()
const selectedPhotoImage = queryByTestId('selectedPhotoImage')
expect(selectedPhotoImage).toBeNull()
})
it('has 1 photos to render', async () => {
const {findByTestId} = render(
<SelectedPhoto
selectedPhotos={['mock-uri']}
onSelectPhotos={jest.fn()}
/>,
)
const selectedPhotosView = await findByTestId('selectedPhotosView')
expect(selectedPhotosView).toBeTruthy()
const selectedPhotoImage = await findByTestId('selectedPhotoImage')
expect(selectedPhotoImage).toBeTruthy()
// @ts-expect-error
expect(selectedPhotoImage).toHaveStyle({width: 250})
})
it('has 2 photos to render', async () => {
const {findAllByTestId} = render(<SelectedPhoto {...mockedProps} />)
const selectedPhotoImage = await findAllByTestId('selectedPhotoImage')
expect(selectedPhotoImage[0]).toBeTruthy()
// @ts-expect-error
expect(selectedPhotoImage[0]).toHaveStyle({width: 175})
})
it('has 3 photos to render', async () => {
const {findAllByTestId} = render(
<SelectedPhoto
selectedPhotos={['mock-uri', 'mock-uri-2', 'mock-uri-3']}
onSelectPhotos={jest.fn()}
/>,
)
const selectedPhotoImage = await findAllByTestId('selectedPhotoImage')
expect(selectedPhotoImage[0]).toBeTruthy()
// @ts-expect-error
expect(selectedPhotoImage[0]).toHaveStyle({width: 85})
})
it('removes a photo', async () => {
const {findAllByTestId} = render(<SelectedPhoto {...mockedProps} />)
const removePhotoButton = await findAllByTestId('removePhotoButton')
fireEvent.press(removePhotoButton[0])
expect(mockedProps.onSelectPhotos).toHaveBeenCalledWith(['mock-uri-2'])
})
})

View file

@ -0,0 +1,60 @@
import React from 'react'
import {Keyboard} from 'react-native'
import {CreateAccount} from '../../../../src/view/com/login/CreateAccount'
import {cleanup, fireEvent, render} from '../../../../jest/test-utils'
import {
mockedLogStore,
mockedRootStore,
mockedSessionStore,
mockedShellStore,
} from '../../../../__mocks__/state-mock'
describe('CreateAccount', () => {
const mockedProps = {
onPressBack: jest.fn(),
}
afterAll(() => {
jest.clearAllMocks()
cleanup()
})
it('renders form and creates new account', async () => {
const {findByTestId} = render(<CreateAccount {...mockedProps} />)
const registerEmailInput = await findByTestId('registerEmailInput')
expect(registerEmailInput).toBeTruthy()
fireEvent.changeText(registerEmailInput, 'test@email.com')
const registerHandleInput = await findByTestId('registerHandleInput')
expect(registerHandleInput).toBeTruthy()
fireEvent.changeText(registerHandleInput, 'test.handle')
const registerPasswordInput = await findByTestId('registerPasswordInput')
expect(registerPasswordInput).toBeTruthy()
fireEvent.changeText(registerPasswordInput, 'testpass')
const registerIs13Input = await findByTestId('registerIs13Input')
expect(registerIs13Input).toBeTruthy()
fireEvent.press(registerIs13Input)
const createAccountButton = await findByTestId('createAccountButton')
expect(createAccountButton).toBeTruthy()
fireEvent.press(createAccountButton)
expect(mockedSessionStore.createAccount).toHaveBeenCalled()
})
it('renders and selects service', async () => {
const keyboardSpy = jest.spyOn(Keyboard, 'dismiss')
const {findByTestId} = render(<CreateAccount {...mockedProps} />)
const registerSelectServiceButton = await findByTestId(
'registerSelectServiceButton',
)
expect(registerSelectServiceButton).toBeTruthy()
fireEvent.press(registerSelectServiceButton)
expect(mockedShellStore.openModal).toHaveBeenCalled()
expect(keyboardSpy).toHaveBeenCalled()
})
})

View file

@ -0,0 +1,128 @@
import React from 'react'
import {Signin} from '../../../../src/view/com/login/Signin'
import {cleanup, fireEvent, render} from '../../../../jest/test-utils'
import {SessionServiceClient, sessionClient as AtpApi} from '@atproto/api'
import {
mockedLogStore,
mockedRootStore,
mockedSessionStore,
mockedShellStore,
} from '../../../../__mocks__/state-mock'
import {Keyboard} from 'react-native'
describe('Signin', () => {
const requestPasswordResetMock = jest.fn()
const resetPasswordMock = jest.fn()
jest.spyOn(AtpApi, 'service').mockReturnValue({
com: {
atproto: {
account: {
requestPasswordReset: requestPasswordResetMock,
resetPassword: resetPasswordMock,
},
},
},
} as unknown as SessionServiceClient)
const mockedProps = {
onPressBack: jest.fn(),
}
afterAll(() => {
jest.clearAllMocks()
cleanup()
})
it('renders logs in form', async () => {
const {findByTestId} = render(<Signin {...mockedProps} />)
const loginFormView = await findByTestId('loginFormView')
expect(loginFormView).toBeTruthy()
const loginUsernameInput = await findByTestId('loginUsernameInput')
expect(loginUsernameInput).toBeTruthy()
fireEvent.changeText(loginUsernameInput, 'testusername')
const loginPasswordInput = await findByTestId('loginPasswordInput')
expect(loginPasswordInput).toBeTruthy()
fireEvent.changeText(loginPasswordInput, 'test pass')
const loginNextButton = await findByTestId('loginNextButton')
expect(loginNextButton).toBeTruthy()
fireEvent.press(loginNextButton)
expect(mockedSessionStore.login).toHaveBeenCalled()
})
it('renders selects service from login form', async () => {
const keyboardSpy = jest.spyOn(Keyboard, 'dismiss')
const {findByTestId} = render(<Signin {...mockedProps} />)
const loginSelectServiceButton = await findByTestId(
'loginSelectServiceButton',
)
expect(loginSelectServiceButton).toBeTruthy()
fireEvent.press(loginSelectServiceButton)
expect(mockedShellStore.openModal).toHaveBeenCalled()
expect(keyboardSpy).toHaveBeenCalled()
})
it('renders new password form', async () => {
const {findByTestId} = render(<Signin {...mockedProps} />)
const forgotPasswordButton = await findByTestId('forgotPasswordButton')
expect(forgotPasswordButton).toBeTruthy()
fireEvent.press(forgotPasswordButton)
const forgotPasswordView = await findByTestId('forgotPasswordView')
expect(forgotPasswordView).toBeTruthy()
const forgotPasswordEmail = await findByTestId('forgotPasswordEmail')
expect(forgotPasswordEmail).toBeTruthy()
fireEvent.changeText(forgotPasswordEmail, 'test@email.com')
const newPasswordButton = await findByTestId('newPasswordButton')
expect(newPasswordButton).toBeTruthy()
fireEvent.press(newPasswordButton)
expect(requestPasswordResetMock).toHaveBeenCalled()
const newPasswordView = await findByTestId('newPasswordView')
expect(newPasswordView).toBeTruthy()
const newPasswordInput = await findByTestId('newPasswordInput')
expect(newPasswordInput).toBeTruthy()
const resetCodeInput = await findByTestId('resetCodeInput')
expect(resetCodeInput).toBeTruthy()
fireEvent.changeText(newPasswordInput, 'test pass')
fireEvent.changeText(resetCodeInput, 'test reset code')
const setNewPasswordButton = await findByTestId('setNewPasswordButton')
expect(setNewPasswordButton).toBeTruthy()
fireEvent.press(setNewPasswordButton)
expect(resetPasswordMock).toHaveBeenCalled()
})
it('renders forgot password form', async () => {
const {findByTestId} = render(<Signin {...mockedProps} />)
const forgotPasswordButton = await findByTestId('forgotPasswordButton')
expect(forgotPasswordButton).toBeTruthy()
fireEvent.press(forgotPasswordButton)
const forgotPasswordSelectServiceButton = await findByTestId(
'forgotPasswordSelectServiceButton',
)
expect(forgotPasswordSelectServiceButton).toBeTruthy()
fireEvent.press(forgotPasswordSelectServiceButton)
expect(mockedShellStore.openModal).toHaveBeenCalled()
})
})

View file

@ -0,0 +1,109 @@
import React from 'react'
import {cleanup, fireEvent, render} from '../../../../jest/test-utils'
import {ProfileViewModel} from '../../../../src/state/models/profile-view'
import {ProfileHeader} from '../../../../src/view/com/profile/ProfileHeader'
import {
mockedNavigationStore,
mockedProfileStore,
mockedShellStore,
} from '../../../../__mocks__/state-mock'
describe('ProfileHeader', () => {
const mockedProps = {
view: mockedProfileStore,
onRefreshAll: jest.fn(),
}
afterAll(() => {
jest.clearAllMocks()
cleanup()
})
it('renders ErrorMessage on error', async () => {
const {findByTestId} = render(
<ProfileHeader
{...{
view: {
...mockedProfileStore,
hasError: true,
} as ProfileViewModel,
onRefreshAll: jest.fn(),
}}
/>,
)
const profileHeaderHasError = await findByTestId('profileHeaderHasError')
expect(profileHeaderHasError).toBeTruthy()
})
it('presses and opens edit profile', async () => {
const {findByTestId} = render(<ProfileHeader {...mockedProps} />)
const profileHeaderEditProfileButton = await findByTestId(
'profileHeaderEditProfileButton',
)
expect(profileHeaderEditProfileButton).toBeTruthy()
fireEvent.press(profileHeaderEditProfileButton)
expect(mockedShellStore.openModal).toHaveBeenCalled()
})
it('presses and opens followers page', async () => {
const {findByTestId} = render(<ProfileHeader {...mockedProps} />)
const profileHeaderFollowersButton = await findByTestId(
'profileHeaderFollowersButton',
)
expect(profileHeaderFollowersButton).toBeTruthy()
fireEvent.press(profileHeaderFollowersButton)
expect(mockedNavigationStore.navigate).toHaveBeenCalledWith(
'/profile/testhandle/followers',
)
})
it('presses and opens avatar modal', async () => {
const {findByTestId} = render(<ProfileHeader {...mockedProps} />)
const profileHeaderAviButton = await findByTestId('profileHeaderAviButton')
expect(profileHeaderAviButton).toBeTruthy()
fireEvent.press(profileHeaderAviButton)
expect(mockedShellStore.openLightbox).toHaveBeenCalled()
})
it('presses and opens follows page', async () => {
const {findByTestId} = render(<ProfileHeader {...mockedProps} />)
const profileHeaderFollowsButton = await findByTestId(
'profileHeaderFollowsButton',
)
expect(profileHeaderFollowsButton).toBeTruthy()
fireEvent.press(profileHeaderFollowsButton)
expect(mockedNavigationStore.navigate).toHaveBeenCalledWith(
'/profile/testhandle/follows',
)
})
it('toggles following', async () => {
const {findByTestId} = render(
<ProfileHeader
{...{
view: {
...mockedProfileStore,
did: 'test did 2',
} as ProfileViewModel,
onRefreshAll: jest.fn(),
}}
/>,
)
const profileHeaderToggleFollowButton = await findByTestId(
'profileHeaderToggleFollowButton',
)
expect(profileHeaderToggleFollowButton).toBeTruthy()
fireEvent.press(profileHeaderToggleFollowButton)
expect(mockedProps.view.toggleFollowing).toHaveBeenCalled()
})
})