From a88f613da7e5567dbfdebd7df94f94507c47c6b5 Mon Sep 17 00:00:00 2001 From: Michael Hunteman Date: Fri, 27 Sep 2024 08:43:02 -0700 Subject: Add vitests --- client/package.json | 10 ++- client/src/AppThemeProvider.tsx | 94 +++++++++++++++++++++++++++ client/src/ThemeContextProvider.tsx | 94 --------------------------- client/src/components/AdminLogin.test.tsx | 30 +++++++++ client/src/components/Dashboard.tsx | 1 + client/src/components/Desktop.tsx | 2 +- client/src/components/GlobalSnackbar.test.tsx | 28 ++++++++ client/src/components/GuestLogin.test.tsx | 29 +++++++++ client/src/components/Mobile.tsx | 2 +- client/src/components/RsvpForm.test.tsx | 31 +++++++++ client/src/main.tsx | 67 ++----------------- client/src/mocks/browser.ts | 4 -- client/src/mocks/handlers.ts | 57 ++++++++++------ client/src/mocks/server.ts | 4 ++ client/src/mocks/worker.ts | 4 ++ client/src/models.ts | 2 +- client/src/renderWithProviders.tsx | 28 ++++++++ client/src/routes.tsx | 59 +++++++++++++++++ client/src/setup.ts | 6 ++ client/src/slices/snackbarSlice.ts | 3 +- client/src/store.ts | 37 +++++++---- client/tsconfig.json | 2 +- client/vite.config.ts | 12 +++- 23 files changed, 403 insertions(+), 203 deletions(-) create mode 100644 client/src/AppThemeProvider.tsx delete mode 100644 client/src/ThemeContextProvider.tsx create mode 100644 client/src/components/AdminLogin.test.tsx create mode 100644 client/src/components/GlobalSnackbar.test.tsx create mode 100644 client/src/components/GuestLogin.test.tsx create mode 100644 client/src/components/RsvpForm.test.tsx delete mode 100644 client/src/mocks/browser.ts create mode 100644 client/src/mocks/server.ts create mode 100644 client/src/mocks/worker.ts create mode 100644 client/src/renderWithProviders.tsx create mode 100644 client/src/routes.tsx create mode 100644 client/src/setup.ts (limited to 'client') diff --git a/client/package.json b/client/package.json index 0d6bab2..0990aa3 100644 --- a/client/package.json +++ b/client/package.json @@ -7,7 +7,8 @@ "dev": "bunx --bun vite --host", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest" }, "dependencies": { "@emotion/react": "^11.13.3", @@ -28,6 +29,9 @@ }, "devDependencies": { "@hookform/devtools": "^4.3.1", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.1", + "@testing-library/user-event": "^14.5.2", "@types/bun": "^1.1.0", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", @@ -37,8 +41,10 @@ "eslint": "^8.56.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "jsdom": "^25.0.0", "msw": "^2.2.1", "typescript": "^5.2.2", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vitest": "^2.1.1" } } diff --git a/client/src/AppThemeProvider.tsx b/client/src/AppThemeProvider.tsx new file mode 100644 index 0000000..a88c328 --- /dev/null +++ b/client/src/AppThemeProvider.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { ReactNode, createContext, useMemo, useState } from 'react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import type { PaletteMode } from '@mui/material'; + +type ThemeContextType = { + toggleColorMode: () => void; +}; + +type ThemeProviderProps = { + children: ReactNode; +}; + +export const ThemeContext = createContext({ + toggleColorMode: () => {}, +}); + +function AppThemeProvider({ children }: ThemeProviderProps) { + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + const [mode, setMode] = useState<'light' | 'dark'>( + prefersDarkMode ? 'dark' : 'light' + ); + + const toggleColorMode = () => { + setMode((prevMode: PaletteMode) => + prevMode === 'light' ? 'dark' : 'light' + ); + }; + + const getDesignTokens = (mode: PaletteMode) => ({ + palette: { + mode, + ...(mode === 'light' + ? { + primary: { + main: '#FFAB91', + }, + } + : { + primary: { + main: '#FF8A65', + }, + }), + }, + }); + + const colorTheme = createTheme(getDesignTokens(mode)); + + const roboto = { + sx: { + fontFamily: 'Roboto, sans-serif', + }, + }; + + const theme = useMemo( + () => + createTheme({ + ...colorTheme, + typography: { + fontFamily: ['Playwrite US Trad', 'cursive'].join(','), + button: { + textTransform: 'none', + }, + body1: { + lineHeight: 1.8, + }, + }, + components: { + MuiInputBase: { + defaultProps: roboto, + }, + MuiInputLabel: { + defaultProps: roboto, + }, + MuiAlert: { + defaultProps: roboto, + }, + MuiFormHelperText: { + defaultProps: roboto, + }, + }, + }), + [mode] + ); + + return ( + + {children} + + ); +} + +export default AppThemeProvider; diff --git a/client/src/ThemeContextProvider.tsx b/client/src/ThemeContextProvider.tsx deleted file mode 100644 index 5e1e2e8..0000000 --- a/client/src/ThemeContextProvider.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import { ReactNode, createContext, useMemo, useState } from 'react'; -import { ThemeProvider, createTheme } from '@mui/material/styles'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import type { PaletteMode } from '@mui/material'; - -type ThemeContextType = { - toggleColorMode: () => void; -}; - -type ThemeProviderProps = { - children: ReactNode; -}; - -export const ThemeContext = createContext({ - toggleColorMode: () => {}, -}); - -function ThemeContextProvider({ children }: ThemeProviderProps) { - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); - const [mode, setMode] = useState<'light' | 'dark'>( - prefersDarkMode ? 'dark' : 'light' - ); - - const toggleColorMode = () => { - setMode((prevMode: PaletteMode) => - prevMode === 'light' ? 'dark' : 'light' - ); - }; - - const getDesignTokens = (mode: PaletteMode) => ({ - palette: { - mode, - ...(mode === 'light' - ? { - primary: { - main: '#FFAB91', - }, - } - : { - primary: { - main: '#FF8A65', - }, - }), - }, - }); - - const colorTheme = createTheme(getDesignTokens(mode)); - - const roboto = { - sx: { - fontFamily: 'Roboto, sans-serif', - }, - }; - - const theme = useMemo( - () => - createTheme({ - ...colorTheme, - typography: { - fontFamily: ['Playwrite US Trad', 'cursive'].join(','), - button: { - textTransform: 'none', - }, - body1: { - lineHeight: 1.8, - }, - }, - components: { - MuiInputBase: { - defaultProps: roboto, - }, - MuiInputLabel: { - defaultProps: roboto, - }, - MuiAlert: { - defaultProps: roboto, - }, - MuiFormHelperText: { - defaultProps: roboto, - }, - }, - }), - [mode] - ); - - return ( - - {children} - - ); -} - -export default ThemeContextProvider; diff --git a/client/src/components/AdminLogin.test.tsx b/client/src/components/AdminLogin.test.tsx new file mode 100644 index 0000000..feffadf --- /dev/null +++ b/client/src/components/AdminLogin.test.tsx @@ -0,0 +1,30 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; +import { describe, expect, it } from 'vitest'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import { renderWithProviders } from '../renderWithProviders'; +import routes from '../routes'; + +describe('Admin Login', async () => { + const memoryRouter = createMemoryRouter(routes, { + initialEntries: ['/admin/login'], + }); + it('can log in', async () => { + const { getByLabelText, getByRole, findByText } = renderWithProviders( + + ); + const user = userEvent.setup(); + + await user.type(getByLabelText(/username/i), 'username'); + await user.type(getByLabelText(/password/i), 'password'); + fireEvent.click(getByRole('button', { name: 'Log in' })); + expect(await findByText(/first name/i)).toBeInTheDocument(); + expect(await findByText(/last name/i)).toBeInTheDocument(); + expect(await findByText(/attendance/i)).toBeInTheDocument(); + expect(await findByText(/email/i)).toBeInTheDocument(); + expect(await findByText(/message/i)).toBeInTheDocument(); + expect(await findByText(/party size/i)).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/Dashboard.tsx b/client/src/components/Dashboard.tsx index 985e48a..56b50a2 100644 --- a/client/src/components/Dashboard.tsx +++ b/client/src/components/Dashboard.tsx @@ -48,6 +48,7 @@ function Dashboard() { columns, data: guests, muiPaginationProps: { + color: 'primary', shape: 'rounded', showRowsPerPage: false, variant: 'outlined', diff --git a/client/src/components/Desktop.tsx b/client/src/components/Desktop.tsx index 0aa4357..d7447f3 100644 --- a/client/src/components/Desktop.tsx +++ b/client/src/components/Desktop.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import { Button, IconButton, useTheme } from '@mui/material'; import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; -import { ThemeContext } from '../ThemeContextProvider'; +import { ThemeContext } from '../AppThemeProvider'; import pages from '../pages'; function Desktop() { diff --git a/client/src/components/GlobalSnackbar.test.tsx b/client/src/components/GlobalSnackbar.test.tsx new file mode 100644 index 0000000..2643816 --- /dev/null +++ b/client/src/components/GlobalSnackbar.test.tsx @@ -0,0 +1,28 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { describe, expect, it } from 'vitest'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import { renderWithProviders } from '../renderWithProviders'; +import routes from '../routes'; +import { showSnackbar } from '../slices/snackbarSlice'; +import setupStore from '../store'; + +describe('Global Snackbar', async () => { + const memoryRouter = createMemoryRouter(routes, { + initialEntries: ['/'], + }); + it('displays message', async () => { + const store = setupStore(); + store.dispatch( + showSnackbar({ + message: 'message', + severity: 'success', + }) + ); + const { findByText } = renderWithProviders( + , + { store } + ); + expect(await findByText(/message/i)).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/GuestLogin.test.tsx b/client/src/components/GuestLogin.test.tsx new file mode 100644 index 0000000..b31a00a --- /dev/null +++ b/client/src/components/GuestLogin.test.tsx @@ -0,0 +1,29 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; +import { describe, expect, it } from 'vitest'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import { renderWithProviders } from '../renderWithProviders'; +import routes from '../routes'; + +describe('Guest Login', async () => { + const memoryRouter = createMemoryRouter(routes, { + initialEntries: ['/guests/login'], + }); + it('can log in', async () => { + const { getByLabelText, getByRole, findByLabelText } = renderWithProviders( + + ); + const user = userEvent.setup(); + + await user.type(getByLabelText(/first name/i), 'Michael'); + await user.type(getByLabelText(/last name/i), 'Hunteman'); + fireEvent.click(getByRole('button', { name: 'Log in' })); + expect(await findByLabelText(/accept/i)).not.toBeChecked(); + expect(await findByLabelText(/decline/i)).toBeChecked(); + expect(await findByLabelText(/email/i)).toHaveValue(''); + expect(await findByLabelText(/party size/i)).toHaveValue(1); + expect(await findByLabelText(/message to the couple/i)).toHaveValue(''); + }); +}); diff --git a/client/src/components/Mobile.tsx b/client/src/components/Mobile.tsx index 38aa20b..d8510c7 100644 --- a/client/src/components/Mobile.tsx +++ b/client/src/components/Mobile.tsx @@ -5,7 +5,7 @@ import { Button, IconButton, Menu, MenuItem } from '@mui/material'; import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; import { useTheme } from '@mui/material/styles'; -import { ThemeContext } from '../ThemeContextProvider'; +import { ThemeContext } from '../AppThemeProvider'; import MenuIcon from '@mui/icons-material/Menu'; import pages from '../pages'; diff --git a/client/src/components/RsvpForm.test.tsx b/client/src/components/RsvpForm.test.tsx new file mode 100644 index 0000000..a871268 --- /dev/null +++ b/client/src/components/RsvpForm.test.tsx @@ -0,0 +1,31 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; +import { describe, expect, it } from 'vitest'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import { renderWithProviders } from '../renderWithProviders'; +import routes from '../routes'; +import setupStore from '../store'; +import { initialGuest } from '../mocks/handlers'; +import { setGuest } from '../slices/auth/guestSlice'; + +describe('RSVP form', async () => { + const memoryRouter = createMemoryRouter(routes, { + initialEntries: ['/rsvp'], + }); + it('can submit', async () => { + const store = setupStore(); + store.dispatch(setGuest(initialGuest)); + const { getByLabelText, getByRole, findByText } = renderWithProviders( + , + { store } + ); + const user = userEvent.setup(); + + fireEvent.click(getByLabelText(/accept/i), { target: { clicked: true } }); + await user.type(getByLabelText(/email/i), 'mhunteman@cox.net'); + fireEvent.click(getByRole('button', { name: 'RSVP' })); + expect(await findByText(/RSVP updated/i)).toBeInTheDocument(); + }); +}); diff --git a/client/src/main.tsx b/client/src/main.tsx index 0e5a2f7..43e61c0 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -2,72 +2,19 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { Provider } from 'react-redux'; -import App from './App'; -import store from './store'; -import ThemeContextProvider from './ThemeContextProvider'; -import Schedule from './components/Schedule'; -import Registry from './components/Registry'; -import GuestLogin from './components/GuestLogin'; -import Rsvp from './components/Rsvp'; -import RsvpForm from './components/RsvpForm'; -import Home from './components/Home'; -import AdminLogin from './components/AdminLogin'; -import Admin from './components/Admin'; -import Dashboard from './components/Dashboard'; +import setupStore from './store'; +import AppThemeProvider from './AppThemeProvider'; +import routes from './routes'; import './main.css'; -const router = createBrowserRouter([ - { - element: , - children: [ - { - path: '/', - element: , - }, - { - path: 'schedule', - element: , - }, - { - path: 'registry', - element: , - }, - { - path: 'guests/login', - element: , - }, - { - path: 'admin/login', - element: , - }, - { - element: , - children: [ - { - path: 'rsvp', - element: , - }, - ], - }, - { - element: , - children: [ - { - path: 'dashboard', - element: , - }, - ], - }, - ], - }, -]); +const router = createBrowserRouter(routes); ReactDOM.createRoot(document.getElementById('root')!).render( - - + + - + ); diff --git a/client/src/mocks/browser.ts b/client/src/mocks/browser.ts deleted file mode 100644 index 0a56427..0000000 --- a/client/src/mocks/browser.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { setupWorker } from 'msw/browser'; -import { handlers } from './handlers'; - -export const worker = setupWorker(...handlers); diff --git a/client/src/mocks/handlers.ts b/client/src/mocks/handlers.ts index 0e882ee..153a70c 100644 --- a/client/src/mocks/handlers.ts +++ b/client/src/mocks/handlers.ts @@ -3,28 +3,43 @@ import { nanoid } from '@reduxjs/toolkit'; const token = nanoid(); +export const initialGuest = { + guest: { + id: 1, + firstName: 'Michael', + lastName: 'Hunteman', + attendance: 'decline', + email: '', + message: '', + partySize: 1, + }, + token: token, +}; + +export const updatedGuest = { + id: 1, + firstName: 'Michael', + lastName: 'Hunteman', + attendance: 'accept', + email: 'mhunteman@cox.net', + message: '', + partySize: 1, + partyList: [], +}; + +export const guests = { + guests: [initialGuest], + token: token, +}; + export const handlers = [ - http.post('/guests/login', () => { - return HttpResponse.json({ - guest: { - id: 1, - firstName: 'Michael', - lastName: 'Hunteman', - attendance: 'false', - email: '', - message: '', - }, - token, - }); + http.post(`${import.meta.env.VITE_BASE_URL}guests/login`, () => { + return HttpResponse.json(initialGuest); + }), + http.put(`${import.meta.env.VITE_BASE_URL}guests/1`, () => { + return HttpResponse.json(updatedGuest); }), - http.patch('/guests/1', () => { - return HttpResponse.json({ - id: 1, - firstName: 'Michael', - lastName: 'Hunteman', - attendance: 'true', - email: '', - message: '', - }); + http.post(`${import.meta.env.VITE_BASE_URL}admin/login`, () => { + return HttpResponse.json(guests); }), ]; diff --git a/client/src/mocks/server.ts b/client/src/mocks/server.ts new file mode 100644 index 0000000..e52fee0 --- /dev/null +++ b/client/src/mocks/server.ts @@ -0,0 +1,4 @@ +import { setupServer } from 'msw/node'; +import { handlers } from './handlers'; + +export const server = setupServer(...handlers); diff --git a/client/src/mocks/worker.ts b/client/src/mocks/worker.ts new file mode 100644 index 0000000..0a56427 --- /dev/null +++ b/client/src/mocks/worker.ts @@ -0,0 +1,4 @@ +import { setupWorker } from 'msw/browser'; +import { handlers } from './handlers'; + +export const worker = setupWorker(...handlers); diff --git a/client/src/models.ts b/client/src/models.ts index 6840a46..201a969 100644 --- a/client/src/models.ts +++ b/client/src/models.ts @@ -6,7 +6,7 @@ export interface Guest { email?: string; message?: string; partySize?: number; - partyList?: Array; + partyList?: Name[]; } export interface Name { diff --git a/client/src/renderWithProviders.tsx b/client/src/renderWithProviders.tsx new file mode 100644 index 0000000..476fdb1 --- /dev/null +++ b/client/src/renderWithProviders.tsx @@ -0,0 +1,28 @@ +import React, { PropsWithChildren, ReactElement } from 'react'; +import { render } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import setupStore from './store'; +import type { AppStore, RootState } from './store'; +import type { RenderOptions } from '@testing-library/react'; + +interface ExtendedRenderOptions extends Omit { + preloadedState?: Partial; + store?: AppStore; +} + +export function renderWithProviders( + ui: ReactElement, + extendedRenderOptions: ExtendedRenderOptions = {} +) { + const { + preloadedState = {}, + store = setupStore(preloadedState), + ...renderOptions + } = extendedRenderOptions; + + const Wrapper = ({ children }: PropsWithChildren) => ( + {children} + ); + + return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }; +} diff --git a/client/src/routes.tsx b/client/src/routes.tsx new file mode 100644 index 0000000..3fec783 --- /dev/null +++ b/client/src/routes.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import App from './App'; +import Schedule from './components/Schedule'; +import Registry from './components/Registry'; +import GuestLogin from './components/GuestLogin'; +import Rsvp from './components/Rsvp'; +import RsvpForm from './components/RsvpForm'; +import Home from './components/Home'; +import AdminLogin from './components/AdminLogin'; +import Admin from './components/Admin'; +import Dashboard from './components/Dashboard'; + +const routes = [ + { + element: , + children: [ + { + path: '/', + element: , + }, + { + path: 'schedule', + element: , + }, + { + path: 'registry', + element: , + }, + { + path: 'guests/login', + element: , + }, + { + path: 'admin/login', + element: , + }, + { + element: , + children: [ + { + path: 'rsvp', + element: , + }, + ], + }, + { + element: , + children: [ + { + path: 'dashboard', + element: , + }, + ], + }, + ], + }, +]; + +export default routes; diff --git a/client/src/setup.ts b/client/src/setup.ts new file mode 100644 index 0000000..0abb5f3 --- /dev/null +++ b/client/src/setup.ts @@ -0,0 +1,6 @@ +import { afterAll, afterEach, beforeAll } from 'vitest'; +import { server } from './mocks/server'; + +beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); +afterAll(() => server.close()); +afterEach(() => server.resetHandlers()); diff --git a/client/src/slices/snackbarSlice.ts b/client/src/slices/snackbarSlice.ts index f76b133..82532ec 100644 --- a/client/src/slices/snackbarSlice.ts +++ b/client/src/slices/snackbarSlice.ts @@ -1,10 +1,11 @@ import { createSlice } from '@reduxjs/toolkit'; +import type { AlertColor } from '@mui/material/Alert/Alert'; import type { RootState } from '../store'; export interface SnackbarState { open: boolean; message: string; - severity?: 'success' | 'error'; + severity?: AlertColor; } const initialState: SnackbarState = { diff --git a/client/src/store.ts b/client/src/store.ts index 4814868..e28bace 100644 --- a/client/src/store.ts +++ b/client/src/store.ts @@ -1,22 +1,31 @@ -import { configureStore } from '@reduxjs/toolkit'; +import { combineReducers, configureStore } from '@reduxjs/toolkit'; import guestReducer from './slices/auth/guestSlice'; import adminReducer from './slices/auth/adminSlice'; import snackbarReducer from './slices/snackbarSlice'; import { guestSlice } from './slices/api/guestSlice'; import { adminSlice } from './slices/api/adminSlice'; -const store = configureStore({ - reducer: { - [guestSlice.reducerPath]: guestSlice.reducer, - [adminSlice.reducerPath]: adminSlice.reducer, - guest: guestReducer, - admin: adminReducer, - snackbar: snackbarReducer, - }, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat(guestSlice.middleware, adminSlice.middleware), +const rootReducer = combineReducers({ + [guestSlice.reducerPath]: guestSlice.reducer, + [adminSlice.reducerPath]: adminSlice.reducer, + guest: guestReducer, + admin: adminReducer, + snackbar: snackbarReducer, }); -export default store; -export type RootState = ReturnType; -export type AppDispatch = typeof store.dispatch; +const setupStore = (preloadedState?: Partial) => { + return configureStore({ + reducer: rootReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat( + guestSlice.middleware, + adminSlice.middleware + ), + preloadedState, + }); +}; + +export default setupStore; +export type RootState = ReturnType; +export type AppStore = ReturnType; +export type AppDispatch = AppStore['dispatch']; diff --git a/client/tsconfig.json b/client/tsconfig.json index f5068c4..4c214ba 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -25,5 +25,5 @@ "noUnusedParameters": true, "noPropertyAccessFromIndexSignature": true }, - "include": ["vite.config.ts"] + "include": ["vite-env.d.ts", "vite.config.ts"] } diff --git a/client/vite.config.ts b/client/vite.config.ts index 5a33944..2942029 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,7 +1,13 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) + test: { + globals: true, + environment: 'jsdom', + setupFiles: './src/setup.ts', + }, +}); -- cgit v1.2.3