Bump react-hook-form, add more covrage for login and register forms
This commit is contained in:
5 files changed
+76
-7
No files matched your search
@@ -1,10 +1,11 @@
|
||||
import { fireEvent, waitFor, type RenderResult } from '@testing-library/react-native';
|
||||
import React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
|
||||
import * as api from '@/api';
|
||||
import { ApiError } from '@/errors';
|
||||
import Login from '@/modules/login/Login';
|
||||
import { renderWithProviders } from '@/testUtils';
|
||||
import { focusedFieldLabel, renderWithProviders } from '@/testUtils';
|
||||
|
||||
// Login is pushed on top of Membership and closes itself with goBack() once
|
||||
// authentication succeeds; stub the navigation object so we can assert on that.
|
||||
@@ -35,6 +36,24 @@ it('renders the login form', () => {
|
||||
expect(getByText('Glemt passord?')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('moves focus to the password field when the username is submitted from the keyboard', async () => {
|
||||
const { getByLabelText } = renderWithProviders(<Login />);
|
||||
|
||||
fireEvent(getByLabelText('E-post / Brukernavn'), 'submitEditing');
|
||||
|
||||
// react-hook-form's setFocus defers the focus call to the next tick
|
||||
await waitFor(() => expect(focusedFieldLabel()).toBe('Passord'));
|
||||
});
|
||||
|
||||
it('opens the forgot password page in the browser', () => {
|
||||
const { getByText } = renderWithProviders(<Login />);
|
||||
|
||||
fireEvent.press(getByText('Glemt passord?'));
|
||||
|
||||
// Not one of our own domains in the test env, so no UTM tagging (covered in utils.test.ts)
|
||||
expect(Linking.openURL).toHaveBeenCalledWith('https://dusken.example.test/auth/password_reset/');
|
||||
});
|
||||
|
||||
it('logs in against the API and returns to the previous screen', async () => {
|
||||
const login = jest.spyOn(api, 'login').mockResolvedValue({ token: 'token-123' });
|
||||
const { getByText, getByLabelText } = renderWithProviders(<Login />);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ActivityIndicator } from 'react-native';
|
||||
import * as api from '@/api';
|
||||
import { ApiError } from '@/errors';
|
||||
import UserRegister from '@/modules/register/UserRegister';
|
||||
import { renderWithProviders, testUser } from '@/testUtils';
|
||||
import { focusedFieldLabel, renderWithProviders, testUser } from '@/testUtils';
|
||||
import type { UserType } from '@/types/data';
|
||||
|
||||
// The screen is pushed on top of Login and closes itself with goBack() once
|
||||
@@ -34,6 +34,10 @@ beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('renders the registration form', () => {
|
||||
const { getByText } = renderWithProviders(<UserRegister />);
|
||||
|
||||
@@ -98,6 +102,47 @@ it('shows server field errors on the matching form field', async () => {
|
||||
expect(mockGoBack).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('moves focus down the form when a field is submitted from the keyboard', async () => {
|
||||
const { getByLabelText } = renderWithProviders(<UserRegister />);
|
||||
const labels = ['Fornavn', 'Etternavn', 'E-post', 'Mobilnummer', 'Passord'];
|
||||
|
||||
for (const [i, label] of labels.slice(0, -1).entries()) {
|
||||
fireEvent(getByLabelText(label), 'submitEditing');
|
||||
// react-hook-form's setFocus defers the focus call to the next tick
|
||||
await waitFor(() => expect(focusedFieldLabel()).toBe(labels[i + 1]));
|
||||
}
|
||||
});
|
||||
|
||||
it('shows a generic error when registration fails on the network', async () => {
|
||||
jest.spyOn(api, 'registerUser').mockRejectedValueOnce(new Error('network down'));
|
||||
const { getByText, getByRole, getByLabelText, findByText } = renderWithProviders(<UserRegister />);
|
||||
|
||||
fillForm(getByLabelText);
|
||||
await waitFor(() => expect(getByRole('button', { name: 'Registrer meg' })).toBeEnabled());
|
||||
fireEvent.press(getByText('Registrer meg'));
|
||||
|
||||
expect(await findByText('Kunne ikke sende inn skjemaet! Prøv igjen senere...')).toBeTruthy();
|
||||
expect(mockGoBack).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('warns instead of submitting when the keyboard submits before validation has caught up', async () => {
|
||||
jest.useFakeTimers();
|
||||
const registerUser = jest.spyOn(api, 'registerUser');
|
||||
const { getByLabelText, findByText, queryByText } = renderWithProviders(<UserRegister />);
|
||||
|
||||
// Filling in and submitting in the same tick reaches the submit handler while isValid still reflects the empty form
|
||||
fillForm(getByLabelText);
|
||||
fireEvent(getByLabelText('Passord'), 'submitEditing');
|
||||
|
||||
const message = 'Noen av feltene er ikke fylt ut riktig';
|
||||
expect(await findByText(message)).toBeTruthy();
|
||||
expect(registerUser).not.toHaveBeenCalled();
|
||||
|
||||
// The toast dismisses itself after five seconds
|
||||
act(() => jest.advanceTimersByTime(5000));
|
||||
expect(queryByText(message)).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects an invalid e-mail address without submitting', async () => {
|
||||
const registerUser = jest.spyOn(api, 'registerUser');
|
||||
const { getByLabelText, findByText } = renderWithProviders(<UserRegister />);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { render } from '@testing-library/react-native';
|
||||
import React from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import { TextInput } from 'react-native';
|
||||
|
||||
import { AuthProvider } from '@/auth/AuthContext';
|
||||
import { queryKeys } from '@/queries/queryKeys';
|
||||
@@ -29,6 +30,10 @@ export const testMembershipTypes: MembershipTypeOption[] = [
|
||||
{ slug: 'support', price: 50000 },
|
||||
];
|
||||
|
||||
/** Label of the field focused last; the mocked TextInput shares a single focus() mock across instances. */
|
||||
export const focusedFieldLabel = () =>
|
||||
(TextInput.prototype.focus as jest.Mock).mock.contexts.at(-1)?.props.accessibilityLabel;
|
||||
|
||||
interface RenderWithProvidersOptions {
|
||||
/** Auth token the app has stored; being set means the user is logged in. */
|
||||
initialToken?: string | null;
|
||||
|
||||
Reference in new issue
Block a user