From 5abcc8e336b3af11a6c98d0d9e662415856478a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferreiro?= Date: Tue, 17 Jan 2023 16:06:00 +0000 Subject: [PATCH] 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 --- .../@react-native-camera-roll/camera-roll.js | 10 +- __mocks__/react-native-image-crop-picker.js | 12 +- __mocks__/state-mock.ts | 934 ++++++++++++++++++ __tests__/lib/images.test.ts | 18 +- .../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 ++ .../view/com/composer/Autocomplete.test.tsx | 43 + .../view/com/composer/ComposePost.test.tsx | 117 +++ .../com/composer/PhotoCarouselPicker.test.tsx | 92 ++ .../view/com/composer/SelectedPhoto.test.tsx | 70 ++ .../view/com/login/CreateAccount.test.tsx | 60 ++ __tests__/view/com/login/Signin.test.tsx | 128 +++ .../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 - .../view/screens/PostDownvotedBy.test.tsx | 19 - .../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 - .../view/screens/ProfileFollowers.test.tsx | 18 - .../view/screens/ProfileFollows.test.tsx | 18 - .../view/screens/ProfileMembers.test.tsx | 18 - __tests__/view/screens/Search.test.tsx | 22 +- __tests__/view/screens/Settings.test.tsx | 16 - .../__snapshots__/Contacts.test.tsx.snap | 205 ---- .../screens/__snapshots__/Home.test.tsx.snap | 594 ----------- .../screens/__snapshots__/Login.test.tsx.snap | 371 ------- .../__snapshots__/NotFound.test.tsx.snap | 431 -------- .../__snapshots__/Notifications.test.tsx.snap | 378 ------- .../__snapshots__/Onboard.test.tsx.snap | 388 -------- .../PostDownvotedBy.test.tsx.snap | 368 ------- .../PostRepostedBy.test.tsx.snap | 368 ------- .../__snapshots__/PostThread.test.tsx.snap | 437 -------- .../__snapshots__/PostUpvotedBy.test.tsx.snap | 368 ------- .../__snapshots__/Profile.test.tsx.snap | 513 ---------- .../ProfileFollowers.test.tsx.snap | 386 -------- .../ProfileFollows.test.tsx.snap | 386 -------- .../ProfileMembers.test.tsx.snap | 386 -------- .../__snapshots__/Search.test.tsx.snap | 514 ---------- .../__snapshots__/Settings.test.tsx.snap | 631 ------------ __tests__/view/shell/mobile/Composer.test.tsx | 23 - __tests__/view/shell/mobile/Menu.test.tsx | 67 +- .../view/shell/mobile/TabsSelector.test.tsx | 96 +- .../__snapshots__/Composer.test.tsx.snap | 659 ------------ .../mobile/__snapshots__/Menu.test.tsx.snap | 837 ---------------- .../__snapshots__/TabsSelector.test.tsx.snap | 651 ------------ .../mobile/__snapshots__/index.test.tsx.snap | 421 -------- __tests__/view/shell/mobile/index.test.tsx | 18 - ios/app.xcodeproj/project.pbxproj | 68 +- jest/jestSetup.js | 26 +- jest/test-utils.tsx | 28 +- package.json | 6 +- patches/react-native-pager-view+5.4.1.patch | 125 --- src/view/com/composer/Autocomplete.tsx | 1 + src/view/com/composer/ComposePost.tsx | 16 +- src/view/com/composer/PhotoCarouselPicker.tsx | 4 + src/view/com/composer/Prompt.tsx | 1 + src/view/com/composer/SelectedPhoto.tsx | 9 +- src/view/com/discover/SuggestedFollows.tsx | 5 +- src/view/com/login/CreateAccount.tsx | 15 +- src/view/com/login/Signin.tsx | 26 +- src/view/com/notifications/InviteAccepter.tsx | 9 +- src/view/com/post-thread/PostRepostedBy.tsx | 5 +- src/view/com/post-thread/PostVotedBy.tsx | 4 +- src/view/com/posts/Feed.tsx | 4 +- src/view/com/profile/ProfileFollowers.tsx | 4 +- src/view/com/profile/ProfileFollows.tsx | 6 +- src/view/com/profile/ProfileHeader.tsx | 15 +- src/view/com/profile/ProfileMembers.tsx | 9 +- src/view/com/util/PostCtrls.tsx | 3 + src/view/com/util/error/ErrorMessage.tsx | 8 +- src/view/com/util/error/ErrorScreen.tsx | 6 +- src/view/com/util/forms/DropdownButton.tsx | 3 +- src/view/screens/Contacts.tsx | 5 +- src/view/screens/Home.tsx | 53 +- src/view/screens/Login.tsx | 12 +- src/view/screens/NotFound.tsx | 8 +- src/view/screens/Profile.tsx | 8 +- src/view/screens/Search.tsx | 3 +- src/view/shell/mobile/Menu.tsx | 5 +- src/view/shell/mobile/TabsSelector.tsx | 18 +- src/view/shell/mobile/index.tsx | 6 +- yarn.lock | 67 +- 95 files changed, 2852 insertions(+), 9936 deletions(-) create mode 100644 __mocks__/state-mock.ts 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 delete mode 100644 patches/react-native-pager-view+5.4.1.patch diff --git a/__mocks__/@react-native-camera-roll/camera-roll.js b/__mocks__/@react-native-camera-roll/camera-roll.js index 43f40208..8f1aea43 100644 --- a/__mocks__/@react-native-camera-roll/camera-roll.js +++ b/__mocks__/@react-native-camera-roll/camera-roll.js @@ -1,3 +1,9 @@ -export default { - CameraRoll: jest.fn(), +export const CameraRoll = { + getPhotos: jest.fn().mockResolvedValue({ + edges: [ + {node: {image: {uri: 'path/to/image1.jpg'}}}, + {node: {image: {uri: 'path/to/image2.jpg'}}}, + {node: {image: {uri: 'path/to/image3.jpg'}}}, + ], + }), } diff --git a/__mocks__/react-native-image-crop-picker.js b/__mocks__/react-native-image-crop-picker.js index 9f2298a7..9f901dfa 100644 --- a/__mocks__/react-native-image-crop-picker.js +++ b/__mocks__/react-native-image-crop-picker.js @@ -1,3 +1,9 @@ -export default { - openPicker: jest.fn().mockImplementation(() => Promise.resolve(result)), -} +export const openPicker = jest + .fn() + .mockImplementation(() => Promise.resolve({uri: ''})) +export const openCamera = jest + .fn() + .mockImplementation(() => Promise.resolve({uri: ''})) +export const openCropper = jest + .fn() + .mockImplementation(() => Promise.resolve({uri: ''})) diff --git a/__mocks__/state-mock.ts b/__mocks__/state-mock.ts new file mode 100644 index 00000000..4009303b --- /dev/null +++ b/__mocks__/state-mock.ts @@ -0,0 +1,934 @@ +import {LogModel} from './../src/state/models/log' +import {LRUMap} from 'lru_map' +import {RootStoreModel} from './../src/state/models/root-store' +import {NavigationTabModel} from './../src/state/models/navigation' +import {SessionModel} from '../src/state/models/session' +import {NavigationModel} from '../src/state/models/navigation' +import {ShellUiModel} from '../src/state/models/shell-ui' +import {MeModel} from '../src/state/models/me' +import {OnboardModel} from '../src/state/models/onboard' +import {ProfilesViewModel} from '../src/state/models/profiles-view' +import {LinkMetasViewModel} from '../src/state/models/link-metas-view' +import {MembershipsViewModel} from '../src/state/models/memberships-view' +import {FeedModel} from '../src/state/models/feed-view' +import {NotificationsViewModel} from '../src/state/models/notifications-view' +import {ProfileViewModel} from '../src/state/models/profile-view' +import {MembersViewModel} from '../src/state/models/members-view' +import {ProfileUiModel, Sections} from '../src/state/models/profile-ui' +import {SessionServiceClient} from '@atproto/api' +import {UserAutocompleteViewModel} from '../src/state/models/user-autocomplete-view' +import {UserLocalPhotosModel} from '../src/state/models/user-local-photos' +import {SuggestedActorsViewModel} from '../src/state/models/suggested-actors-view' +import {UserFollowersViewModel} from '../src/state/models/user-followers-view' +import {UserFollowsViewModel} from '../src/state/models/user-follows-view' +import {NotificationsViewItemModel} from './../src/state/models/notifications-view' +import { + PostThreadViewModel, + PostThreadViewPostModel, +} from '../src/state/models/post-thread-view' +import {FeedItemModel} from '../src/state/models/feed-view' +import {RepostedByViewModel} from '../src/state/models/reposted-by-view' +import {VotesViewModel} from '../src/state/models/votes-view' + +export const mockedProfileStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: true, + error: '', + params: { + actor: '', + }, + did: 'test did', + handle: 'testhandle', + declaration: { + cid: '', + actorType: '', + }, + creator: 'test did', + displayName: '', + description: '', + avatar: '', + banner: '', + followersCount: 0, + followsCount: 0, + membersCount: 0, + postsCount: 0, + myState: { + follow: '', + member: '', + }, + rootStore: {} as RootStoreModel, + hasContent: true, + hasError: false, + isEmpty: false, + isUser: true, + isScene: false, + setup: jest.fn().mockResolvedValue({aborted: false}), + refresh: jest.fn(), + toggleFollowing: jest.fn().mockResolvedValue({}), + updateProfile: jest.fn(), + // unknown required because of the missing private methods: _xLoading, _xIdle, _load, _replaceAll +} as unknown as ProfileViewModel + +export const mockedMembersStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: true, + error: '', + params: { + actor: 'test actor', + }, + subject: { + did: 'test did', + handle: '', + displayName: '', + declaration: { + cid: '', + actorType: '', + }, + avatar: undefined, + }, + members: [ + { + did: 'test did2', + declaration: { + cid: '', + actorType: '', + }, + handle: 'testhandle', + displayName: 'test name', + indexedAt: '', + }, + ], + rootStore: {} as RootStoreModel, + hasContent: true, + hasError: false, + isEmpty: false, + isMember: jest.fn(), + setup: jest.fn().mockResolvedValue({aborted: false}), + refresh: jest.fn(), + loadMore: jest.fn(), + removeMember: jest.fn(), + // unknown required because of the missing private methods: _xLoading, _xIdle, _fetch, _replaceAll, _append +} as unknown as MembersViewModel + +export const mockedMembershipsStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: true, + error: '', + params: { + actor: '', + limit: 1, + before: '', + }, + subject: { + did: 'test did', + handle: '', + displayName: '', + declaration: {cid: '', actorType: ''}, + avatar: undefined, + }, + memberships: [ + { + did: 'test did', + declaration: { + cid: '', + actorType: 'app.bsky.system.actorUser', + }, + handle: ',', + displayName: '', + createdAt: '', + indexedAt: '', + _reactKey: 'item-1', + }, + ], + rootStore: {} as RootStoreModel, + hasContent: true, + hasError: false, + isEmpty: false, + isMemberOf: jest.fn(), + setup: jest.fn().mockResolvedValue({aborted: false}), + refresh: jest.fn(), + loadMore: jest.fn(), + // unknown required because of the missing private methods: _xLoading, _xIdle, _fetch, _replaceAll, _append +} as unknown as MembershipsViewModel + +export const mockedFeedItemStore = { + _reactKey: 'item-1', + _isThreadParent: false, + _isThreadChildElided: false, + _isThreadChild: false, + post: { + uri: 'testuri', + cid: 'test cid', + author: { + did: 'test did', + handle: 'test.handle', + displayName: 'test name', + declaration: {cid: '', actorType: ''}, + }, + record: { + $type: 'app.bsky.feed.post', + createdAt: '2022-12-29T16:39:57.919Z', + text: 'Sup', + }, + replyCount: 0, + repostCount: 0, + upvoteCount: 0, + downvoteCount: 0, + indexedAt: '2022-12-29T16:39:57.919Z', + viewer: {}, + }, + postRecord: { + $type: 'app.bsky.feed.post', + text: 'test text', + createdAt: '1', + reply: { + root: { + uri: 'testuri', + cid: 'tes cid', + }, + parent: { + uri: 'testuri', + cid: 'tes cid', + }, + }, + }, + rootStore: {} as RootStoreModel, + copy: jest.fn(), + toggleUpvote: jest.fn().mockResolvedValue({}), + toggleDownvote: jest.fn(), + toggleRepost: jest.fn().mockResolvedValue({}), + delete: jest.fn().mockResolvedValue({}), + reasonRepost: { + by: { + did: 'test did', + handle: 'test.handle', + declaration: {cid: '', actorType: ''}, + }, + indexedAt: '', + }, + reasonTrend: { + by: { + did: 'test did', + handle: 'test.handle', + declaration: {cid: '', actorType: ''}, + }, + indexedAt: '', + }, + reply: { + parent: { + author: { + did: 'test did', + handle: 'test.handle', + displayName: 'test name', + declaration: {cid: '', actorType: ''}, + }, + cid: '', + downvoteCount: 0, + indexedAt: '2023-01-10T11:17:46.945Z', + record: {}, + replyCount: 1, + repostCount: 0, + upvoteCount: 0, + uri: 'testuri', + viewer: {}, + }, + root: { + author: { + did: 'test did', + handle: 'test.handle', + displayName: 'test name', + declaration: {cid: '', actorType: ''}, + }, + cid: '', + downvoteCount: 0, + indexedAt: '2023-01-10T11:17:46.739Z', + record: {}, + replyCount: 1, + repostCount: 0, + upvoteCount: 1, + uri: 'testuri', + viewer: {}, + }, + }, +} as FeedItemModel + +export const mockedFeedStore = { + isLoading: false, + isRefreshing: false, + hasNewLatest: false, + hasLoaded: true, + error: '', + hasMore: true, + params: { + actor: '', + limit: 1, + before: '', + }, + feed: [], + rootStore: {} as RootStoreModel, + feedType: 'home', + hasContent: true, + hasError: false, + isEmpty: false, + nonReplyFeed: [ + { + _reactKey: 'item-1', + post: { + author: { + handle: 'handle.test', + displayName: 'test name', + avatar: '', + }, + cid: 'bafyreihkwjoy2vbfqld2lp3tv4ce6yfr354sqgp32qoplrudso4gyyjiwe', + downvoteCount: 0, + indexedAt: '2022-12-29T16:35:55.270Z', + record: { + $type: 'app.bsky.feed.post', + createdAt: '2022-12-29T16:39:57.919Z', + text: 'Sup', + }, + replyCount: 0, + repostCount: 0, + upvoteCount: 0, + uri: 'at://did:plc:wcizmlgv3rdslk64t6q4silu/app.bsky.feed.post/3jkzce5kfvn2h', + viewer: { + handle: 'handle.test', + displayName: 'test name', + avatar: '', + }, + }, + reason: undefined, + reply: undefined, + }, + ], + setHasNewLatest: jest.fn(), + setup: jest.fn().mockResolvedValue({}), + refresh: jest.fn().mockResolvedValue({}), + loadMore: jest.fn().mockResolvedValue({}), + loadLatest: jest.fn(), + update: jest.fn(), + checkForLatest: jest.fn().mockRejectedValue('Error checking for latest'), + // unknown required because of the missing private methods: _xLoading, _xIdle, _pendingWork, _initialLoad, _loadLatest, _loadMore, _update, _replaceAll, _appendAll, _prependAll, _updateAll, _getFeed, loadMoreCursor, pollCursor, _loadPromise, _updatePromise, _loadLatestPromise, _loadMorePromise +} as unknown as FeedModel + +export const mockedPostThreadViewPostStore = { + _reactKey: 'item-1', + _depth: 0, + _isHighlightedPost: false, + _hasMore: false, + postRecord: { + text: 'test text', + createdAt: '', + reply: { + root: { + uri: 'testuri', + cid: 'tes cid', + }, + parent: { + uri: 'testuri', + cid: 'tes cid', + }, + }, + }, + post: { + uri: 'testuri', + cid: 'testcid', + record: {}, + author: { + did: 'test did', + handle: 'test.handle', + declaration: {cid: '', actorType: ''}, + viewer: { + muted: true, + }, + }, + replyCount: 0, + repostCount: 0, + upvoteCount: 0, + downvoteCount: 0, + indexedAt: '', + viewer: { + repost: '', + upvote: '', + downvote: '', + }, + }, + rootStore: {} as RootStoreModel, + assignTreeModels: jest.fn(), + toggleRepost: jest.fn().mockRejectedValue({}), + toggleUpvote: jest.fn().mockRejectedValue({}), + toggleDownvote: jest.fn(), + delete: jest.fn(), +} as PostThreadViewPostModel + +export const mockedPostThreadViewStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: false, + error: '', + notFound: false, + resolvedUri: 'testuri', + params: { + uri: 'testuri', + }, + thread: mockedPostThreadViewPostStore, + hasContent: true, + hasError: false, + setup: jest.fn(), + refresh: jest.fn().mockResolvedValue({}), + update: jest.fn(), + // unknown required because of the missing private methods: _xLoading, _xIdle, _resolveUri, _load, _replaceAll +} as unknown as PostThreadViewModel + +export const mockedNotificationsViewItemStore = { + _reactKey: 'item-1', + uri: 'testuri', + cid: '', + author: { + did: 'test did', + handle: 'test.handle', + declaration: {cid: '', actorType: ''}, + }, + rootStore: {} as RootStoreModel, + copy: jest.fn(), + reason: 'test reason', + isRead: true, + indexedAt: '', + isUpvote: true, + isRepost: false, + isTrend: false, + isMention: false, + isReply: false, + isFollow: false, + isAssertion: false, + needsAdditionalData: false, + isInvite: false, + subjectUri: 'testuri', + toSupportedRecord: jest.fn().mockReturnValue({ + text: 'test text', + createdAt: '', + }), + fetchAdditionalData: jest.fn(), +} as NotificationsViewItemModel + +export const mockedNotificationsStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: true, + error: '', + params: { + limit: 1, + before: '', + }, + hasMore: true, + notifications: [mockedNotificationsViewItemStore], + rootStore: {} as RootStoreModel, + hasContent: true, + hasError: false, + isEmpty: false, + setup: jest.fn().mockResolvedValue({aborted: false}), + refresh: jest.fn().mockResolvedValue({}), + loadMore: jest.fn().mockResolvedValue({}), + update: jest.fn().mockResolvedValue(null), + updateReadState: jest.fn(), + // unknown required because of the missing private methods: _xLoading, _xIdle, _pendingWork, _initialLoad, _loadMore, _update, _replaceAll, _appendAll, _updateAll, loadMoreCursor, _loadPromise, _updatePromise, _loadLatestPromise, _loadMorePromise +} as unknown as NotificationsViewModel + +export const mockedSessionStore = { + serialize: jest.fn(), + hydrate: jest.fn(), + data: { + service: '', + refreshJwt: '', + accessJwt: '', + handle: '', + did: 'test did', + }, + online: false, + attemptingConnect: false, + rootStore: {} as RootStoreModel, + hasSession: true, + clear: jest.fn(), + setState: jest.fn(), + setOnline: jest.fn(), + updateAuthTokens: jest.fn(), + connect: jest.fn(), + describeService: jest.fn().mockResolvedValue({ + availableUserDomains: ['test'], + links: { + termsOfService: 'https://testTermsOfService', + privacyPolicy: 'https://testPrivacyPolicy', + }, + }), + login: jest.fn(), + createAccount: jest.fn(), + logout: jest.fn(), + + // unknown required because of the missing private methods: _connectPromise, configureApi & _connect +} as unknown as SessionModel + +export const mockedNavigationTabStore = { + serialize: jest.fn(), + hydrate: jest.fn(), + id: 0, + history: [ + { + url: '', + ts: 0, + title: '', + id: 0, + }, + ], + index: 0, + isNewTab: false, + current: { + url: '', + ts: 0, + title: '', + id: 0, + }, + canGoBack: false, + canGoForward: false, + backTen: [ + { + url: '', + title: '', + index: 0, + id: 0, + }, + ], + forwardTen: [ + { + url: '', + title: '', + index: 0, + id: 0, + }, + ], + navigate: jest.fn(), + refresh: jest.fn(), + goBack: jest.fn(), + fixedTabReset: jest.fn(), + goForward: jest.fn(), + goToIndex: jest.fn(), + setTitle: jest.fn(), + setIsNewTab: jest.fn(), + fixedTabPurpose: 0, + getBackList: () => [ + { + url: '/', + title: '', + index: 1, + id: 1, + }, + ], + getForwardList: jest.fn(), +} as NavigationTabModel + +export const mockedNavigationStore = { + serialize: jest.fn(), + hydrate: jest.fn(), + tabs: [mockedNavigationTabStore], + tabIndex: 0, + clear: jest.fn(), + tab: mockedNavigationTabStore, + tabCount: 1, + isCurrentScreen: jest.fn(), + navigate: jest.fn(), + refresh: jest.fn(), + setTitle: jest.fn(), + handleLink: jest.fn(), + switchTo: jest.fn(), + setActiveTab: jest.fn(), + closeTab: jest.fn(), + newTab: jest.fn(), +} as NavigationModel + +export const mockedShellStore = { + serialize: jest.fn(), + hydrate: jest.fn(), + minimalShellMode: false, + isMainMenuOpen: false, + isModalActive: false, + activeModal: undefined, + isLightboxActive: false, + activeLightbox: undefined, + isComposerActive: false, + composerOpts: undefined, + darkMode: false, + setDarkMode: jest.fn(), + setMainMenuOpen: jest.fn(), + setMinimalShellMode: jest.fn(), + openModal: jest.fn(), + closeModal: jest.fn(), + closeComposer: jest.fn(), + closeLightbox: jest.fn(), + openComposer: jest.fn(), + openLightbox: jest.fn(), +} as ShellUiModel + +export const mockedMeStore = { + serialize: jest.fn(), + hydrate: jest.fn(), + did: 'test did', + handle: 'test', + displayName: 'test', + description: 'test', + avatar: '', + notificationCount: 0, + rootStore: {} as RootStoreModel, + memberships: mockedMembershipsStore, + mainFeed: mockedFeedStore, + notifications: mockedNotificationsStore, + clear: jest.fn(), + load: jest.fn(), + clearNotificationCount: jest.fn(), + fetchStateUpdate: jest.fn(), + refreshMemberships: jest.fn(), +} as MeModel + +export const mockedOnboardStore = { + serialize: jest.fn(), + hydrate: jest.fn(), + isOnboarding: false, + stage: '', + start: jest.fn(), + stop: jest.fn(), + next: jest.fn(), +} as OnboardModel + +export const mockedProfilesStore = { + hydrate: jest.fn(), + serialize: jest.fn(), + cache: new LRUMap(100), + rootStore: {} as RootStoreModel, + getProfile: jest.fn().mockResolvedValue({data: {}}), + overwrite: jest.fn(), +} as ProfilesViewModel + +export const mockedLinkMetasStore = { + hydrate: jest.fn(), + serialize: jest.fn(), + cache: new LRUMap(100), + rootStore: {} as RootStoreModel, + getLinkMeta: jest.fn(), +} as LinkMetasViewModel + +export const mockedLogStore = { + entries: [], + serialize: jest.fn(), + hydrate: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + // unknown required because of the missing private methods: add +} as unknown as LogModel + +export const mockedRootStore = { + api: { + com: {}, + app: { + bsky: { + graph: { + confirmation: { + delete: jest.fn().mockResolvedValue({}), + }, + getFollowers: jest.fn().mockResolvedValue({}), + getMembers: jest.fn().mockResolvedValue({}), + }, + }, + }, + } as unknown as SessionServiceClient, + resolveName: jest.fn(), + serialize: jest.fn(), + hydrate: jest.fn(), + fetchStateUpdate: jest.fn(), + clearAll: jest.fn(), + session: mockedSessionStore, + nav: mockedNavigationStore, + shell: mockedShellStore, + me: mockedMeStore, + onboard: mockedOnboardStore, + profiles: mockedProfilesStore, + linkMetas: mockedLinkMetasStore, + log: mockedLogStore, +} as RootStoreModel + +export const mockedProfileUiStore = { + profile: mockedProfileStore, + feed: mockedFeedStore, + memberships: mockedMembershipsStore, + members: mockedMembersStore, + selectedViewIndex: 0, + rootStore: mockedRootStore, + params: { + user: 'test user', + }, + currentView: mockedFeedStore, + isInitialLoading: false, + isRefreshing: false, + isUser: true, + isScene: false, + selectorItems: [Sections.Posts, Sections.PostsWithReplies, Sections.Scenes], + selectedView: Sections.Posts, + setSelectedViewIndex: jest.fn(), + setup: jest.fn().mockResolvedValue({aborted: false}), + update: jest.fn(), + refresh: jest.fn(), + loadMore: jest.fn(), +} as ProfileUiModel + +export const mockedAutocompleteViewStore = { + isLoading: false, + isActive: true, + prefix: '', + follows: [ + { + did: 'test did', + declaration: { + cid: '', + actorType: 'app.bsky.system.actorUser', + }, + handle: '', + displayName: '', + createdAt: '', + indexedAt: '', + }, + ], + searchRes: [ + { + did: 'test did', + declaration: { + cid: '', + actorType: 'app.bsky.system.actorUser', + }, + handle: '', + displayName: '', + }, + ], + knownHandles: new Set(), + suggestions: [ + { + handle: 'handle.test', + displayName: 'Test Display', + }, + { + handle: 'handle2.test', + displayName: 'Test Display 2', + }, + ], + rootStore: {} as RootStoreModel, + setup: jest.fn(), + setActive: jest.fn(), + setPrefix: jest.fn(), + // unknown required because of the missing private methods: _searchPromise, _getFollows , _search +} as unknown as UserAutocompleteViewModel + +export const mockedLocalPhotosStore = { + photos: { + node: { + type: '', + group_name: '', + image: { + filename: '', + extension: '', + uri: '', + height: 1000, + width: 1000, + fileSize: null, + playableDuration: 0, + }, + timestamp: 1672847197, + location: null, + }, + }, + rootStore: {} as RootStoreModel, + setup: jest.fn(), + // unknown required because of the missing private methods: _getPhotos +} as unknown as UserLocalPhotosModel + +export const mockedSuggestedActorsStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: false, + error: '', + suggestions: [ + { + did: '1', + declaration: { + cid: '', + actorType: 'app.bsky.system.actorUser', + }, + handle: 'handle1.test', + displayName: 'test name 1', + description: 'desc', + indexedAt: '', + _reactKey: '1', + }, + { + did: '2', + declaration: { + cid: '', + actorType: 'app.bsky.system.actorUser', + }, + handle: '', + displayName: 'handle2.test', + description: 'desc', + indexedAt: '', + _reactKey: '2', + }, + ], + rootStore: {} as RootStoreModel, + hasContent: true, + hasError: false, + isEmpty: false, + setup: jest.fn().mockResolvedValue(null), + refresh: jest.fn(), + // unknown required because of the missing private methods: _xLoading, _xIdle, _fetch, _appendAll, _append +} as unknown as SuggestedActorsViewModel + +export const mockedUserFollowersStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: false, + error: '', + params: { + user: 'test user', + }, + subject: { + did: 'test did', + handle: '', + declaration: {cid: '', actorType: ''}, + }, + followers: [ + { + did: 'test did', + declaration: {cid: '', actorType: ''}, + handle: 'testhandle', + displayName: 'test name', + indexedAt: '', + _reactKey: '1', + }, + { + did: 'test did2', + declaration: {cid: '', actorType: ''}, + handle: 'testhandle2', + displayName: 'test name 2', + indexedAt: '', + _reactKey: '2', + }, + ], + rootStore: {} as RootStoreModel, + hasContent: true, + hasError: false, + isEmpty: false, + setup: jest.fn(), + refresh: jest.fn(), + loadMore: jest.fn(), + // unknown required because of the missing private methods: _xIdle, _xLoading, _fetch, _replaceAll, _append +} as unknown as UserFollowersViewModel + +export const mockedUserFollowsStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: false, + error: '', + params: { + user: 'test user', + }, + subject: { + did: 'test did', + handle: '', + declaration: {cid: '', actorType: ''}, + }, + follows: [ + { + did: 'test did', + declaration: {cid: '', actorType: ''}, + handle: 'testhandle', + displayName: 'test name', + indexedAt: '', + _reactKey: '1', + }, + { + did: 'test did2', + declaration: {cid: '', actorType: ''}, + handle: 'testhandle2', + displayName: 'test name 2', + indexedAt: '', + _reactKey: '2', + }, + ], + rootStore: {} as RootStoreModel, + hasContent: true, + hasError: false, + isEmpty: false, + setup: jest.fn(), + refresh: jest.fn(), + loadMore: jest.fn(), + // unknown required because of the missing private methods: _xIdle, _xLoading, _fetch, _replaceAll, _append +} as unknown as UserFollowsViewModel + +export const mockedRepostedByViewStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: false, + error: '', + resolvedUri: '', + params: { + uri: 'testuri', + }, + uri: '', + repostedBy: [ + { + _reactKey: '', + did: '', + handle: '', + displayName: '', + declaration: {cid: '', actorType: ''}, + indexedAt: '', + }, + ], + hasContent: false, + hasError: false, + isEmpty: false, + setup: jest.fn().mockResolvedValue({}), + refresh: jest.fn().mockResolvedValue({}), + loadMore: jest.fn().mockResolvedValue({}), + // unknown required because of the missing private methods: _xIdle, _xLoading, _resolveUri, _fetch, _refresh, _replaceAll, _append +} as unknown as RepostedByViewModel + +export const mockedVotesViewStore = { + isLoading: false, + isRefreshing: false, + hasLoaded: false, + error: '', + resolvedUri: '', + params: { + uri: 'testuri', + }, + uri: '', + votes: [ + { + _reactKey: '', + direction: 'up', + indexedAt: '', + createdAt: '', + actor: { + did: '', + handle: '', + declaration: {cid: '', actorType: ''}, + }, + }, + ], + hasContent: false, + hasError: false, + isEmpty: false, + setup: jest.fn(), + refresh: jest.fn().mockResolvedValue({}), + loadMore: jest.fn().mockResolvedValue({}), + // unknown required because of the missing private methods: _xIdle, _xLoading, _resolveUri, _fetch, _replaceAll, _append +} as unknown as VotesViewModel diff --git a/__tests__/lib/images.test.ts b/__tests__/lib/images.test.ts index 461bd04c..d53a5bc0 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 00000000..03741893 --- /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 00000000..b5a6d0db --- /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 00000000..a1ffa3fb --- /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 00000000..bc49d2ee --- /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 00000000..02ee0feb --- /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 00000000..ccaa6f83 --- /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 00000000..8324609a --- /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 00000000..57539b75 --- /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 00000000..84377f62 --- /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 00000000..ef847765 --- /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 00000000..26059ae3 --- /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 00000000..2de99b2a --- /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 00000000..51b41183 --- /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 00000000..52b04e64 --- /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 00000000..762dcc8f --- /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 00000000..9a31e79e --- /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 8dc4e56e..00000000 --- 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 353d4ea5..00000000 --- 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 d9faf08a..e347534b 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 047d309e..fd3c84b0 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 2c5e32cd..00000000 --- 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 69d6f0a7..00000000 --- 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 8c4119b4..00000000 --- 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 00122435..00000000 --- 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 87164ed7..00000000 --- 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 97912ded..00000000 --- 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 8912cbfb..00000000 --- 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 230209aa..00000000 --- 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 e4571b5c..00000000 --- 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 a33e03a1..00000000 --- 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 477e077a..f769c7a5 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 475639eb..00000000 --- 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 61a85708..00000000 --- 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 4d2c5109..00000000 --- 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 b86d8656..00000000 --- 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 a9365718..00000000 --- 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 6c1eef57..00000000 --- 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 5422fb0d..00000000 --- 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 aa41d7fb..00000000 --- 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 f6af5ec5..00000000 --- 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 abb36931..00000000 --- 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 a7bb6aae..00000000 --- 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 e9640b6e..00000000 --- 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 237773b4..00000000 --- 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 cba1a734..00000000 --- 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 e36a4b08..00000000 --- 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 13055207..00000000 --- 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 77402da2..00000000 --- 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 7b84cfd8..00000000 --- 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 5305bd77..5eee3f12 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 7908f442..9388d244 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 6ced9871..00000000 --- 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 78c34b96..00000000 --- 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 03e0636d..00000000 --- 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 793668b7..00000000 --- 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 96f16126..00000000 --- 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() - }) -}) diff --git a/ios/app.xcodeproj/project.pbxproj b/ios/app.xcodeproj/project.pbxproj index 20496a26..0a4df190 100644 --- a/ios/app.xcodeproj/project.pbxproj +++ b/ios/app.xcodeproj/project.pbxproj @@ -11,9 +11,9 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 56F0250941FE75DA8CF8094A /* libPods-app-appTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F5D2424078D205E2F98F0707 /* libPods-app-appTests.a */; }; + 5698CA584FD738B2091BD18F /* libPods-app-appTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0C7BDE7769B84011D3747DC /* libPods-app-appTests.a */; }; + 67BF1AE6AABFC881715B2D6A /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8789F612EDA2C48C6064ADD6 /* libPods-app.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - BFADB63545C91424F7C8293A /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C668306DD1C0A63684EDCB0 /* libPods-app.a */; }; E4BBD590292C1F5200296224 /* app.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = E4437C9E28581FA7006DA9E7 /* app.entitlements */; }; /* End PBXBuildFile section */ @@ -37,15 +37,15 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = app/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = app/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = app/main.m; sourceTree = ""; }; - 3D3D4D80CD734363E3EADE03 /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = ""; }; - 65D2EDDD9D88DA37994C7743 /* Pods-app-appTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.debug.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.debug.xcconfig"; sourceTree = ""; }; + 53DBA218C184B95B107AC33E /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = app/LaunchScreen.storyboard; sourceTree = ""; }; - 9C668306DD1C0A63684EDCB0 /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - A79BD0E1A3F5B868AD4F5D68 /* Pods-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.debug.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.debug.xcconfig"; sourceTree = ""; }; - AA0AD1B60CF7C90FFDB948B9 /* Pods-app-appTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.release.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.release.xcconfig"; sourceTree = ""; }; + 8789F612EDA2C48C6064ADD6 /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BB4EDB104E125B8A1913E74 /* Pods-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.debug.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.debug.xcconfig"; sourceTree = ""; }; + A8E093A0B5DA947150924A68 /* Pods-app-appTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.release.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.release.xcconfig"; sourceTree = ""; }; + C01FB6762BC17DADC0319338 /* Pods-app-appTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.debug.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.debug.xcconfig"; sourceTree = ""; }; + C0C7BDE7769B84011D3747DC /* libPods-app-appTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-appTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E4437C9E28581FA7006DA9E7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = app.entitlements; path = app/app.entitlements; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - F5D2424078D205E2F98F0707 /* libPods-app-appTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-appTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,7 +53,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 56F0250941FE75DA8CF8094A /* libPods-app-appTests.a in Frameworks */, + 5698CA584FD738B2091BD18F /* libPods-app-appTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -61,7 +61,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - BFADB63545C91424F7C8293A /* libPods-app.a in Frameworks */, + 67BF1AE6AABFC881715B2D6A /* libPods-app.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -103,8 +103,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 9C668306DD1C0A63684EDCB0 /* libPods-app.a */, - F5D2424078D205E2F98F0707 /* libPods-app-appTests.a */, + 8789F612EDA2C48C6064ADD6 /* libPods-app.a */, + C0C7BDE7769B84011D3747DC /* libPods-app-appTests.a */, ); name = Frameworks; sourceTree = ""; @@ -143,10 +143,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - A79BD0E1A3F5B868AD4F5D68 /* Pods-app.debug.xcconfig */, - 3D3D4D80CD734363E3EADE03 /* Pods-app.release.xcconfig */, - 65D2EDDD9D88DA37994C7743 /* Pods-app-appTests.debug.xcconfig */, - AA0AD1B60CF7C90FFDB948B9 /* Pods-app-appTests.release.xcconfig */, + 8BB4EDB104E125B8A1913E74 /* Pods-app.debug.xcconfig */, + 53DBA218C184B95B107AC33E /* Pods-app.release.xcconfig */, + C01FB6762BC17DADC0319338 /* Pods-app-appTests.debug.xcconfig */, + A8E093A0B5DA947150924A68 /* Pods-app-appTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -158,11 +158,11 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "appTests" */; buildPhases = ( - A8646FFAA087C80271BB2CC6 /* [CP] Check Pods Manifest.lock */, + A6849EFFDCB214D4F4EAE972 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - 8DC349844744C81F79F94A1E /* [CP] Copy Pods Resources */, + DFA0C2B14E2F369C4B2337CE /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -178,13 +178,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "app" */; buildPhases = ( - AFA1F7779F8B1AF36C3A7914 /* [CP] Check Pods Manifest.lock */, + DDF15D430A078CE70E577FDA /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - EEF3465D9B31EDDCEB2438EA /* [CP] Copy Pods Resources */, + 45E56D79C207C80AF89FBAA2 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -266,24 +266,24 @@ shellPath = /bin/sh; shellScript = "set -e\n\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 8DC349844744C81F79F94A1E /* [CP] Copy Pods Resources */ = { + 45E56D79C207C80AF89FBAA2 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources.sh\"\n"; showEnvVarsInLog = 0; }; - A8646FFAA087C80271BB2CC6 /* [CP] Check Pods Manifest.lock */ = { + A6849EFFDCB214D4F4EAE972 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -305,7 +305,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - AFA1F7779F8B1AF36C3A7914 /* [CP] Check Pods Manifest.lock */ = { + DDF15D430A078CE70E577FDA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -327,21 +327,21 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - EEF3465D9B31EDDCEB2438EA /* [CP] Copy Pods Resources */ = { + DFA0C2B14E2F369C4B2337CE /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; FD10A7F022414F080027D42C /* Start Packager */ = { @@ -396,7 +396,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 65D2EDDD9D88DA37994C7743 /* Pods-app-appTests.debug.xcconfig */; + baseConfigurationReference = C01FB6762BC17DADC0319338 /* Pods-app-appTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -424,7 +424,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AA0AD1B60CF7C90FFDB948B9 /* Pods-app-appTests.release.xcconfig */; + baseConfigurationReference = A8E093A0B5DA947150924A68 /* Pods-app-appTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -449,7 +449,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A79BD0E1A3F5B868AD4F5D68 /* Pods-app.debug.xcconfig */; + baseConfigurationReference = 8BB4EDB104E125B8A1913E74 /* Pods-app.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -479,7 +479,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3D3D4D80CD734363E3EADE03 /* Pods-app.release.xcconfig */; + baseConfigurationReference = 53DBA218C184B95B107AC33E /* Pods-app.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/jest/jestSetup.js b/jest/jestSetup.js index 7a2f10d2..1cad50c7 100644 --- a/jest/jestSetup.js +++ b/jest/jestSetup.js @@ -1,17 +1,17 @@ +import 'react-native-gesture-handler/jestSetup' jest.mock('@react-native-async-storage/async-storage', () => require('@react-native-async-storage/async-storage/jest/async-storage-mock'), ) jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter') +// Silence the warning: Animated: `useNativeDriver` is not supported +jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper') +require('react-native-reanimated/lib/reanimated2/jestUtils').setUpTests() + jest.mock('@fortawesome/react-native-fontawesome', () => ({ FontAwesomeIcon: '', })) -require('react-native-reanimated/lib/reanimated2/jestUtils').setUpTests() - -// Silence the warning: Animated: `useNativeDriver` is not supported -jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper') - jest.mock('react-native-safe-area-context', () => { const inset = {top: 0, right: 0, bottom: 0, left: 0} return { @@ -36,4 +36,18 @@ jest.mock('@gorhom/bottom-sheet', () => { } }) -jest.useFakeTimers() +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(), +})) + +import {View as mockedView} from 'react-native' +jest.mock('react-native-tab-view', () => ({ + ...jest.requireActual('react-native-tab-view'), + TabView: mockedView, +})) diff --git a/jest/test-utils.tsx b/jest/test-utils.tsx index a5946ed0..c84ee637 100644 --- a/jest/test-utils.tsx +++ b/jest/test-utils.tsx @@ -1,29 +1,27 @@ import React from 'react' -import RN from 'react-native' import {render} from '@testing-library/react-native' import {GestureHandlerRootView} from 'react-native-gesture-handler' import {RootSiblingParent} from 'react-native-root-siblings' import {SafeAreaProvider} from 'react-native-safe-area-context' -import {DEFAULT_SERVICE, RootStoreModel, RootStoreProvider} from '../src/state' -import {SessionServiceClient} from '../src/third-party/api/src' -import {sessionClient as AtpApi} from '../src/third-party/api' +import {RootStoreProvider} from '../src/state' +import {mockedRootStore} from '../__mocks__/state-mock' -const WrappedComponent = ({children}: any) => { - const api = AtpApi.service(DEFAULT_SERVICE) as SessionServiceClient - const rootStore = new RootStoreModel(api) - return ( +const customRender = (ui: any, storeMock?: any) => + render( + // eslint-disable-next-line react-native/no-inline-styles - - {children} + + {ui} - + , ) -} - -const customRender = (ui: any, options?: any) => - render(ui, {wrapper: WrappedComponent, ...options}) // re-export everything export * from '@testing-library/react-native' diff --git a/package.json b/package.json index ba918a52..45bb2143 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "start": "react-native start", "clean-cache": "rm -rf node_modules/.cache/babel-loader/*", "test": "jest --coverage", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx", - "postinstall": "patch-package" + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, "dependencies": { "@atproto/api": "^0.0.6", @@ -34,7 +33,6 @@ "lru_map": "^0.4.1", "mobx": "^6.6.1", "mobx-react-lite": "^3.4.0", - "patch-package": "^6.5.0", "react": "17.0.2", "react-circular-progressbar": "^2.1.0", "react-dom": "17.0.2", @@ -112,6 +110,8 @@ "/node_modules/", "/src/platform", "/src/third-party", + "/src/view/com/util", + "/src/state/lib", "/__tests__/test-utils.js" ] }, diff --git a/patches/react-native-pager-view+5.4.1.patch b/patches/react-native-pager-view+5.4.1.patch deleted file mode 100644 index 31690e5d..00000000 --- a/patches/react-native-pager-view+5.4.1.patch +++ /dev/null @@ -1,125 +0,0 @@ -# HOTFIX - https://github.com/satya164/react-native-tab-view/issues/1104 - -diff --git a/node_modules/react-native-pager-view/lib/commonjs/PagerView.js b/node_modules/react-native-pager-view/lib/commonjs/PagerView.js -index 40afb41..850c151 100644 ---- a/node_modules/react-native-pager-view/lib/commonjs/PagerView.js -+++ b/node_modules/react-native-pager-view/lib/commonjs/PagerView.js -@@ -131,17 +131,20 @@ class PagerView extends _react.default.Component { - } - - render() { -- return /*#__PURE__*/_react.default.createElement(_PagerViewNative.PagerViewViewManager, _extends({}, this.props, { -- ref: this.PagerView -- /** TODO: Fix ref type */ -- , -+ const { -+ children, -+ forwardedRef, -+ ...rest -+ } = this.props; -+ return /*#__PURE__*/_react.default.createElement(_PagerViewNative.PagerViewViewManager, _extends({}, rest, { -+ // ref={this.PagerView as any /** TODO: Fix ref type */} - style: this.props.style, - layoutDirection: this.deducedLayoutDirection, - onPageScroll: this._onPageScroll, - onPageScrollStateChanged: this._onPageScrollStateChanged, - onPageSelected: this._onPageSelected, - onMoveShouldSetResponderCapture: this._onMoveShouldSetResponderCapture, -- children: (0, _utils.childrenWithOverriddenStyle)(this.props.children) -+ children: (0, _utils.childrenWithOverriddenStyle)(children) - })); - } - -diff --git a/node_modules/react-native-pager-view/lib/commonjs/PagerView.js.map b/node_modules/react-native-pager-view/lib/commonjs/PagerView.js.map -index ce9ff7f..c55d898 100644 ---- a/node_modules/react-native-pager-view/lib/commonjs/PagerView.js.map -+++ b/node_modules/react-native-pager-view/lib/commonjs/PagerView.js.map -@@ -1 +1 @@ --{"version":3,"sources":["PagerView.tsx"],"names":["PagerView","React","Component","createRef","current","getInnerViewNode","e","props","onPageScroll","Platform","OS","keyboardDismissMode","Keyboard","dismiss","onPageScrollStateChanged","isScrolling","nativeEvent","pageScrollState","onPageSelected","selectedPage","UIManager","dispatchViewManagerCommand","ReactNative","findNodeHandle","Commands","setPage","setPageWithoutAnimation","scrollEnabled","setScrollEnabled","deducedLayoutDirection","shouldUseDeviceRtlSetup","layoutDirection","I18nManager","isRTL","render","style","_onPageScroll","_onPageScrollStateChanged","_onPageSelected","_onMoveShouldSetResponderCapture","children"],"mappings":"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","sourcesContent":["import React, { ReactElement } from 'react';\nimport { Platform, UIManager, Keyboard } from 'react-native';\nimport ReactNative, { I18nManager } from 'react-native';\nimport type {\n PagerViewOnPageScrollEvent,\n PagerViewOnPageSelectedEvent,\n PageScrollStateChangedNativeEvent,\n PagerViewProps,\n} from './types';\n\nimport { childrenWithOverriddenStyle } from './utils';\nimport { getViewManagerConfig, PagerViewViewManager } from './PagerViewNative';\n\n/**\n * Container that allows to flip left and right between child views. Each\n * child view of the `PagerView` will be treated as a separate page\n * and will be stretched to fill the `PagerView`.\n *\n * It is important all children are ``s and not composite components.\n * You can set style properties like `padding` or `backgroundColor` for each\n * child. It is also important that each child have a `key` prop.\n *\n * Example:\n *\n * ```\n * render: function() {\n * return (\n * \n * \n * First page\n * \n * \n * Second page\n * \n * \n * );\n * }\n *\n * ...\n *\n * var styles = {\n * ...\n * PagerView: {\n * flex: 1\n * },\n * pageStyle: {\n * alignItems: 'center',\n * padding: 20,\n * }\n * }\n * ```\n */\n\nexport class PagerView extends React.Component {\n private isScrolling = false;\n private PagerView = React.createRef();\n\n public getInnerViewNode = (): ReactElement => {\n return this.PagerView.current!.getInnerViewNode();\n };\n\n private _onPageScroll = (e: PagerViewOnPageScrollEvent) => {\n if (this.props.onPageScroll) {\n this.props.onPageScroll(e);\n }\n // Not implemented on iOS yet\n if (Platform.OS === 'android') {\n if (this.props.keyboardDismissMode === 'on-drag') {\n Keyboard.dismiss();\n }\n }\n };\n\n private _onPageScrollStateChanged = (\n e: PageScrollStateChangedNativeEvent\n ) => {\n if (this.props.onPageScrollStateChanged) {\n this.props.onPageScrollStateChanged(e);\n }\n this.isScrolling = e.nativeEvent.pageScrollState === 'dragging';\n };\n\n private _onPageSelected = (e: PagerViewOnPageSelectedEvent) => {\n if (this.props.onPageSelected) {\n this.props.onPageSelected(e);\n }\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will be animated.\n */\n public setPage = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPage,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will *not* be animated.\n */\n public setPageWithoutAnimation = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPageWithoutAnimation,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to enable/disable scroll imperatively\n * The recommended way is using the scrollEnabled prop, however, there might be a case where a\n * imperative solution is more useful (e.g. for not blocking an animation)\n */\n public setScrollEnabled = (scrollEnabled: boolean) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setScrollEnabled,\n [scrollEnabled]\n );\n };\n\n private _onMoveShouldSetResponderCapture = () => {\n return this.isScrolling;\n };\n\n private get deducedLayoutDirection() {\n const shouldUseDeviceRtlSetup =\n !this.props.layoutDirection || this.props.layoutDirection === 'locale';\n\n if (shouldUseDeviceRtlSetup) {\n return I18nManager.isRTL ? 'rtl' : 'ltr';\n } else {\n return this.props.layoutDirection;\n }\n }\n\n render() {\n return (\n \n );\n }\n}\n"]} -\ No newline at end of file -+{"version":3,"sources":["PagerView.tsx"],"names":["PagerView","React","Component","createRef","current","getInnerViewNode","e","props","onPageScroll","Platform","OS","keyboardDismissMode","Keyboard","dismiss","onPageScrollStateChanged","isScrolling","nativeEvent","pageScrollState","onPageSelected","selectedPage","UIManager","dispatchViewManagerCommand","ReactNative","findNodeHandle","Commands","setPage","setPageWithoutAnimation","scrollEnabled","setScrollEnabled","deducedLayoutDirection","shouldUseDeviceRtlSetup","layoutDirection","I18nManager","isRTL","render","children","forwardedRef","rest","style","_onPageScroll","_onPageScrollStateChanged","_onPageSelected","_onMoveShouldSetResponderCapture"],"mappings":";;;;;;;AAAA;;AACA;;AASA;;AACA;;;;;;;;;;;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO,MAAMA,SAAN,SAAwBC,eAAMC,SAA9B,CAAgF;AAAA;AAAA;;AAAA,yCAC/D,KAD+D;;AAAA,oDAEjED,eAAME,SAAN,EAFiE;;AAAA,8CAI3D,MAAoB;AAC5C,aAAO,KAAKH,SAAL,CAAeI,OAAf,CAAwBC,gBAAxB,EAAP;AACD,KANoF;;AAAA,2CAQ5DC,CAAD,IAAmC;AACzD,UAAI,KAAKC,KAAL,CAAWC,YAAf,EAA6B;AAC3B,aAAKD,KAAL,CAAWC,YAAX,CAAwBF,CAAxB;AACD,OAHwD,CAIzD;;;AACA,UAAIG,sBAASC,EAAT,KAAgB,SAApB,EAA+B;AAC7B,YAAI,KAAKH,KAAL,CAAWI,mBAAX,KAAmC,SAAvC,EAAkD;AAChDC,gCAASC,OAAT;AACD;AACF;AACF,KAlBoF;;AAAA,uDAqBnFP,CADkC,IAE/B;AACH,UAAI,KAAKC,KAAL,CAAWO,wBAAf,EAAyC;AACvC,aAAKP,KAAL,CAAWO,wBAAX,CAAoCR,CAApC;AACD;;AACD,WAAKS,WAAL,GAAmBT,CAAC,CAACU,WAAF,CAAcC,eAAd,KAAkC,UAArD;AACD,KA3BoF;;AAAA,6CA6B1DX,CAAD,IAAqC;AAC7D,UAAI,KAAKC,KAAL,CAAWW,cAAf,EAA+B;AAC7B,aAAKX,KAAL,CAAWW,cAAX,CAA0BZ,CAA1B;AACD;AACF,KAjCoF;;AAAA,qCAuCnEa,YAAD,IAA0B;AACzCC,6BAAUC,0BAAV,CACEC,qBAAYC,cAAZ,CAA2B,IAA3B,CADF,EAEE,6CAAuBC,QAAvB,CAAgCC,OAFlC,EAGE,CAACN,YAAD,CAHF;AAKD,KA7CoF;;AAAA,qDAmDnDA,YAAD,IAA0B;AACzDC,6BAAUC,0BAAV,CACEC,qBAAYC,cAAZ,CAA2B,IAA3B,CADF,EAEE,6CAAuBC,QAAvB,CAAgCE,uBAFlC,EAGE,CAACP,YAAD,CAHF;AAKD,KAzDoF;;AAAA,8CAgE1DQ,aAAD,IAA4B;AACpDP,6BAAUC,0BAAV,CACEC,qBAAYC,cAAZ,CAA2B,IAA3B,CADF,EAEE,6CAAuBC,QAAvB,CAAgCI,gBAFlC,EAGE,CAACD,aAAD,CAHF;AAKD,KAtEoF;;AAAA,8DAwE1C,MAAM;AAC/C,aAAO,KAAKZ,WAAZ;AACD,KA1EoF;AAAA;;AA4EnD,MAAtBc,sBAAsB,GAAG;AACnC,UAAMC,uBAAuB,GAC3B,CAAC,KAAKvB,KAAL,CAAWwB,eAAZ,IAA+B,KAAKxB,KAAL,CAAWwB,eAAX,KAA+B,QADhE;;AAGA,QAAID,uBAAJ,EAA6B;AAC3B,aAAOE,yBAAYC,KAAZ,GAAoB,KAApB,GAA4B,KAAnC;AACD,KAFD,MAEO;AACL,aAAO,KAAK1B,KAAL,CAAWwB,eAAlB;AACD;AACF;;AAEDG,EAAAA,MAAM,GAAG;AACP,UAAM;AAAEC,MAAAA,QAAF;AAAYC,MAAAA,YAAZ;AAA0B,SAAGC;AAA7B,QAAsC,KAAK9B,KAAjD;AAEA,wBACE,6BAAC,qCAAD,eACM8B,IADN;AAEE;AACA,MAAA,KAAK,EAAE,KAAK9B,KAAL,CAAW+B,KAHpB;AAIE,MAAA,eAAe,EAAE,KAAKT,sBAJxB;AAKE,MAAA,YAAY,EAAE,KAAKU,aALrB;AAME,MAAA,wBAAwB,EAAE,KAAKC,yBANjC;AAOE,MAAA,cAAc,EAAE,KAAKC,eAPvB;AAQE,MAAA,+BAA+B,EAAE,KAAKC,gCARxC;AASE,MAAA,QAAQ,EAAE,wCAA4BP,QAA5B;AATZ,OADF;AAaD;;AAvGoF","sourcesContent":["import React, { ReactElement } from 'react';\nimport { Platform, UIManager, Keyboard } from 'react-native';\nimport ReactNative, { I18nManager } from 'react-native';\nimport type {\n PagerViewOnPageScrollEvent,\n PagerViewOnPageSelectedEvent,\n PageScrollStateChangedNativeEvent,\n PagerViewProps,\n} from './types';\n\nimport { childrenWithOverriddenStyle } from './utils';\nimport { getViewManagerConfig, PagerViewViewManager } from './PagerViewNative';\n\n/**\n * Container that allows to flip left and right between child views. Each\n * child view of the `PagerView` will be treated as a separate page\n * and will be stretched to fill the `PagerView`.\n *\n * It is important all children are ``s and not composite components.\n * You can set style properties like `padding` or `backgroundColor` for each\n * child. It is also important that each child have a `key` prop.\n *\n * Example:\n *\n * ```\n * render: function() {\n * return (\n * \n * \n * First page\n * \n * \n * Second page\n * \n * \n * );\n * }\n *\n * ...\n *\n * var styles = {\n * ...\n * PagerView: {\n * flex: 1\n * },\n * pageStyle: {\n * alignItems: 'center',\n * padding: 20,\n * }\n * }\n * ```\n */\n\nexport class PagerView extends React.Component {\n private isScrolling = false;\n private PagerView = React.createRef();\n\n public getInnerViewNode = (): ReactElement => {\n return this.PagerView.current!.getInnerViewNode();\n };\n\n private _onPageScroll = (e: PagerViewOnPageScrollEvent) => {\n if (this.props.onPageScroll) {\n this.props.onPageScroll(e);\n }\n // Not implemented on iOS yet\n if (Platform.OS === 'android') {\n if (this.props.keyboardDismissMode === 'on-drag') {\n Keyboard.dismiss();\n }\n }\n };\n\n private _onPageScrollStateChanged = (\n e: PageScrollStateChangedNativeEvent\n ) => {\n if (this.props.onPageScrollStateChanged) {\n this.props.onPageScrollStateChanged(e);\n }\n this.isScrolling = e.nativeEvent.pageScrollState === 'dragging';\n };\n\n private _onPageSelected = (e: PagerViewOnPageSelectedEvent) => {\n if (this.props.onPageSelected) {\n this.props.onPageSelected(e);\n }\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will be animated.\n */\n public setPage = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPage,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will *not* be animated.\n */\n public setPageWithoutAnimation = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPageWithoutAnimation,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to enable/disable scroll imperatively\n * The recommended way is using the scrollEnabled prop, however, there might be a case where a\n * imperative solution is more useful (e.g. for not blocking an animation)\n */\n public setScrollEnabled = (scrollEnabled: boolean) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setScrollEnabled,\n [scrollEnabled]\n );\n };\n\n private _onMoveShouldSetResponderCapture = () => {\n return this.isScrolling;\n };\n\n private get deducedLayoutDirection() {\n const shouldUseDeviceRtlSetup =\n !this.props.layoutDirection || this.props.layoutDirection === 'locale';\n\n if (shouldUseDeviceRtlSetup) {\n return I18nManager.isRTL ? 'rtl' : 'ltr';\n } else {\n return this.props.layoutDirection;\n }\n }\n\n render() {\n const { children, forwardedRef, ...rest } = this.props;\n\n return (\n \n );\n }\n}\n"]} -\ No newline at end of file -diff --git a/node_modules/react-native-pager-view/lib/module/PagerView.js b/node_modules/react-native-pager-view/lib/module/PagerView.js -index dc1ddc5..f95b242 100644 ---- a/node_modules/react-native-pager-view/lib/module/PagerView.js -+++ b/node_modules/react-native-pager-view/lib/module/PagerView.js -@@ -116,17 +116,20 @@ export class PagerView extends React.Component { - } - - render() { -- return /*#__PURE__*/React.createElement(PagerViewViewManager, _extends({}, this.props, { -- ref: this.PagerView -- /** TODO: Fix ref type */ -- , -+ const { -+ children, -+ forwardedRef, -+ ...rest -+ } = this.props; -+ return /*#__PURE__*/React.createElement(PagerViewViewManager, _extends({}, rest, { -+ // ref={this.PagerView as any /** TODO: Fix ref type */} - style: this.props.style, - layoutDirection: this.deducedLayoutDirection, - onPageScroll: this._onPageScroll, - onPageScrollStateChanged: this._onPageScrollStateChanged, - onPageSelected: this._onPageSelected, - onMoveShouldSetResponderCapture: this._onMoveShouldSetResponderCapture, -- children: childrenWithOverriddenStyle(this.props.children) -+ children: childrenWithOverriddenStyle(children) - })); - } - -diff --git a/node_modules/react-native-pager-view/lib/module/PagerView.js.map b/node_modules/react-native-pager-view/lib/module/PagerView.js.map -index 6c26c01..ea08ccc 100644 ---- a/node_modules/react-native-pager-view/lib/module/PagerView.js.map -+++ b/node_modules/react-native-pager-view/lib/module/PagerView.js.map -@@ -1 +1 @@ --{"version":3,"sources":["PagerView.tsx"],"names":["React","Platform","UIManager","Keyboard","ReactNative","I18nManager","childrenWithOverriddenStyle","getViewManagerConfig","PagerViewViewManager","PagerView","Component","createRef","current","getInnerViewNode","e","props","onPageScroll","OS","keyboardDismissMode","dismiss","onPageScrollStateChanged","isScrolling","nativeEvent","pageScrollState","onPageSelected","selectedPage","dispatchViewManagerCommand","findNodeHandle","Commands","setPage","setPageWithoutAnimation","scrollEnabled","setScrollEnabled","deducedLayoutDirection","shouldUseDeviceRtlSetup","layoutDirection","isRTL","render","style","_onPageScroll","_onPageScrollStateChanged","_onPageSelected","_onMoveShouldSetResponderCapture","children"],"mappings":";;;;AAAA,OAAOA,KAAP,MAAoC,OAApC;AACA,SAASC,QAAT,EAAmBC,SAAnB,EAA8BC,QAA9B,QAA8C,cAA9C;AACA,OAAOC,WAAP,IAAsBC,WAAtB,QAAyC,cAAzC;AAQA,SAASC,2BAAT,QAA4C,SAA5C;AACA,SAASC,oBAAT,EAA+BC,oBAA/B,QAA2D,mBAA3D;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,OAAO,MAAMC,SAAN,SAAwBT,KAAK,CAACU,SAA9B,CAAwD;AAAA;AAAA;;AAAA,yCACvC,KADuC;;AAAA,oDAEzCV,KAAK,CAACW,SAAN,EAFyC;;AAAA,8CAInC,MAAoB;AAC5C,aAAO,KAAKF,SAAL,CAAeG,OAAf,CAAwBC,gBAAxB,EAAP;AACD,KAN4D;;AAAA,2CAQpCC,CAAD,IAAmC;AACzD,UAAI,KAAKC,KAAL,CAAWC,YAAf,EAA6B;AAC3B,aAAKD,KAAL,CAAWC,YAAX,CAAwBF,CAAxB;AACD,OAHwD,CAIzD;;;AACA,UAAIb,QAAQ,CAACgB,EAAT,KAAgB,SAApB,EAA+B;AAC7B,YAAI,KAAKF,KAAL,CAAWG,mBAAX,KAAmC,SAAvC,EAAkD;AAChDf,UAAAA,QAAQ,CAACgB,OAAT;AACD;AACF;AACF,KAlB4D;;AAAA,uDAqB3DL,CADkC,IAE/B;AACH,UAAI,KAAKC,KAAL,CAAWK,wBAAf,EAAyC;AACvC,aAAKL,KAAL,CAAWK,wBAAX,CAAoCN,CAApC;AACD;;AACD,WAAKO,WAAL,GAAmBP,CAAC,CAACQ,WAAF,CAAcC,eAAd,KAAkC,UAArD;AACD,KA3B4D;;AAAA,6CA6BlCT,CAAD,IAAqC;AAC7D,UAAI,KAAKC,KAAL,CAAWS,cAAf,EAA+B;AAC7B,aAAKT,KAAL,CAAWS,cAAX,CAA0BV,CAA1B;AACD;AACF,KAjC4D;;AAAA,qCAuC3CW,YAAD,IAA0B;AACzCvB,MAAAA,SAAS,CAACwB,0BAAV,CACEtB,WAAW,CAACuB,cAAZ,CAA2B,IAA3B,CADF,EAEEpB,oBAAoB,GAAGqB,QAAvB,CAAgCC,OAFlC,EAGE,CAACJ,YAAD,CAHF;AAKD,KA7C4D;;AAAA,qDAmD3BA,YAAD,IAA0B;AACzDvB,MAAAA,SAAS,CAACwB,0BAAV,CACEtB,WAAW,CAACuB,cAAZ,CAA2B,IAA3B,CADF,EAEEpB,oBAAoB,GAAGqB,QAAvB,CAAgCE,uBAFlC,EAGE,CAACL,YAAD,CAHF;AAKD,KAzD4D;;AAAA,8CAgElCM,aAAD,IAA4B;AACpD7B,MAAAA,SAAS,CAACwB,0BAAV,CACEtB,WAAW,CAACuB,cAAZ,CAA2B,IAA3B,CADF,EAEEpB,oBAAoB,GAAGqB,QAAvB,CAAgCI,gBAFlC,EAGE,CAACD,aAAD,CAHF;AAKD,KAtE4D;;AAAA,8DAwElB,MAAM;AAC/C,aAAO,KAAKV,WAAZ;AACD,KA1E4D;AAAA;;AA4E3B,MAAtBY,sBAAsB,GAAG;AACnC,UAAMC,uBAAuB,GAC3B,CAAC,KAAKnB,KAAL,CAAWoB,eAAZ,IAA+B,KAAKpB,KAAL,CAAWoB,eAAX,KAA+B,QADhE;;AAGA,QAAID,uBAAJ,EAA6B;AAC3B,aAAO7B,WAAW,CAAC+B,KAAZ,GAAoB,KAApB,GAA4B,KAAnC;AACD,KAFD,MAEO;AACL,aAAO,KAAKrB,KAAL,CAAWoB,eAAlB;AACD;AACF;;AAEDE,EAAAA,MAAM,GAAG;AACP,wBACE,oBAAC,oBAAD,eACM,KAAKtB,KADX;AAEE,MAAA,GAAG,EAAE,KAAKN;AAAiB;AAF7B;AAGE,MAAA,KAAK,EAAE,KAAKM,KAAL,CAAWuB,KAHpB;AAIE,MAAA,eAAe,EAAE,KAAKL,sBAJxB;AAKE,MAAA,YAAY,EAAE,KAAKM,aALrB;AAME,MAAA,wBAAwB,EAAE,KAAKC,yBANjC;AAOE,MAAA,cAAc,EAAE,KAAKC,eAPvB;AAQE,MAAA,+BAA+B,EAAE,KAAKC,gCARxC;AASE,MAAA,QAAQ,EAAEpC,2BAA2B,CAAC,KAAKS,KAAL,CAAW4B,QAAZ;AATvC,OADF;AAaD;;AArG4D","sourcesContent":["import React, { ReactElement } from 'react';\nimport { Platform, UIManager, Keyboard } from 'react-native';\nimport ReactNative, { I18nManager } from 'react-native';\nimport type {\n PagerViewOnPageScrollEvent,\n PagerViewOnPageSelectedEvent,\n PageScrollStateChangedNativeEvent,\n PagerViewProps,\n} from './types';\n\nimport { childrenWithOverriddenStyle } from './utils';\nimport { getViewManagerConfig, PagerViewViewManager } from './PagerViewNative';\n\n/**\n * Container that allows to flip left and right between child views. Each\n * child view of the `PagerView` will be treated as a separate page\n * and will be stretched to fill the `PagerView`.\n *\n * It is important all children are ``s and not composite components.\n * You can set style properties like `padding` or `backgroundColor` for each\n * child. It is also important that each child have a `key` prop.\n *\n * Example:\n *\n * ```\n * render: function() {\n * return (\n * \n * \n * First page\n * \n * \n * Second page\n * \n * \n * );\n * }\n *\n * ...\n *\n * var styles = {\n * ...\n * PagerView: {\n * flex: 1\n * },\n * pageStyle: {\n * alignItems: 'center',\n * padding: 20,\n * }\n * }\n * ```\n */\n\nexport class PagerView extends React.Component {\n private isScrolling = false;\n private PagerView = React.createRef();\n\n public getInnerViewNode = (): ReactElement => {\n return this.PagerView.current!.getInnerViewNode();\n };\n\n private _onPageScroll = (e: PagerViewOnPageScrollEvent) => {\n if (this.props.onPageScroll) {\n this.props.onPageScroll(e);\n }\n // Not implemented on iOS yet\n if (Platform.OS === 'android') {\n if (this.props.keyboardDismissMode === 'on-drag') {\n Keyboard.dismiss();\n }\n }\n };\n\n private _onPageScrollStateChanged = (\n e: PageScrollStateChangedNativeEvent\n ) => {\n if (this.props.onPageScrollStateChanged) {\n this.props.onPageScrollStateChanged(e);\n }\n this.isScrolling = e.nativeEvent.pageScrollState === 'dragging';\n };\n\n private _onPageSelected = (e: PagerViewOnPageSelectedEvent) => {\n if (this.props.onPageSelected) {\n this.props.onPageSelected(e);\n }\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will be animated.\n */\n public setPage = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPage,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will *not* be animated.\n */\n public setPageWithoutAnimation = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPageWithoutAnimation,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to enable/disable scroll imperatively\n * The recommended way is using the scrollEnabled prop, however, there might be a case where a\n * imperative solution is more useful (e.g. for not blocking an animation)\n */\n public setScrollEnabled = (scrollEnabled: boolean) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setScrollEnabled,\n [scrollEnabled]\n );\n };\n\n private _onMoveShouldSetResponderCapture = () => {\n return this.isScrolling;\n };\n\n private get deducedLayoutDirection() {\n const shouldUseDeviceRtlSetup =\n !this.props.layoutDirection || this.props.layoutDirection === 'locale';\n\n if (shouldUseDeviceRtlSetup) {\n return I18nManager.isRTL ? 'rtl' : 'ltr';\n } else {\n return this.props.layoutDirection;\n }\n }\n\n render() {\n return (\n \n );\n }\n}\n"]} -\ No newline at end of file -+{"version":3,"sources":["PagerView.tsx"],"names":["React","Platform","UIManager","Keyboard","ReactNative","I18nManager","childrenWithOverriddenStyle","getViewManagerConfig","PagerViewViewManager","PagerView","Component","createRef","current","getInnerViewNode","e","props","onPageScroll","OS","keyboardDismissMode","dismiss","onPageScrollStateChanged","isScrolling","nativeEvent","pageScrollState","onPageSelected","selectedPage","dispatchViewManagerCommand","findNodeHandle","Commands","setPage","setPageWithoutAnimation","scrollEnabled","setScrollEnabled","deducedLayoutDirection","shouldUseDeviceRtlSetup","layoutDirection","isRTL","render","children","forwardedRef","rest","style","_onPageScroll","_onPageScrollStateChanged","_onPageSelected","_onMoveShouldSetResponderCapture"],"mappings":"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","sourcesContent":["import React, { ReactElement } from 'react';\nimport { Platform, UIManager, Keyboard } from 'react-native';\nimport ReactNative, { I18nManager } from 'react-native';\nimport type {\n PagerViewOnPageScrollEvent,\n PagerViewOnPageSelectedEvent,\n PageScrollStateChangedNativeEvent,\n PagerViewProps,\n} from './types';\n\nimport { childrenWithOverriddenStyle } from './utils';\nimport { getViewManagerConfig, PagerViewViewManager } from './PagerViewNative';\n\n/**\n * Container that allows to flip left and right between child views. Each\n * child view of the `PagerView` will be treated as a separate page\n * and will be stretched to fill the `PagerView`.\n *\n * It is important all children are ``s and not composite components.\n * You can set style properties like `padding` or `backgroundColor` for each\n * child. It is also important that each child have a `key` prop.\n *\n * Example:\n *\n * ```\n * render: function() {\n * return (\n * \n * \n * First page\n * \n * \n * Second page\n * \n * \n * );\n * }\n *\n * ...\n *\n * var styles = {\n * ...\n * PagerView: {\n * flex: 1\n * },\n * pageStyle: {\n * alignItems: 'center',\n * padding: 20,\n * }\n * }\n * ```\n */\n\nexport class PagerView extends React.Component {\n private isScrolling = false;\n private PagerView = React.createRef();\n\n public getInnerViewNode = (): ReactElement => {\n return this.PagerView.current!.getInnerViewNode();\n };\n\n private _onPageScroll = (e: PagerViewOnPageScrollEvent) => {\n if (this.props.onPageScroll) {\n this.props.onPageScroll(e);\n }\n // Not implemented on iOS yet\n if (Platform.OS === 'android') {\n if (this.props.keyboardDismissMode === 'on-drag') {\n Keyboard.dismiss();\n }\n }\n };\n\n private _onPageScrollStateChanged = (\n e: PageScrollStateChangedNativeEvent\n ) => {\n if (this.props.onPageScrollStateChanged) {\n this.props.onPageScrollStateChanged(e);\n }\n this.isScrolling = e.nativeEvent.pageScrollState === 'dragging';\n };\n\n private _onPageSelected = (e: PagerViewOnPageSelectedEvent) => {\n if (this.props.onPageSelected) {\n this.props.onPageSelected(e);\n }\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will be animated.\n */\n public setPage = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPage,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to scroll to a specific page in the PagerView.\n * The transition between pages will *not* be animated.\n */\n public setPageWithoutAnimation = (selectedPage: number) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setPageWithoutAnimation,\n [selectedPage]\n );\n };\n\n /**\n * A helper function to enable/disable scroll imperatively\n * The recommended way is using the scrollEnabled prop, however, there might be a case where a\n * imperative solution is more useful (e.g. for not blocking an animation)\n */\n public setScrollEnabled = (scrollEnabled: boolean) => {\n UIManager.dispatchViewManagerCommand(\n ReactNative.findNodeHandle(this),\n getViewManagerConfig().Commands.setScrollEnabled,\n [scrollEnabled]\n );\n };\n\n private _onMoveShouldSetResponderCapture = () => {\n return this.isScrolling;\n };\n\n private get deducedLayoutDirection() {\n const shouldUseDeviceRtlSetup =\n !this.props.layoutDirection || this.props.layoutDirection === 'locale';\n\n if (shouldUseDeviceRtlSetup) {\n return I18nManager.isRTL ? 'rtl' : 'ltr';\n } else {\n return this.props.layoutDirection;\n }\n }\n\n render() {\n const { children, forwardedRef, ...rest } = this.props;\n\n return (\n \n );\n }\n}\n"]} -\ No newline at end of file -diff --git a/node_modules/react-native-pager-view/lib/typescript/PagerView.d.ts b/node_modules/react-native-pager-view/lib/typescript/PagerView.d.ts -index f70d3bc..5610963 100644 ---- a/node_modules/react-native-pager-view/lib/typescript/PagerView.d.ts -+++ b/node_modules/react-native-pager-view/lib/typescript/PagerView.d.ts -@@ -41,7 +41,9 @@ import type { PagerViewProps } from './types'; - * } - * ``` - */ --export declare class PagerView extends React.Component { -+export declare class PagerView extends React.Component { - private isScrolling; - private PagerView; - getInnerViewNode: () => ReactElement; -diff --git a/node_modules/react-native-pager-view/lib/typescript/__tests__/index.test.d.ts b/node_modules/react-native-pager-view/lib/typescript/__tests__/index.test.d.ts -new file mode 100644 -index 0000000..e69de29 -diff --git a/node_modules/react-native-pager-view/src/PagerView.tsx b/node_modules/react-native-pager-view/src/PagerView.tsx -index f7585d5..ee9843b 100644 ---- a/node_modules/react-native-pager-view/src/PagerView.tsx -+++ b/node_modules/react-native-pager-view/src/PagerView.tsx -@@ -141,17 +141,19 @@ export class PagerView extends React.Component { - } - - render() { -+ const {children, forwardedRef, ...rest} = this.props; -+ - return ( - - ); - } \ No newline at end of file diff --git a/src/view/com/composer/Autocomplete.tsx b/src/view/com/composer/Autocomplete.tsx index 4ee527ee..2ccd0565 100644 --- a/src/view/com/composer/Autocomplete.tsx +++ b/src/view/com/composer/Autocomplete.tsx @@ -46,6 +46,7 @@ export function Autocomplete({ {items.map((item, i) => ( onSelect(item.handle)}> diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index dc0bec13..790e0f78 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -56,11 +56,12 @@ export const ComposePost = observer(function ComposePost({ const [isSelectingPhotos, setIsSelectingPhotos] = useState(false) const [selectedPhotos, setSelectedPhotos] = useState([]) - const autocompleteView = useMemo( + // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment + const autocompleteView = React.useMemo( () => new UserAutocompleteViewModel(store), [store], ) - const localPhotos = useMemo( + const localPhotos = React.useMemo( () => new UserLocalPhotosModel(store), [store], ) @@ -179,11 +180,14 @@ export const ComposePost = observer(function ComposePost({ return ( - + Cancel @@ -192,7 +196,9 @@ export const ComposePost = observer(function ComposePost({ ) : canPost ? ( - + diff --git a/src/view/com/composer/PhotoCarouselPicker.tsx b/src/view/com/composer/PhotoCarouselPicker.tsx index 6c6cd0a4..12dac582 100644 --- a/src/view/com/composer/PhotoCarouselPicker.tsx +++ b/src/view/com/composer/PhotoCarouselPicker.tsx @@ -85,21 +85,25 @@ export const PhotoCarouselPicker = ({ return ( {localPhotos.photos.map((item: any, index: number) => ( handleSelectPhoto(item.node.image.uri)}> diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx index 682a9990..e8f52f84 100644 --- a/src/view/com/composer/Prompt.tsx +++ b/src/view/com/composer/Prompt.tsx @@ -17,6 +17,7 @@ export function ComposePrompt({ const pal = usePalette('default') return ( + {selectedPhotos.length !== 0 && selectedPhotos.map((item, index) => ( handleRemovePhoto(item)} style={styles.removePhotoButton}> - + ))} diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx index 017bd08c..07f39744 100644 --- a/src/view/com/discover/SuggestedFollows.tsx +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useEffect, useState} from 'react' +import React, {useEffect, useState} from 'react' import { ActivityIndicator, FlatList, @@ -36,7 +36,8 @@ export const SuggestedFollows = observer( const store = useStores() const [follows, setFollows] = useState>({}) - const view = useMemo( + // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment + const view = React.useMemo( () => new SuggestedActorsViewModel(store), [], ) diff --git a/src/view/com/login/CreateAccount.tsx b/src/view/com/login/CreateAccount.tsx index b68d3859..83d17d37 100644 --- a/src/view/com/login/CreateAccount.tsx +++ b/src/view/com/login/CreateAccount.tsx @@ -171,7 +171,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { const isReady = !!email && !!password && !!handle && is13 return ( - + @@ -193,6 +193,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { @@ -235,6 +236,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { style={styles.groupContentIcon} /> void}) => { void}) => { void}) => { setIs13(!is13)}> @@ -339,7 +344,9 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { {isReady ? ( - + {isProcessing ? ( ) : ( @@ -347,7 +354,9 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { )} ) : !serviceDescription && error ? ( - + Retry ) : !serviceDescription ? ( diff --git a/src/view/com/login/Signin.tsx b/src/view/com/login/Signin.tsx index 03c634c4..f60b637b 100644 --- a/src/view/com/login/Signin.tsx +++ b/src/view/com/login/Signin.tsx @@ -69,7 +69,7 @@ export const Signin = ({onPressBack}: {onPressBack: () => void}) => { const onPressRetryConnect = () => setRetryDescribeTrigger({}) return ( - + @@ -194,8 +194,9 @@ const LoginForm = ({ const isReady = !!serviceDescription && !!handle && !!password return ( <> - + @@ -213,6 +214,7 @@ const LoginForm = ({ Forgot @@ -260,7 +264,9 @@ const LoginForm = ({ {!serviceDescription && error ? ( - + Retry ) : !serviceDescription ? ( @@ -271,7 +277,7 @@ const LoginForm = ({ ) : isProcessing ? ( ) : isReady ? ( - + Next ) : undefined} @@ -339,8 +345,9 @@ const ForgotPasswordForm = ({ Enter the email you used to create your account. We'll send you a "reset code" so you can set a new password. - + @@ -359,6 +366,7 @@ const ForgotPasswordForm = ({ Next ) : ( - + Next )} @@ -451,10 +459,11 @@ const SetNewPasswordForm = ({ You will receive an email with a "reset code." Enter that code here, then enter your new password. - + Next ) : ( - + Next )} diff --git a/src/view/com/notifications/InviteAccepter.tsx b/src/view/com/notifications/InviteAccepter.tsx index eefe7a27..a8789b17 100644 --- a/src/view/com/notifications/InviteAccepter.tsx +++ b/src/view/com/notifications/InviteAccepter.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' import LinearGradient from 'react-native-linear-gradient' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' @@ -13,7 +13,8 @@ import {s, colors, gradients} from '../../lib/styles' export function InviteAccepter({item}: {item: NotificationsViewItemModel}) { const store = useStores() - const [confirmationUri, setConfirmationUri] = useState('') + // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment + const [confirmationUri, setConfirmationUri] = React.useState('') const isMember = confirmationUri !== '' || store.me.memberships?.isMemberOf(item.author.did) const onPressAccept = async () => { @@ -54,7 +55,7 @@ export function InviteAccepter({item}: {item: NotificationsViewItemModel}) { return ( {!isMember ? ( - + ) : ( - + Invite accepted diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 5dbb6219..12d5a217 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react' +import React, {useEffect} from 'react' import {observer} from 'mobx-react-lite' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import { @@ -18,7 +18,8 @@ export const PostRepostedBy = observer(function PostRepostedBy({ uri: string }) { const store = useStores() - const [view, setView] = useState() + // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment + const [view, setView] = React.useState() useEffect(() => { if (view?.params.uri === uri) { diff --git a/src/view/com/post-thread/PostVotedBy.tsx b/src/view/com/post-thread/PostVotedBy.tsx index 17ed9f9f..af5bc247 100644 --- a/src/view/com/post-thread/PostVotedBy.tsx +++ b/src/view/com/post-thread/PostVotedBy.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react' +import React, {useEffect} from 'react' import {observer} from 'mobx-react-lite' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import { @@ -20,7 +20,7 @@ export const PostVotedBy = observer(function PostVotedBy({ direction: 'up' | 'down' }) { const store = useStores() - const [view, setView] = useState() + const [view, setView] = React.useState() useEffect(() => { if (view?.params.uri === uri) { diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 76f595cd..f3402428 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -25,6 +25,7 @@ export const Feed = observer(function Feed({ onPressCompose, onPressTryAgain, onScroll, + testID, }: { feed: FeedModel style?: StyleProp @@ -32,6 +33,7 @@ export const Feed = observer(function Feed({ onPressCompose: () => void onPressTryAgain?: () => void onScroll?: OnScrollCb + testID?: string }) { // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf // VirtualizedList: You have a large list that is slow to update - make sure your @@ -83,7 +85,7 @@ export const Feed = observer(function Feed({ ) return ( - + {!data && } {feed.isLoading && !data && } {feed.hasError && ( diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index e6e710ff..26939c7c 100644 --- a/src/view/com/profile/ProfileFollowers.tsx +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react' +import React, {useEffect} from 'react' import {observer} from 'mobx-react-lite' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import { @@ -19,7 +19,7 @@ export const ProfileFollowers = observer(function ProfileFollowers({ name: string }) { const store = useStores() - const [view, setView] = useState() + const [view, setView] = React.useState() useEffect(() => { if (view?.params.user === name) { diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index 73e765d1..03c5b13b 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react' +import React, {useEffect} from 'react' import {observer} from 'mobx-react-lite' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import { @@ -10,7 +10,7 @@ import {Link} from '../util/Link' import {Text} from '../util/text/Text' import {ErrorMessage} from '../util/error/ErrorMessage' import {UserAvatar} from '../util/UserAvatar' -import {s, colors} from '../../lib/styles' +import {s} from '../../lib/styles' import {usePalette} from '../../lib/hooks/usePalette' export const ProfileFollows = observer(function ProfileFollows({ @@ -19,7 +19,7 @@ export const ProfileFollows = observer(function ProfileFollows({ name: string }) { const store = useStores() - const [view, setView] = useState() + const [view, setView] = React.useState() useEffect(() => { if (view?.params.user === name) { diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index a4d7c7a9..4fd76695 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -147,7 +147,7 @@ export const ProfileHeader = observer(function ProfileHeader({ // = if (view.hasError) { return ( - + {view.error} ) @@ -192,6 +192,7 @@ export const ProfileHeader = observer(function ProfileHeader({ {isMe ? ( @@ -214,7 +215,9 @@ export const ProfileHeader = observer(function ProfileHeader({ ) : ( - + @@ -268,6 +272,7 @@ export const ProfileHeader = observer(function ProfileHeader({ {view.isUser ? ( @@ -280,6 +285,7 @@ export const ProfileHeader = observer(function ProfileHeader({ ) : undefined} {view.isScene ? ( @@ -350,7 +356,9 @@ export const ProfileHeader = observer(function ProfileHeader({ {view.isScene && view.creator === store.me.did ? ( - + ) : undefined} () + // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment + const [view, setView] = React.useState() useEffect(() => { if (view?.params.actor === name) { @@ -37,7 +38,7 @@ export const ProfileMembers = observer(function ProfileMembers({ view.params.actor !== name ) { return ( - + ) @@ -68,7 +69,7 @@ export const ProfileMembers = observer(function ProfileMembers({ /> ) return ( - + item._reactKey} diff --git a/src/view/com/util/PostCtrls.tsx b/src/view/com/util/PostCtrls.tsx index c0ef412d..25f17159 100644 --- a/src/view/com/util/PostCtrls.tsx +++ b/src/view/com/util/PostCtrls.tsx @@ -115,6 +115,7 @@ export function PostCtrls(opts: PostCtrlsOpts) { @@ -130,6 +131,7 @@ export function PostCtrls(opts: PostCtrlsOpts) { @@ -156,6 +158,7 @@ export function PostCtrls(opts: PostCtrlsOpts) { diff --git a/src/view/com/util/error/ErrorMessage.tsx b/src/view/com/util/error/ErrorMessage.tsx index 905268d3..ee31ad2c 100644 --- a/src/view/com/util/error/ErrorMessage.tsx +++ b/src/view/com/util/error/ErrorMessage.tsx @@ -8,7 +8,6 @@ import { } from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Text} from '../text/Text' -import {colors} from '../../../lib/styles' import {useTheme} from '../../../lib/ThemeContext' import {usePalette} from '../../../lib/hooks/usePalette' @@ -26,7 +25,7 @@ export function ErrorMessage({ const theme = useTheme() const pal = usePalette('error') return ( - + @@ -38,7 +37,10 @@ export function ErrorMessage({ {message} {onPressTryAgain && ( - + void + testID?: string }) { const theme = useTheme() const pal = usePalette('error') return ( - + {message} {details && ( diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx index c81ccf6c..33387f89 100644 --- a/src/view/com/util/forms/DropdownButton.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -75,7 +75,8 @@ export function DropdownButton({ style={style} onPress={onPress} hitSlop={HITSLOP} - ref={ref}> + // Fix an issue where specific references cause runtime error in jest environment + ref={process.env.JEST_WORKER_ID != null ? null : ref}> {children} ) diff --git a/src/view/screens/Contacts.tsx b/src/view/screens/Contacts.tsx index 8de56d79..b22e52fe 100644 --- a/src/view/screens/Contacts.tsx +++ b/src/view/screens/Contacts.tsx @@ -25,7 +25,9 @@ export const Contacts = ({navIdx, visible, params}: ScreenParams) => { return ( - Contacts + + Contacts + @@ -35,6 +37,7 @@ export const Contacts = ({navIdx, visible, params}: ScreenParams) => { style={styles.searchIcon} /> (false) + const [wasVisible, setWasVisible] = React.useState(false) const {appState} = useAppState({ onForeground: () => doPoll(true), }) - const doPoll = (knownActive = false) => { - if ((!knownActive && appState !== 'active') || !visible) { - return - } - if (store.me.mainFeed.isLoading) { - return - } - store.log.debug('Polling home feed') - store.me.mainFeed.checkForLatest().catch(e => { - store.log.error('Failed to poll feed', e) - }) - } + const doPoll = React.useCallback( + (knownActive = false) => { + if ((!knownActive && appState !== 'active') || !visible) { + return + } + if (store.me.mainFeed.isLoading) { + return + } + store.log.debug('Polling home feed') + store.me.mainFeed.checkForLatest().catch(e => { + store.log.error('Failed to poll feed', e) + }) + }, + [appState, visible, store], + ) useEffect(() => { - let aborted = false const pollInterval = setInterval(() => doPoll(), 15e3) if (!visible) { + setWasVisible(false) + return + } else if (wasVisible) { return } + setWasVisible(true) - if (hasSetup) { - store.log.debug('Updating home feed') + store.nav.setTitle(navIdx, 'Home') + store.log.debug('Updating home feed') + if (store.me.mainFeed.hasContent) { store.me.mainFeed.update() - doPoll() } else { - store.nav.setTitle(navIdx, 'Home') - store.log.debug('Fetching home feed') - store.me.mainFeed.setup().then(() => { - if (aborted) return - setHasSetup(true) - }) + store.me.mainFeed.setup() } return () => { clearInterval(pollInterval) - aborted = true } - }, [visible, store]) + }, [visible, store, navIdx, doPoll, wasVisible]) const onPressCompose = () => { store.shell.openComposer({}) @@ -82,6 +82,7 @@ export const Home = observer(function Home({ Bluesky [ private beta ] - - + + Create a new account @@ -60,7 +63,10 @@ const SigninOrCreateAccount = ({ or - + Sign in diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx index 3591b696..79477fa9 100644 --- a/src/view/screens/NotFound.tsx +++ b/src/view/screens/NotFound.tsx @@ -7,7 +7,7 @@ import {useStores} from '../../state' export const NotFound = () => { const stores = useStores() return ( - + { paddingTop: 100, }}> Page not found -