From 1b4466046adf4351770e848113af9e56dcc71098 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 8 Nov 2022 22:06:52 +0100 Subject: [PATCH 1/3] refactor: apply better queries when applicable --- examples/basic/__tests__/App.test.tsx | 40 +++++++++++++++++-------- examples/basic/components/Home.tsx | 4 ++- examples/basic/components/LoginForm.tsx | 17 +++++++---- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/examples/basic/__tests__/App.test.tsx b/examples/basic/__tests__/App.test.tsx index 671fb2071..7d928e2e2 100644 --- a/examples/basic/__tests__/App.test.tsx +++ b/examples/basic/__tests__/App.test.tsx @@ -11,7 +11,9 @@ test('renders correctly', () => { // Idiom: `getByXxx` is a predicate by itself, 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(); }); /** @@ -26,7 +28,9 @@ test('User can sign in successully with correct credentials', async () => { // Idiom: `getByXxx` is a predicate by itself, 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.getByRole('header', { name: 'Sign in to Example App' }) + ).toBeTruthy(); expect(screen.getByText('Username')).toBeTruthy(); expect(screen.getByText('Password')).toBeTruthy(); @@ -35,16 +39,20 @@ test('User can sign in successully with correct credentials', async () => { fireEvent.changeText(screen.getByLabelText('Password'), 'admin1'); // Hint: we can use `getByText` to find our button by its text. - fireEvent.press(screen.getByText('Sign In')); + fireEvent.press(screen.getByRole('button', { name: 'Sign In' })); // 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 // already finished - expect(await screen.findByText('Welcome admin!')).toBeTruthy(); + expect( + await screen.findByRole('header', { name: '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.queryByRole('header', { name: 'Sign in to Example App' }) + ).toBeFalsy(); expect(screen.queryByText('Username')).toBeFalsy(); expect(screen.queryByText('Password')).toBeFalsy(); }); @@ -64,20 +72,24 @@ test('User can sign in successully with correct credentials', async () => { test('User will see errors for incorrect credentials', async () => { render(); - expect(screen.getByText('Sign in to Example App')).toBeTruthy(); + expect( + screen.getByRole('header', { name: 'Sign in to Example App' }) + ).toBeTruthy(); expect(screen.getByText('Username')).toBeTruthy(); expect(screen.getByText('Password')).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.getByRole('header', { name: 'Sign in to Example App' }) + ).toBeTruthy(); expect(screen.getByText('Username')).toBeTruthy(); expect(screen.getByText('Password')).toBeTruthy(); }); @@ -88,20 +100,22 @@ test('User will see errors for incorrect credentials', async () => { test('User can sign in after incorrect attempt', async () => { render(); - expect(screen.getByText('Sign in to Example App')).toBeTruthy(); + expect( + screen.getByRole('header', { name: 'Sign in to Example App' }) + ).toBeTruthy(); expect(screen.getByText('Username')).toBeTruthy(); expect(screen.getByText('Password')).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(); diff --git a/examples/basic/components/Home.tsx b/examples/basic/components/Home.tsx index 74317d50d..d1cc588e6 100644 --- a/examples/basic/components/Home.tsx +++ b/examples/basic/components/Home.tsx @@ -8,7 +8,9 @@ type Props = { export function Home({ user }: Props) { return ( - Welcome {user}! + + Welcome {user}! + ); } diff --git a/examples/basic/components/LoginForm.tsx b/examples/basic/components/LoginForm.tsx index dc3961e48..191509ea1 100644 --- a/examples/basic/components/LoginForm.tsx +++ b/examples/basic/components/LoginForm.tsx @@ -4,7 +4,7 @@ import { View, Text, TextInput, - TouchableOpacity, + Pressable, ActivityIndicator, } from 'react-native'; @@ -34,7 +34,9 @@ export function LoginForm({ onLoginSuccess }: Props) { return ( - Sign in to Example App + + Sign in to Example App + Username {error && ( - + {error} )} - + {isLoading ? ( ) : ( Sign In )} - + ); } From 2eafd48ed18b363d3393616800f1455fae0de733 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 8 Nov 2022 22:17:06 +0100 Subject: [PATCH 2/3] chore: update package versions --- examples/basic/package.json | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/basic/package.json b/examples/basic/package.json index 3a196bb25..95f620c48 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -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 } From eaeaada8905a4d3d58f9172cc6ac0569ec1f9187 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Tue, 8 Nov 2022 22:27:25 +0100 Subject: [PATCH 3/3] docs: tweak comments --- examples/basic/__tests__/App.test.tsx | 40 ++++++++++++--------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/examples/basic/__tests__/App.test.tsx b/examples/basic/__tests__/App.test.tsx index 7d928e2e2..ecf09a946 100644 --- a/examples/basic/__tests__/App.test.tsx +++ b/examples/basic/__tests__/App.test.tsx @@ -9,7 +9,7 @@ test('renders correctly', () => { // Idiom: no need to capture render output, as we will use `screen` for queries. render(); - // 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.getByRole('header', { name: 'Sign in to Example App' }) @@ -25,36 +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(); - // 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.getByRole('header', { name: 'Sign in to Example App' }) ).toBeTruthy(); - expect(screen.getByText('Username')).toBeTruthy(); - expect(screen.getByText('Password')).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. + // 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.findByRole('header', { name: 'Welcome admin!' }) ).toBeTruthy(); - // Idiom: use `queryByXxx` with `expect().toBeFalsy()` to assess that element is not present. + // 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.queryByText('Username')).toBeFalsy(); - expect(screen.queryByText('Password')).toBeFalsy(); + expect(screen.queryByLabelText('Username')).toBeFalsy(); + expect(screen.queryByLabelText('Password')).toBeFalsy(); }); /** @@ -64,7 +62,7 @@ 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. @@ -75,8 +73,6 @@ test('User will see errors for incorrect credentials', async () => { expect( screen.getByRole('header', { name: 'Sign in to Example App' }) ).toBeTruthy(); - expect(screen.getByText('Username')).toBeTruthy(); - expect(screen.getByText('Password')).toBeTruthy(); fireEvent.changeText(screen.getByLabelText('Username'), 'admin'); fireEvent.changeText(screen.getByLabelText('Password'), 'qwerty123'); @@ -90,8 +86,8 @@ test('User will see errors for incorrect credentials', async () => { expect( screen.getByRole('header', { name: 'Sign in to Example App' }) ).toBeTruthy(); - expect(screen.getByText('Username')).toBeTruthy(); - expect(screen.getByText('Password')).toBeTruthy(); + expect(screen.getByLabelText('Username')).toBeTruthy(); + expect(screen.getByLabelText('Password')).toBeTruthy(); }); /** @@ -103,8 +99,6 @@ test('User can sign in after incorrect attempt', async () => { expect( screen.getByRole('header', { name: 'Sign in to Example App' }) ).toBeTruthy(); - expect(screen.getByText('Username')).toBeTruthy(); - expect(screen.getByText('Password')).toBeTruthy(); fireEvent.changeText(screen.getByLabelText('Username'), 'admin'); fireEvent.changeText(screen.getByLabelText('Password'), 'qwerty123'); @@ -118,7 +112,9 @@ test('User can sign in after incorrect attempt', async () => { 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(); });