Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

13 set up unit and integration testing #15

Merged
merged 14 commits into from
May 6, 2024
26 changes: 25 additions & 1 deletion .github/workflows/auto.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
name: Automated Testing and Verification 🤖

on:
workflow_dispatch:
push:
pull_request:
paths-ignore:
- 'README.md'

jobs:
inspection:
name: Run inspection 🧪
runs-on: ubuntu-latest

steps:
- name: Use Node.js 21.5.0
uses: actions/setup-node@v3
with:
node-version: '21.5.0'

- name: Checkout repository 🔎
uses: actions/checkout@v3

- name: Install dependencies 📦
run: yarn install

- name: Lint code ✍️️
run: yarn lint

- name: Run tests ✅
run: yarn test

build:
name: Build image 🛠
runs-on: ubuntu-latest
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ on:
workflow_dispatch:

jobs:
inspection:
name: Run inspection 🧪
runs-on: ubuntu-latest

steps:
- name: Use Node.js 21.5.0
uses: actions/setup-node@v3
with:
node-version: '21.5.0'

- name: Checkout repository 🔎
uses: actions/checkout@v3

- name: Install dependencies 📦
run: yarn install

- name: Lint code ✍️️
run: yarn lint

- name: Run tests ✅
run: yarn test

build:
name: Build and publish image 🛠️
runs-on: ubuntu-latest
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ on:
workflow_dispatch:

jobs:
inspection:
name: Run inspection 🧪
runs-on: ubuntu-latest

steps:
- name: Use Node.js 21.5.0
uses: actions/setup-node@v3
with:
node-version: '21.5.0'

- name: Checkout repository 🔎
uses: actions/checkout@v3

- name: Install dependencies 📦
run: yarn install

- name: Lint code ✍️️
run: yarn lint

- name: Run tests ✅
run: yarn test

build:
name: Build and publish image 🛠️
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions docker/production/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FROM node:21.5.0-alpine as base
RUN apk add --no-cache g++ make py3-pip libc6-compat
RUN apk add --no-cache git
WORKDIR /app
COPY package*.json ./
EXPOSE 8081
Expand All @@ -8,14 +9,13 @@ FROM base as builder
WORKDIR /app
COPY . .
RUN yarn install
RUN yarn lint
RUN yarn build

FROM base as production
WORKDIR /app

ENV NODE_ENV=production
RUN yarn install --frozen-lockfile
RUN yarn install

RUN addgroup -g 1001 -S nodejs
RUN adduser -S minddaily-frontend -u 1001
Expand Down
4 changes: 2 additions & 2 deletions docker/staging/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FROM node:21.5.0-alpine as base
RUN apk add --no-cache g++ make py3-pip libc6-compat
RUN apk add --no-cache git
WORKDIR /app
COPY package*.json ./
EXPOSE 8083
Expand All @@ -8,14 +9,13 @@ FROM base as builder
WORKDIR /app
COPY . .
RUN yarn install
RUN yarn lint
RUN yarn build

FROM base as stage
WORKDIR /app

ENV NODE_ENV=stage
RUN yarn install --frozen-lockfile
RUN yarn install

RUN addgroup -g 1001 -S nodejs
RUN adduser -S minddaily-frontend -u 1001
Expand Down
7 changes: 4 additions & 3 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ const createJestConfig = nextJest({
// Add any custom config to be passed to Jest
const config: Config = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',

'^@/(.*)$': '<rootDir>/$1',
'^@/public/(.*)$': '<rootDir>/public/$1',
},
clearMocks: true,
collectCoverage: true,
passWithNoTests: true,
collectCoverageFrom: [
'./src/**/*.{js,jsx,ts,tsx}',
'!./src/**/_*.{js,jsx,ts,tsx}',
Expand All @@ -30,8 +30,9 @@ const config: Config = {
statements: 0,
},
},
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/tests/'],
testPathIgnorePatterns: ['<rootDir>/node_modules/'],
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@
"url": "https://github.com/DreamUnit/minddaily-frontend"
},
"docs": {
"url": ""
"url": "https://minddaily-docs.vercel.app/"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"format": "prettier --check .",
"test": "jest",
"test:e2e": "playwright test",
"lint": "eslint ./"
},
"engines": {
"node": "21.5.0"
},
"dependencies": {
"@hookform/resolvers": "^3.3.4",
"@percy/playwright": "^1.0.5",
"@percy/sdk-utils": "^1.28.3",
"@tabler/icons-react": "^3.3.0",
"@testing-library/jest-dom": "^6.4.3",
"@testing-library/react": "^15.0.6",
"axios": "^1.6.7",
"daisyui": "^4.10.1",
"jest": "^29.7.0",
Expand All @@ -39,7 +39,8 @@
"zod": "^3.22.5"
},
"devDependencies": {
"@playwright/test": "^1.43.1",
"@types/jest": "^29.5.12",
"@types/mocha": "^10.0.6",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand All @@ -50,6 +51,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-tailwindcss": "^3.15.1",
"mocha": "^10.4.0",
"postcss": "^8",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
Expand Down
32 changes: 32 additions & 0 deletions src/__tests__/unit/AppError.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AppError } from '@/src/models/AppError';

describe('AppError', () => {
it('should create an instance with the provided message', () => {
const errorMessage = 'Test error message';
const error = new AppError(errorMessage);

expect(error instanceof AppError).toBe(true);
expect(error.message).toBe(errorMessage);
expect(error.name).toBe('AppError');
expect(error.cause).toBeUndefined();
});

it('should create an instance with the provided message and cause', () => {
const errorMessage = 'Test error message';
const cause = 'Test cause';
const error = new AppError(errorMessage, cause);

expect(error instanceof AppError).toBe(true);
expect(error.message).toBe(errorMessage);
expect(error.name).toBe('AppError');
expect(error.cause).toBe(cause);
});

it('should inherit from the Error class', () => {
const errorMessage = 'Test error message';
const error = new AppError(errorMessage);

expect(error instanceof Error).toBe(true);
expect(error.message).toBe(errorMessage);
});
});
83 changes: 83 additions & 0 deletions src/__tests__/unit/LoginForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import '@testing-library/jest-dom';

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import authService from '@/src/services/authService';
import { LoginForm } from '@/src/components/auth/LoginForm';
import { SocialAuthType } from '@/src/@types';

jest.mock('@/src/services/authService', () => ({
login: jest.fn(),
socialSignIn: jest.fn(),
}));

describe('LoginForm', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('renders login form correctly', () => {
render(<LoginForm />);
expect(screen.getByText('Sign in to MindDaily')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Email')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
expect(screen.getByText('Sign in')).toBeInTheDocument();
});

it('submits login form with valid credentials', async () => {
const email = '[email protected]';
const password = 'password';
(authService.login as jest.Mock).mockResolvedValueOnce({ email, password });

render(<LoginForm />);
fireEvent.change(screen.getByPlaceholderText('Email'), {
target: { value: email },
});
fireEvent.change(screen.getByPlaceholderText('Password'), {
target: { value: password },
});
fireEvent.click(screen.getByText('Sign in'));

await waitFor(() => {
expect(authService.login).toHaveBeenCalledTimes(1);
expect(authService.login).toHaveBeenCalledWith({ email, password });
});
});

it('displays error messages for invalid credentials', async () => {
const email = 'test';
const password = '123456_A';
(authService.login as jest.Mock).mockResolvedValueOnce({
email,
password,
});

render(<LoginForm />);
fireEvent.change(screen.getByPlaceholderText('Email'), {
target: { value: email },
});
fireEvent.change(screen.getByPlaceholderText('Password'), {
target: { value: password },
});
fireEvent.click(screen.getByText('Sign in'));

await waitFor(() => {
expect(authService.login).toHaveBeenCalledTimes(0);
});
});

it('submits social sign in with Google', async () => {
(authService.socialSignIn as jest.Mock).mockResolvedValueOnce(
SocialAuthType.Google
);

render(<LoginForm />);
fireEvent.click(screen.getByText('Continue with Google'));

await waitFor(() => {
expect(authService.socialSignIn).toHaveBeenCalledTimes(1);
expect(authService.socialSignIn).toHaveBeenCalledWith(
SocialAuthType.Google
);
});
});
});
Loading
Loading