Skip to content

Commit

Permalink
Updated tests and created custom hook (#266)
Browse files Browse the repository at this point in the history
* WIP : Fixing test for email verification using msw

* Test : Updated test with msw

* Feat : Added custom redirect hook

* Feat : Utilised custom redirect hook

* Chore : Updated yarn lock

* Chore : Updated test file name

* Chore : Removed unused dependency

* Chore : Moved test file for a hook
  • Loading branch information
joyguptaa authored Apr 25, 2024
1 parent 41bf187 commit 1a2fa7e
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 64 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
},
"devDependencies": {
"@babel/plugin-transform-private-property-in-object": "^7.22.11",
"@testing-library/react-hooks": "^8.0.1",
"eslint": "^8.47.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
Expand Down
30 changes: 30 additions & 0 deletions client/src/hooks/useCountdownTimer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {useEffect} from 'react';

const useCountdownTimer = (
isSuccess,
navigate,
countdown,
setCountdown,
route = '/welcome',
) => {
useEffect(() => {
let timer = null;
if (isSuccess) {
timer = setInterval(() => {
if (countdown > 1) {
setCountdown((prevCount) => prevCount - 1);
} else {
clearInterval(timer);
navigate(route);
}
}, 1000);
}
return () => {
clearInterval(timer);
};
}, [isSuccess, countdown, navigate]);

return countdown;
};

export default useCountdownTimer;
66 changes: 66 additions & 0 deletions client/src/hooks/useCountdownTimer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {renderHook, act} from '@testing-library/react-hooks';
import useCountdownTimer from './useCountdownTimer';

jest.useFakeTimers();

describe('useCountdownTimer', () => {
afterEach(() => {
jest.clearAllTimers();
jest.useRealTimers();
});
beforeEach(() => {
jest.useFakeTimers();
});
it('should navigate to "/welcome" when countdown reaches 0', () => {
const navigateMock = jest.fn();
let countdown = 3;
const setCountdownMock = jest.fn().mockImplementation((value) => {
countdown = value;
});
const {result} = renderHook(() =>
useCountdownTimer(true, navigateMock, countdown, setCountdownMock),
);
act(() => {
jest.advanceTimersByTime(3000);
});
setTimeout(() => {
expect(result.current).toBe(0);
expect(navigateMock).toHaveBeenCalledWith('/welcome');
}, 3000);
});

it('should not navigate when isSuccess is false', () => {
const navigateMock = jest.fn();
let countdown = 3;
const setCountdownMock = jest.fn().mockImplementation((value) => {
countdown = value;
});
const {result} = renderHook(() =>
useCountdownTimer(false, navigateMock, countdown, setCountdownMock),
);
act(() => {
jest.advanceTimersByTime(3000);
});
setTimeout(() => {
expect(result.current).toBe(3);
expect(navigateMock).not.toHaveBeenCalled();
}, 3000);
});

it('should update countdown correctly', () => {
const navigateMock = jest.fn();
let countdown = 3;
const setCountdownMock = jest.fn().mockImplementation((value) => {
countdown = value;
});
const {result} = renderHook(() =>
useCountdownTimer(true, navigateMock, countdown, setCountdownMock),
);
act(() => {
jest.advanceTimersByTime(2000);
});
setTimeout(() => {
expect(result.current).toBe(1);
}, 2000);
});
});
7 changes: 6 additions & 1 deletion client/src/mocks/handlers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {signinHandler} from './handlers/signin-handler';
import {signupHandler} from './handlers/signup-handler';
import {emailVerificationHandler} from './handlers/emailVerification-handler';

const handlers = [...signinHandler, ...signupHandler];
const handlers = [
...signinHandler,
...signupHandler,
...emailVerificationHandler,
];

export default handlers;
28 changes: 28 additions & 0 deletions client/src/mocks/handlers/emailVerification-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {rest} from 'msw';
import {STATUS_CODES} from 'http';

export const emailVerificationHandler = [
rest.get('/api/auth/verify', (req, res, ctx) => {
const {searchParams} = req.url;
const token = searchParams.get('token');
if (token === 'exampleToken') {
return res(
ctx.status(200),
ctx.json({
message: 'Verification successful',
statusCode: 200,
error: STATUS_CODES[200],
}),
);
} else {
return res(
ctx.status(403),
ctx.json({
message: 'Token Expired',
statusCode: 403,
error: STATUS_CODES[403],
}),
);
}
}),
];
18 changes: 2 additions & 16 deletions client/src/pages/reset-password/ResetPassword.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {useApi} from '../../hooks/useApi';
import ResponseCard from '../../components/common/responseCard/ResponseCard';
import {FaCheck} from 'react-icons/fa6';
import {RxCross2} from 'react-icons/rx';
import useCountdownTimer from '../../hooks/useCountdownTimer';
import './ResetPassword.css';

function ResetPassword() {
Expand Down Expand Up @@ -66,22 +67,7 @@ function ResetPassword() {
}
}, []);

useEffect(() => {
let timer = null;
if (success) {
timer = setInterval(() => {
if (countdown > 0) {
setCountdown((prevCount) => prevCount - 1);
} else {
clearInterval(timer);
navigate('/signin');
}
}, 1000);
}
return () => {
clearInterval(timer);
};
}, [success, countdown]);
useCountdownTimer(success, navigate, countdown, setCountdown, '/signin');
return tokenError ? (
<ResponseCard
message={tokenError}
Expand Down
19 changes: 2 additions & 17 deletions client/src/pages/verification/Verification.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {useState, useEffect} from 'react';
import {FaCheck} from 'react-icons/fa6';
import {RxCross2} from 'react-icons/rx';
import ResponseCard from '../../components/common/responseCard/ResponseCard';
import useCountdownTimer from '../../hooks/useCountdownTimer';

export default function Verification() {
const location = useLocation();
Expand All @@ -20,23 +21,7 @@ export default function Verification() {
},
true,
);
useEffect(() => {
let timer = null;
if (isSuccess) {
timer = setInterval(() => {
if (countdown > 1) {
setCountdown((prevCount) => prevCount - 1);
} else {
clearInterval(timer);
navigate('/welcome');
}
}, 1000);
}
return () => {
clearInterval(timer);
};
}, [isSuccess, countdown, navigate]);

useCountdownTimer(isSuccess, navigate, countdown, setCountdown);
useEffect(() => {
makeRequest();
}, []);
Expand Down
44 changes: 14 additions & 30 deletions client/src/pages/verification/Verification.test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import React from 'react';
import {MemoryRouter, Routes, Route} from 'react-router-dom';
import {render, waitFor, screen} from '@testing-library/react';
import {useApi} from '../../hooks/useApi';
import Home from '../home/Home';
import Verification from './Verification';

jest.mock('../../hooks/useApi');
import * as router from 'react-router';

describe('VerificationStatus Component', () => {
const navigate = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate);
});

it('should nvigate to welcome page when token is not provided', async () => {
useApi.mockReturnValue({
errorMsg: null,
makeRequest: jest.fn(),
isSuccess: true,
loading: false,
});
afterAll(() => {
jest.restoreAllMocks();
});

it('should navigate to welcome page when token is not provided', async () => {
const token = 'exampleToken';
const queryParams = new URLSearchParams();
queryParams.append('token', token);
Expand All @@ -32,20 +29,15 @@ describe('VerificationStatus Component', () => {
</MemoryRouter>,
);
await waitFor(() => {
expect(screen.getByTestId('home-container')).toBeInTheDocument();
expect(navigate).not.toHaveBeenCalled();
});
setTimeout(() => {
expect(navigate).toHaveBeenCalledWith('/welcome');
}, 3000);
expect(screen.getByTestId('home-container')).toBeInTheDocument();
});

it('renders "Verification successful" message when token is valid', async () => {
useApi.mockReturnValue({
errorMsg: null,
makeRequest: jest.fn(),
isSuccess: true,
loading: false,
data: {
message: 'Verification successful',
},
});
const token = 'exampleToken';
const queryParams = new URLSearchParams();
queryParams.append('token', token);
Expand All @@ -58,21 +50,13 @@ describe('VerificationStatus Component', () => {
</Routes>
</MemoryRouter>,
);

await waitFor(() => {
expect(screen.getByText('Verification successful')).toBeInTheDocument();
});
});

it('renders error message message when token is invalid', async () => {
useApi.mockReturnValue({
errorMsg: 'Token Expired',
makeRequest: jest.fn(),
isSuccess: false,
loading: false,
});

const token = 'exampleToken';
const token = 'malformedToken';
const queryParams = new URLSearchParams();
queryParams.append('token', token);
const url = `/verify?${queryParams.toString()}`;
Expand Down
15 changes: 15 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4904,6 +4904,14 @@
lodash "^4.17.15"
redent "^3.0.0"

"@testing-library/react-hooks@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12"
integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==
dependencies:
"@babel/runtime" "^7.12.5"
react-error-boundary "^3.1.0"

"@testing-library/react@^13.4.0":
version "13.4.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966"
Expand Down Expand Up @@ -16631,6 +16639,13 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"

react-error-boundary@^3.1.0:
version "3.1.4"
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
dependencies:
"@babel/runtime" "^7.12.5"

react-error-overlay@^6.0.11:
version "6.0.11"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
Expand Down

0 comments on commit 1a2fa7e

Please sign in to comment.