tests: improve coverage on newly created code
This commit is contained in:
parent
fa45be3020
commit
cbf1240512
8 changed files with 248 additions and 19 deletions
109
src/client/hooks/__tests__/useLocale.test.ts
Normal file
109
src/client/hooks/__tests__/useLocale.test.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import nookies from 'nookies';
|
||||||
|
import { getTRPCMock } from '@/client/mocks/getTrpcMock';
|
||||||
|
import { server } from '@/client/mocks/server';
|
||||||
|
import { renderHook, waitFor } from '../../../../tests/test-utils';
|
||||||
|
import { useLocale } from '../useLocale';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
nookies.destroy(null, 'locale');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('test: useLocale()', () => {
|
||||||
|
describe('test: locale', () => {
|
||||||
|
it('should return users locale if logged in', async () => {
|
||||||
|
// arrange
|
||||||
|
const locale = 'fr-FR';
|
||||||
|
// @ts-expect-error - we're mocking the trpc context
|
||||||
|
server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale } }));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const { result } = renderHook(() => useLocale());
|
||||||
|
|
||||||
|
// assert
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.locale).toEqual(locale);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return cookie locale if not logged in', async () => {
|
||||||
|
// arrange
|
||||||
|
const locale = 'fr-FR';
|
||||||
|
nookies.set(null, 'locale', locale);
|
||||||
|
server.use(getTRPCMock({ path: ['auth', 'me'], response: null }));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const { result } = renderHook(() => useLocale());
|
||||||
|
|
||||||
|
// assert
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.locale).toEqual(locale);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return browser locale if not logged in and no cookie', async () => {
|
||||||
|
// arrange
|
||||||
|
const locale = 'fr-FR';
|
||||||
|
jest.spyOn(window.navigator, 'language', 'get').mockReturnValueOnce(locale);
|
||||||
|
server.use(getTRPCMock({ path: ['auth', 'me'], response: null }));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const { result } = renderHook(() => useLocale());
|
||||||
|
|
||||||
|
// assert
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.locale).toEqual(locale);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default to english if no locale is found', async () => {
|
||||||
|
// arrange
|
||||||
|
server.use(getTRPCMock({ path: ['auth', 'me'], response: null }));
|
||||||
|
// @ts-expect-error - we're mocking window.navigator
|
||||||
|
jest.spyOn(window.navigator, 'language', 'get').mockReturnValueOnce(undefined);
|
||||||
|
|
||||||
|
// act
|
||||||
|
const { result } = renderHook(() => useLocale());
|
||||||
|
|
||||||
|
// assert
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.locale).toEqual('en');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('test: changeLocale()', () => {
|
||||||
|
it('should set the locale in the cookie', async () => {
|
||||||
|
// arrange
|
||||||
|
const locale = 'fr-FR';
|
||||||
|
const { result } = renderHook(() => useLocale());
|
||||||
|
|
||||||
|
// act
|
||||||
|
result.current.changeLocale(locale);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nookies.get(null)).toEqual({ locale: 'fr-FR' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the locale in the user profile when logged in', async () => {
|
||||||
|
// arrange
|
||||||
|
const locale = 'en';
|
||||||
|
// @ts-expect-error - we're mocking the trpc context
|
||||||
|
server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'fr-FR' } }));
|
||||||
|
server.use(getTRPCMock({ path: ['auth', 'changeLocale'], type: 'mutation', response: true }));
|
||||||
|
const { result } = renderHook(() => useLocale());
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.locale).toEqual('fr-FR');
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
result.current.changeLocale(locale);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nookies.get(null)).toEqual({ locale });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
1
src/client/messages/fr-FR.json
Normal file
1
src/client/messages/fr-FR.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import type { MessageKey } from '@/server/utils/errors';
|
||||||
import { trpc } from '../../../../utils/trpc';
|
import { trpc } from '../../../../utils/trpc';
|
||||||
import { AuthFormLayout } from '../../components/AuthFormLayout';
|
import { AuthFormLayout } from '../../components/AuthFormLayout';
|
||||||
import { LoginForm } from '../../components/LoginForm';
|
import { LoginForm } from '../../components/LoginForm';
|
||||||
|
@ -16,10 +17,7 @@ export const LoginContainer: React.FC = () => {
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const login = trpc.auth.login.useMutation({
|
const login = trpc.auth.login.useMutation({
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
let toastMessage = e.message;
|
const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
|
||||||
if (e.data?.translatedError) {
|
|
||||||
toastMessage = t(e.data.translatedError);
|
|
||||||
}
|
|
||||||
toast.error(toastMessage);
|
toast.error(toastMessage);
|
||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
|
@ -34,10 +32,7 @@ export const LoginContainer: React.FC = () => {
|
||||||
|
|
||||||
const verifyTotp = trpc.auth.verifyTotp.useMutation({
|
const verifyTotp = trpc.auth.verifyTotp.useMutation({
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
let toastMessage = e.message;
|
const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
|
||||||
if (e.data?.translatedError) {
|
|
||||||
toastMessage = t(e.data.translatedError);
|
|
||||||
}
|
|
||||||
toast.error(toastMessage);
|
toast.error(toastMessage);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useLocale } from '@/client/hooks/useLocale';
|
import { useLocale } from '@/client/hooks/useLocale';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import type { MessageKey } from '@/server/utils/errors';
|
||||||
import { trpc } from '../../../../utils/trpc';
|
import { trpc } from '../../../../utils/trpc';
|
||||||
import { AuthFormLayout } from '../../components/AuthFormLayout';
|
import { AuthFormLayout } from '../../components/AuthFormLayout';
|
||||||
import { RegisterForm } from '../../components/RegisterForm';
|
import { RegisterForm } from '../../components/RegisterForm';
|
||||||
|
@ -16,10 +17,7 @@ export const RegisterContainer: React.FC = () => {
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const register = trpc.auth.register.useMutation({
|
const register = trpc.auth.register.useMutation({
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
let toastMessage = e.message;
|
const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
|
||||||
if (e.data?.translatedError) {
|
|
||||||
toastMessage = t(e.data.translatedError);
|
|
||||||
}
|
|
||||||
toast.error(toastMessage);
|
toast.error(toastMessage);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useRouter } from 'next/router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import type { MessageKey } from '@/server/utils/errors';
|
||||||
import { Button } from '../../../../components/ui/Button';
|
import { Button } from '../../../../components/ui/Button';
|
||||||
import { trpc } from '../../../../utils/trpc';
|
import { trpc } from '../../../../utils/trpc';
|
||||||
import { AuthFormLayout } from '../../components/AuthFormLayout';
|
import { AuthFormLayout } from '../../components/AuthFormLayout';
|
||||||
|
@ -22,10 +23,7 @@ export const ResetPasswordContainer: React.FC<Props> = ({ isRequested }) => {
|
||||||
utils.auth.checkPasswordChangeRequest.invalidate();
|
utils.auth.checkPasswordChangeRequest.invalidate();
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
let toastMessage = e.message;
|
const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
|
||||||
if (e.data?.translatedError) {
|
|
||||||
toastMessage = t(e.data.translatedError);
|
|
||||||
}
|
|
||||||
toast.error(toastMessage);
|
toast.error(toastMessage);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { getAuthedPageProps } from '../page-helpers';
|
import nookies from 'nookies';
|
||||||
|
import merge from 'lodash.merge';
|
||||||
|
import { getAuthedPageProps, getMessagesPageProps } from '../page-helpers';
|
||||||
|
import englishMessages from '../../messages/en.json';
|
||||||
|
import frenchMessages from '../../messages/fr-FR.json';
|
||||||
|
|
||||||
describe('getAuthedPageProps', () => {
|
describe('test: getAuthedPageProps()', () => {
|
||||||
it('should redirect to /login if there is no user id in session', async () => {
|
it('should redirect to /login if there is no user id in session', async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const ctx = { req: { session: {} } };
|
const ctx = { req: { session: {} } };
|
||||||
|
@ -26,3 +30,58 @@ describe('getAuthedPageProps', () => {
|
||||||
expect(props).toEqual({});
|
expect(props).toEqual({});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('test: getMessagesPageProps()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nookies.destroy(null, 'locale');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct messages if the locale is in the session', async () => {
|
||||||
|
// arrange
|
||||||
|
const ctx = { req: { session: { locale: 'fr' }, headers: {} } };
|
||||||
|
|
||||||
|
// act
|
||||||
|
// @ts-expect-error - we're passing in a partial context
|
||||||
|
const { props } = await getMessagesPageProps(ctx);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(props.messages).toEqual(merge(frenchMessages, englishMessages));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct messages if the locale in the cookie', async () => {
|
||||||
|
// arrange
|
||||||
|
const ctx = { req: { session: {}, headers: {} } };
|
||||||
|
nookies.set(null, 'locale', 'fr-FR');
|
||||||
|
|
||||||
|
// act
|
||||||
|
// @ts-expect-error - we're passing in a partial context
|
||||||
|
const { props } = await getMessagesPageProps(ctx);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(props.messages).toEqual(merge(frenchMessages, englishMessages));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct messages if the locale is detected from the browser', async () => {
|
||||||
|
// arrange
|
||||||
|
const ctx = { req: { session: {}, headers: { 'accept-language': 'fr-FR' } } };
|
||||||
|
|
||||||
|
// act
|
||||||
|
// @ts-expect-error - we're passing in a partial context
|
||||||
|
const { props } = await getMessagesPageProps(ctx);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(props.messages).toEqual(merge(frenchMessages, englishMessages));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default to english messages if the locale is not found', async () => {
|
||||||
|
// arrange
|
||||||
|
const ctx = { req: { session: {}, headers: {} } };
|
||||||
|
|
||||||
|
// act
|
||||||
|
// @ts-expect-error - we're passing in a partial context
|
||||||
|
const { props } = await getMessagesPageProps(ctx);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(props.messages).toEqual(englishMessages);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -84,7 +84,7 @@ describe('Test: register', () => {
|
||||||
|
|
||||||
// act
|
// act
|
||||||
try {
|
try {
|
||||||
await caller.register({ username: 'test@test.com', password: '123' });
|
await caller.register({ username: 'test@test.com', password: '123', locale: 'en' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e as { code: string };
|
error = e as { code: string };
|
||||||
}
|
}
|
||||||
|
@ -327,3 +327,38 @@ describe('Test: resetPassword', () => {
|
||||||
expect(error?.code).not.toBe('UNAUTHORIZED');
|
expect(error?.code).not.toBe('UNAUTHORIZED');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Test: changeLocale', () => {
|
||||||
|
it('should not be accessible without an account', async () => {
|
||||||
|
// arrange
|
||||||
|
const caller = authRouter.createCaller(fromPartial({ req: { session: {} } }));
|
||||||
|
let error;
|
||||||
|
|
||||||
|
// act
|
||||||
|
try {
|
||||||
|
await caller.changeLocale({ locale: 'en' });
|
||||||
|
} catch (e) {
|
||||||
|
error = e as { code: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(error?.code).toBe('UNAUTHORIZED');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be accessible with an account', async () => {
|
||||||
|
// arrange
|
||||||
|
await createUser({ id: 122, locale: 'en' }, db);
|
||||||
|
const caller = authRouter.createCaller(fromPartial({ req: { session: { userId: 122 } } }));
|
||||||
|
let error;
|
||||||
|
|
||||||
|
// act
|
||||||
|
try {
|
||||||
|
await caller.changeLocale({ locale: 'fr-FR' });
|
||||||
|
} catch (e) {
|
||||||
|
error = e as { code: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(error?.code).not.toBe('UNAUTHORIZED');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -687,3 +687,37 @@ describe('Test: changePassword', () => {
|
||||||
expect(sessions).toHaveLength(0);
|
expect(sessions).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('test: changeLocale()', () => {
|
||||||
|
it('should change the locale of the user', async () => {
|
||||||
|
// arrange
|
||||||
|
const email = faker.internet.email();
|
||||||
|
const user = await createUser({ email }, database);
|
||||||
|
const locale = 'fr-FR';
|
||||||
|
|
||||||
|
// act
|
||||||
|
await AuthService.changeLocale({ userId: user.id, locale });
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const updatedUser = await getUserById(user.id, database);
|
||||||
|
expect(updatedUser?.locale).toBe(locale);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the user does not exist', async () => {
|
||||||
|
// arrange
|
||||||
|
const locale = 'fr-FR';
|
||||||
|
|
||||||
|
// act & assert
|
||||||
|
await expect(AuthService.changeLocale({ userId: 1, locale })).rejects.toThrowError('server-messages.errors.user-not-found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if the locale is invalid', async () => {
|
||||||
|
// arrange
|
||||||
|
const email = faker.internet.email();
|
||||||
|
const user = await createUser({ email }, database);
|
||||||
|
const locale = 'invalid';
|
||||||
|
|
||||||
|
// act & assert
|
||||||
|
await expect(AuthService.changeLocale({ userId: user.id, locale })).rejects.toThrowError('server-messages.errors.invalid-locale');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue