Skip to content

Commit

Permalink
✅ update unit test and test architectural
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnsonMao committed Oct 29, 2023
1 parent 6de4ec4 commit db627c4
Show file tree
Hide file tree
Showing 15 changed files with 420 additions and 53 deletions.
14 changes: 7 additions & 7 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ const createJestConfig = nextJest({ dir: './' });
// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const jestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
setupFilesAfterEnv: ['<rootDir>/tests/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
collectCoverageFrom: [
'<rootDir>/src/**/*.{ts,tsx}',
'!<rootDir>/src/middleware.ts',
'!<rootDir>/src/app/*.tsx',
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^~/(.*)$': '<rootDir>/$1',
},
collectCoverageFrom: [
'./src/components/**',
'./src/hooks/**',
'./src/plugins/**',
'./src/utils/**',
],
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
Expand Down
15 changes: 15 additions & 0 deletions src/app/[lang]/__tests__/footer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { render, screen } from '@testing-library/react';
import Footer from '../Footer';

describe('Footer component', () => {
it('should render correct element', () => {
const footerCopyright = 'footer copyright';

render(<Footer copyright={footerCopyright} />);

const footer = screen.getByRole('contentinfo');

expect(footer).toBeInTheDocument();
expect(footer).toHaveTextContent(footerCopyright);
});
});
107 changes: 107 additions & 0 deletions src/app/[lang]/__tests__/header.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { act, render, screen, waitFor } from '@testing-library/react';
import Header, { Avatar } from '../Header';

describe('Header component', () => {
const avatar = (
<Avatar src="https://external.com/test.jpg" alt="test avatar image alt" />
);

it('should render correct element', () => {
render(<Header avatar={avatar} />);

const brandLink = screen.getByRole('img');

expect(brandLink).toBeInTheDocument();
expect(brandLink.parentElement).toHaveAttribute('href', '/');
});

it('should hide header on scroll down and show on scroll up', async () => {
Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
configurable: true,
value: 50,
});

render(<Header avatar={avatar} scrollThreshold={100} />);

const header = screen.getByRole('banner');

expect(header.tagName).toBe('HEADER');

act(() => {
window.scrollY = 19;
window.dispatchEvent(new Event('scroll'));
window.scrollY = 20;
window.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(header).toHaveStyle({ '--header-translate-y': '0px' });
});

act(() => {
window.scrollY = 98;
window.dispatchEvent(new Event('scroll'));
window.scrollY = 99;
window.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(header).toHaveStyle({ '--header-translate-y': '0px' });
});

act(() => {
window.scrollY = 149;
window.dispatchEvent(new Event('scroll'));
window.scrollY = 150;
window.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(header).toHaveStyle({ '--header-translate-y': '150px' });
});

act(() => {
window.scrollY = 251;
window.dispatchEvent(new Event('scroll'));
window.scrollY = 250;
window.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(header).toHaveStyle({ '--header-translate-y': '200px' });
});

act(() => {
window.scrollY = 1;
window.dispatchEvent(new Event('scroll'));
window.scrollY = 0;
window.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(header).toHaveStyle({ '--header-translate-y': '200px' });
});
});

it('should calculate the correct styles even when `clientHeight` is `undefined`', async () => {
Object.defineProperty(HTMLElement.prototype, 'clientHeight', {
configurable: true,
value: undefined,
});

render(<Header avatar={avatar} scrollThreshold={100} />);

const header = screen.getByRole('banner');

expect(header.tagName).toBe('HEADER');

act(() => {
window.scrollY = 200;
window.dispatchEvent(new Event('scroll'));
});

await waitFor(() => {
expect(header).toHaveStyle({ '--header-translate-y': '200px' });
});
});
});
44 changes: 44 additions & 0 deletions src/app/[lang]/__tests__/layout.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { render, screen } from '@testing-library/react';
import { locales } from '~/i18n';
import mockPathname from '~/tests/navigation';
import Layout, { generateMetadata, generateStaticParams } from '../layout';

describe('I18n layout component', () => {
it('should render correct element', async () => {
const testText = 'Test layout component';

mockPathname.mockReturnValueOnce('/');

const layout = await Layout({
children: <h2>{testText}</h2>,
params: { lang: 'en' }
});

render(layout);

const testChildren = await screen.findByRole('heading');
const header = await screen.findByRole('banner');
const nav = await screen.findByRole('navigation');
const footer = await screen.findByRole('contentinfo');

expect(testChildren).toHaveTextContent(testText);
expect(header).toBeInTheDocument();
expect(nav).toBeInTheDocument();
expect(footer).toBeInTheDocument();
});

it('should generate correct metadata', async () => {
const metadata = await generateMetadata({
params: { lang: 'en' },
});

expect(metadata).toBeTruthy();
});

it('should generate correct static params', async () => {
const staticParams = await generateStaticParams();
const expected = locales.map(lang => ({ lang }));

expect(staticParams).toStrictEqual(expected);
});
});
55 changes: 55 additions & 0 deletions src/app/[lang]/__tests__/menu.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { render, screen } from '@testing-library/react';
import mockPathname from '~/tests/navigation';
import Menu, { MenuProps } from '../Menu';

describe('Menu component', () => {
const menu: MenuProps['menu'] = [
{ text: 'Home', href: '/' },
{ text: 'Post', href: '/posts' },
];

it('should render correct element', () => {
mockPathname.mockReturnValueOnce('/');

render(<Menu menu={menu} />);

const nav = screen.getByRole('navigation');
const linkA = screen.getByRole('link', { name: menu[0].text });
const linkB = screen.getByRole('link', { name: menu[1].text });

expect(nav).toBeInTheDocument();
expect(nav.tagName).toBe('NAV');

expect(linkA).toHaveTextContent(menu[0].text);
expect(linkA).toHaveAttribute('href', menu[0].href);

expect(linkB).toHaveTextContent(menu[1].text);
expect(linkB).toHaveAttribute('href', menu[1].href);
});

it.each([
['/', menu[0].text],
['/posts', menu[1].text],
['/en', menu[0].text],
['/en/posts', menu[1].text],
['/en/posts/test', menu[1].text],
])(
'should render correct active link based on the pathname "%s"',
(pathname, activeLinkText) => {
mockPathname.mockReturnValueOnce(pathname);

render(<Menu menu={menu} />);

const links = screen.getAllByRole('link');
const activeClassName = 'text-blue-500 hover:text-blue-500';

links.forEach((link) => {
if (link.textContent === activeLinkText) {
expect(link).toHaveClass(activeClassName);
} else {
expect(link).not.toHaveClass(activeClassName);
}
});
}
);
});
29 changes: 29 additions & 0 deletions src/app/[lang]/__tests__/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { render, screen } from '@testing-library/react';
import en from '~/i18n/locales/en';
import Page, { generateMetadata } from '../page';

jest.mock('@/utils/mdx', () => ({
getAllDataFrontmatter: () => []
}));

describe('Root page component', () => {
it('should render correct element', async () => {
const page = await Page({
params: { lang: 'en' },
});

render(page);

const heading = await screen.findByRole('heading', { level: 1 });

expect(heading).toBeInTheDocument();
});

it('should generate correct metadata', async () => {
const metadata = await generateMetadata({
params: { lang: 'en' },
});

expect(metadata).toStrictEqual({ title: en.common.home });
});
});
98 changes: 98 additions & 0 deletions src/app/[lang]/posts/[postId]/__tests__/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { render, screen } from '@testing-library/react';
import Page, { generateMetadata, generateStaticParams } from '../page';

const mockNotFound = jest.fn();
const mockDataList = jest.fn();
const mockData = jest.fn();

jest.mock('next/navigation', () => ({
notFound: () => mockNotFound(),
usePathname: () => ''
}));

jest.mock('@/components/TableOfContents', () => () => null);

jest.mock('@/utils/mdx', () => ({
getAllDataFrontmatter: () => mockDataList(),
getDataById: () => mockData(),
}));

describe('Post page component', () => {
beforeEach(() => {
mockNotFound.mockClear();
mockDataList.mockClear();
mockData.mockClear();
});

it('should render correct element', async () => {
const testText = 'test content';

mockData.mockReturnValueOnce({
id: 'test-id',
content: <h2>{testText}</h2>,
source: `## ${testText}`,
frontmatter: {
date: '2023/10/28',
title: 'test title',
},
});

const page = await Page({
params: { postId: 'test-id' },
});

render(page);

const heading = await screen.findByRole('heading', { level: 1 });

expect(heading).toBeInTheDocument();
});

it('should generate correct metadata', async () => {
const expected = {
date: '2023/10/28',
title: 'test title',
};

mockData.mockReturnValueOnce({ frontmatter: expected });

const metadata = await generateMetadata({
params: { postId: 'test-id' },
});

expect(metadata).toStrictEqual(expected);
});

it('should generate correct static params', async () => {
const source = [
{
id: 'test-id-1',
},
{
id: 'test-id-2',
},
];
mockDataList.mockReturnValueOnce(source);
const staticParams = await generateStaticParams();
const expected = source.map(({ id }) => ({ postId: id }));

expect(staticParams).toStrictEqual(expected);
});

it('should call notFound when post data is null', async () => {
mockData.mockReturnValue(null);

await Page({
params: { postId: 'test-id' },
});

expect(mockNotFound).toBeCalled();
expect(mockNotFound).toBeCalledTimes(1);

await generateMetadata({
params: { postId: 'test-id' },
});

expect(mockNotFound).toBeCalledTimes(2);
});
});
Loading

0 comments on commit db627c4

Please sign in to comment.