mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-09 15:56:11 -04:00
Add item create iOS maestro test (#1404)
This commit is contained in:
@@ -45,8 +45,9 @@ appId: net.aliasvault.app
|
||||
|
||||
- hideKeyboard
|
||||
|
||||
# Go back to login screen (cross-platform)
|
||||
- runFlow: ../utils/go-back.yaml
|
||||
# Go back to login screen
|
||||
- tapOn:
|
||||
id: "back-button"
|
||||
|
||||
# Wait for login screen to be visible again
|
||||
- extendedWaitUntil:
|
||||
|
||||
101
apps/mobile-app/.maestro/flows/04-create-item.yaml
Normal file
101
apps/mobile-app/.maestro/flows/04-create-item.yaml
Normal file
@@ -0,0 +1,101 @@
|
||||
# Test 04: Create New Item
|
||||
# Verifies item creation flow
|
||||
#
|
||||
# Prerequisites: User is logged in (run 03-successful-login.yaml first)
|
||||
# Expected: New item is created and visible in vault
|
||||
|
||||
appId: net.aliasvault.app
|
||||
---
|
||||
# Generate unique item name using timestamp
|
||||
- runScript:
|
||||
file: ../utils/generate-unique-name.js
|
||||
env:
|
||||
PREFIX: "E2E Test"
|
||||
|
||||
# Ensure we're on the items screen (assumes logged in)
|
||||
- assertVisible:
|
||||
id: "items-screen"
|
||||
optional: true
|
||||
|
||||
# Tap the FAB (Floating Action Button) to add new item
|
||||
- tapOn:
|
||||
id: "add-item-button"
|
||||
|
||||
# Wait for add/edit screen to load
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
id: "add-edit-screen"
|
||||
timeout: 10000
|
||||
|
||||
- takeScreenshot: "04-1-add-item-screen"
|
||||
|
||||
# Enter item name (uses unique name generated by script)
|
||||
- tapOn:
|
||||
id: "item-name-input"
|
||||
- inputText: ${output.UNIQUE_NAME}
|
||||
|
||||
# Enter service URL
|
||||
- tapOn:
|
||||
id: "service-url-input"
|
||||
- inputText: "https://example.com"
|
||||
|
||||
# Add email field (not visible by default for Login type)
|
||||
- tapOn:
|
||||
id: "add-email-button"
|
||||
|
||||
# Enter email
|
||||
- tapOn:
|
||||
id: "login-email-input"
|
||||
- inputText: "e2e-test@example.com"
|
||||
|
||||
# Enter username (optional - field may not be visible)
|
||||
- tapOn:
|
||||
id: "login-username-input"
|
||||
optional: true
|
||||
- inputText:
|
||||
text: "e2euser"
|
||||
optional: true
|
||||
|
||||
- hideKeyboard
|
||||
|
||||
- takeScreenshot: "04-2-item-filled"
|
||||
|
||||
# Save the item
|
||||
- tapOn:
|
||||
id: "save-button"
|
||||
|
||||
# Wait for item detail screen to load (app navigates here after save)
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
text: "Login credentials"
|
||||
timeout: 10000
|
||||
|
||||
- takeScreenshot: "04-3-item-detail-screen"
|
||||
|
||||
# Wait for back button to be ready
|
||||
- extendedWaitUntil:
|
||||
visible: "Wait_for_1_sec"
|
||||
optional: true
|
||||
timeout: 1000
|
||||
|
||||
# Go back to items list
|
||||
- tapOn:
|
||||
id: "back-button"
|
||||
|
||||
# Wait for items screen to be visible
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
id: "items-screen"
|
||||
timeout: 10000
|
||||
|
||||
# Verify the newly created item appears in the list by tapping on it
|
||||
- tapOn:
|
||||
text: ${output.UNIQUE_NAME}
|
||||
|
||||
# Wait for item detail screen to confirm we tapped the right item
|
||||
- extendedWaitUntil:
|
||||
visible:
|
||||
text: "Login credentials"
|
||||
timeout: 10000
|
||||
|
||||
- takeScreenshot: "04-4-item-verified"
|
||||
11
apps/mobile-app/.maestro/utils/generate-unique-name.js
Normal file
11
apps/mobile-app/.maestro/utils/generate-unique-name.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/* global PREFIX, output */
|
||||
// Generate a unique name using timestamp
|
||||
// Usage: Set PREFIX env var to customize the prefix (default: "Test")
|
||||
// Output: UNIQUE_NAME variable containing the generated name
|
||||
|
||||
const prefix = PREFIX || 'Test';
|
||||
const timestamp = Date.now();
|
||||
// Use last 6 digits to keep it readable but unique
|
||||
const shortId = String(timestamp).slice(-6);
|
||||
|
||||
output.UNIQUE_NAME = `${prefix} ${shortId}`;
|
||||
@@ -1,7 +1,8 @@
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, View, Text, StyleSheet, Linking, Platform } from 'react-native'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ActivityIndicator, View, Text, StyleSheet, Linking, Platform } from 'react-native';
|
||||
import Toast from 'react-native-toast-message';
|
||||
|
||||
import type { Item } from '@/utils/dist/core/models/vault';
|
||||
@@ -21,6 +22,7 @@ import { ThemedContainer } from '@/components/themed/ThemedContainer';
|
||||
import { ThemedScrollView } from '@/components/themed/ThemedScrollView';
|
||||
import { ThemedText } from '@/components/themed/ThemedText';
|
||||
import { ThemedView } from '@/components/themed/ThemedView';
|
||||
import { HeaderBackButton } from '@/components/ui/HeaderBackButton';
|
||||
import { RobustPressable } from '@/components/ui/RobustPressable';
|
||||
import { useDb } from '@/context/DbContext';
|
||||
|
||||
@@ -35,6 +37,7 @@ export default function ItemDetailsScreen() : React.ReactNode {
|
||||
const navigation = useNavigation();
|
||||
const colors = useColors();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
/**
|
||||
* Handle the edit button press.
|
||||
@@ -45,11 +48,20 @@ export default function ItemDetailsScreen() : React.ReactNode {
|
||||
|
||||
// Set header buttons
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
const headerOptions: Record<string, unknown> = {
|
||||
/**
|
||||
* Header back button.
|
||||
*/
|
||||
headerLeft: (): React.ReactNode => (
|
||||
<HeaderBackButton
|
||||
label={t('items.title')}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
/**
|
||||
* Header right button.
|
||||
*/
|
||||
headerRight: () => (
|
||||
headerRight: (): React.ReactNode => (
|
||||
<View style={styles.headerRightContainer}>
|
||||
<RobustPressable
|
||||
onPress={handleEdit}
|
||||
@@ -63,8 +75,10 @@ export default function ItemDetailsScreen() : React.ReactNode {
|
||||
</RobustPressable>
|
||||
</View>
|
||||
),
|
||||
});
|
||||
}, [navigation, item, handleEdit, colors.primary]);
|
||||
};
|
||||
|
||||
navigation.setOptions(headerOptions);
|
||||
}, [navigation, item, handleEdit, colors.primary, router, t]);
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as Haptics from 'expo-haptics';
|
||||
import { Stack, useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StyleSheet, View, Alert, Keyboard, Platform, ScrollView, KeyboardAvoidingView } from 'react-native';
|
||||
import { StyleSheet, View, Alert, Keyboard, Platform, ScrollView, KeyboardAvoidingView, TouchableOpacity } from 'react-native';
|
||||
import Toast from 'react-native-toast-message';
|
||||
|
||||
import type { Folder } from '@/utils/db/repositories/FolderRepository';
|
||||
@@ -959,6 +959,19 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
/**
|
||||
* Get testID for a field based on its field key.
|
||||
*/
|
||||
const getFieldTestId = useCallback((fieldKey: string): string | undefined => {
|
||||
const testIdMap: Record<string, string> = {
|
||||
'login.url': 'service-url-input',
|
||||
'login.email': 'login-email-input',
|
||||
'login.username': 'login-username-input',
|
||||
'login.password': 'login-password-input',
|
||||
};
|
||||
return testIdMap[fieldKey];
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Render a field input based on field type.
|
||||
*/
|
||||
@@ -972,6 +985,7 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
): React.ReactNode => {
|
||||
const value = fieldValues[fieldKey] || '';
|
||||
const stringValue = Array.isArray(value) ? value[0] || '' : value;
|
||||
const testID = getFieldTestId(fieldKey);
|
||||
|
||||
switch (fieldType) {
|
||||
case FieldTypes.Password:
|
||||
@@ -984,6 +998,7 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
onShowPasswordChange={setIsPasswordVisible}
|
||||
isNewCredential={!isEditMode}
|
||||
onRemove={onRemove}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -995,6 +1010,7 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
label={label}
|
||||
keyboardType={fieldKey === 'card.pin' || fieldKey === 'card.cvv' ? 'numeric' : 'default'}
|
||||
onRemove={onRemove}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1005,6 +1021,7 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
onChange={(val) => handleFieldChange(fieldKey, val)}
|
||||
label={label}
|
||||
onRemove={onRemove}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1018,6 +1035,7 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
numberOfLines={4}
|
||||
textAlignVertical="top"
|
||||
onRemove={onRemove}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1039,6 +1057,7 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
onPress: generateRandomUsername
|
||||
}]}
|
||||
onRemove={onRemove}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1050,10 +1069,11 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
placeholder={fieldKey === 'alias.birthdate' ? t('items.birthDatePlaceholder') : undefined}
|
||||
keyboardType={fieldType === FieldTypes.Phone || fieldType === FieldTypes.Number ? 'numeric' : 'default'}
|
||||
onRemove={onRemove}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}, [fieldValues, handleFieldChange, isPasswordVisible, isEditMode, aliasFieldsShownByDefault, generateRandomUsername, t]);
|
||||
}, [fieldValues, handleFieldChange, isPasswordVisible, isEditMode, aliasFieldsShownByDefault, generateRandomUsername, t, getFieldTestId]);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
@@ -1184,18 +1204,19 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
* Show the save button
|
||||
*/
|
||||
headerRight: () => (
|
||||
<RobustPressable
|
||||
<TouchableOpacity
|
||||
onPress={onSubmit}
|
||||
style={[styles.headerRightButton, isSaveDisabled && styles.headerRightButtonDisabled]}
|
||||
disabled={isSaveDisabled}
|
||||
testID="save-button"
|
||||
accessibilityLabel="save-button"
|
||||
>
|
||||
<MaterialIcons
|
||||
name="save"
|
||||
size={Platform.OS === 'android' ? 24 : 22}
|
||||
color={colors.primary}
|
||||
/>
|
||||
</RobustPressable>
|
||||
</TouchableOpacity>
|
||||
),
|
||||
});
|
||||
}, [navigation, onSubmit, colors.primary, isEditMode, router, styles.headerLeftButton, styles.headerLeftButtonText, styles.headerRightButton, styles.headerRightButtonDisabled, isSaveDisabled, t, handleCancel]);
|
||||
@@ -1381,6 +1402,7 @@ export default function AddEditItemScreen(): React.ReactNode {
|
||||
<RobustPressable
|
||||
onPress={() => handleAddOptionalField('login.email')}
|
||||
style={styles.addEmailBadge}
|
||||
testID="add-email-button"
|
||||
>
|
||||
<MaterialIcons name="add" size={14} color={colors.textMuted} />
|
||||
<ThemedText style={styles.addEmailBadgeText}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useNavigation } from 'expo-router';
|
||||
import { useNavigation, useRouter } from 'expo-router';
|
||||
import { useState, useEffect, useCallback, useMemo, useLayoutEffect } from 'react';
|
||||
import { StyleSheet, View, Text, TextInput, ActivityIndicator } from 'react-native';
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { ThemedContainer } from '@/components/themed/ThemedContainer';
|
||||
import { ThemedScrollView } from '@/components/themed/ThemedScrollView';
|
||||
import { ThemedView } from '@/components/themed/ThemedView';
|
||||
import { HeaderBackButton } from '@/components/ui/HeaderBackButton';
|
||||
import { RobustPressable } from '@/components/ui/RobustPressable';
|
||||
import NativeVaultManager from '@/specs/NativeVaultManager';
|
||||
|
||||
@@ -24,6 +25,7 @@ type ApiOption = {
|
||||
export default function SettingsScreen() : React.ReactNode {
|
||||
const colors = useColors();
|
||||
const navigation = useNavigation();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const [selectedOption, setSelectedOption] = useState<string>(AppInfo.DEFAULT_API_URL);
|
||||
const [customUrl, setCustomUrl] = useState<string>('');
|
||||
@@ -37,9 +39,17 @@ export default function SettingsScreen() : React.ReactNode {
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: t('app.navigation.loginSettings'),
|
||||
headerBackTitle: t('app.navigation.login'),
|
||||
/**
|
||||
* Header left button (custom back button with testID for E2E tests).
|
||||
*/
|
||||
headerLeft: (): React.ReactNode => (
|
||||
<HeaderBackButton
|
||||
label={t('app.navigation.login')}
|
||||
onPress={() => router.back()}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [navigation, t]);
|
||||
}, [navigation, router, t]);
|
||||
|
||||
/**
|
||||
* Load the stored settings from native layer.
|
||||
|
||||
@@ -185,6 +185,7 @@ export function ServerSyncIndicator(): React.ReactNode {
|
||||
style={[styles.container, styles.offline]}
|
||||
onPress={handleRetry}
|
||||
disabled={isRetrying}
|
||||
testID="sync-indicator-offline"
|
||||
>
|
||||
<View>
|
||||
{isRetrying ? (
|
||||
@@ -209,7 +210,7 @@ export function ServerSyncIndicator(): React.ReactNode {
|
||||
// Uses showSyncing which has minimum display time to prevent flickering
|
||||
if (showSyncing) {
|
||||
return (
|
||||
<View style={[styles.container, styles.syncing]}>
|
||||
<View style={[styles.container, styles.syncing]} testID="sync-indicator-syncing">
|
||||
<ActivityIndicator size="small" color={colors.success ?? '#16a34a'} />
|
||||
<ThemedText style={[styles.text, styles.syncingText]}>
|
||||
{t('sync.syncing')}
|
||||
|
||||
@@ -27,6 +27,8 @@ type AdvancedPasswordFieldProps = Omit<TextInputProps, 'value' | 'onChangeText'>
|
||||
isNewCredential?: boolean;
|
||||
/** Optional callback for remove button - when provided, shows X button in label row */
|
||||
onRemove?: () => void;
|
||||
/** Optional testID for the text input */
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, AdvancedPasswordFieldProps>(({
|
||||
@@ -38,6 +40,7 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
onShowPasswordChange,
|
||||
isNewCredential = false,
|
||||
onRemove,
|
||||
testID,
|
||||
...props
|
||||
}, ref) => {
|
||||
const colors = useColors();
|
||||
@@ -386,6 +389,8 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
autoCorrect={false}
|
||||
clearButtonMode={Platform.OS === 'ios' ? "while-editing" : "never"}
|
||||
secureTextEntry={!showPassword}
|
||||
testID={testID}
|
||||
accessibilityLabel={testID}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ type EmailDomainFieldProps = {
|
||||
label: string;
|
||||
/** Optional callback for remove button - when provided, shows X button in label row */
|
||||
onRemove?: () => void;
|
||||
/** Optional testID for the text input */
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
// Hardcoded public email domains (same as in browser extension)
|
||||
@@ -42,7 +44,8 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
error,
|
||||
required = false,
|
||||
label,
|
||||
onRemove
|
||||
onRemove,
|
||||
testID
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
@@ -371,6 +374,8 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
keyboardType="email-address"
|
||||
multiline={false}
|
||||
numberOfLines={1}
|
||||
testID={testID}
|
||||
accessibilityLabel={testID}
|
||||
/>
|
||||
|
||||
{!isCustomDomain && (
|
||||
|
||||
@@ -25,6 +25,8 @@ type FormFieldProps = Omit<TextInputProps, 'onChangeText'> & {
|
||||
error?: string;
|
||||
/** Optional callback for remove button - when provided, shows X button in label row */
|
||||
onRemove?: () => void;
|
||||
/** Optional testID for the text input */
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,6 +40,7 @@ const FormFieldComponent = forwardRef<FormFieldRef, FormFieldProps>(({
|
||||
buttons,
|
||||
error,
|
||||
onRemove,
|
||||
testID,
|
||||
...props
|
||||
}, ref) => {
|
||||
const colors = useColors();
|
||||
@@ -143,6 +146,8 @@ const FormFieldComponent = forwardRef<FormFieldRef, FormFieldProps>(({
|
||||
clearButtonMode={Platform.OS === 'ios' ? "while-editing" : "never"}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
testID={testID}
|
||||
accessibilityLabel={testID}
|
||||
{...props}
|
||||
/>
|
||||
{showClearButton && (
|
||||
|
||||
@@ -20,6 +20,8 @@ type HiddenFieldProps = {
|
||||
keyboardType?: 'default' | 'numeric';
|
||||
/** Optional callback for remove button */
|
||||
onRemove?: () => void;
|
||||
/** Optional testID for the text input */
|
||||
testID?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,6 +35,7 @@ export const HiddenField: React.FC<HiddenFieldProps> = ({
|
||||
placeholder,
|
||||
keyboardType = 'default',
|
||||
onRemove,
|
||||
testID,
|
||||
}) => {
|
||||
const colors = useColors();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
@@ -97,6 +100,8 @@ export const HiddenField: React.FC<HiddenFieldProps> = ({
|
||||
keyboardType={keyboardType}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
testID={testID}
|
||||
accessibilityLabel={testID}
|
||||
/>
|
||||
<RobustPressable
|
||||
onPress={() => setIsVisible(!isVisible)}
|
||||
|
||||
@@ -289,6 +289,7 @@ export function ItemCard({ item, onItemDelete }: ItemCardProps): React.ReactNode
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
testID="item-card"
|
||||
accessibilityLabel={item.Name}
|
||||
>
|
||||
<View style={styles.itemContent}>
|
||||
<ItemIcon item={item} style={styles.logo} />
|
||||
|
||||
57
apps/mobile-app/components/ui/HeaderBackButton.tsx
Normal file
57
apps/mobile-app/components/ui/HeaderBackButton.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
|
||||
import { RobustPressable } from './RobustPressable';
|
||||
|
||||
type HeaderBackButtonProps = {
|
||||
/** The label to display next to the back arrow */
|
||||
label: string;
|
||||
/** Callback when the button is pressed */
|
||||
onPress: () => void;
|
||||
/** Optional testID for E2E testing */
|
||||
testID?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A reusable header back button component for navigation headers.
|
||||
* Displays a chevron icon with a label, styled to match iOS navigation patterns.
|
||||
* Uses RobustPressable for reliable touch handling in E2E tests.
|
||||
*/
|
||||
export const HeaderBackButton: React.FC<HeaderBackButtonProps> = ({
|
||||
label,
|
||||
onPress,
|
||||
testID = 'back-button',
|
||||
}) => {
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<RobustPressable
|
||||
onPress={onPress}
|
||||
style={styles.container}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="chevron-left"
|
||||
size={28}
|
||||
color={colors.primary}
|
||||
/>
|
||||
<Text testID={testID} style={[styles.label, { color: colors.primary }]}>
|
||||
{label}
|
||||
</Text>
|
||||
</RobustPressable>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
marginLeft: -8,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
label: {
|
||||
fontSize: 17,
|
||||
},
|
||||
});
|
||||
@@ -9,6 +9,7 @@ interface IRobustPressableProps {
|
||||
disabled?: boolean;
|
||||
pressRetentionOffset?: number;
|
||||
hitSlop?: number;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ export const RobustPressable: React.FC<IRobustPressableProps> = ({
|
||||
disabled,
|
||||
pressRetentionOffset = 10,
|
||||
hitSlop = 10,
|
||||
testID,
|
||||
}) => {
|
||||
return (
|
||||
<Pressable
|
||||
@@ -36,6 +38,8 @@ export const RobustPressable: React.FC<IRobustPressableProps> = ({
|
||||
style,
|
||||
{ opacity: pressed ? 0.6 : 1 },
|
||||
]}
|
||||
testID={testID}
|
||||
accessibilityLabel={testID}
|
||||
>
|
||||
{children}
|
||||
</Pressable>
|
||||
|
||||
Reference in New Issue
Block a user