Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 46 additions & 36 deletions examples/basic/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ test('renders correctly', () => {
// Idiom: no need to capture render output, as we will use `screen` for queries.
render(<App />);

// Idiom: `getByXxx` is a predicate by itself, but we will use it with `expect().toBeTruthy()`
// Idiom: `getBy*` queries are predicates by themselves, but we will use it with `expect().toBeTruthy()`
// to clarify our intent.
expect(screen.getByText('Sign in to Example App')).toBeTruthy();
expect(
screen.getByRole('header', { name: 'Sign in to Example App' })
).toBeTruthy();
});

/**
Expand All @@ -23,30 +25,34 @@ test('User can sign in successully with correct credentials', async () => {
// Idiom: no need to capture render output, as we will use `screen` for queries.
render(<App />);

// Idiom: `getByXxx` is a predicate by itself, but we will use it with `expect().toBeTruthy()` to
// clarify our intent.
// Idiom: `getBy*` queries are predicates by themselves, but we will use it with `expect().toBeTruthy()`
// to clarify our intent.
// Note: `.toBeTruthy()` is the preferred matcher for checking that elements are present.
expect(screen.getByText('Sign in to Example App')).toBeTruthy();
expect(screen.getByText('Username')).toBeTruthy();
expect(screen.getByText('Password')).toBeTruthy();
expect(
screen.getByRole('header', { name: 'Sign in to Example App' })
).toBeTruthy();

// Hint: we can use `getByLabelText` to find our text inputs in accessibility-friendly way.
// Hint: we can use `getByLabelText` to find our text inputs using their labels.
fireEvent.changeText(screen.getByLabelText('Username'), 'admin');
fireEvent.changeText(screen.getByLabelText('Password'), 'admin1');

// Hint: we can use `getByText` to find our button by its text.
fireEvent.press(screen.getByText('Sign In'));
// Hint: we can use `getByRole` to find our button with given text.
fireEvent.press(screen.getByRole('button', { name: 'Sign In' }));

// Idiom: since pressing button triggers async operation we need to use `findBy` query to wait
// Idiom: since pressing button triggers async operation we need to use `findBy*` query to wait
// for the action to complete.
// Hint: subsequent queries do not need to use `findBy`, because they are used after the async action
// Hint: subsequent queries do not need to use `findBy*`, because they are used after the async action
// already finished
expect(await screen.findByText('Welcome admin!')).toBeTruthy();

// Idiom: use `queryByXxx` with `expect().toBeFalsy()` to assess that element is not present.
expect(screen.queryByText('Sign in to Example App')).toBeFalsy();
expect(screen.queryByText('Username')).toBeFalsy();
expect(screen.queryByText('Password')).toBeFalsy();
expect(
await screen.findByRole('header', { name: 'Welcome admin!' })
).toBeTruthy();

// Idiom: use `queryBy*` with `expect().toBeFalsy()` to assess that element is not present.
expect(
screen.queryByRole('header', { name: 'Sign in to Example App' })
).toBeFalsy();
expect(screen.queryByLabelText('Username')).toBeFalsy();
expect(screen.queryByLabelText('Password')).toBeFalsy();
});

/**
Expand All @@ -56,30 +62,32 @@ test('User can sign in successully with correct credentials', async () => {
* that is not directly reflected in the UI.
*
* For this reason prefer quries that correspond to things directly observable by the user like:
* `getByText`, `getByLabelText`, `getByPlaceholderText, `getByDisplayValue`, `getByRole`, etc.
* `getByRole`, `getByText`, `getByLabelText`, `getByPlaceholderText, `getByDisplayValue`, etc.
* over `getByTestId` which is not directly observable by the user.
*
* Note: that some times you will have to resort to `getByTestId`, but treat it as a last resort.
*/
test('User will see errors for incorrect credentials', async () => {
render(<App />);

expect(screen.getByText('Sign in to Example App')).toBeTruthy();
expect(screen.getByText('Username')).toBeTruthy();
expect(screen.getByText('Password')).toBeTruthy();
expect(
screen.getByRole('header', { name: 'Sign in to Example App' })
).toBeTruthy();

fireEvent.changeText(screen.getByLabelText('Username'), 'admin');
fireEvent.changeText(screen.getByLabelText('Password'), 'qwerty123');
fireEvent.press(screen.getByText('Sign In'));
fireEvent.press(screen.getByRole('button', { name: 'Sign In' }));

// Hint: you can use custom Jest Native matcher to check text content.
expect(await screen.findByLabelText('Error')).toHaveTextContent(
expect(await screen.findByRole('alert')).toHaveTextContent(
'Incorrect username or password'
);

expect(screen.getByText('Sign in to Example App')).toBeTruthy();
expect(screen.getByText('Username')).toBeTruthy();
expect(screen.getByText('Password')).toBeTruthy();
expect(
screen.getByRole('header', { name: 'Sign in to Example App' })
).toBeTruthy();
expect(screen.getByLabelText('Username')).toBeTruthy();
expect(screen.getByLabelText('Password')).toBeTruthy();
});

/**
Expand All @@ -88,23 +96,25 @@ test('User will see errors for incorrect credentials', async () => {
test('User can sign in after incorrect attempt', async () => {
render(<App />);

expect(screen.getByText('Sign in to Example App')).toBeTruthy();
expect(screen.getByText('Username')).toBeTruthy();
expect(screen.getByText('Password')).toBeTruthy();
expect(
screen.getByRole('header', { name: 'Sign in to Example App' })
).toBeTruthy();

fireEvent.changeText(screen.getByLabelText('Username'), 'admin');
fireEvent.changeText(screen.getByLabelText('Password'), 'qwerty123');
fireEvent.press(screen.getByText('Sign In'));
fireEvent.press(screen.getByRole('button', { name: 'Sign In' }));

expect(await screen.findByLabelText('Error')).toHaveTextContent(
expect(await screen.findByRole('alert')).toHaveTextContent(
'Incorrect username or password'
);

fireEvent.changeText(screen.getByLabelText('Password'), 'admin1');
fireEvent.press(screen.getByText('Sign In'));
fireEvent.press(screen.getByRole('button', { name: 'Sign In' }));

expect(await screen.findByText('Welcome admin!')).toBeTruthy();
expect(screen.queryByText('Sign in to Example App')).toBeFalsy();
expect(screen.queryByText('Username')).toBeFalsy();
expect(screen.queryByText('Password')).toBeFalsy();
expect(
screen.queryByRole('header', { name: 'Sign in to Example App' })
).toBeFalsy();
expect(screen.queryByLabelText('Username')).toBeFalsy();
expect(screen.queryByLabelText('Password')).toBeFalsy();
});
4 changes: 3 additions & 1 deletion examples/basic/components/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ type Props = {
export function Home({ user }: Props) {
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome {user}!</Text>
<Text accessibilityRole="header" style={styles.title}>
Welcome {user}!
</Text>
</View>
);
}
Expand Down
17 changes: 12 additions & 5 deletions examples/basic/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
View,
Text,
TextInput,
TouchableOpacity,
Pressable,
ActivityIndicator,
} from 'react-native';

Expand Down Expand Up @@ -34,7 +34,9 @@ export function LoginForm({ onLoginSuccess }: Props) {

return (
<View style={styles.container}>
<Text style={styles.title}>Sign in to Example App</Text>
<Text accessibilityRole="header" style={styles.title}>
Sign in to Example App
</Text>

<Text style={styles.textLabel}>Username</Text>
<TextInput
Expand All @@ -55,18 +57,23 @@ export function LoginForm({ onLoginSuccess }: Props) {
/>

{error && (
<Text accessibilityLabel="Error" style={styles.validator}>
<Text accessibilityRole="alert" style={styles.validator}>
{error}
</Text>
)}

<TouchableOpacity onPress={handleSignIn} style={styles.button}>
<Pressable
accessibilityRole="button"
disabled={isLoading}
onPress={handleSignIn}
style={styles.button}
>
{isLoading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.buttonText}>Sign In</Text>
)}
</TouchableOpacity>
</Pressable>
</View>
);
}
Expand Down
29 changes: 15 additions & 14 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"test": "jest"
"test": "jest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"expo": "^46.0.0",
"expo-status-bar": "~1.4.0",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-native": "0.69.4",
"expo": "^47.0.0",
"expo-status-bar": "~1.4.2",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-native": "0.70.5",
"react-native-web": "~0.18.7"
},
"devDependencies": {
"@babel/core": "^7.18.6",
"@testing-library/jest-native": "^5.0.0",
"@testing-library/react-native": "^11.0.0",
"@types/react": "~18.0.0",
"@types/react-native": "~0.69.1",
"jest": "^28.0.0",
"react-test-renderer": "18.0.0",
"typescript": "^4.6.3"
"@babel/core": "^7.19.3",
"@testing-library/jest-native": "^5.1.2",
"@testing-library/react-native": "^11.4.0",
"@types/react": "~18.0.24",
"@types/react-native": "~0.70.6",
"jest": "^29.3.0",
"react-test-renderer": "18.1.0",
"typescript": "^4.8.4"
},
"private": true
}