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

Vitest installed #123

Merged
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
11,072 changes: 6,246 additions & 4,826 deletions frontend/package-lock.json

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"dev": "vite --host",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@redux-devtools/extension": "^3.3.0",
"axios": "^1.4.0",
"formik": "^2.4.2",
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
Expand All @@ -27,10 +28,11 @@
"yup": "^1.2.0"
},
"devDependencies": {
"@babel/preset-env": "^7.24.3",
"@babel/preset-react": "^7.24.1",
"@babel/preset-typescript": "^7.24.1",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^15.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/axios": "^0.14.0",
"@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^9.0.6",
"@types/jwt-decode": "^3.1.0",
"@types/node": "^20.10.6",
Expand All @@ -43,16 +45,19 @@
"@typescript-eslint/parser": "^5.59.0",
"@vitejs/plugin-react": "^4.0.0",
"autoprefixer": "^10.4.14",
"axios": "^1.6.8",
"eslint": "^8.38.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"jsdom": "^24.0.0",
"postcss": "^8.4.24",
"postcss-nesting": "^12.0.0",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.4.1",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.3",
"vite": "^4.4.9"
"vite": "^4.4.9",
"vitest": "^1.5.0"
}
}
163 changes: 109 additions & 54 deletions frontend/src/pages/PatientManager/NewPatientForm.test.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
jest.mock('../../config/envConfig', () => ({
getEnv: () => ({
apiUrl: 'http://localhost:8000/api/listMeds/',
}),
}));

import React from 'react';
// mock React's useState function
jest.mock('react', () => {
const originalReact = jest.requireActual('react'); // Import actual React module
return {
...originalReact, // Spread all original exports
useState: jest.fn(), // Override only useState
};
});


import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import NewPatientForm from './NewPatientForm';
import axios from 'axios';

vi.mock('axios');
const mockedAxios = vi.mocked(axios, true);

// Implement a mock function that can handle both overloads
window.scrollTo = vi.fn((...args: [ScrollToOptions?] | [number, number]) => {
if (args.length === 1 && typeof args[0] === 'object') {
console.log(`Scrolling with options`, args[0]);
} else if (typeof args[0] === 'number' && typeof args[1] === 'number') {
console.log(`Scrolling to position (${args[0]}, ${args[1]})`);
}
}) as typeof window.scrollTo;

// Define mock data
export interface PatientInfo {
interface PatientInfo {
ID?: string;
Diagnosis?: string;
OtherDiagnosis?: string;
Expand Down Expand Up @@ -70,46 +65,106 @@ const mockPatientInfo: PatientInfo = {
Reproductive: 'No',
risk_pregnancy: 'No',
};

// Mock functions for setting state
const mockSetPatientInfo = jest.fn();
const mockSetAllPatientInfo = jest.fn();
const mockSetPatientInfo = vi.fn();
const mockSetAllPatientInfo = vi.fn();

// Mock array for allPatientInfo
const mockAllPatientInfo = [mockPatientInfo];
const mockAllPatientInfo = [mockPatientInfo];

// At the top of your test file
// jest.mock('axios');
// import axios from 'axios';
// const mockedAxios = axios as jest.Mocked<typeof axios>;
// Mock localStorage
Object.defineProperty(window, 'localStorage', {
value: {
getItem: vi.fn(() => JSON.stringify([mockPatientInfo])),
setItem: vi.fn(() => null),
},
writable: true
});

const props = {
patientInfo: mockPatientInfo,
setPatientInfo: mockSetPatientInfo,
allPatientInfo: mockAllPatientInfo,
setAllPatientInfo: mockSetAllPatientInfo
};

describe('NewPatientForm useEffect', () => {
it('loads patient info from localStorage on mount', () => {
render(<NewPatientForm {...props} />);
expect(mockSetAllPatientInfo).toHaveBeenCalledWith([mockPatientInfo]);
});
});

describe('NewPatientForm Component', () => {
beforeEach(() => {
// Clear all mocks before each test
jest.clearAllMocks();

// Reset useState mock to a clean state
const originalReact = jest.requireActual('react');
(React.useState as jest.Mock)
.mockImplementation((initial: any) => originalReact.useState(initial));
});
beforeEach(() => {
mockedAxios.post.mockClear();
vi.spyOn(Storage.prototype, 'getItem').mockReturnValue(JSON.stringify([mockPatientInfo]));

// Mocking Axios post request to return specific data needed for the component
mockedAxios.post.mockResolvedValue({
data: {
first: ["MedicationA1", "MedicationA2"],
second: ["MedicationB1", "MedicationB2"],
third: ["MedicationC1", "MedicationC2"]
}
});
});

afterEach(() => {
vi.restoreAllMocks();
});

it('renders without crashing', () => {
render(<NewPatientForm {...props} />);
expect(screen.getByText('Enter Patient Details')).toBeInTheDocument();
});

it('prevents form submission if the diagnosis is "Null"', async () => {
render(<NewPatientForm {...props} />);

// Attempt to submit the form without changing the diagnosis
const submitButton = screen.getByRole('button', { name: 'submit-test' })
fireEvent.click(submitButton);

await waitFor(() => expect(mockedAxios.post).not.toHaveBeenCalled());
});

it('handles patient info on form submit', async () => {
const { getByRole, getByLabelText } = render(<NewPatientForm {...props} />);

fireEvent.change(getByLabelText('Current state'), { target: { value: 'Manic' } });
fireEvent.click(getByRole('button', { name: 'submit-test' }));

await waitFor(() => {
expect(mockedAxios.post).toHaveBeenCalled();
});
});

it('renders without crashing', () => {
(React.useState as jest.Mock)
.mockImplementationOnce(() => [false, jest.fn()]) // Mock for isPressed
.mockImplementationOnce(() => [false, jest.fn()]);
render(<NewPatientForm
patientInfo={mockPatientInfo}
setPatientInfo={mockSetPatientInfo}
allPatientInfo={mockAllPatientInfo}
setAllPatientInfo={mockSetAllPatientInfo}
/>);

// Look for a static element that should always be present
// For example, if your form always renders a submit button, check for that
// expect(screen.getByRole('button', { name: /submit/i })).toBeInTheDocument();
});
});
it('clears the form when the clear button is clicked', async () => {
render(<NewPatientForm {...props} />);
const diagnosisSelect = screen.getByRole('combobox', { name: /current state/i }) as HTMLSelectElement;

fireEvent.change(diagnosisSelect, { target: { value: 'Manic' } });

const clearButton = screen.getByTestId('clear-form-button');
fireEvent.click(clearButton);

await waitFor(() => {
expect(diagnosisSelect).toHaveValue('Null');
});
});

it('handles patient info on form submit', async () => {
render(<NewPatientForm {...props} />);

fireEvent.change(screen.getByLabelText('Current state'), { target: { value: 'Manic' } });
fireEvent.click(screen.getByRole('button', { name: 'submit-test' }));

await waitFor(() => {
expect(mockedAxios.post).toHaveBeenCalledWith(
'http://localhost:3000/chatgpt/list_meds',
{ state: 'Manic' }
);
});
});
});
1 change: 1 addition & 0 deletions frontend/src/testSetup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom'
3 changes: 2 additions & 1 deletion frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"types": ["vitest/globals"],

/* Bundler mode */
"moduleResolution": "bundler",
Expand All @@ -22,6 +23,6 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "custom.d.ts", "vite-env.d.ts"],
"include": ["src", "custom.d.ts", "vite-env.d.ts", ".src/testSetup.tsx"],
"references": [{ "path": "./tsconfig.node.json" }]
}
1 change: 1 addition & 0 deletions frontend/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// <reference types="vite/client" />
/// <reference types="vitest" />
12 changes: 12 additions & 0 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import type { UserConfig as VitestUserConfig } from 'vitest/config';

declare module 'vite' {
export interface UserConfig {
test: VitestUserConfig['test'];
}
}
// https://vitejs.dev/config/
export default defineConfig({
build: {
Expand All @@ -16,4 +22,10 @@ export default defineConfig({
strictPort: true,
port: 3000,
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/testSetup.tsx',
css: true,
},
});