Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions frontend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const nextJest = require('next/jest');

const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
});

// Add any custom config to be passed to Jest
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.test.{js,jsx,ts,tsx}',
'!src/**/*.spec.{js,jsx,ts,tsx}',
],
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig);
2 changes: 2 additions & 0 deletions frontend/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
254 changes: 254 additions & 0 deletions frontend/src/app/layout.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import { render, screen } from '@testing-library/react';
import RootLayout, { metadata } from './layout';

// Mock next/font/google
jest.mock('next/font/google', () => ({
Geist: jest.fn(() => ({
variable: '--font-geist-sans-mock',
subsets: ['latin'],
})),
Geist_Mono: jest.fn(() => ({
variable: '--font-geist-mono-mock',
subsets: ['latin'],
})),
}));

// Mock CSS imports
jest.mock('./globals.css', () => ({}));

describe('RootLayout Component', () => {
describe('Metadata Export', () => {
it('should export metadata with correct title', () => {
expect(metadata).toBeDefined();
expect(metadata.title).toBe('Create Next App');
});

it('should export metadata with correct description', () => {
expect(metadata.description).toBe('Generated by create next app');
});

it('should have metadata as an object with required properties', () => {
expect(typeof metadata).toBe('object');
expect(metadata).toHaveProperty('title');
expect(metadata).toHaveProperty('description');
});

// Boundary: empty-like values (metadata structure)
it('should have non-empty title string', () => {
expect(typeof metadata.title).toBe('string');
expect(metadata.title.length).toBeGreaterThan(0);
});

it('should have non-empty description string', () => {
expect(typeof metadata.description).toBe('string');
expect(metadata.description.length).toBeGreaterThan(0);
});
});

describe('Layout Rendering', () => {
it('should render html element with correct lang attribute', () => {
const { container } = render(
<RootLayout>
<div>Test Child</div>
</RootLayout>
);
const htmlElement = container.querySelector('html');
expect(htmlElement).toBeInTheDocument();
expect(htmlElement).toHaveAttribute('lang', 'en');
});

it('should render html element with font variables in className', () => {
const { container } = render(
<RootLayout>
<div>Test Child</div>
</RootLayout>
);
const htmlElement = container.querySelector('html');
expect(htmlElement?.className).toContain('--font-geist-sans-mock');
expect(htmlElement?.className).toContain('--font-geist-mono-mock');
});

it('should render html element with antialiased class', () => {
const { container } = render(
<RootLayout>
<div>Test Child</div>
</RootLayout>
);
const htmlElement = container.querySelector('html');
expect(htmlElement?.className).toContain('antialiased');
});

it('should render html element with h-full class', () => {
const { container } = render(
<RootLayout>
<div>Test Child</div>
</RootLayout>
);
const htmlElement = container.querySelector('html');
expect(htmlElement?.className).toContain('h-full');
});

it('should render body element with correct classes', () => {
const { container } = render(
<RootLayout>
<div>Test Child</div>
</RootLayout>
);
const bodyElement = container.querySelector('body');
expect(bodyElement).toBeInTheDocument();
expect(bodyElement).toHaveClass('min-h-full');
expect(bodyElement).toHaveClass('flex');
expect(bodyElement).toHaveClass('flex-col');
});

it('should render children inside body element', () => {
render(
<RootLayout>
<div data-testid="test-child">Test Child Content</div>
</RootLayout>
);
const childElement = screen.getByTestId('test-child');
expect(childElement).toBeInTheDocument();
expect(childElement).toHaveTextContent('Test Child Content');
});

// Boundary: null/undefined children handling
it('should handle when children is null', () => {
const { container } = render(
<RootLayout>
{null}
</RootLayout>
);
const bodyElement = container.querySelector('body');
expect(bodyElement).toBeInTheDocument();
expect(bodyElement?.textContent).toBe('');
});

it('should handle when children is undefined', () => {
const { container } = render(
<RootLayout>
{undefined}
</RootLayout>
);
const bodyElement = container.querySelector('body');
expect(bodyElement).toBeInTheDocument();
});

// Boundary: empty children
it('should handle empty string as children', () => {
render(
<RootLayout>
{''}
</RootLayout>
);
const bodyElement = document.querySelector('body');
expect(bodyElement).toBeInTheDocument();
});

// Equivalence Class: different types of children
it('should render text node children', () => {
render(
<RootLayout>
Plain text content
</RootLayout>
);
expect(screen.getByText('Plain text content')).toBeInTheDocument();
});

it('should render multiple children elements', () => {
render(
<RootLayout>
<div data-testid="child-1">First Child</div>
<div data-testid="child-2">Second Child</div>
</RootLayout>
);
expect(screen.getByTestId('child-1')).toBeInTheDocument();
expect(screen.getByTestId('child-2')).toBeInTheDocument();
});

it('should render nested children structures', () => {
render(
<RootLayout>
<div data-testid="parent">
<div data-testid="nested-child">Nested Content</div>
</div>
</RootLayout>
);
expect(screen.getByTestId('parent')).toBeInTheDocument();
expect(screen.getByTestId('nested-child')).toBeInTheDocument();
});

it('should render fragment children', () => {
render(
<RootLayout>
<>
<div data-testid="fragment-child-1">Fragment Child 1</div>
<div data-testid="fragment-child-2">Fragment Child 2</div>
</>
</RootLayout>
);
expect(screen.getByTestId('fragment-child-1')).toBeInTheDocument();
expect(screen.getByTestId('fragment-child-2')).toBeInTheDocument();
});
});

describe('Structure and Hierarchy', () => {
it('should maintain correct DOM hierarchy: html > body > children', () => {
const { container } = render(
<RootLayout>
<div data-testid="content">Content</div>
</RootLayout>
);
const htmlElement = container.querySelector('html');
const bodyElement = htmlElement?.querySelector('body');
const contentElement = bodyElement?.querySelector('[data-testid="content"]');

expect(htmlElement).toBeInTheDocument();
expect(bodyElement).toBeInTheDocument();
expect(contentElement).toBeInTheDocument();
});

it('should have exactly one body element inside html', () => {
const { container } = render(
<RootLayout>
<div>Content</div>
</RootLayout>
);
const htmlElement = container.querySelector('html');
const bodyElements = htmlElement?.querySelectorAll('body');
expect(bodyElements?.length).toBe(1);
});
});

describe('CSS Class Application', () => {
it('should apply all required body classes together', () => {
const { container } = render(
<RootLayout>
<div>Test</div>
</RootLayout>
);
const bodyElement = container.querySelector('body');
const classList = Array.from(bodyElement?.classList || []);

expect(classList).toContain('min-h-full');
expect(classList).toContain('flex');
expect(classList).toContain('flex-col');
expect(classList.length).toBe(3);
});

it('should apply all required html classes including font variables', () => {
const { container } = render(
<RootLayout>
<div>Test</div>
</RootLayout>
);
const htmlElement = container.querySelector('html');
const className = htmlElement?.className || '';

expect(className).toContain('--font-geist-sans-mock');
expect(className).toContain('--font-geist-mono-mock');
expect(className).toContain('h-full');
expect(className).toContain('antialiased');
});
});
});
Loading