Skip to content

Commit

Permalink
feat(not-found)!: implement 404 page with dynamic link behavior (#20)
Browse files Browse the repository at this point in the history
* feat(not-found): implement 404 page with dynamic link behavior
- Create not-found.tsx to display 404 errors.
- Disable SSR in not-found.tsx for better UI experience.
- Add dynamic Link atom to not-found.tsx for navigation.
- Add testing suite for not-found.tsx

* feat(not-found)!: improve navigation link and styling on 404 page
- Enhance 404 page navigation by dynamically setting `backLink` based on document referrer.
- Update heading styles, setting "404" as an `h1` and the descriptive text as an `h2'.
- Add `ariaLabel` for improved accessibility on the navigation link.
- Refactor the code to use `useEffect` to manage `backLink`, `linkLabel`, and `ariaLabel` setup.
- Update tests to mock `useRouter` and validate new navigation behaviors.

* fix(not-found): correct link label text to 'Go to Login'
  • Loading branch information
ishaan000 authored Nov 15, 2024
1 parent 8c77dfc commit f5105d0
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 0 deletions.
84 changes: 84 additions & 0 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use client';
import { Container, Divider } from '@mui/material';
import React, { useEffect, useState } from 'react';
import Link from '../../src/components/atoms/Link';
import Text from '../../src/components/atoms/Text';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/navigation';

export const NotFoundContent = () => {
const router = useRouter();

const [backLink, setBackLink] = useState('/');
const [linkLabel, setLinkLabel] = useState('Go to Login');
const [ariaLabel, setAriaLabel] = useState('Go to Login');

useEffect(() => {
if (typeof window !== 'undefined' && document.referrer) {
const referrerOrigin = new URL(document.referrer).origin;
const currentOrigin = window.location.origin;

const initialBackLink =
referrerOrigin === currentOrigin ? document.referrer : '/';
const initialLinkLabel =
initialBackLink === '/' ? 'Go to Login' : 'Return to Previous Page';

setBackLink(initialBackLink);
setLinkLabel(initialLinkLabel);

const initialAriaLabel =
initialBackLink === '/' ? 'Go to Login' : 'Return to Previous Page';
setAriaLabel(initialAriaLabel);
}
}, []);

const handleBack = (event: React.MouseEvent) => {
event.preventDefault();
if (backLink === document.referrer) {
router.back();
} else {
router.push(backLink);
}
};

return (
<Container
maxWidth="md"
style={{
padding: '2rem',
minHeight: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<Text variant="h1">404</Text>
<Divider flexItem orientation="vertical" style={{ height: '40px' }} />
<Text variant="h2">This page could not be found.</Text>
</div>
<Link
aria-label={ariaLabel}
href={backLink}
onClick={handleBack}
sx={{
marginTop: '1rem',
color: 'primary.main',
'&:hover': {
color: 'primary.dark',
},
}}
>
{linkLabel}
</Link>
</Container>
);
};

const NotFound = dynamic(() => Promise.resolve(NotFoundContent), {
ssr: false,
});

export default NotFound;
120 changes: 120 additions & 0 deletions tests/app/not-found.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { NotFoundContent } from '../../src/app/not-found';
import React from 'react';
import { useRouter } from 'next/navigation';

jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
}));

describe('NotFoundContent Component', () => {
const mockBack = jest.fn();
const mockPush = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
(useRouter as jest.Mock).mockReturnValue({
back: mockBack,
push: mockPush,
});
});

afterEach(() => {
jest.resetAllMocks();
});

it('should render the 404 message', async () => {
render(<NotFoundContent />);

await waitFor(() => {
expect(screen.getByText('404')).toBeInTheDocument();
expect(
screen.getByText('This page could not be found.')
).toBeInTheDocument();
});
});

it('should render "Go to Login" when there is no referrer', async () => {
Object.defineProperty(window, 'location', {
value: { origin: 'http://localhost' },
writable: true,
});
Object.defineProperty(document, 'referrer', { value: '', writable: true });

render(<NotFoundContent />);

expect(await screen.findByText('Go to Login')).toBeInTheDocument();
});

it('should render "Return to Previous Page" when referrer is from the same origin', async () => {
Object.defineProperty(window, 'location', {
value: { origin: 'http://localhost' },
writable: true,
});
Object.defineProperty(document, 'referrer', {
value: 'http://localhost/previous-page',
writable: true,
});

render(<NotFoundContent />);

expect(
await screen.findByText('Return to Previous Page')
).toBeInTheDocument();
});

it('should render "Go to Login" when referrer is from a different origin', async () => {
Object.defineProperty(window, 'location', {
value: { origin: 'http://localhost' },
writable: true,
});
Object.defineProperty(document, 'referrer', {
value: 'https://external.com',
writable: true,
});

render(<NotFoundContent />);

expect(await screen.findByText('Go to Login')).toBeInTheDocument();
});

it('clicking the link calls router.back() when backLink matches document.referrer', async () => {
Object.defineProperty(window, 'location', {
value: { origin: 'http://localhost' },
writable: true,
});
const referrer = 'http://localhost/previous-page';
Object.defineProperty(document, 'referrer', {
value: referrer,
writable: true,
});

render(<NotFoundContent />);

const linkElement = await screen.findByText('Return to Previous Page');
fireEvent.click(linkElement);

expect(mockBack).toHaveBeenCalled();
expect(mockPush).not.toHaveBeenCalled();
});

it('clicking the link calls router.push("/") when backLink does not match document.referrer', async () => {
Object.defineProperty(window, 'location', {
value: { origin: 'http://localhost' },
writable: true,
});
Object.defineProperty(document, 'referrer', {
value: '',
writable: true,
});

render(<NotFoundContent />);

const linkElement = await screen.findByText('Go to Login');
fireEvent.click(linkElement);

expect(mockPush).toHaveBeenCalledWith('/');
expect(mockBack).not.toHaveBeenCalled();
});
});

0 comments on commit f5105d0

Please sign in to comment.