Unit Testing (#35)
* add testing lib * remove coverage folder from git * finished basic test setup * fix tests typescript and import paths * add first snapshot * testing utils * rename test files; update script flags; ++tests * testing utils functions * testing downloadAndResize wip * remove download test * specify unwanted coverage paths; remove update snapshots flag * fix strings tests * testing downloadAndResize method * increasing testing * fixing snapshots wip * fixed shell mobile snapshot * adding snapshots for the screens * fix onboard snapshot * fix typescript issues * fix TabsSelector snapshot * Account for testing device's locale in ago() tests * Remove platform detection on regex * mocking store state wip * mocking store state * increasing test coverage * increasing test coverage * increasing test coverage on src/screens * src/screens (except for profile) above 80% cov * testing profile screen wip * increase coverage on Menu and TabsSelector * mocking profile ui state wip * mocking profile ui state wip * fixing mobileshell tests wip * snapshots using testing-library * fixing profile tests wip * removing mobile shell tests * src/view/com tests wip * remove unnecessary patch-package * fixed profile test error * clear mocks after every test * fix base mocked store values (getters) * fix base mocked store values (hasLoaded, nonReplyFeed) * profile screen above 80% coverage * testing custom hooks * improving composer coverage * fix tests after merge * finishing composer coverage * improving src/com/discover coverage * improve src/view/com/login coverage fix SuggestedFollows tests adding some comments * fix SuggestedFollows tests * improve src/view/com/profile coverage extra minor fixes * improve src/view/com/notifications coverage * update coverage ignore patterns * rename errorMessageTryAgainButton increase SuggestedFollows converage * improve src/view/com/posts coverage * improve src/view/com/onboard coverage * update snapshot * improve src/view/com/post coverage * improve src/view/com/post-thread coverage rename ErrorMessage tests test Debug and Log components * init testing state * testing root-store * updating comments * small fixes * removed extra console logs * improve src/state/models coverage refactor rootStore rename some spies * adding cleanup method after tests * improve src/state/models coverage * improve src/state/models coverage * improve src/state/models coverage * improve src/state/models coverage * test setInterval in setupState * Clean up tests and update Home screen state management * Remove some tests we dont need * Remove snapshot tests * Remove any tests that dont demonstrate clear value * Cleanup Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
parent
11c861d2d3
commit
5abcc8e336
95 changed files with 2852 additions and 9936 deletions
|
@ -46,6 +46,7 @@ export function Autocomplete({
|
|||
<Animated.View style={[styles.outer, pal.view, pal.border, topAnimStyle]}>
|
||||
{items.map((item, i) => (
|
||||
<TouchableOpacity
|
||||
testID="autocompleteButton"
|
||||
key={i}
|
||||
style={[pal.border, styles.item]}
|
||||
onPress={() => onSelect(item.handle)}>
|
||||
|
|
|
@ -56,11 +56,12 @@ export const ComposePost = observer(function ComposePost({
|
|||
const [isSelectingPhotos, setIsSelectingPhotos] = useState(false)
|
||||
const [selectedPhotos, setSelectedPhotos] = useState<string[]>([])
|
||||
|
||||
const autocompleteView = useMemo<UserAutocompleteViewModel>(
|
||||
// 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<UserAutocompleteViewModel>(
|
||||
() => new UserAutocompleteViewModel(store),
|
||||
[store],
|
||||
)
|
||||
const localPhotos = useMemo<UserLocalPhotosModel>(
|
||||
const localPhotos = React.useMemo<UserLocalPhotosModel>(
|
||||
() => new UserLocalPhotosModel(store),
|
||||
[store],
|
||||
)
|
||||
|
@ -179,11 +180,14 @@ export const ComposePost = observer(function ComposePost({
|
|||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
testID="composePostView"
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={[pal.view, styles.outer]}>
|
||||
<SafeAreaView style={s.flex1}>
|
||||
<View style={styles.topbar}>
|
||||
<TouchableOpacity onPress={onPressCancel}>
|
||||
<TouchableOpacity
|
||||
testID="composerCancelButton"
|
||||
onPress={onPressCancel}>
|
||||
<Text style={[pal.link, s.f18]}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
|
@ -192,7 +196,9 @@ export const ComposePost = observer(function ComposePost({
|
|||
<ActivityIndicator />
|
||||
</View>
|
||||
) : canPost ? (
|
||||
<TouchableOpacity onPress={onPressPublish}>
|
||||
<TouchableOpacity
|
||||
testID="composerPublishButton"
|
||||
onPress={onPressPublish}>
|
||||
<LinearGradient
|
||||
colors={[gradients.primary.start, gradients.primary.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
|
@ -257,6 +263,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
size={50}
|
||||
/>
|
||||
<TextInput
|
||||
testID="composerTextInput"
|
||||
ref={textInput}
|
||||
multiline
|
||||
scrollEnabled
|
||||
|
@ -283,6 +290,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
)}
|
||||
<View style={[pal.border, styles.bottomBar]}>
|
||||
<TouchableOpacity
|
||||
testID="composerSelectPhotosButton"
|
||||
onPress={onPressSelectPhotos}
|
||||
style={[s.pl5]}
|
||||
hitSlop={HITSLOP}>
|
||||
|
|
|
@ -85,21 +85,25 @@ export const PhotoCarouselPicker = ({
|
|||
|
||||
return (
|
||||
<ScrollView
|
||||
testID="photoCarouselPickerView"
|
||||
horizontal
|
||||
style={[pal.view, styles.photosContainer]}
|
||||
showsHorizontalScrollIndicator={false}>
|
||||
<TouchableOpacity
|
||||
testID="openCameraButton"
|
||||
style={[styles.galleryButton, pal.border, styles.photo]}
|
||||
onPress={handleOpenCamera}>
|
||||
<FontAwesomeIcon icon="camera" size={24} style={pal.link} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
testID="openGalleryButton"
|
||||
style={[styles.galleryButton, pal.border, styles.photo]}
|
||||
onPress={handleOpenGallery}>
|
||||
<FontAwesomeIcon icon="image" style={pal.link} size={24} />
|
||||
</TouchableOpacity>
|
||||
{localPhotos.photos.map((item: any, index: number) => (
|
||||
<TouchableOpacity
|
||||
testID="openSelectPhotoButton"
|
||||
key={`local-image-${index}`}
|
||||
style={[pal.border, styles.photoButton]}
|
||||
onPress={() => handleSelectPhoto(item.node.image.uri)}>
|
||||
|
|
|
@ -17,6 +17,7 @@ export function ComposePrompt({
|
|||
const pal = usePalette('default')
|
||||
return (
|
||||
<TouchableOpacity
|
||||
testID="composePromptButton"
|
||||
style={[
|
||||
pal.view,
|
||||
pal.border,
|
||||
|
|
|
@ -25,13 +25,14 @@ export const SelectedPhoto = ({
|
|||
)
|
||||
|
||||
return selectedPhotos.length !== 0 ? (
|
||||
<View style={styles.imageContainer}>
|
||||
<View testID="selectedPhotosView" style={styles.imageContainer}>
|
||||
{selectedPhotos.length !== 0 &&
|
||||
selectedPhotos.map((item, index) => (
|
||||
<View
|
||||
key={`selected-image-${index}`}
|
||||
style={[styles.image, imageStyle]}>
|
||||
<TouchableOpacity
|
||||
testID="removePhotoButton"
|
||||
onPress={() => handleRemovePhoto(item)}
|
||||
style={styles.removePhotoButton}>
|
||||
<FontAwesomeIcon
|
||||
|
@ -41,7 +42,11 @@ export const SelectedPhoto = ({
|
|||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Image style={[styles.image, imageStyle]} source={{uri: item}} />
|
||||
<Image
|
||||
testID="selectedPhotoImage"
|
||||
style={[styles.image, imageStyle]}
|
||||
source={{uri: item}}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
|
|
@ -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<Record<string, string>>({})
|
||||
|
||||
const view = useMemo<SuggestedActorsViewModel>(
|
||||
// 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<SuggestedActorsViewModel>(
|
||||
() => new SuggestedActorsViewModel(store),
|
||||
[],
|
||||
)
|
||||
|
|
|
@ -171,7 +171,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
|
||||
const isReady = !!email && !!password && !!handle && is13
|
||||
return (
|
||||
<ScrollView style={{flex: 1}}>
|
||||
<ScrollView testID="createAccount" style={{flex: 1}}>
|
||||
<KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
|
||||
<View style={styles.logoHero}>
|
||||
<Logo />
|
||||
|
@ -193,6 +193,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
<View style={styles.groupContent}>
|
||||
<FontAwesomeIcon icon="globe" style={styles.groupContentIcon} />
|
||||
<TouchableOpacity
|
||||
testID="registerSelectServiceButton"
|
||||
style={styles.textBtn}
|
||||
onPress={onPressSelectService}>
|
||||
<Text style={styles.textBtnLabel}>
|
||||
|
@ -235,6 +236,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
style={styles.groupContentIcon}
|
||||
/>
|
||||
<TextInput
|
||||
testID="registerEmailInput"
|
||||
style={[styles.textInput]}
|
||||
placeholder="Email address"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -248,6 +250,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
<View style={styles.groupContent}>
|
||||
<FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
|
||||
<TextInput
|
||||
testID="registerPasswordInput"
|
||||
style={[styles.textInput]}
|
||||
placeholder="Choose your password"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -273,6 +276,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
<View style={styles.groupContent}>
|
||||
<FontAwesomeIcon icon="at" style={styles.groupContentIcon} />
|
||||
<TextInput
|
||||
testID="registerHandleInput"
|
||||
style={[styles.textInput]}
|
||||
placeholder="eg alice"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -317,6 +321,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
</View>
|
||||
<View style={styles.groupContent}>
|
||||
<TouchableOpacity
|
||||
testID="registerIs13Input"
|
||||
style={styles.textBtn}
|
||||
onPress={() => setIs13(!is13)}>
|
||||
<View style={is13 ? styles.checkboxFilled : styles.checkbox}>
|
||||
|
@ -339,7 +344,9 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
{isReady ? (
|
||||
<TouchableOpacity onPress={onPressNext}>
|
||||
<TouchableOpacity
|
||||
testID="createAccountButton"
|
||||
onPress={onPressNext}>
|
||||
{isProcessing ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
|
@ -347,7 +354,9 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
)}
|
||||
</TouchableOpacity>
|
||||
) : !serviceDescription && error ? (
|
||||
<TouchableOpacity onPress={onPressRetryConnect}>
|
||||
<TouchableOpacity
|
||||
testID="registerRetryButton"
|
||||
onPress={onPressRetryConnect}>
|
||||
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Retry</Text>
|
||||
</TouchableOpacity>
|
||||
) : !serviceDescription ? (
|
||||
|
|
|
@ -69,7 +69,7 @@ export const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
|||
const onPressRetryConnect = () => setRetryDescribeTrigger({})
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
|
||||
<KeyboardAvoidingView testID="signIn" behavior="padding" style={{flex: 1}}>
|
||||
<View style={styles.logoHero}>
|
||||
<Logo />
|
||||
</View>
|
||||
|
@ -194,8 +194,9 @@ const LoginForm = ({
|
|||
const isReady = !!serviceDescription && !!handle && !!password
|
||||
return (
|
||||
<>
|
||||
<View style={styles.group}>
|
||||
<View testID="loginFormView" style={styles.group}>
|
||||
<TouchableOpacity
|
||||
testID="loginSelectServiceButton"
|
||||
style={[styles.groupTitle, {paddingRight: 0, paddingVertical: 6}]}
|
||||
onPress={onPressSelectService}>
|
||||
<Text style={[s.flex1, s.white, s.f18, s.bold]} numberOfLines={1}>
|
||||
|
@ -213,6 +214,7 @@ const LoginForm = ({
|
|||
<View style={styles.groupContent}>
|
||||
<FontAwesomeIcon icon="at" style={styles.groupContentIcon} />
|
||||
<TextInput
|
||||
testID="loginUsernameInput"
|
||||
style={styles.textInput}
|
||||
placeholder="Username"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -227,6 +229,7 @@ const LoginForm = ({
|
|||
<View style={styles.groupContent}>
|
||||
<FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
|
||||
<TextInput
|
||||
testID="loginPasswordInput"
|
||||
style={styles.textInput}
|
||||
placeholder="Password"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -238,6 +241,7 @@ const LoginForm = ({
|
|||
editable={!isProcessing}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
testID="forgotPasswordButton"
|
||||
style={styles.textInputInnerBtn}
|
||||
onPress={onPressForgotPassword}>
|
||||
<Text style={styles.textInputInnerBtnLabel}>Forgot</Text>
|
||||
|
@ -260,7 +264,9 @@ const LoginForm = ({
|
|||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
{!serviceDescription && error ? (
|
||||
<TouchableOpacity onPress={onPressRetryConnect}>
|
||||
<TouchableOpacity
|
||||
testID="loginRetryButton"
|
||||
onPress={onPressRetryConnect}>
|
||||
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Retry</Text>
|
||||
</TouchableOpacity>
|
||||
) : !serviceDescription ? (
|
||||
|
@ -271,7 +277,7 @@ const LoginForm = ({
|
|||
) : isProcessing ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : isReady ? (
|
||||
<TouchableOpacity onPress={onPressNext}>
|
||||
<TouchableOpacity testID="loginNextButton" onPress={onPressNext}>
|
||||
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
|
||||
</TouchableOpacity>
|
||||
) : 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.
|
||||
</Text>
|
||||
<View style={styles.group}>
|
||||
<View testID="forgotPasswordView" style={styles.group}>
|
||||
<TouchableOpacity
|
||||
testID="forgotPasswordSelectServiceButton"
|
||||
style={[styles.groupContent, {borderTopWidth: 0}]}
|
||||
onPress={onPressSelectService}>
|
||||
<FontAwesomeIcon icon="globe" style={styles.groupContentIcon} />
|
||||
|
@ -359,6 +366,7 @@ const ForgotPasswordForm = ({
|
|||
<View style={styles.groupContent}>
|
||||
<FontAwesomeIcon icon="envelope" style={styles.groupContentIcon} />
|
||||
<TextInput
|
||||
testID="forgotPasswordEmail"
|
||||
style={styles.textInput}
|
||||
placeholder="Email address"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -391,7 +399,7 @@ const ForgotPasswordForm = ({
|
|||
) : !email ? (
|
||||
<Text style={[s.blue1, s.f18, s.bold, s.pr5]}>Next</Text>
|
||||
) : (
|
||||
<TouchableOpacity onPress={onPressNext}>
|
||||
<TouchableOpacity testID="newPasswordButton" onPress={onPressNext}>
|
||||
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
@ -451,10 +459,11 @@ const SetNewPasswordForm = ({
|
|||
You will receive an email with a "reset code." Enter that code here,
|
||||
then enter your new password.
|
||||
</Text>
|
||||
<View style={styles.group}>
|
||||
<View testID="newPasswordView" style={styles.group}>
|
||||
<View style={[styles.groupContent, {borderTopWidth: 0}]}>
|
||||
<FontAwesomeIcon icon="ticket" style={styles.groupContentIcon} />
|
||||
<TextInput
|
||||
testID="resetCodeInput"
|
||||
style={[styles.textInput]}
|
||||
placeholder="Reset code"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -469,6 +478,7 @@ const SetNewPasswordForm = ({
|
|||
<View style={styles.groupContent}>
|
||||
<FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
|
||||
<TextInput
|
||||
testID="newPasswordInput"
|
||||
style={styles.textInput}
|
||||
placeholder="New password"
|
||||
placeholderTextColor={colors.blue0}
|
||||
|
@ -501,7 +511,7 @@ const SetNewPasswordForm = ({
|
|||
) : !resetCode || !password ? (
|
||||
<Text style={[s.blue1, s.f18, s.bold, s.pr5]}>Next</Text>
|
||||
) : (
|
||||
<TouchableOpacity onPress={onPressNext}>
|
||||
<TouchableOpacity testID="setNewPasswordButton" onPress={onPressNext}>
|
||||
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
|
|
@ -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<string>('')
|
||||
// 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<string>('')
|
||||
const isMember =
|
||||
confirmationUri !== '' || store.me.memberships?.isMemberOf(item.author.did)
|
||||
const onPressAccept = async () => {
|
||||
|
@ -54,7 +55,7 @@ export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {
|
|||
return (
|
||||
<View style={styles.container}>
|
||||
{!isMember ? (
|
||||
<TouchableOpacity onPress={onPressAccept}>
|
||||
<TouchableOpacity testID="acceptInviteButton" onPress={onPressAccept}>
|
||||
<LinearGradient
|
||||
colors={[gradients.primary.start, gradients.primary.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
|
@ -64,7 +65,7 @@ export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {
|
|||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={styles.inviteAccepted}>
|
||||
<View testID="inviteAccepted" style={styles.inviteAccepted}>
|
||||
<FontAwesomeIcon icon="check" size={14} style={s.mr5} />
|
||||
<Text style={[s.gray5, s.f15]}>Invite accepted</Text>
|
||||
</View>
|
||||
|
|
|
@ -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<RepostedByViewModel | 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<RepostedByViewModel | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (view?.params.uri === uri) {
|
||||
|
|
|
@ -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<VotesViewModel | undefined>()
|
||||
const [view, setView] = React.useState<VotesViewModel | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (view?.params.uri === uri) {
|
||||
|
|
|
@ -25,6 +25,7 @@ export const Feed = observer(function Feed({
|
|||
onPressCompose,
|
||||
onPressTryAgain,
|
||||
onScroll,
|
||||
testID,
|
||||
}: {
|
||||
feed: FeedModel
|
||||
style?: StyleProp<ViewStyle>
|
||||
|
@ -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({
|
|||
<View />
|
||||
)
|
||||
return (
|
||||
<View style={style}>
|
||||
<View testID={testID} style={style}>
|
||||
{!data && <ComposePrompt onPressCompose={onPressCompose} />}
|
||||
{feed.isLoading && !data && <PostFeedLoadingPlaceholder />}
|
||||
{feed.hasError && (
|
||||
|
|
|
@ -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<UserFollowersViewModel | undefined>()
|
||||
const [view, setView] = React.useState<UserFollowersViewModel | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (view?.params.user === name) {
|
||||
|
|
|
@ -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<UserFollowsViewModel | undefined>()
|
||||
const [view, setView] = React.useState<UserFollowsViewModel | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (view?.params.user === name) {
|
||||
|
|
|
@ -147,7 +147,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
// =
|
||||
if (view.hasError) {
|
||||
return (
|
||||
<View>
|
||||
<View testID="profileHeaderHasError">
|
||||
<Text>{view.error}</Text>
|
||||
</View>
|
||||
)
|
||||
|
@ -192,6 +192,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
<View style={[styles.buttonsLine]}>
|
||||
{isMe ? (
|
||||
<TouchableOpacity
|
||||
testID="profileHeaderEditProfileButton"
|
||||
onPress={onPressEditProfile}
|
||||
style={[styles.btn, styles.mainBtn, pal.btn]}>
|
||||
<Text type="button" style={pal.text}>
|
||||
|
@ -214,7 +215,9 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity onPress={onPressToggleFollow}>
|
||||
<TouchableOpacity
|
||||
testID="profileHeaderToggleFollowButton"
|
||||
onPress={onPressToggleFollow}>
|
||||
<LinearGradient
|
||||
colors={[gradient[1], gradient[0]]}
|
||||
start={{x: 0, y: 0}}
|
||||
|
@ -257,6 +260,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
</View>
|
||||
<View style={styles.metricsLine}>
|
||||
<TouchableOpacity
|
||||
testID="profileHeaderFollowersButton"
|
||||
style={[s.flexRow, s.mr10]}
|
||||
onPress={onPressFollowers}>
|
||||
<Text type="body2" style={[s.bold, s.mr2, pal.text]}>
|
||||
|
@ -268,6 +272,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
</TouchableOpacity>
|
||||
{view.isUser ? (
|
||||
<TouchableOpacity
|
||||
testID="profileHeaderFollowsButton"
|
||||
style={[s.flexRow, s.mr10]}
|
||||
onPress={onPressFollows}>
|
||||
<Text type="body2" style={[s.bold, s.mr2, pal.text]}>
|
||||
|
@ -280,6 +285,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
) : undefined}
|
||||
{view.isScene ? (
|
||||
<TouchableOpacity
|
||||
testID="profileHeaderMembersButton"
|
||||
style={[s.flexRow, s.mr10]}
|
||||
onPress={onPressMembers}>
|
||||
<Text type="body2" style={[s.bold, s.mr2, pal.text]}>
|
||||
|
@ -350,7 +356,9 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
</View>
|
||||
{view.isScene && view.creator === store.me.did ? (
|
||||
<View style={[styles.sceneAdminContainer, pal.border]}>
|
||||
<TouchableOpacity onPress={onPressInviteMembers}>
|
||||
<TouchableOpacity
|
||||
testID="profileHeaderInviteMembersButton"
|
||||
onPress={onPressInviteMembers}>
|
||||
<LinearGradient
|
||||
colors={[gradient[1], gradient[0]]}
|
||||
start={{x: 0, y: 0}}
|
||||
|
@ -369,6 +377,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
</View>
|
||||
) : undefined}
|
||||
<TouchableOpacity
|
||||
testID="profileHeaderAviButton"
|
||||
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}
|
||||
onPress={onPressAvi}>
|
||||
<UserAvatar
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useState, useEffect} from 'react'
|
||||
import React, {useEffect} from 'react'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {ActivityIndicator, FlatList, View} from 'react-native'
|
||||
import {MembersViewModel, MemberItem} from '../../../state/models/members-view'
|
||||
|
@ -12,7 +12,8 @@ export const ProfileMembers = observer(function ProfileMembers({
|
|||
name: string
|
||||
}) {
|
||||
const store = useStores()
|
||||
const [view, setView] = useState<MembersViewModel | 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<MembersViewModel | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (view?.params.actor === name) {
|
||||
|
@ -37,7 +38,7 @@ export const ProfileMembers = observer(function ProfileMembers({
|
|||
view.params.actor !== name
|
||||
) {
|
||||
return (
|
||||
<View>
|
||||
<View testID="profileMembersActivityIndicatorView">
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
)
|
||||
|
@ -68,7 +69,7 @@ export const ProfileMembers = observer(function ProfileMembers({
|
|||
/>
|
||||
)
|
||||
return (
|
||||
<View>
|
||||
<View testID="profileMembersFlatList">
|
||||
<FlatList
|
||||
data={view.members}
|
||||
keyExtractor={item => item._reactKey}
|
||||
|
|
|
@ -115,6 +115,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
|
|||
<View style={[styles.ctrls, opts.style]}>
|
||||
<View style={s.flex1}>
|
||||
<TouchableOpacity
|
||||
testID="postCtrlsReplyButton"
|
||||
style={styles.ctrl}
|
||||
hitSlop={HITSLOP}
|
||||
onPress={opts.onPressReply}>
|
||||
|
@ -130,6 +131,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
|
|||
</View>
|
||||
<View style={s.flex1}>
|
||||
<TouchableOpacity
|
||||
testID="postCtrlsToggleRepostButton"
|
||||
hitSlop={HITSLOP}
|
||||
onPress={onPressToggleRepostWrapper}
|
||||
style={styles.ctrl}>
|
||||
|
@ -156,6 +158,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
|
|||
</View>
|
||||
<View style={s.flex1}>
|
||||
<TouchableOpacity
|
||||
testID="postCtrlsToggleUpvoteButton"
|
||||
style={styles.ctrl}
|
||||
hitSlop={HITSLOP}
|
||||
onPress={onPressToggleUpvoteWrapper}>
|
||||
|
|
|
@ -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 (
|
||||
<View style={[styles.outer, pal.view, style]}>
|
||||
<View testID="errorMessageView" style={[styles.outer, pal.view, style]}>
|
||||
<View
|
||||
style={[styles.errorIcon, {backgroundColor: theme.palette.error.icon}]}>
|
||||
<FontAwesomeIcon icon="exclamation" style={pal.text} size={16} />
|
||||
|
@ -38,7 +37,10 @@ export function ErrorMessage({
|
|||
{message}
|
||||
</Text>
|
||||
{onPressTryAgain && (
|
||||
<TouchableOpacity style={styles.btn} onPress={onPressTryAgain}>
|
||||
<TouchableOpacity
|
||||
testID="errorMessageTryAgainButton"
|
||||
style={styles.btn}
|
||||
onPress={onPressTryAgain}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrows-rotate"
|
||||
style={{color: theme.palette.error.icon}}
|
||||
|
|
|
@ -11,16 +11,18 @@ export function ErrorScreen({
|
|||
message,
|
||||
details,
|
||||
onPressTryAgain,
|
||||
testID,
|
||||
}: {
|
||||
title: string
|
||||
message: string
|
||||
details?: string
|
||||
onPressTryAgain?: () => void
|
||||
testID?: string
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const pal = usePalette('error')
|
||||
return (
|
||||
<View style={[styles.outer, pal.view]}>
|
||||
<View testID={testID} style={[styles.outer, pal.view]}>
|
||||
<View style={styles.errorIconContainer}>
|
||||
<View
|
||||
style={[
|
||||
|
@ -40,6 +42,7 @@ export function ErrorScreen({
|
|||
<Text style={[styles.message, pal.textLight]}>{message}</Text>
|
||||
{details && (
|
||||
<Text
|
||||
testID={`${testID}-details`}
|
||||
type="body2"
|
||||
style={[
|
||||
styles.details,
|
||||
|
@ -52,6 +55,7 @@ export function ErrorScreen({
|
|||
{onPressTryAgain && (
|
||||
<View style={styles.btnContainer}>
|
||||
<TouchableOpacity
|
||||
testID="errorScreenTryAgainButton"
|
||||
style={[styles.btn, {backgroundColor: theme.palette.error.icon}]}
|
||||
onPress={onPressTryAgain}>
|
||||
<FontAwesomeIcon icon="arrows-rotate" style={pal.text} size={16} />
|
||||
|
|
|
@ -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}
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
|
|
@ -25,7 +25,9 @@ export const Contacts = ({navIdx, visible, params}: ScreenParams) => {
|
|||
return (
|
||||
<View>
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.title}>Contacts</Text>
|
||||
<Text testID="contactsTitle" style={styles.title}>
|
||||
Contacts
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.section}>
|
||||
<View style={styles.searchContainer}>
|
||||
|
@ -35,6 +37,7 @@ export const Contacts = ({navIdx, visible, params}: ScreenParams) => {
|
|||
style={styles.searchIcon}
|
||||
/>
|
||||
<TextInput
|
||||
testID="contactsTextInput"
|
||||
ref={inputRef}
|
||||
value={searchText}
|
||||
style={styles.searchInput}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useState, useEffect} from 'react'
|
||||
import React, {useEffect} from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import useAppState from 'react-native-appstate-hook'
|
||||
|
@ -24,48 +24,48 @@ export const Home = observer(function Home({
|
|||
const store = useStores()
|
||||
const onMainScroll = useOnMainScroll(store)
|
||||
const safeAreaInsets = useSafeAreaInsets()
|
||||
const [hasSetup, setHasSetup] = useState<boolean>(false)
|
||||
const [wasVisible, setWasVisible] = React.useState<boolean>(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({
|
|||
<View style={s.flex1}>
|
||||
<ViewHeader title="Bluesky" subtitle="Private Beta" canGoBack={false} />
|
||||
<Feed
|
||||
testID="homeFeed"
|
||||
key="default"
|
||||
feed={store.me.mainFeed}
|
||||
scrollElRef={scrollElRef}
|
||||
|
|
|
@ -35,8 +35,11 @@ const SigninOrCreateAccount = ({
|
|||
<Text style={styles.title}>Bluesky</Text>
|
||||
<Text style={styles.subtitle}>[ private beta ]</Text>
|
||||
</View>
|
||||
<View style={s.flex1}>
|
||||
<TouchableOpacity style={styles.btn} onPress={onPressCreateAccount}>
|
||||
<View testID="signinOrCreateAccount" style={s.flex1}>
|
||||
<TouchableOpacity
|
||||
testID="createAccountButton"
|
||||
style={styles.btn}
|
||||
onPress={onPressCreateAccount}>
|
||||
<Text style={styles.btnLabel}>Create a new account</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.or}>
|
||||
|
@ -60,7 +63,10 @@ const SigninOrCreateAccount = ({
|
|||
</Svg>
|
||||
<Text style={styles.orLabel}>or</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.btn} onPress={onPressSignin}>
|
||||
<TouchableOpacity
|
||||
testID="signInButton"
|
||||
style={styles.btn}
|
||||
onPress={onPressSignin}>
|
||||
<Text style={styles.btnLabel}>Sign in</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
|
@ -7,7 +7,7 @@ import {useStores} from '../../state'
|
|||
export const NotFound = () => {
|
||||
const stores = useStores()
|
||||
return (
|
||||
<View>
|
||||
<View testID="notFoundView">
|
||||
<ViewHeader title="Page not found" />
|
||||
<View
|
||||
style={{
|
||||
|
@ -16,7 +16,11 @@ export const NotFound = () => {
|
|||
paddingTop: 100,
|
||||
}}>
|
||||
<Text style={{fontSize: 40, fontWeight: 'bold'}}>Page not found</Text>
|
||||
<Button title="Home" onPress={() => stores.nav.navigate('/')} />
|
||||
<Button
|
||||
testID="navigateHomeButton"
|
||||
title="Home"
|
||||
onPress={() => stores.nav.navigate('/')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useEffect, useState, useMemo} from 'react'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
|
@ -30,7 +30,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
|||
const store = useStores()
|
||||
const onMainScroll = useOnMainScroll(store)
|
||||
const [hasSetup, setHasSetup] = useState<boolean>(false)
|
||||
const uiState = useMemo(
|
||||
const uiState = React.useMemo(
|
||||
() => new ProfileUiModel(store, {user: params.name}),
|
||||
[params.user],
|
||||
)
|
||||
|
@ -201,6 +201,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
|||
? () => (
|
||||
<>
|
||||
<FontAwesomeIcon
|
||||
testID="shouldAdminButton"
|
||||
icon="user-xmark"
|
||||
style={[s.mr5]}
|
||||
size={14}
|
||||
|
@ -242,10 +243,11 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
|||
const title =
|
||||
uiState.profile.displayName || uiState.profile.handle || params.name
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View testID="profileView" style={styles.container}>
|
||||
<ViewHeader title={title} />
|
||||
{uiState.profile.hasError ? (
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title="Failed to load profile"
|
||||
message={`There was an issue when attempting to load ${params.name}`}
|
||||
details={uiState.profile.error}
|
||||
|
|
|
@ -57,6 +57,7 @@ export const Search = ({navIdx, visible, params}: ScreenParams) => {
|
|||
<View style={[pal.view, pal.border, styles.inputContainer]}>
|
||||
<MagnifyingGlassIcon style={[pal.text, styles.inputIcon]} />
|
||||
<TextInput
|
||||
testID="searchTextInput"
|
||||
ref={textInput}
|
||||
placeholder="Type your query here..."
|
||||
placeholderTextColor={pal.colors.textLight}
|
||||
|
@ -68,7 +69,7 @@ export const Search = ({navIdx, visible, params}: ScreenParams) => {
|
|||
</View>
|
||||
<View style={styles.outputContainer}>
|
||||
{query ? (
|
||||
<ScrollView onScroll={Keyboard.dismiss}>
|
||||
<ScrollView testID="searchScrollView" onScroll={Keyboard.dismiss}>
|
||||
{autocompleteView.searchRes.map((item, i) => (
|
||||
<TouchableOpacity
|
||||
key={i}
|
||||
|
|
|
@ -75,6 +75,7 @@ export const Menu = observer(
|
|||
onPress?: () => void
|
||||
}) => (
|
||||
<TouchableOpacity
|
||||
testID="menuItemButton"
|
||||
style={styles.menuItem}
|
||||
onPress={onPress ? onPress : () => onNavigate(url || '/')}>
|
||||
<View style={[styles.menuItemIconWrapper]}>
|
||||
|
@ -98,8 +99,9 @@ export const Menu = observer(
|
|||
)
|
||||
|
||||
return (
|
||||
<ScrollView style={[styles.view, pal.view]}>
|
||||
<ScrollView testID="menuView" style={[styles.view, pal.view]}>
|
||||
<TouchableOpacity
|
||||
testID="profileCardButton"
|
||||
onPress={() => onNavigate(`/profile/${store.me.handle}`)}
|
||||
style={styles.profileCard}>
|
||||
<UserAvatar
|
||||
|
@ -123,6 +125,7 @@ export const Menu = observer(
|
|||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
testID="searchBtn"
|
||||
style={[styles.searchBtn, pal.btn]}
|
||||
onPress={() => onNavigate('/search')}>
|
||||
<MagnifyingGlassIcon
|
||||
|
|
|
@ -116,11 +116,12 @@ export const TabsSelector = observer(
|
|||
}
|
||||
|
||||
if (!active) {
|
||||
return <View />
|
||||
return <View testID="emptyView" />
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
testID="tabsSelectorView"
|
||||
style={[
|
||||
styles.wrapper,
|
||||
{bottom: insets.bottom + 55},
|
||||
|
@ -129,7 +130,9 @@ export const TabsSelector = observer(
|
|||
<View onLayout={onLayout}>
|
||||
<View style={[s.p10, styles.section]}>
|
||||
<View style={styles.btns}>
|
||||
<TouchableWithoutFeedback onPress={onPressShareTab}>
|
||||
<TouchableWithoutFeedback
|
||||
testID="shareButton"
|
||||
onPress={onPressShareTab}>
|
||||
<View style={[styles.btn]}>
|
||||
<View style={styles.btnIcon}>
|
||||
<FontAwesomeIcon size={16} icon="share" />
|
||||
|
@ -137,7 +140,9 @@ export const TabsSelector = observer(
|
|||
<Text style={styles.btnText}>Share</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<TouchableWithoutFeedback onPress={onPressCloneTab}>
|
||||
<TouchableWithoutFeedback
|
||||
testID="cloneButton"
|
||||
onPress={onPressCloneTab}>
|
||||
<View style={[styles.btn]}>
|
||||
<View style={styles.btnIcon}>
|
||||
<FontAwesomeIcon size={16} icon={['far', 'clone']} />
|
||||
|
@ -145,7 +150,9 @@ export const TabsSelector = observer(
|
|||
<Text style={styles.btnText}>Clone tab</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<TouchableWithoutFeedback onPress={onPressNewTab}>
|
||||
<TouchableWithoutFeedback
|
||||
testID="newTabButton"
|
||||
onPress={onPressNewTab}>
|
||||
<View style={[styles.btn]}>
|
||||
<View style={styles.btnIcon}>
|
||||
<FontAwesomeIcon size={16} icon="plus" />
|
||||
|
@ -164,6 +171,7 @@ export const TabsSelector = observer(
|
|||
return (
|
||||
<Swipeable
|
||||
key={tab.id}
|
||||
testID="tabsSwipable"
|
||||
renderLeftActions={renderSwipeActions}
|
||||
renderRightActions={renderSwipeActions}
|
||||
leftThreshold={100}
|
||||
|
@ -185,6 +193,7 @@ export const TabsSelector = observer(
|
|||
isActive && styles.active,
|
||||
]}>
|
||||
<TouchableWithoutFeedback
|
||||
testID="changeTabButton"
|
||||
onPress={() => onPressChangeTab(tabIndex)}>
|
||||
<View style={styles.tabInner}>
|
||||
<View style={styles.tabIcon}>
|
||||
|
@ -203,6 +212,7 @@ export const TabsSelector = observer(
|
|||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<TouchableWithoutFeedback
|
||||
testID="closeTabButton"
|
||||
onPress={() => onCloseTab(tabIndex)}>
|
||||
<View style={styles.tabClose}>
|
||||
<FontAwesomeIcon
|
||||
|
|
|
@ -327,7 +327,7 @@ export const MobileShell: React.FC = observer(() => {
|
|||
start={{x: 0, y: 0.8}}
|
||||
end={{x: 0, y: 1}}
|
||||
style={styles.outerContainer}>
|
||||
<SafeAreaView style={styles.innerContainer}>
|
||||
<SafeAreaView testID="noSessionView" style={styles.innerContainer}>
|
||||
<ErrorBoundary>
|
||||
<Login />
|
||||
</ErrorBoundary>
|
||||
|
@ -338,7 +338,7 @@ export const MobileShell: React.FC = observer(() => {
|
|||
}
|
||||
if (store.onboard.isOnboarding) {
|
||||
return (
|
||||
<View style={styles.outerContainer}>
|
||||
<View testID="onboardOuterView" style={styles.outerContainer}>
|
||||
<View style={styles.innerContainer}>
|
||||
<ErrorBoundary>
|
||||
<Onboard />
|
||||
|
@ -355,7 +355,7 @@ export const MobileShell: React.FC = observer(() => {
|
|||
backgroundColor: theme.colorScheme === 'dark' ? colors.gray7 : colors.gray1,
|
||||
}
|
||||
return (
|
||||
<View style={[styles.outerContainer, pal.view]}>
|
||||
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
|
||||
<StatusBar
|
||||
barStyle={
|
||||
theme.colorScheme === 'dark' ? 'light-content' : 'dark-content'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue