From 5abcc8e336b3af11a6c98d0d9e662415856478a0 Mon Sep 17 00:00:00 2001 From: João Ferreiro Date: Tue, 17 Jan 2023 16:06:00 +0000 Subject: 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 --- __tests__/lib/images.test.ts | 18 +- __tests__/state/models/link-metas-view.test.ts | 72 ++ __tests__/state/models/log.test.ts | 153 ++++ __tests__/state/models/me.test.ts | 183 +++++ __tests__/state/models/navigation.test.ts | 154 ++++ __tests__/state/models/onboard.test.ts | 46 ++ __tests__/state/models/root-store.test.ts | 73 ++ __tests__/state/models/shell-ui.test.ts | 59 ++ __tests__/view/com/composer/Autocomplete.test.tsx | 43 ++ __tests__/view/com/composer/ComposePost.test.tsx | 117 +++ .../view/com/composer/PhotoCarouselPicker.test.tsx | 92 +++ __tests__/view/com/composer/SelectedPhoto.test.tsx | 70 ++ __tests__/view/com/login/CreateAccount.test.tsx | 60 ++ __tests__/view/com/login/Signin.test.tsx | 128 ++++ __tests__/view/com/profile/ProfileHeader.test.tsx | 109 +++ __tests__/view/lib/useAnimatedValue.test.tsx | 17 + __tests__/view/lib/useOnMainScroll.test.tsx | 49 ++ __tests__/view/screens/Contacts.test.tsx | 16 - __tests__/view/screens/Home.test.tsx | 16 - __tests__/view/screens/Login.test.tsx | 36 +- __tests__/view/screens/NotFound.test.tsx | 20 +- __tests__/view/screens/Notifications.test.tsx | 16 - __tests__/view/screens/Onboard.test.tsx | 11 - __tests__/view/screens/PostDownvotedBy.test.tsx | 19 - __tests__/view/screens/PostRepostedBy.test.tsx | 19 - __tests__/view/screens/PostThread.test.tsx | 19 - __tests__/view/screens/PostUpvotedBy.test.tsx | 19 - __tests__/view/screens/Profile.test.tsx | 19 - __tests__/view/screens/ProfileFollowers.test.tsx | 18 - __tests__/view/screens/ProfileFollows.test.tsx | 18 - __tests__/view/screens/ProfileMembers.test.tsx | 18 - __tests__/view/screens/Search.test.tsx | 22 +- __tests__/view/screens/Settings.test.tsx | 16 - .../screens/__snapshots__/Contacts.test.tsx.snap | 205 ----- .../view/screens/__snapshots__/Home.test.tsx.snap | 594 --------------- .../view/screens/__snapshots__/Login.test.tsx.snap | 371 --------- .../screens/__snapshots__/NotFound.test.tsx.snap | 431 ----------- .../__snapshots__/Notifications.test.tsx.snap | 378 ---------- .../screens/__snapshots__/Onboard.test.tsx.snap | 388 ---------- .../__snapshots__/PostDownvotedBy.test.tsx.snap | 368 --------- .../__snapshots__/PostRepostedBy.test.tsx.snap | 368 --------- .../screens/__snapshots__/PostThread.test.tsx.snap | 437 ----------- .../__snapshots__/PostUpvotedBy.test.tsx.snap | 368 --------- .../screens/__snapshots__/Profile.test.tsx.snap | 513 ------------- .../__snapshots__/ProfileFollowers.test.tsx.snap | 386 ---------- .../__snapshots__/ProfileFollows.test.tsx.snap | 386 ---------- .../__snapshots__/ProfileMembers.test.tsx.snap | 386 ---------- .../screens/__snapshots__/Search.test.tsx.snap | 514 ------------- .../screens/__snapshots__/Settings.test.tsx.snap | 631 ---------------- __tests__/view/shell/mobile/Composer.test.tsx | 23 - __tests__/view/shell/mobile/Menu.test.tsx | 67 +- __tests__/view/shell/mobile/TabsSelector.test.tsx | 96 ++- .../mobile/__snapshots__/Composer.test.tsx.snap | 659 ---------------- .../shell/mobile/__snapshots__/Menu.test.tsx.snap | 837 --------------------- .../__snapshots__/TabsSelector.test.tsx.snap | 651 ---------------- .../shell/mobile/__snapshots__/index.test.tsx.snap | 421 ----------- __tests__/view/shell/mobile/index.test.tsx | 18 - 57 files changed, 1645 insertions(+), 9596 deletions(-) create mode 100644 __tests__/state/models/link-metas-view.test.ts create mode 100644 __tests__/state/models/log.test.ts create mode 100644 __tests__/state/models/me.test.ts create mode 100644 __tests__/state/models/navigation.test.ts create mode 100644 __tests__/state/models/onboard.test.ts create mode 100644 __tests__/state/models/root-store.test.ts create mode 100644 __tests__/state/models/shell-ui.test.ts create mode 100644 __tests__/view/com/composer/Autocomplete.test.tsx create mode 100644 __tests__/view/com/composer/ComposePost.test.tsx create mode 100644 __tests__/view/com/composer/PhotoCarouselPicker.test.tsx create mode 100644 __tests__/view/com/composer/SelectedPhoto.test.tsx create mode 100644 __tests__/view/com/login/CreateAccount.test.tsx create mode 100644 __tests__/view/com/login/Signin.test.tsx create mode 100644 __tests__/view/com/profile/ProfileHeader.test.tsx create mode 100644 __tests__/view/lib/useAnimatedValue.test.tsx create mode 100644 __tests__/view/lib/useOnMainScroll.test.tsx delete mode 100644 __tests__/view/screens/Contacts.test.tsx delete mode 100644 __tests__/view/screens/Home.test.tsx delete mode 100644 __tests__/view/screens/Notifications.test.tsx delete mode 100644 __tests__/view/screens/Onboard.test.tsx delete mode 100644 __tests__/view/screens/PostDownvotedBy.test.tsx delete mode 100644 __tests__/view/screens/PostRepostedBy.test.tsx delete mode 100644 __tests__/view/screens/PostThread.test.tsx delete mode 100644 __tests__/view/screens/PostUpvotedBy.test.tsx delete mode 100644 __tests__/view/screens/Profile.test.tsx delete mode 100644 __tests__/view/screens/ProfileFollowers.test.tsx delete mode 100644 __tests__/view/screens/ProfileFollows.test.tsx delete mode 100644 __tests__/view/screens/ProfileMembers.test.tsx delete mode 100644 __tests__/view/screens/Settings.test.tsx delete mode 100644 __tests__/view/screens/__snapshots__/Contacts.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/Home.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/Login.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/NotFound.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/Notifications.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/Onboard.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/PostDownvotedBy.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/PostRepostedBy.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/PostThread.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/PostUpvotedBy.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/Profile.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/ProfileFollowers.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/ProfileFollows.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/ProfileMembers.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/Search.test.tsx.snap delete mode 100644 __tests__/view/screens/__snapshots__/Settings.test.tsx.snap delete mode 100644 __tests__/view/shell/mobile/Composer.test.tsx delete mode 100644 __tests__/view/shell/mobile/__snapshots__/Composer.test.tsx.snap delete mode 100644 __tests__/view/shell/mobile/__snapshots__/Menu.test.tsx.snap delete mode 100644 __tests__/view/shell/mobile/__snapshots__/TabsSelector.test.tsx.snap delete mode 100644 __tests__/view/shell/mobile/__snapshots__/index.test.tsx.snap delete mode 100644 __tests__/view/shell/mobile/index.test.tsx (limited to '__tests__') diff --git a/__tests__/lib/images.test.ts b/__tests__/lib/images.test.ts index 461bd04cc..d53a5bc05 100644 --- a/__tests__/lib/images.test.ts +++ b/__tests__/lib/images.test.ts @@ -2,31 +2,27 @@ import {downloadAndResize, DownloadAndResizeOpts} from '../../src/lib/images' import ImageResizer from '@bam.tech/react-native-image-resizer' import RNFetchBlob from 'rn-fetch-blob' -jest.mock('rn-fetch-blob', () => ({ - config: jest.fn().mockReturnThis(), - cancel: jest.fn(), - fetch: jest.fn(), -})) -jest.mock('@bam.tech/react-native-image-resizer', () => ({ - createResizedImage: jest.fn(), -})) - describe('downloadAndResize', () => { const errorSpy = jest.spyOn(global.console, 'error') const mockResizedImage = { path: jest.fn().mockReturnValue('file://resized-image.jpg'), size: 100, + width: 50, + height: 50, + mime: 'image/jpeg', } beforeEach(() => { - jest.clearAllMocks() - const mockedCreateResizedImage = ImageResizer.createResizedImage as jest.Mock mockedCreateResizedImage.mockResolvedValue(mockResizedImage) }) + afterEach(() => { + jest.clearAllMocks() + }) + it('should return resized image for valid URI and options', async () => { const mockedFetch = RNFetchBlob.fetch as jest.Mock mockedFetch.mockResolvedValueOnce({ diff --git a/__tests__/state/models/link-metas-view.test.ts b/__tests__/state/models/link-metas-view.test.ts new file mode 100644 index 000000000..037418932 --- /dev/null +++ b/__tests__/state/models/link-metas-view.test.ts @@ -0,0 +1,72 @@ +import {RootStoreModel} from '../../../src/state/models/root-store' +import {LinkMetasViewModel} from '../../../src/state/models/link-metas-view' +import * as LinkMetaLib from '../../../src/lib/link-meta' +import {LikelyType} from './../../../src/lib/link-meta' +import {sessionClient, SessionServiceClient} from '@atproto/api' +import {DEFAULT_SERVICE} from '../../../src/state' + +describe('LinkMetasViewModel', () => { + let viewModel: LinkMetasViewModel + let rootStore: RootStoreModel + + const getLinkMetaMockSpy = jest.spyOn(LinkMetaLib, 'getLinkMeta') + const mockedMeta = { + title: 'Test Title', + url: 'testurl', + likelyType: LikelyType.Other, + } + + beforeEach(() => { + const api = sessionClient.service(DEFAULT_SERVICE) as SessionServiceClient + rootStore = new RootStoreModel(api) + viewModel = new LinkMetasViewModel(rootStore) + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + describe('getLinkMeta', () => { + it('should return link meta if it is cached', async () => { + const url = 'http://example.com' + + viewModel.cache.set(url, mockedMeta) + + const result = await viewModel.getLinkMeta(url) + + expect(getLinkMetaMockSpy).not.toHaveBeenCalled() + expect(result).toEqual(mockedMeta) + }) + + it('should return link meta if it is not cached', async () => { + getLinkMetaMockSpy.mockResolvedValueOnce(mockedMeta) + + const result = await viewModel.getLinkMeta(mockedMeta.url) + + expect(getLinkMetaMockSpy).toHaveBeenCalledWith(mockedMeta.url) + expect(result).toEqual(mockedMeta) + }) + + it('should cache the link meta if it is successfully returned', async () => { + getLinkMetaMockSpy.mockResolvedValueOnce(mockedMeta) + + await viewModel.getLinkMeta(mockedMeta.url) + + expect(viewModel.cache.get(mockedMeta.url)).toEqual(mockedMeta) + }) + + it('should not cache the link meta if it fails to return', async () => { + const url = 'http://example.com' + const error = new Error('Failed to fetch link meta') + getLinkMetaMockSpy.mockRejectedValueOnce(error) + + try { + await viewModel.getLinkMeta(url) + fail('Error was not thrown') + } catch (e) { + expect(e).toEqual(error) + expect(viewModel.cache.get(url)).toBeUndefined() + } + }) + }) +}) diff --git a/__tests__/state/models/log.test.ts b/__tests__/state/models/log.test.ts new file mode 100644 index 000000000..b5a6d0db0 --- /dev/null +++ b/__tests__/state/models/log.test.ts @@ -0,0 +1,153 @@ +import {LogModel} from '../../../src/state/models/log' + +describe('LogModel', () => { + let logModel: LogModel + + beforeEach(() => { + logModel = new LogModel() + jest.spyOn(console, 'debug') + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should call a log method and add a log entry to the entries array', () => { + logModel.debug('Test log') + expect(logModel.entries.length).toEqual(1) + expect(logModel.entries[0]).toEqual({ + id: logModel.entries[0].id, + type: 'debug', + summary: 'Test log', + details: undefined, + ts: logModel.entries[0].ts, + }) + + logModel.warn('Test log') + expect(logModel.entries.length).toEqual(2) + expect(logModel.entries[1]).toEqual({ + id: logModel.entries[1].id, + type: 'warn', + summary: 'Test log', + details: undefined, + ts: logModel.entries[1].ts, + }) + + logModel.error('Test log') + expect(logModel.entries.length).toEqual(3) + expect(logModel.entries[2]).toEqual({ + id: logModel.entries[2].id, + type: 'error', + summary: 'Test log', + details: undefined, + ts: logModel.entries[2].ts, + }) + }) + + it('should call the console.debug after calling the debug method', () => { + logModel.debug('Test log') + expect(console.debug).toHaveBeenCalledWith('Test log', '') + }) + + it('should call the serialize method', () => { + logModel.debug('Test log') + expect(logModel.serialize()).toEqual({ + entries: [ + { + id: logModel.entries[0].id, + type: 'debug', + summary: 'Test log', + details: undefined, + ts: logModel.entries[0].ts, + }, + ], + }) + }) + + it('should call the hydrate method with valid properties', () => { + logModel.hydrate({ + entries: [ + { + id: '123', + type: 'debug', + summary: 'Test log', + details: undefined, + ts: 123, + }, + ], + }) + expect(logModel.entries).toEqual([ + { + id: '123', + type: 'debug', + summary: 'Test log', + details: undefined, + ts: 123, + }, + ]) + }) + + it('should call the hydrate method with invalid properties', () => { + logModel.hydrate({ + entries: [ + { + id: '123', + type: 'debug', + summary: 'Test log', + details: undefined, + ts: 123, + }, + { + summary: 'Invalid entry', + }, + ], + }) + expect(logModel.entries).toEqual([ + { + id: '123', + type: 'debug', + summary: 'Test log', + details: undefined, + ts: 123, + }, + ]) + }) + + it('should stringify the details if it is not a string', () => { + logModel.debug('Test log', {details: 'test'}) + expect(logModel.entries[0].details).toEqual('{\n "details": "test"\n}') + }) + + it('should stringify the details object if it is of a specific error', () => { + class TestError extends Error { + constructor() { + super() + this.name = 'TestError' + } + } + const error = new TestError() + logModel.error('Test error log', error) + expect(logModel.entries[0].details).toEqual('TestError') + + class XRPCInvalidResponseErrorMock { + validationError = {toString: () => 'validationError'} + lexiconNsid = 'test' + } + const xrpcInvalidResponseError = new XRPCInvalidResponseErrorMock() + logModel.error('Test error log', xrpcInvalidResponseError) + expect(logModel.entries[1].details).toEqual( + '{\n "validationError": {},\n "lexiconNsid": "test"\n}', + ) + + class XRPCErrorMock { + status = 'status' + error = 'error' + message = 'message' + } + const xrpcError = new XRPCErrorMock() + logModel.error('Test error log', xrpcError) + expect(logModel.entries[2].details).toEqual( + '{\n "status": "status",\n "error": "error",\n "message": "message"\n}', + ) + }) +}) diff --git a/__tests__/state/models/me.test.ts b/__tests__/state/models/me.test.ts new file mode 100644 index 000000000..a1ffa3fbe --- /dev/null +++ b/__tests__/state/models/me.test.ts @@ -0,0 +1,183 @@ +import {RootStoreModel} from '../../../src/state/models/root-store' +import {MeModel} from '../../../src/state/models/me' +import {MembershipsViewModel} from './../../../src/state/models/memberships-view' +import {NotificationsViewModel} from './../../../src/state/models/notifications-view' +import {sessionClient, SessionServiceClient} from '@atproto/api' +import {DEFAULT_SERVICE} from './../../../src/state/index' + +describe('MeModel', () => { + let rootStore: RootStoreModel + let meModel: MeModel + + beforeEach(() => { + const api = sessionClient.service(DEFAULT_SERVICE) as SessionServiceClient + rootStore = new RootStoreModel(api) + meModel = new MeModel(rootStore) + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should clear() correctly', () => { + meModel.did = '123' + meModel.handle = 'handle' + meModel.displayName = 'John Doe' + meModel.description = 'description' + meModel.avatar = 'avatar' + meModel.notificationCount = 1 + meModel.clear() + expect(meModel.did).toEqual('') + expect(meModel.handle).toEqual('') + expect(meModel.displayName).toEqual('') + expect(meModel.description).toEqual('') + expect(meModel.avatar).toEqual('') + expect(meModel.notificationCount).toEqual(0) + expect(meModel.memberships).toBeUndefined() + }) + + it('should hydrate() successfully with valid properties', () => { + meModel.hydrate({ + did: '123', + handle: 'handle', + displayName: 'John Doe', + description: 'description', + avatar: 'avatar', + }) + expect(meModel.did).toEqual('123') + expect(meModel.handle).toEqual('handle') + expect(meModel.displayName).toEqual('John Doe') + expect(meModel.description).toEqual('description') + expect(meModel.avatar).toEqual('avatar') + }) + + it('should not hydrate() with invalid properties', () => { + meModel.hydrate({ + did: '', + handle: 'handle', + displayName: 'John Doe', + description: 'description', + avatar: 'avatar', + }) + expect(meModel.did).toEqual('') + expect(meModel.handle).toEqual('') + expect(meModel.displayName).toEqual('') + expect(meModel.description).toEqual('') + expect(meModel.avatar).toEqual('') + + meModel.hydrate({ + did: '123', + displayName: 'John Doe', + description: 'description', + avatar: 'avatar', + }) + expect(meModel.did).toEqual('') + expect(meModel.handle).toEqual('') + expect(meModel.displayName).toEqual('') + expect(meModel.description).toEqual('') + expect(meModel.avatar).toEqual('') + }) + + it('should load() successfully', async () => { + jest + .spyOn(rootStore.api.app.bsky.actor, 'getProfile') + .mockImplementationOnce((): Promise => { + return Promise.resolve({ + data: { + displayName: 'John Doe', + description: 'description', + avatar: 'avatar', + }, + }) + }) + rootStore.session.data = { + did: '123', + handle: 'handle', + service: 'test service', + accessJwt: 'test token', + refreshJwt: 'test token', + } + await meModel.load() + expect(meModel.did).toEqual('123') + expect(meModel.handle).toEqual('handle') + expect(meModel.displayName).toEqual('John Doe') + expect(meModel.description).toEqual('description') + expect(meModel.avatar).toEqual('avatar') + }) + + it('should load() successfully without profile data', async () => { + jest + .spyOn(rootStore.api.app.bsky.actor, 'getProfile') + .mockImplementationOnce((): Promise => { + return Promise.resolve({ + data: null, + }) + }) + rootStore.session.data = { + did: '123', + handle: 'handle', + service: 'test service', + accessJwt: 'test token', + refreshJwt: 'test token', + } + await meModel.load() + expect(meModel.did).toEqual('123') + expect(meModel.handle).toEqual('handle') + expect(meModel.displayName).toEqual('') + expect(meModel.description).toEqual('') + expect(meModel.avatar).toEqual('') + }) + + it('should load() to nothing when no session', async () => { + rootStore.session.data = null + await meModel.load() + expect(meModel.did).toEqual('') + expect(meModel.handle).toEqual('') + expect(meModel.displayName).toEqual('') + expect(meModel.description).toEqual('') + expect(meModel.avatar).toEqual('') + expect(meModel.notificationCount).toEqual(0) + expect(meModel.memberships).toBeUndefined() + }) + + it('should serialize() key information', () => { + meModel.did = '123' + meModel.handle = 'handle' + meModel.displayName = 'John Doe' + meModel.description = 'description' + meModel.avatar = 'avatar' + + expect(meModel.serialize()).toEqual({ + did: '123', + handle: 'handle', + displayName: 'John Doe', + description: 'description', + avatar: 'avatar', + }) + }) + + it('should clearNotificationCount() successfully', () => { + meModel.clearNotificationCount() + expect(meModel.notificationCount).toBe(0) + }) + + it('should update notifs count with fetchStateUpdate()', async () => { + meModel.notifications = { + refresh: jest.fn(), + } as unknown as NotificationsViewModel + + jest + .spyOn(rootStore.api.app.bsky.notification, 'getCount') + .mockImplementationOnce((): Promise => { + return Promise.resolve({ + data: { + count: 1, + }, + }) + }) + + await meModel.fetchStateUpdate() + expect(meModel.notificationCount).toBe(1) + expect(meModel.notifications.refresh).toHaveBeenCalled() + }) +}) diff --git a/__tests__/state/models/navigation.test.ts b/__tests__/state/models/navigation.test.ts new file mode 100644 index 000000000..bc49d2ee3 --- /dev/null +++ b/__tests__/state/models/navigation.test.ts @@ -0,0 +1,154 @@ +import { + NavigationModel, + NavigationTabModel, +} from './../../../src/state/models/navigation' +import * as flags from '../../../src/build-flags' + +describe('NavigationModel', () => { + let model: NavigationModel + + beforeEach(() => { + model = new NavigationModel() + model.setTitle([0, 0], 'title') + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should clear() to the correct base state', async () => { + await model.clear() + expect(model.tabCount).toBe(2) + expect(model.tab).toEqual({ + fixedTabPurpose: 0, + history: [ + { + id: expect.anything(), + ts: expect.anything(), + url: '/', + }, + ], + id: expect.anything(), + index: 0, + isNewTab: false, + }) + }) + + it('should call the navigate method', async () => { + const navigateSpy = jest.spyOn(model.tab, 'navigate') + await model.navigate('testurl', 'teststring') + expect(navigateSpy).toHaveBeenCalledWith('testurl', 'teststring') + }) + + it('should call the refresh method', async () => { + const refreshSpy = jest.spyOn(model.tab, 'refresh') + await model.refresh() + expect(refreshSpy).toHaveBeenCalled() + }) + + it('should call the isCurrentScreen method', () => { + expect(model.isCurrentScreen(11, 0)).toEqual(false) + }) + + it('should call the tab getter', () => { + expect(model.tab).toEqual({ + fixedTabPurpose: 0, + history: [ + { + id: expect.anything(), + ts: expect.anything(), + url: '/', + }, + ], + id: expect.anything(), + index: 0, + isNewTab: false, + }) + }) + + it('should call the tabCount getter', () => { + expect(model.tabCount).toBe(2) + }) + + describe('tabs not enabled', () => { + jest.mock('../../../src/build-flags', () => ({ + TABS_ENABLED: false, + })) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should not create new tabs', () => { + // @ts-expect-error + flags.TABS_ENABLED = false + model.newTab('testurl') + expect(model.tab.isNewTab).toBe(false) + expect(model.tabIndex).toBe(0) + }) + + it('should not change the active tab', () => { + // @ts-expect-error + flags.TABS_ENABLED = false + model.setActiveTab(2) + expect(model.tabIndex).toBe(0) + }) + + it('should note close tabs', () => { + // @ts-expect-error + flags.TABS_ENABLED = false + model.closeTab(0) + expect(model.tabCount).toBe(2) + }) + }) + + describe('tabs enabled', () => { + jest.mock('../../../src/build-flags', () => ({ + TABS_ENABLED: true, + })) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should create new tabs', () => { + // @ts-expect-error + flags.TABS_ENABLED = true + + model.newTab('testurl', 'title') + expect(model.tab.isNewTab).toBe(true) + expect(model.tabIndex).toBe(2) + }) + + it('should change the current tab', () => { + // @ts-expect-error + flags.TABS_ENABLED = true + + model.setActiveTab(0) + expect(model.tabIndex).toBe(0) + }) + + it('should close tabs', () => { + // @ts-expect-error + flags.TABS_ENABLED = true + + model.closeTab(0) + expect(model.tabs).toEqual([ + { + fixedTabPurpose: 1, + history: [ + { + id: expect.anything(), + ts: expect.anything(), + url: '/notifications', + }, + ], + id: expect.anything(), + index: 0, + isNewTab: false, + }, + ]) + expect(model.tabIndex).toBe(0) + }) + }) +}) diff --git a/__tests__/state/models/onboard.test.ts b/__tests__/state/models/onboard.test.ts new file mode 100644 index 000000000..02ee0feb6 --- /dev/null +++ b/__tests__/state/models/onboard.test.ts @@ -0,0 +1,46 @@ +import { + OnboardModel, + OnboardStageOrder, +} from '../../../src/state/models/onboard' + +describe('OnboardModel', () => { + let onboardModel: OnboardModel + + beforeEach(() => { + onboardModel = new OnboardModel() + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should start/stop correctly', () => { + onboardModel.start() + expect(onboardModel.isOnboarding).toBe(true) + onboardModel.stop() + expect(onboardModel.isOnboarding).toBe(false) + }) + + it('should call the next method until it has no more stages', () => { + onboardModel.start() + onboardModel.next() + expect(onboardModel.stage).toBe(OnboardStageOrder[1]) + + onboardModel.next() + expect(onboardModel.isOnboarding).toBe(false) + expect(onboardModel.stage).toBe(OnboardStageOrder[0]) + }) + + it('serialize and hydrate', () => { + const serialized = onboardModel.serialize() + const newModel = new OnboardModel() + newModel.hydrate(serialized) + expect(newModel).toEqual(onboardModel) + + onboardModel.start() + onboardModel.next() + const serialized2 = onboardModel.serialize() + newModel.hydrate(serialized2) + expect(newModel).toEqual(onboardModel) + }) +}) diff --git a/__tests__/state/models/root-store.test.ts b/__tests__/state/models/root-store.test.ts new file mode 100644 index 000000000..ccaa6f83f --- /dev/null +++ b/__tests__/state/models/root-store.test.ts @@ -0,0 +1,73 @@ +import {RootStoreModel} from '../../../src/state/models/root-store' +import {setupState} from '../../../src/state' + +describe('rootStore', () => { + let rootStore: RootStoreModel + + beforeAll(() => { + jest.useFakeTimers() + }) + + beforeEach(async () => { + rootStore = await setupState() + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('resolveName() handles inputs correctly', () => { + const spyMethod = jest + .spyOn(rootStore.api.com.atproto.handle, 'resolve') + .mockResolvedValue({success: true, headers: {}, data: {did: 'testdid'}}) + + rootStore.resolveName('teststring') + expect(spyMethod).toHaveBeenCalledWith({handle: 'teststring'}) + + expect(rootStore.resolveName('')).rejects.toThrow('Invalid handle: ""') + + expect(rootStore.resolveName('did:123')).resolves.toReturnWith('did:123') + }) + + it('should call the clearAll() resets state correctly', () => { + rootStore.clearAll() + + expect(rootStore.session.data).toEqual(null) + expect(rootStore.nav.tabs).toEqual([ + { + fixedTabPurpose: 0, + history: [ + { + id: expect.anything(), + ts: expect.anything(), + url: '/', + }, + ], + id: expect.anything(), + index: 0, + isNewTab: false, + }, + { + fixedTabPurpose: 1, + history: [ + { + id: expect.anything(), + ts: expect.anything(), + url: '/notifications', + }, + ], + id: expect.anything(), + index: 0, + isNewTab: false, + }, + ]) + expect(rootStore.nav.tabIndex).toEqual(0) + expect(rootStore.me.did).toEqual('') + expect(rootStore.me.handle).toEqual('') + expect(rootStore.me.displayName).toEqual('') + expect(rootStore.me.description).toEqual('') + expect(rootStore.me.avatar).toEqual('') + expect(rootStore.me.notificationCount).toEqual(0) + expect(rootStore.me.memberships).toBeUndefined() + }) +}) diff --git a/__tests__/state/models/shell-ui.test.ts b/__tests__/state/models/shell-ui.test.ts new file mode 100644 index 000000000..8324609a1 --- /dev/null +++ b/__tests__/state/models/shell-ui.test.ts @@ -0,0 +1,59 @@ +import { + ConfirmModal, + ImageLightbox, + ShellUiModel, +} from './../../../src/state/models/shell-ui' + +describe('ShellUiModel', () => { + let model: ShellUiModel + + beforeEach(() => { + model = new ShellUiModel() + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it('should call the openModal & closeModal method', () => { + model.openModal(ConfirmModal) + expect(model.isModalActive).toEqual(true) + expect(model.activeModal).toEqual(ConfirmModal) + + model.closeModal() + expect(model.isModalActive).toEqual(false) + expect(model.activeModal).toBeUndefined() + }) + + it('should call the openLightbox & closeLightbox method', () => { + model.openLightbox(new ImageLightbox('uri')) + expect(model.isLightboxActive).toEqual(true) + expect(model.activeLightbox).toEqual(new ImageLightbox('uri')) + + model.closeLightbox() + expect(model.isLightboxActive).toEqual(false) + expect(model.activeLightbox).toBeUndefined() + }) + + it('should call the openComposer & closeComposer method', () => { + const composer = { + replyTo: { + uri: 'uri', + cid: 'cid', + text: 'text', + author: { + handle: 'handle', + displayName: 'name', + }, + }, + onPost: jest.fn(), + } + model.openComposer(composer) + expect(model.isComposerActive).toEqual(true) + expect(model.composerOpts).toEqual(composer) + + model.closeComposer() + expect(model.isComposerActive).toEqual(false) + expect(model.composerOpts).toBeUndefined() + }) +}) diff --git a/__tests__/view/com/composer/Autocomplete.test.tsx b/__tests__/view/com/composer/Autocomplete.test.tsx new file mode 100644 index 000000000..57539b75e --- /dev/null +++ b/__tests__/view/com/composer/Autocomplete.test.tsx @@ -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() + const autocompleteButton = await findAllByTestId('autocompleteButton') + expect(autocompleteButton.length).toBe(2) + }) + + it('triggers onSelect by pressing the button', async () => { + const {findAllByTestId} = render() + const autocompleteButton = await findAllByTestId('autocompleteButton') + + fireEvent.press(autocompleteButton[0]) + expect(onSelectMock).toHaveBeenCalledWith('handle.test') + + fireEvent.press(autocompleteButton[1]) + expect(onSelectMock).toHaveBeenCalledWith('handle2.test') + }) +}) diff --git a/__tests__/view/com/composer/ComposePost.test.tsx b/__tests__/view/com/composer/ComposePost.test.tsx new file mode 100644 index 000000000..84377f62f --- /dev/null +++ b/__tests__/view/com/composer/ComposePost.test.tsx @@ -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() + const composePostView = await findByTestId('composePostView') + expect(composePostView).toBeTruthy() + }) + + it('closes composer', async () => { + const {findByTestId} = render() + 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() + + 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(), + 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() + const autocompleteButton = await findAllByTestId('autocompleteButton') + + fireEvent.press(autocompleteButton[0]) + expect(mockedAutocompleteViewStore.setActive).toHaveBeenCalledWith(false) + }) + + it('selects photos', async () => { + const {findByTestId, queryByTestId} = render( + , + ) + 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() + }) +}) diff --git a/__tests__/view/com/composer/PhotoCarouselPicker.test.tsx b/__tests__/view/com/composer/PhotoCarouselPicker.test.tsx new file mode 100644 index 000000000..ef8477652 --- /dev/null +++ b/__tests__/view/com/composer/PhotoCarouselPicker.test.tsx @@ -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() + const photoCarouselPickerView = await findByTestId( + 'photoCarouselPickerView', + ) + expect(photoCarouselPickerView).toBeTruthy() + }) + + it('triggers openCamera', async () => { + const {findByTestId} = render() + 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() + 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() + 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, + }) + }) +}) diff --git a/__tests__/view/com/composer/SelectedPhoto.test.tsx b/__tests__/view/com/composer/SelectedPhoto.test.tsx new file mode 100644 index 000000000..26059ae30 --- /dev/null +++ b/__tests__/view/com/composer/SelectedPhoto.test.tsx @@ -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( + , + ) + const selectedPhotosView = queryByTestId('selectedPhotosView') + expect(selectedPhotosView).toBeNull() + + const selectedPhotoImage = queryByTestId('selectedPhotoImage') + expect(selectedPhotoImage).toBeNull() + }) + + it('has 1 photos to render', async () => { + const {findByTestId} = render( + , + ) + 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() + 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( + , + ) + 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() + const removePhotoButton = await findAllByTestId('removePhotoButton') + fireEvent.press(removePhotoButton[0]) + expect(mockedProps.onSelectPhotos).toHaveBeenCalledWith(['mock-uri-2']) + }) +}) diff --git a/__tests__/view/com/login/CreateAccount.test.tsx b/__tests__/view/com/login/CreateAccount.test.tsx new file mode 100644 index 000000000..2de99b2a3 --- /dev/null +++ b/__tests__/view/com/login/CreateAccount.test.tsx @@ -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() + + 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() + + const registerSelectServiceButton = await findByTestId( + 'registerSelectServiceButton', + ) + expect(registerSelectServiceButton).toBeTruthy() + fireEvent.press(registerSelectServiceButton) + + expect(mockedShellStore.openModal).toHaveBeenCalled() + expect(keyboardSpy).toHaveBeenCalled() + }) +}) diff --git a/__tests__/view/com/login/Signin.test.tsx b/__tests__/view/com/login/Signin.test.tsx new file mode 100644 index 000000000..51b411836 --- /dev/null +++ b/__tests__/view/com/login/Signin.test.tsx @@ -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() + + 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() + + 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() + + 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() + + 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() + }) +}) diff --git a/__tests__/view/com/profile/ProfileHeader.test.tsx b/__tests__/view/com/profile/ProfileHeader.test.tsx new file mode 100644 index 000000000..52b04e649 --- /dev/null +++ b/__tests__/view/com/profile/ProfileHeader.test.tsx @@ -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( + , + ) + + const profileHeaderHasError = await findByTestId('profileHeaderHasError') + expect(profileHeaderHasError).toBeTruthy() + }) + + it('presses and opens edit profile', async () => { + const {findByTestId} = render() + + 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() + + 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() + + 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() + + 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( + , + ) + + const profileHeaderToggleFollowButton = await findByTestId( + 'profileHeaderToggleFollowButton', + ) + expect(profileHeaderToggleFollowButton).toBeTruthy() + fireEvent.press(profileHeaderToggleFollowButton) + + expect(mockedProps.view.toggleFollowing).toHaveBeenCalled() + }) +}) diff --git a/__tests__/view/lib/useAnimatedValue.test.tsx b/__tests__/view/lib/useAnimatedValue.test.tsx new file mode 100644 index 000000000..762dcc8f2 --- /dev/null +++ b/__tests__/view/lib/useAnimatedValue.test.tsx @@ -0,0 +1,17 @@ +import {renderHook} from '../../../jest/test-utils' +import {useAnimatedValue} from '../../../src/view/lib/hooks/useAnimatedValue' + +describe('useAnimatedValue', () => { + it('creates an Animated.Value with the initial value passed to the hook', () => { + const {result} = renderHook(() => useAnimatedValue(10)) + // @ts-expect-error + expect(result.current.__getValue()).toEqual(10) + }) + + it('returns the same Animated.Value instance on subsequent renders', () => { + const {result, rerender} = renderHook(() => useAnimatedValue(10)) + const firstValue = result.current + rerender({}) + expect(result.current).toBe(firstValue) + }) +}) diff --git a/__tests__/view/lib/useOnMainScroll.test.tsx b/__tests__/view/lib/useOnMainScroll.test.tsx new file mode 100644 index 000000000..9a31e79e8 --- /dev/null +++ b/__tests__/view/lib/useOnMainScroll.test.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import {fireEvent, render} from '../../../jest/test-utils' +import {Home} from '../../../src/view/screens/Home' +import {mockedRootStore, mockedShellStore} from '../../../__mocks__/state-mock' + +describe('useOnMainScroll', () => { + const mockedProps = { + navIdx: [0, 0] as [number, number], + params: {}, + visible: true, + } + + it('toggles minimalShellMode to true', () => { + jest.useFakeTimers() + const {getByTestId} = render() + + fireEvent.scroll(getByTestId('homeFeed'), { + nativeEvent: { + contentOffset: {y: 20}, + contentSize: {height: 100}, + layoutMeasurement: {height: 50}, + }, + }) + + expect(mockedRootStore.shell.setMinimalShellMode).toHaveBeenCalledWith(true) + }) + + it('toggles minimalShellMode to false', () => { + jest.useFakeTimers() + const {getByTestId} = render(, { + ...mockedRootStore, + shell: { + ...mockedShellStore, + minimalShellMode: true, + }, + }) + + fireEvent.scroll(getByTestId('homeFeed'), { + nativeEvent: { + contentOffset: {y: 0}, + contentSize: {height: 100}, + layoutMeasurement: {height: 50}, + }, + }) + expect(mockedRootStore.shell.setMinimalShellMode).toHaveBeenCalledWith( + false, + ) + }) +}) diff --git a/__tests__/view/screens/Contacts.test.tsx b/__tests__/view/screens/Contacts.test.tsx deleted file mode 100644 index 8dc4e56ef..000000000 --- a/__tests__/view/screens/Contacts.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import {Contacts} from '../../../src/view/screens/Contacts' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('Contacts', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: {}, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/Home.test.tsx b/__tests__/view/screens/Home.test.tsx deleted file mode 100644 index 353d4ea50..000000000 --- a/__tests__/view/screens/Home.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import {Home} from '../../../src/view/screens/Home' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('Home', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: {}, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/Login.test.tsx b/__tests__/view/screens/Login.test.tsx index d9faf08a1..e347534b4 100644 --- a/__tests__/view/screens/Login.test.tsx +++ b/__tests__/view/screens/Login.test.tsx @@ -1,11 +1,37 @@ import React from 'react' import {Login} from '../../../src/view/screens/Login' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' +import {cleanup, fireEvent, render} from '../../../jest/test-utils' describe('Login', () => { - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() + afterAll(() => { + jest.clearAllMocks() + cleanup() + }) + + it('renders initial screen', () => { + const {getByTestId} = render() + const signUpScreen = getByTestId('signinOrCreateAccount') + + expect(signUpScreen).toBeTruthy() + }) + + it('renders Signin screen', () => { + const {getByTestId} = render() + const signInButton = getByTestId('signInButton') + + fireEvent.press(signInButton) + + const signInScreen = getByTestId('signIn') + expect(signInScreen).toBeTruthy() + }) + + it('renders CreateAccount screen', () => { + const {getByTestId} = render() + const createAccountButton = getByTestId('createAccountButton') + + fireEvent.press(createAccountButton) + + const createAccountScreen = getByTestId('createAccount') + expect(createAccountScreen).toBeTruthy() }) }) diff --git a/__tests__/view/screens/NotFound.test.tsx b/__tests__/view/screens/NotFound.test.tsx index 047d309e3..fd3c84b07 100644 --- a/__tests__/view/screens/NotFound.test.tsx +++ b/__tests__/view/screens/NotFound.test.tsx @@ -1,11 +1,21 @@ import React from 'react' import {NotFound} from '../../../src/view/screens/NotFound' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' +import {cleanup, fireEvent, render} from '../../../jest/test-utils' +import {mockedNavigationStore} from '../../../__mocks__/state-mock' describe('NotFound', () => { - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() + afterAll(() => { + jest.clearAllMocks() + cleanup() + }) + + it('navigates home', async () => { + const navigationSpy = jest.spyOn(mockedNavigationStore, 'navigate') + const {getByTestId} = render() + const navigateHomeButton = getByTestId('navigateHomeButton') + + fireEvent.press(navigateHomeButton) + + expect(navigationSpy).toHaveBeenCalledWith('/') }) }) diff --git a/__tests__/view/screens/Notifications.test.tsx b/__tests__/view/screens/Notifications.test.tsx deleted file mode 100644 index 2c5e32cd7..000000000 --- a/__tests__/view/screens/Notifications.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import {Notifications} from '../../../src/view/screens/Notifications' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('Notifications', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: {}, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/Onboard.test.tsx b/__tests__/view/screens/Onboard.test.tsx deleted file mode 100644 index 69d6f0a72..000000000 --- a/__tests__/view/screens/Onboard.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import {Onboard} from '../../../src/view/screens/Onboard' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('Onboard', () => { - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/PostDownvotedBy.test.tsx b/__tests__/view/screens/PostDownvotedBy.test.tsx deleted file mode 100644 index 8c4119b41..000000000 --- a/__tests__/view/screens/PostDownvotedBy.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import {PostDownvotedBy} from '../../../src/view/screens/PostDownvotedBy' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('PostDownvotedBy', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - rkey: '123123123', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/PostRepostedBy.test.tsx b/__tests__/view/screens/PostRepostedBy.test.tsx deleted file mode 100644 index 001224356..000000000 --- a/__tests__/view/screens/PostRepostedBy.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import {PostRepostedBy} from '../../../src/view/screens/PostRepostedBy' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('PostRepostedBy', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - rkey: '123123123', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/PostThread.test.tsx b/__tests__/view/screens/PostThread.test.tsx deleted file mode 100644 index 87164ed73..000000000 --- a/__tests__/view/screens/PostThread.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import {PostThread} from '../../../src/view/screens/PostThread' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('PostThread', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - rkey: '123123123', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/PostUpvotedBy.test.tsx b/__tests__/view/screens/PostUpvotedBy.test.tsx deleted file mode 100644 index 97912ded6..000000000 --- a/__tests__/view/screens/PostUpvotedBy.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import {PostUpvotedBy} from '../../../src/view/screens/PostUpvotedBy' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('PostUpvotedBy', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - rkey: '123123123', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/Profile.test.tsx b/__tests__/view/screens/Profile.test.tsx deleted file mode 100644 index 8912cbfb2..000000000 --- a/__tests__/view/screens/Profile.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import {Profile} from '../../../src/view/screens/Profile' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('Profile', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - user: 'test.user', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/ProfileFollowers.test.tsx b/__tests__/view/screens/ProfileFollowers.test.tsx deleted file mode 100644 index 230209aa8..000000000 --- a/__tests__/view/screens/ProfileFollowers.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import {ProfileFollowers} from '../../../src/view/screens/ProfileFollowers' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('ProfileFollowers', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/ProfileFollows.test.tsx b/__tests__/view/screens/ProfileFollows.test.tsx deleted file mode 100644 index e4571b5cb..000000000 --- a/__tests__/view/screens/ProfileFollows.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import {ProfileFollows} from '../../../src/view/screens/ProfileFollows' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('ProfileFollows', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/ProfileMembers.test.tsx b/__tests__/view/screens/ProfileMembers.test.tsx deleted file mode 100644 index a33e03a1f..000000000 --- a/__tests__/view/screens/ProfileMembers.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import {ProfileMembers} from '../../../src/view/screens/ProfileMembers' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('ProfileMembers', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: { - name: 'test name', - }, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/Search.test.tsx b/__tests__/view/screens/Search.test.tsx index 477e077af..f769c7a58 100644 --- a/__tests__/view/screens/Search.test.tsx +++ b/__tests__/view/screens/Search.test.tsx @@ -1,9 +1,9 @@ import React from 'react' import {Search} from '../../../src/view/screens/Search' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' +import {cleanup, fireEvent, render} from '../../../jest/test-utils' describe('Search', () => { + jest.useFakeTimers() const mockedProps = { navIdx: [0, 0] as [number, number], params: { @@ -11,8 +11,20 @@ describe('Search', () => { }, visible: true, } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() + + afterAll(() => { + jest.clearAllMocks() + cleanup() + }) + + it('renders with query', async () => { + const {findByTestId} = render() + const searchTextInput = await findByTestId('searchTextInput') + + expect(searchTextInput).toBeTruthy() + fireEvent.changeText(searchTextInput, 'test') + + const searchScrollView = await findByTestId('searchScrollView') + expect(searchScrollView).toBeTruthy() }) }) diff --git a/__tests__/view/screens/Settings.test.tsx b/__tests__/view/screens/Settings.test.tsx deleted file mode 100644 index 475639ebb..000000000 --- a/__tests__/view/screens/Settings.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import {Settings} from '../../../src/view/screens/Settings' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('Settings', () => { - const mockedProps = { - navIdx: [0, 0] as [number, number], - params: {}, - visible: true, - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/screens/__snapshots__/Contacts.test.tsx.snap b/__tests__/view/screens/__snapshots__/Contacts.test.tsx.snap deleted file mode 100644 index 61a857088..000000000 --- a/__tests__/view/screens/__snapshots__/Contacts.test.tsx.snap +++ /dev/null @@ -1,205 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Contacts renders correctly 1`] = ` - - - - Contacts - - - - - < - icon="magnifying-glass" - size={16} - style={ - Object { - "color": "#645454", - "marginRight": 8, - } - } - /> - - - - - - - - All - - - - - Following - - - - - Scenes - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/Home.test.tsx.snap b/__tests__/view/screens/__snapshots__/Home.test.tsx.snap deleted file mode 100644 index 4d2c51097..000000000 --- a/__tests__/view/screens/__snapshots__/Home.test.tsx.snap +++ /dev/null @@ -1,594 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Home renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Bluesky - - - Private Beta - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - - - - - - - - - - - - - - - What's up? - - - - - Post - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/Login.test.tsx.snap b/__tests__/view/screens/__snapshots__/Login.test.tsx.snap deleted file mode 100644 index b86d8656e..000000000 --- a/__tests__/view/screens/__snapshots__/Login.test.tsx.snap +++ /dev/null @@ -1,371 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Login renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Bluesky - - - [ private beta ] - - - - - - Create a new account - - - - - - - - - - - or - - - - - Sign in - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/NotFound.test.tsx.snap b/__tests__/view/screens/__snapshots__/NotFound.test.tsx.snap deleted file mode 100644 index a9365718c..000000000 --- a/__tests__/view/screens/__snapshots__/NotFound.test.tsx.snap +++ /dev/null @@ -1,431 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NotFound renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Page not found - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - Page not found - - - - - Home - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/Notifications.test.tsx.snap b/__tests__/view/screens/__snapshots__/Notifications.test.tsx.snap deleted file mode 100644 index 6c1eef57e..000000000 --- a/__tests__/view/screens/__snapshots__/Notifications.test.tsx.snap +++ /dev/null @@ -1,378 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Notifications renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Notifications - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/Onboard.test.tsx.snap b/__tests__/view/screens/__snapshots__/Onboard.test.tsx.snap deleted file mode 100644 index 5422fb0de..000000000 --- a/__tests__/view/screens/__snapshots__/Onboard.test.tsx.snap +++ /dev/null @@ -1,388 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Onboard renders correctly 1`] = ` - - - - - - - - - Welcome to - - Bluesky - - - - Let's do a quick tour through the new features. - - - - - - - - - - - - - ° - - - - - ° - - - - - - - - - Skip - - - - - - Next - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/PostDownvotedBy.test.tsx.snap b/__tests__/view/screens/__snapshots__/PostDownvotedBy.test.tsx.snap deleted file mode 100644 index aa41d7fb2..000000000 --- a/__tests__/view/screens/__snapshots__/PostDownvotedBy.test.tsx.snap +++ /dev/null @@ -1,368 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PostDownvotedBy renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Downvoted by - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/PostRepostedBy.test.tsx.snap b/__tests__/view/screens/__snapshots__/PostRepostedBy.test.tsx.snap deleted file mode 100644 index f6af5ec5a..000000000 --- a/__tests__/view/screens/__snapshots__/PostRepostedBy.test.tsx.snap +++ /dev/null @@ -1,368 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PostRepostedBy renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Reposted by - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/PostThread.test.tsx.snap b/__tests__/view/screens/__snapshots__/PostThread.test.tsx.snap deleted file mode 100644 index abb36931c..000000000 --- a/__tests__/view/screens/__snapshots__/PostThread.test.tsx.snap +++ /dev/null @@ -1,437 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PostThread renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Post - - - by test name - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - } - refreshing={false} - removeClippedSubviews={false} - renderItem={[Function]} - scrollEventThrottle={50} - stickyHeaderIndices={Array []} - style={ - Object { - "flex": 1, - } - } - viewabilityConfigCallbackPairs={Array []} - > - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/PostUpvotedBy.test.tsx.snap b/__tests__/view/screens/__snapshots__/PostUpvotedBy.test.tsx.snap deleted file mode 100644 index a7bb6aae5..000000000 --- a/__tests__/view/screens/__snapshots__/PostUpvotedBy.test.tsx.snap +++ /dev/null @@ -1,368 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PostUpvotedBy renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Upvoted by - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/Profile.test.tsx.snap b/__tests__/view/screens/__snapshots__/Profile.test.tsx.snap deleted file mode 100644 index e9640b6ee..000000000 --- a/__tests__/view/screens/__snapshots__/Profile.test.tsx.snap +++ /dev/null @@ -1,513 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Profile renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - test name - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - - - - - - - - - - - - - - - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/ProfileFollowers.test.tsx.snap b/__tests__/view/screens/__snapshots__/ProfileFollowers.test.tsx.snap deleted file mode 100644 index 237773b42..000000000 --- a/__tests__/view/screens/__snapshots__/ProfileFollowers.test.tsx.snap +++ /dev/null @@ -1,386 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProfileFollowers renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Followers - - - of test name - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/ProfileFollows.test.tsx.snap b/__tests__/view/screens/__snapshots__/ProfileFollows.test.tsx.snap deleted file mode 100644 index cba1a7343..000000000 --- a/__tests__/view/screens/__snapshots__/ProfileFollows.test.tsx.snap +++ /dev/null @@ -1,386 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProfileFollows renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Followed - - - by test name - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/ProfileMembers.test.tsx.snap b/__tests__/view/screens/__snapshots__/ProfileMembers.test.tsx.snap deleted file mode 100644 index e36a4b080..000000000 --- a/__tests__/view/screens/__snapshots__/ProfileMembers.test.tsx.snap +++ /dev/null @@ -1,386 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProfileMembers renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Members - - - of test name - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/Search.test.tsx.snap b/__tests__/view/screens/__snapshots__/Search.test.tsx.snap deleted file mode 100644 index 130552076..000000000 --- a/__tests__/view/screens/__snapshots__/Search.test.tsx.snap +++ /dev/null @@ -1,514 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Search renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Search - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - - - - - - - - - - - - - - - -`; diff --git a/__tests__/view/screens/__snapshots__/Settings.test.tsx.snap b/__tests__/view/screens/__snapshots__/Settings.test.tsx.snap deleted file mode 100644 index 77402da21..000000000 --- a/__tests__/view/screens/__snapshots__/Settings.test.tsx.snap +++ /dev/null @@ -1,631 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Settings renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - Settings - - - - < - icon="plus" - size={18} - /> - - - - - - - - - - < - icon="signal" - size={18} - style={ - Array [ - Object { - "color": "#000000", - }, - ] - } - /> - < - icon="x" - size={12} - style={ - Object { - "backgroundColor": "#ffffff", - "color": "#d1106f", - "left": -4, - "position": "relative", - "top": 6, - } - } - /> - - - - - - Signed in as - - - - - Sign out - - - - - - - - - - - - - - - - - - - - - - @ - - - - - - - -`; diff --git a/__tests__/view/shell/mobile/Composer.test.tsx b/__tests__/view/shell/mobile/Composer.test.tsx deleted file mode 100644 index 7b84cfd88..000000000 --- a/__tests__/view/shell/mobile/Composer.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import {Composer} from '../../../../src/view/shell/mobile/Composer' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' - -describe('Composer', () => { - const mockedProps = { - active: true, - winHeight: 844, - replyTo: { - author: {avatar: undefined, displayName: 'Alice', handle: 'alice.test'}, - cid: 'bafyreieucrv36ylxrut4dr4jj264q2jj2vt2vfvhjfchgw3vua4gksvzia', - text: 'Captain, maybe we ought to turn on the searchlights now. No… that’s just what they’ll be expecting us to do.', - uri: 'at://did:plc:v3xz273ea2dzjpu2szsjzfue/app.bsky.feed.post/3jkcir3fhqv2u', - }, - onPost: jest.fn(), - onClose: jest.fn(), - } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() - }) -}) diff --git a/__tests__/view/shell/mobile/Menu.test.tsx b/__tests__/view/shell/mobile/Menu.test.tsx index 5305bd77a..5eee3f125 100644 --- a/__tests__/view/shell/mobile/Menu.test.tsx +++ b/__tests__/view/shell/mobile/Menu.test.tsx @@ -1,15 +1,70 @@ import React from 'react' import {Menu} from '../../../../src/view/shell/mobile/Menu' -import renderer from 'react-test-renderer' -// import {render} from '../../../../jest/test-utils' +import {cleanup, fireEvent, render} from '../../../../jest/test-utils' +import { + mockedNavigationStore, + mockedShellStore, +} from '../../../../__mocks__/state-mock' describe('Menu', () => { + const onCloseMock = jest.fn() + const mockedProps = { visible: true, - onClose: jest.fn(), + onClose: onCloseMock, } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() + + afterAll(() => { + jest.clearAllMocks() + cleanup() + }) + + it('renders menu', () => { + const {getByTestId} = render() + + const menuView = getByTestId('menuView') + + expect(menuView).toBeTruthy() + }) + + it('presses profile card button', () => { + const {getByTestId} = render() + + const profileCardButton = getByTestId('profileCardButton') + fireEvent.press(profileCardButton) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.switchTo).toHaveBeenCalledWith(0, true) + }) + + it('presses search button', () => { + const {getByTestId} = render() + + const searchBtn = getByTestId('searchBtn') + fireEvent.press(searchBtn) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.switchTo).toHaveBeenCalledWith(0, true) + expect(mockedNavigationStore.navigate).toHaveBeenCalledWith('/search') + }) + + it("presses notifications menu item' button", () => { + const {getAllByTestId} = render() + + const menuItemButton = getAllByTestId('menuItemButton') + fireEvent.press(menuItemButton[1]) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.switchTo).toHaveBeenCalledWith(1, true) + }) + + it('presses new scene button', () => { + const {getAllByTestId} = render() + + const menuItemButton = getAllByTestId('menuItemButton') + fireEvent.press(menuItemButton[3]) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedShellStore.openModal).toHaveBeenCalled() }) }) diff --git a/__tests__/view/shell/mobile/TabsSelector.test.tsx b/__tests__/view/shell/mobile/TabsSelector.test.tsx index 7908f442e..9388d2440 100644 --- a/__tests__/view/shell/mobile/TabsSelector.test.tsx +++ b/__tests__/view/shell/mobile/TabsSelector.test.tsx @@ -1,17 +1,99 @@ import React from 'react' -import {Animated} from 'react-native' -import renderer from 'react-test-renderer' +import {Animated, Share} from 'react-native' import {TabsSelector} from '../../../../src/view/shell/mobile/TabsSelector' -// import {render} from '../../../../jest/test-utils' +import {cleanup, fireEvent, render} from '../../../../jest/test-utils' +import {mockedNavigationStore} from '../../../../__mocks__/state-mock' describe('TabsSelector', () => { + const onCloseMock = jest.fn() + const mockedProps = { active: true, tabMenuInterp: new Animated.Value(0), - onClose: jest.fn(), + onClose: onCloseMock, } - it('renders correctly', () => { - const tree = renderer.create().toJSON() - expect(tree).toMatchSnapshot() + + afterAll(() => { + jest.clearAllMocks() + cleanup() + }) + + it('renders tabs selector', () => { + const {getByTestId} = render() + + const tabsSelectorView = getByTestId('tabsSelectorView') + + expect(tabsSelectorView).toBeTruthy() + }) + + it('renders nothing if inactive', () => { + const {getByTestId} = render( + , + ) + + const emptyView = getByTestId('emptyView') + + expect(emptyView).toBeTruthy() + }) + + it('presses share button', () => { + const shareSpy = jest.spyOn(Share, 'share') + const {getByTestId} = render() + + const shareButton = getByTestId('shareButton') + fireEvent.press(shareButton) + + expect(onCloseMock).toHaveBeenCalled() + expect(shareSpy).toHaveBeenCalledWith({url: 'https://bsky.app/'}) + }) + + it('presses clone button', () => { + const {getByTestId} = render() + + const cloneButton = getByTestId('cloneButton') + fireEvent.press(cloneButton) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.newTab).toHaveBeenCalled() + }) + + it('presses new tab button', () => { + const {getByTestId} = render() + + const newTabButton = getByTestId('newTabButton') + fireEvent.press(newTabButton) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.newTab).toHaveBeenCalledWith('/') + }) + + it('presses change tab button', () => { + const {getAllByTestId} = render() + + const changeTabButton = getAllByTestId('changeTabButton') + fireEvent.press(changeTabButton[0]) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.newTab).toHaveBeenCalledWith('/') + }) + + it('presses close tab button', () => { + const {getAllByTestId} = render() + + const closeTabButton = getAllByTestId('closeTabButton') + fireEvent.press(closeTabButton[0]) + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.setActiveTab).toHaveBeenCalledWith(0) + }) + + it('presses swipes to close the tab', () => { + const {getByTestId} = render() + + const tabsSwipable = getByTestId('tabsSwipable') + fireEvent(tabsSwipable, 'swipeableRightOpen') + + expect(onCloseMock).toHaveBeenCalled() + expect(mockedNavigationStore.setActiveTab).toHaveBeenCalledWith(0) }) }) diff --git a/__tests__/view/shell/mobile/__snapshots__/Composer.test.tsx.snap b/__tests__/view/shell/mobile/__snapshots__/Composer.test.tsx.snap deleted file mode 100644 index 6ced9871b..000000000 --- a/__tests__/view/shell/mobile/__snapshots__/Composer.test.tsx.snap +++ /dev/null @@ -1,659 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Composer renders correctly 1`] = ` - - - - - - - Cancel - - - - - - - Reply - - - - - - - - - - - - - - - - - - - - - Alice - - - Captain, maybe we ought to turn on the searchlights now. No… that’s just what they’ll be expecting us to do. - - - - - - - - - - - - - - - - - - - - - - < - icon={ - Array [ - "far", - "image", - ] - } - size={24} - style={ - Object { - "color": "#0085ff", - } - } - /> - - - - 256 - - - - - - - - - - - - - - - - -`; diff --git a/__tests__/view/shell/mobile/__snapshots__/Menu.test.tsx.snap b/__tests__/view/shell/mobile/__snapshots__/Menu.test.tsx.snap deleted file mode 100644 index 78c34b967..000000000 --- a/__tests__/view/shell/mobile/__snapshots__/Menu.test.tsx.snap +++ /dev/null @@ -1,837 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Menu renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Search - - - - - - - - - - - - - Home - - - - - - - - - - - - - Notifications - - - - - - Scenes - - - - - - - - - - - - - Create a scene - - - - - - - - - - - - - Settings - - - - - - Build version - ( - ) - - - -`; diff --git a/__tests__/view/shell/mobile/__snapshots__/TabsSelector.test.tsx.snap b/__tests__/view/shell/mobile/__snapshots__/TabsSelector.test.tsx.snap deleted file mode 100644 index 03e0636de..000000000 --- a/__tests__/view/shell/mobile/__snapshots__/TabsSelector.test.tsx.snap +++ /dev/null @@ -1,651 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TabsSelector renders correctly 1`] = ` - - - - - - - < - icon="share" - size={16} - /> - - - Share - - - - - < - icon={ - Array [ - "far", - "clone", - ] - } - size={16} - /> - - - Clone tab - - - - - < - icon="plus" - size={16} - /> - - - New tab - - - - - - - - - - - - - - - - - - - - - - < - icon="house" - size={20} - /> - - - / - - - - < - icon="x" - size={14} - style={ - Object { - "color": "#655", - } - } - /> - - - - - - - - - - - - - - - - - - - - < - icon="bell" - size={20} - /> - - - /notifications - - - - < - icon="x" - size={14} - style={ - Object { - "color": "#655", - } - } - /> - - - - - - - - - - -`; diff --git a/__tests__/view/shell/mobile/__snapshots__/index.test.tsx.snap b/__tests__/view/shell/mobile/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 793668b73..000000000 --- a/__tests__/view/shell/mobile/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,421 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MobileShell renders correctly 1`] = ` - - - - - - - - - - - - - - - - - - - - Bluesky - - - [ private beta ] - - - - - - Create a new account - - - - - - - - - - - or - - - - - Sign in - - - - - - - - - -`; diff --git a/__tests__/view/shell/mobile/index.test.tsx b/__tests__/view/shell/mobile/index.test.tsx deleted file mode 100644 index 96f161260..000000000 --- a/__tests__/view/shell/mobile/index.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import {MobileShell} from '../../../../src/view/shell/mobile' -import renderer from 'react-test-renderer' -import {SafeAreaProvider} from 'react-native-safe-area-context' -// import {render} from '../../../../jest/test-utils' - -describe('MobileShell', () => { - it('renders correctly', () => { - const tree = renderer - .create( - - - , - ) - .toJSON() - expect(tree).toMatchSnapshot() - }) -}) -- cgit 1.4.1