From b4e636e62aa5d22bd059e12ff1faa9cc3de1d536 Mon Sep 17 00:00:00 2001 From: Jordan Wong Date: Wed, 18 Dec 2024 15:38:06 -1000 Subject: [PATCH] Replace Jest with Vitest --- .github/workflows/badges.yml | 67 ++----------------- .github/workflows/ci.yml | 28 ++------ README.md | 2 +- package-lock.json | 6 -- ui/.gitignore | 1 + ui/jest.config.ts | 27 -------- ui/package.json | 26 +++---- .../_components/technical-info-section.tsx | 2 +- ui/src/lib/access/authorization.ts | 27 +------- ui/src/lib/fetchers.ts | 31 +++++++-- .../(home)/_components/after-login.test.tsx | 19 +++--- .../(home)/_components/announcements.test.tsx | 5 +- .../(home)/_components/before-login.test.tsx | 1 + .../(home)/_components/login-button.test.tsx | 3 +- .../_components/general-info-section.test.tsx | 1 + .../technical-info-section.test.tsx | 48 +++---------- .../what-happens-if-section.test.tsx | 1 + ui/tests/app/about/page.test.tsx | 1 + ui/tests/app/admin/page.test.tsx | 7 +- .../feedback/_components/error-alert.test.tsx | 1 + .../_components/feedback-form.test.tsx | 37 +++++----- .../_components/success-alert.test.tsx | 1 + ui/tests/app/feedback/page.test.tsx | 5 +- .../_components/description-form.test.tsx | 13 ++-- .../_components/export-dropdown.test.tsx | 1 + .../_components/grouping-header.test.tsx | 3 +- .../_components/return-buttons.test.tsx | 5 +- .../_components/side-nav.test.tsx | 5 +- .../groupings/[groupingPath]/layout.test.tsx | 11 +-- .../[groupingPath]/tab/actions/page.test.tsx | 1 + .../tab/all-members/page.test.tsx | 1 + .../[groupingPath]/tab/basis/page.test.tsx | 1 + .../[groupingPath]/tab/exclude/page.test.tsx | 1 + .../[groupingPath]/tab/include/page.test.tsx | 1 + .../[groupingPath]/tab/layout.test.tsx | 1 + .../[groupingPath]/tab/owners/page.test.tsx | 1 + .../tab/preferences/page.test.tsx | 1 + .../tab/sync-destinations/page.test.tsx | 1 + ui/tests/app/groupings/layout.test.tsx | 1 + ui/tests/app/groupings/page.test.tsx | 5 +- ui/tests/app/memberships/page.test.tsx | 1 + ui/tests/components/layout/footer.test.tsx | 1 + ui/tests/components/layout/heading.test.tsx | 1 + .../layout/navbar/dept-account-icon.test.tsx | 6 +- .../layout/navbar/login-button.test.tsx | 3 +- .../layout/navbar/navbar-menu.test.tsx | 1 + .../components/layout/navbar/navbar.test.tsx | 13 ++-- .../components/modal/api-error-modal.test.tsx | 1 + .../components/modal/dynamic-modal.test.tsx | 10 +-- .../components/modal/timeout-modal.test.tsx | 29 ++++---- .../groupings-table-skeleton.test.tsx | 0 .../groupings-table/groupings-table.test.tsx | 1 + .../grouping-description-cell.test.tsx | 1 + .../table-element/grouping-name-cell.test.tsx | 5 +- .../table-element/grouping-path-cell.test.tsx | 5 +- .../table-element/column-settings.test.tsx | 9 +-- .../table-element/global-filter.test.tsx | 3 +- .../table-element/pagination-bar.test.tsx | 21 +++--- .../table/table-element/sort-arrow.test.tsx | 1 + .../tooltip-on-truncate.test.tsx | 1 + .../components/uh-groupings-info.test.tsx | 1 + ui/tests/lib/access/authorization.test.ts | 39 ++++------- ui/tests/lib/access/role.test.ts | 1 + ui/tests/lib/access/user.test.ts | 14 ++-- ui/tests/lib/actions.test.ts | 45 +++++++------ ui/tests/lib/fetchers.test.ts | 63 ++++++++++++++--- ui/tests/middleware.test.tsx | 19 +++--- ui/tests/setup-jest.ts | 14 ---- ui/tests/vitest.setup.ts | 15 +++++ ui/vitest.config.mts | 23 +++++++ 70 files changed, 364 insertions(+), 383 deletions(-) delete mode 100644 package-lock.json delete mode 100644 ui/jest.config.ts rename ui/tests/components/table/{ => groupings-table}/groupings-table-skeleton.test.tsx (100%) delete mode 100644 ui/tests/setup-jest.ts create mode 100644 ui/tests/vitest.setup.ts create mode 100644 ui/vitest.config.mts diff --git a/.github/workflows/badges.yml b/.github/workflows/badges.yml index 9733087e..1fc42150 100644 --- a/.github/workflows/badges.yml +++ b/.github/workflows/badges.yml @@ -2,7 +2,7 @@ name: Generate badges on: push: - branches: [ main ] + branches: [main] jobs: create-branch: @@ -29,7 +29,7 @@ jobs: echo "Branch $branch_name already exists." fi - generate-jest-badges: + generate-badges: needs: create-branch runs-on: ubuntu-latest steps: @@ -41,7 +41,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v2 with: - node-version: 16.x + node-version: 18.x - name: Installing dependencies run: | @@ -59,9 +59,9 @@ jobs: git switch badges - name: Generating coverage badges - uses: jpb06/jest-badges-action@latest + uses: jpb06/coverage-badges-action@latest with: - branches: '*' + branches: "*" coverage-summary-path: ui/coverage/coverage-summary.json no-commit: true target-branch: badges @@ -73,61 +73,6 @@ jobs: git config --global user.name 'Groupings Project' git config --global user.email 'actions@noreply.its.hawaii.edu' git add *.svg - git commit -m "Autogenerated Jest badges" *.svg + git commit -m "Autogenerated Vitest badges" *.svg git push origin badges -f fi - - # Re-enable once UH Groupings API is added to this repo - # generate-jacoco-badges: - # needs: create-branch - # runs-on: ubuntu-latest - # steps: - # - name: Checkout repository - # uses: actions/checkout@v3 - # with: - # fetch-depth: 0 - - # - name: Set up JDK 17 - # uses: actions/setup-java@v1 - # with: - # java-version: 17 - - # - name: Build with Maven and Generate JaCoCo Report - # run: | - # mvn clean test jacoco:report -f api/pom.xml -D'logging.level.edu.hawaii.its.holiday=OFF' -D'logging.level.org.springframework=ERROR' -D'spring.main.banner-mode=off' - # mv api/target/ target/ - - # - name: Switch to badges branch - # run: | - # git fetch - # git switch badges - - # - name: Generate Jacoco Badge - # id: jacoco - # uses: cicirello/jacoco-badge-generator@v2 - # with: - # coverage-label: junit coverage - # badges-directory: badges - # generate-branches-badge: true - - # - name: Log coverage percentage - # run: | - # echo "coverage = ${{ steps.jacoco.outputs.coverage }}" - - # - name: Commit and push - # if: ${{ github.event_name != 'pull_request' }} - # run: | - # cd badges - # if [[ `git status --porcelain *.svg` ]]; then - # git config --global user.name 'Groupings Project' - # git config --global user.email 'actions@noreply.its.hawaii.edu' - # git add *.svg - # git commit -m "Autogenerated JaCoCo coverage badge" *.svg - # git push origin badges -f - # fi - - # - name: Upload Jacoco coverage report - # uses: actions/upload-artifact@v2 - # with: - # name: jacoco-report - # path: target/site/jacoco/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6eb5e614..4db8534b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,34 +2,16 @@ name: CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: - # Re-enable once UH Groupings API is added to this repo - # junit: - # runs-on: [ubuntu-latest, macos-latest, windows-latest] - - # steps: - # - name: Checkout code - # uses: actions/checkout@v3 - - # - name: Set up JDK 17 - # uses: actions/setup-java@v1 - # with: - # java-version: 17 - - # - name: Build with Maven - # run: | - # cd api - # mvn clean test - - jest: + vitest: strategy: matrix: os: [ubuntu-latest, macos-13, windows-latest] - node-version: [ 18.x, 20.x ] + node-version: [18.x, 20.x] runs-on: ${{ matrix.os }} steps: @@ -51,7 +33,7 @@ jobs: cd ui npm run lint - - name: Run Jest Tests + - name: Run Vitest Tests run: | cd ui npm run test diff --git a/README.md b/README.md index 86b5539e..158c568d 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,4 @@ Groupings can be synchronized with one or more of the following: email LISTSERV UH Groupings utilizes the Internet2 Grouper project. Grouper is an enterprise access management system designed for the highly distributed management environment and heterogeneous information technology environment common to universities. [![CI](https://github.com/uhawaii-system-its-ti-iam/uh-groupings/actions/workflows/ci.yml/badge.svg)](https://github.com/uhawaii-system-its-ti-iam/uh-groupings/actions/workflows/ci.yml) -![Jest coverage](https://github.com/uhawaii-system-its-ti-iam/uh-groupings/blob/badges/badges/coverage-jest%20coverage.svg) +![Test coverage](https://github.com/uhawaii-system-its-ti-iam/uh-groupings/blob/badges/badges/ui/coverage-total.svg) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 5ab54d6d..00000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "uh-groupings", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/ui/.gitignore b/ui/.gitignore index 6d333829..e655f1a2 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -9,6 +9,7 @@ package-lock.json # testing /coverage +.swc # next.js /.next/ diff --git a/ui/jest.config.ts b/ui/jest.config.ts deleted file mode 100644 index a9a1576b..00000000 --- a/ui/jest.config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Config } from 'jest'; -import nextJest from 'next/jest.js'; - -const createJestConfig = nextJest({ - dir: './' -}); - -const config: Config = { - clearMocks: true, - collectCoverageFrom: ['./src/**/*.ts*'], - coveragePathIgnorePatterns: [ - './src/components/ui' // Ignore shadcn/ui components - ], - coverageReporters: ['json-summary', 'text', 'html'], - testEnvironment: 'jsdom', - testEnvironmentOptions: { - customExportConditions: [] - }, - setupFilesAfterEnv: ['/tests/setup-jest.ts'], - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - moduleDirectories: ['node_modules', ''], - moduleNameMapper: { - '^@/(.*)$': '/src/$1' - } -}; - -export default createJestConfig(config); diff --git a/ui/package.json b/ui/package.json index 8f36ac89..613107cf 100644 --- a/ui/package.json +++ b/ui/package.json @@ -8,9 +8,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "test": "jest", - "test:coverage": "jest --coverage", - "test:watch": "jest --watch" + "test": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage" }, "dependencies": { "@fortawesome/free-regular-svg-icons": "^6.6.0", @@ -33,7 +33,7 @@ "dotenv": "^16.4.1", "lucide-react": "^0.453.0", "next": "14.2.15", - "next-cas-client": "^1.2.2", + "next-cas-client": "^1.3.2", "react": "^18", "react-dom": "^18", "react-hook-form": "^7.50.1", @@ -45,32 +45,34 @@ }, "devDependencies": { "@stylistic/eslint-plugin": "^2.8.0", - "@swc/core": "^1.3.106", - "@testing-library/jest-dom": "^6.3.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", - "@types/jest": "^29.5.11", "@types/node": "^22.7.4", "@types/react": "^18", "@types/react-dom": "^18", "@types/uniqid": "^5.3.4", "@typescript-eslint/eslint-plugin": "^6.19.1", "@typescript-eslint/parser": "^6.19.1", + "@vitejs/plugin-react": "^4.3.4", + "@vitest/coverage-istanbul": "^2.1.8", + "@vitest/ui": "^2.1.8", "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.2.15", "eslint-config-prettier": "^9.1.0", "eslint-plugin-testing-library": "^6.2.0", "eslint-plugin-tsdoc": "^0.2.17", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "jest-fetch-mock": "^3.0.3", + "jsdom": "^25.0.1", "postcss": "^8.4.33", - "postcss-preset-mantine": "^1.12.3", "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.5", "tailwindcss": "^3.3.0", "ts-node": "^10.9.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^2.1.8", + "vitest-fetch-mock": "^0.4.2" } } diff --git a/ui/src/app/about/_components/technical-info-section.tsx b/ui/src/app/about/_components/technical-info-section.tsx index d979c507..2bb9afed 100644 --- a/ui/src/app/about/_components/technical-info-section.tsx +++ b/ui/src/app/about/_components/technical-info-section.tsx @@ -2,7 +2,7 @@ const TechnicalInfoSection = () => { const technicalInfoItems = [ { name: 'React.js', url: 'https://react.dev/learn', description: '(Quickstart)' }, { name: 'shadcn/ui', url: 'https://ui.shadcn.com/docs', description: '(Guide)' }, - { name: 'Jest', url: 'https://jestjs.io/docs/getting-started', description: '(Introduction)' }, + { name: 'Vitest', url: 'https://vitest.dev/guide/', description: '(Introduction)' }, { name: 'Next.js', url: 'https://nextjs.org/docs', description: '(Introduction)' }, { name: 'Tanstack Table', diff --git a/ui/src/lib/access/authorization.ts b/ui/src/lib/access/authorization.ts index 593893c2..875de938 100644 --- a/ui/src/lib/access/authorization.ts +++ b/ui/src/lib/access/authorization.ts @@ -1,8 +1,7 @@ +import { isOwner, isAdmin } from '../fetchers'; import Role from './role'; import User from './user'; -const apiBaseUrl = process.env.NEXT_PUBLIC_API_2_1_BASE_URL as string; - /** * Sets the appropriate roles for a user. * @@ -26,30 +25,6 @@ export const setRoles = async (user: User): Promise => { } }; -/** - * Calls UH Groupings API to check if the uhIdentifier is an owner. - * - * @param uhIdentifier - The uid or uhUuid - * - * @returns True if the uhIdentifier is an owner of a grouping - */ -const isOwner = async (uhIdentifier: string): Promise => - await fetch(`${apiBaseUrl}/owners`, { headers: { current_user: uhIdentifier } }) - .then((res) => res.json()) - .catch(() => false); - -/** - * Calls UH Groupings API to check if the uhIdentifier is an admin. - * - * @param uhIdentifier - The uid or uhUuid - * - * @returns True if the uhIdentifier is an admin - */ -const isAdmin = async (uhIdentifier: string): Promise => - await fetch(`${apiBaseUrl}/admins`, { headers: { current_user: uhIdentifier } }) - .then((res) => res.json()) - .catch(() => false); - /** * Checks if uhUuid is valid using Regex. * diff --git a/ui/src/lib/fetchers.ts b/ui/src/lib/fetchers.ts index dc7a2558..afd9d2ab 100644 --- a/ui/src/lib/fetchers.ts +++ b/ui/src/lib/fetchers.ts @@ -7,8 +7,7 @@ import { GroupingGroupMembers, GroupingPaths, MembershipResults, - GroupingGroupsMembers, - ApiError + GroupingGroupsMembers } from './types'; import { getUser } from '@/lib/access/user'; @@ -97,7 +96,7 @@ export const groupingOptAttributes = async (groupingPath: string): Promise => { +export const groupingAdmins = async (): Promise => { const currentUser = await getUser(); const endpoint = `${baseUrl}/groupings/admins`; return getRequest(endpoint, currentUser.uid); @@ -108,7 +107,7 @@ export const groupingAdmins = async (): Promise * * @returns The promise of all the grouping paths */ -export const getAllGroupings = async (): Promise => { +export const getAllGroupings = async (): Promise => { const currentUser = await getUser(); const endpoint = `${baseUrl}/groupings`; return getRequest(endpoint, currentUser.uid); @@ -208,3 +207,27 @@ export const isSoleOwner = async (uhIdentifier: string, groupingPath: string): P const endpoint = `${baseUrl}/groupings/${groupingPath}/owners/${uhIdentifier}`; return getRequest(endpoint, currentUser.uid); }; + +/** + * Check if the uhIdentifier is an owner. + * + * @param uhIdentifier - The uid or uhUuid + * + * @returns True if the uhIdentifier is an owner of a grouping + */ +export const isOwner = async (uhIdentifier: string): Promise => { + const endpoint = `${baseUrl}/owners`; + return getRequest(endpoint, uhIdentifier); +}; + +/** + * Check if the uhIdentifier is an admin. + * + * @param uhIdentifier - The uid or uhUuid + * + * @returns True if the uhIdentifier is an admin + */ +export const isAdmin = async (uhIdentifier: string): Promise => { + const endpoint = `${baseUrl}/admins`; + return getRequest(endpoint, uhIdentifier); +}; diff --git a/ui/tests/app/(home)/_components/after-login.test.tsx b/ui/tests/app/(home)/_components/after-login.test.tsx index 313ce8a0..f656db71 100644 --- a/ui/tests/app/(home)/_components/after-login.test.tsx +++ b/ui/tests/app/(home)/_components/after-login.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; import Role from '@/lib/access/role'; import { render, screen } from '@testing-library/react'; import User from '@/lib/access/user'; @@ -5,8 +6,8 @@ import * as Fetchers from '@/lib/fetchers'; import * as NextCasClient from 'next-cas-client/app'; import afterLogin from '@/app/(home)/_components/after-login'; -jest.mock('@/lib/fetchers'); -jest.mock('next-cas-client/app'); +vi.mock('@/lib/fetchers'); +vi.mock('next-cas-client/app'); const testUser: User = JSON.parse(process.env.TEST_USER_A as string); @@ -30,8 +31,8 @@ describe('AfterLogin', () => { }; const expectWelcome = (User: User, role: string) => { - expect(screen.getByLabelText('user')).toBeInTheDocument(); - expect(screen.getByLabelText('key-round')).toBeInTheDocument(); + expect(screen.getAllByLabelText('user')[0]).toBeInTheDocument(); + expect(screen.getAllByLabelText('key-round')[0]).toBeInTheDocument(); expect(screen.getByTestId('welcome-message')).toHaveTextContent(`Welcome, ${User.firstName}!`); expect(screen.getByTestId('role')).toHaveTextContent(`Role: ${role}`); }; @@ -100,12 +101,12 @@ describe('AfterLogin', () => { }; beforeEach(() => { - jest.spyOn(Fetchers, 'getNumberOfGroupings').mockResolvedValue(numberOfGroupings); - jest.spyOn(Fetchers, 'getNumberOfMemberships').mockResolvedValue(numberOfMemberships); + vi.spyOn(Fetchers, 'getNumberOfGroupings').mockResolvedValue(numberOfGroupings); + vi.spyOn(Fetchers, 'getNumberOfMemberships').mockResolvedValue(numberOfMemberships); }); it('Should render correctly when logged in as an admin', async () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(admin); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(admin); render(await afterLogin()); expectWelcome(admin, 'Admin'); expectAdministration(true); @@ -114,7 +115,7 @@ describe('AfterLogin', () => { }); it('Should render correctly when logged in as Owner', async () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(owner); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(owner); render(await afterLogin()); expectWelcome(owner, 'Owner'); expectAdministration(false); @@ -123,7 +124,7 @@ describe('AfterLogin', () => { }); it('Should render correctly when logged in as a user with a UH account', async () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(uhUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(uhUser); render(await afterLogin()); expectWelcome(uhUser, 'Member'); expectAdministration(false); diff --git a/ui/tests/app/(home)/_components/announcements.test.tsx b/ui/tests/app/(home)/_components/announcements.test.tsx index 181c3a84..2b448bf8 100644 --- a/ui/tests/app/(home)/_components/announcements.test.tsx +++ b/ui/tests/app/(home)/_components/announcements.test.tsx @@ -1,8 +1,9 @@ +import { vi, describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import Announcements from '@/app/(home)/_components/announcements'; import * as Fetchers from '@/lib/fetchers'; -jest.mock('@/lib/fetchers'); +vi.mock('@/lib/fetchers'); const message = 'test announcement'; const message1 = 'test1 announcement'; @@ -33,7 +34,7 @@ const announcements = { describe('Announcements', () => { it('renders announcement correctly', async () => { - jest.spyOn(Fetchers, 'getAnnouncements').mockResolvedValue(announcements); + vi.spyOn(Fetchers, 'getAnnouncements').mockResolvedValue(announcements); render(await Announcements()); expect(screen.getByText(message)).toBeInTheDocument(); diff --git a/ui/tests/app/(home)/_components/before-login.test.tsx b/ui/tests/app/(home)/_components/before-login.test.tsx index 1dc34cae..a5bffb2e 100644 --- a/ui/tests/app/(home)/_components/before-login.test.tsx +++ b/ui/tests/app/(home)/_components/before-login.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import BeforeLogin from '@/app/(home)/_components/before-login'; diff --git a/ui/tests/app/(home)/_components/login-button.test.tsx b/ui/tests/app/(home)/_components/login-button.test.tsx index 3f2e9882..3a633e06 100644 --- a/ui/tests/app/(home)/_components/login-button.test.tsx +++ b/ui/tests/app/(home)/_components/login-button.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, it, expect, beforeAll } from 'vitest'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import User, { AnonymousUser } from '@/lib/access/user'; @@ -7,7 +8,7 @@ import * as NextCasClient from 'next-cas-client'; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client'); +vi.mock('next-cas-client'); describe('LoginButton', () => { describe('User is not logged in', () => { diff --git a/ui/tests/app/about/_components/general-info-section.test.tsx b/ui/tests/app/about/_components/general-info-section.test.tsx index dc6aad30..e650cd2e 100644 --- a/ui/tests/app/about/_components/general-info-section.test.tsx +++ b/ui/tests/app/about/_components/general-info-section.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import GeneralInfoSection from '@/app/about/_components/general-info-section'; diff --git a/ui/tests/app/about/_components/technical-info-section.test.tsx b/ui/tests/app/about/_components/technical-info-section.test.tsx index deeba7a8..9fed417f 100644 --- a/ui/tests/app/about/_components/technical-info-section.test.tsx +++ b/ui/tests/app/about/_components/technical-info-section.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import TechnicalInfoSection from '@/app/about/_components/technical-info-section'; @@ -10,52 +11,31 @@ describe('TechnicalInfoSection', () => { expect(screen.getByText('React.js')).toBeInTheDocument(); expect(screen.getByText('(Quickstart)')).toBeInTheDocument(); - expect(screen.getByText('(Quickstart)')).toHaveAttribute( - 'href', - 'https://react.dev/learn' - ); + expect(screen.getByText('(Quickstart)')).toHaveAttribute('href', 'https://react.dev/learn'); expect(screen.getByText('shadcn/ui')).toBeInTheDocument(); expect(guideLinks[0]).toBeInTheDocument(); - expect(guideLinks[0]).toHaveAttribute( - 'href', - 'https://ui.shadcn.com/docs' - ); + expect(guideLinks[0]).toHaveAttribute('href', 'https://ui.shadcn.com/docs'); - expect(screen.getByText('Jest')).toBeInTheDocument(); + expect(screen.getByText('Vitest')).toBeInTheDocument(); expect(introductionLinks[0]).toBeInTheDocument(); - expect(introductionLinks[0]).toHaveAttribute( - 'href', - 'https://jestjs.io/docs/getting-started' - ); + expect(introductionLinks[0]).toHaveAttribute('href', 'https://vitest.dev/guide/'); expect(screen.getByText('Next.js')).toBeInTheDocument(); expect(introductionLinks[1]).toBeInTheDocument(); - expect(introductionLinks[1]).toHaveAttribute( - 'href', - 'https://nextjs.org/docs' - ); + expect(introductionLinks[1]).toHaveAttribute('href', 'https://nextjs.org/docs'); expect(screen.getByText('Tanstack Table')).toBeInTheDocument(); expect(introductionLinks[2]).toBeInTheDocument(); - expect(introductionLinks[2]).toHaveAttribute( - 'href', - 'https://tanstack.com/table/v8/docs/introduction' - ); + expect(introductionLinks[2]).toHaveAttribute('href', 'https://tanstack.com/table/v8/docs/introduction'); expect(screen.getByText('React Testing Library')).toBeInTheDocument(); expect(screen.getByText('(Documentation)')).toBeInTheDocument(); - expect(screen.getByText('(Documentation)')).toHaveAttribute( - 'href', - 'https://testing-library.com/' - ); + expect(screen.getByText('(Documentation)')).toHaveAttribute('href', 'https://testing-library.com/'); expect(screen.getByText('Typescript')).toBeInTheDocument(); expect(guideLinks[1]).toBeInTheDocument(); - expect(guideLinks[1]).toHaveAttribute( - 'href', - 'https://www.typescriptlang.org/docs/' - ); + expect(guideLinks[1]).toHaveAttribute('href', 'https://www.typescriptlang.org/docs/'); expect(screen.getByText('Tanstack Query')).toBeInTheDocument(); expect(guideLinks[2]).toBeInTheDocument(); @@ -66,16 +46,10 @@ describe('TechnicalInfoSection', () => { expect(screen.getByText('Tailwind CSS')).toBeInTheDocument(); expect(guideLinks[3]).toBeInTheDocument(); - expect(guideLinks[3]).toHaveAttribute( - 'href', - 'https://v2.tailwindcss.com/docs' - ); + expect(guideLinks[3]).toHaveAttribute('href', 'https://v2.tailwindcss.com/docs'); expect(screen.getByText('Iron Session')).toBeInTheDocument(); expect(screen.getByText('(GitHub)')).toBeInTheDocument(); - expect(screen.getByText('(GitHub)')).toHaveAttribute( - 'href', - 'https://github.com/vvo/iron-session' - ); + expect(screen.getByText('(GitHub)')).toHaveAttribute('href', 'https://github.com/vvo/iron-session'); }); }); diff --git a/ui/tests/app/about/_components/what-happens-if-section.test.tsx b/ui/tests/app/about/_components/what-happens-if-section.test.tsx index 50206e4f..cb80bade 100644 --- a/ui/tests/app/about/_components/what-happens-if-section.test.tsx +++ b/ui/tests/app/about/_components/what-happens-if-section.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import WhatHappensIfSection from '@/app/about/_components/what-happens-if-section'; diff --git a/ui/tests/app/about/page.test.tsx b/ui/tests/app/about/page.test.tsx index 737166be..d8cafbbe 100644 --- a/ui/tests/app/about/page.test.tsx +++ b/ui/tests/app/about/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import About from '@/app/about/page'; diff --git a/ui/tests/app/admin/page.test.tsx b/ui/tests/app/admin/page.test.tsx index 92ec293a..9563281d 100644 --- a/ui/tests/app/admin/page.test.tsx +++ b/ui/tests/app/admin/page.test.tsx @@ -1,11 +1,12 @@ //TODO: import AdminLayout from '@/app/admin/layout'; +import { vi, beforeEach, describe, it, expect } from 'vitest'; import Admin from '@/app/admin/page'; import { render, screen, waitFor } from '@testing-library/react'; import * as Fetchers from '@/lib/fetchers'; import { MemberResult, GroupingPaths } from '@/lib/types'; import Groupings from '@/app/groupings/page'; -jest.mock('@/lib/fetchers'); +vi.mock('@/lib/fetchers'); const mockGroupingsData: GroupingPaths = { resultCode: 'SUCCESS', @@ -26,8 +27,8 @@ const mockAdminsData: MemberResult = { }; beforeEach(() => { - jest.spyOn(Fetchers, 'ownerGroupings').mockResolvedValue(mockGroupingsData); - jest.spyOn(Fetchers, 'groupingAdmins').mockResolvedValue(mockAdminsData); + vi.spyOn(Fetchers, 'ownerGroupings').mockResolvedValue(mockGroupingsData); + vi.spyOn(Fetchers, 'groupingAdmins').mockResolvedValue(mockAdminsData); }); describe('Groupings', () => { diff --git a/ui/tests/app/feedback/_components/error-alert.test.tsx b/ui/tests/app/feedback/_components/error-alert.test.tsx index f0abe038..a917f331 100644 --- a/ui/tests/app/feedback/_components/error-alert.test.tsx +++ b/ui/tests/app/feedback/_components/error-alert.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import ErrorAlert from '@/app/feedback/_components/error-alert'; import { render, screen } from '@testing-library/react'; diff --git a/ui/tests/app/feedback/_components/feedback-form.test.tsx b/ui/tests/app/feedback/_components/feedback-form.test.tsx index f55120ed..4f85be36 100644 --- a/ui/tests/app/feedback/_components/feedback-form.test.tsx +++ b/ui/tests/app/feedback/_components/feedback-form.test.tsx @@ -3,8 +3,9 @@ import userEvent from '@testing-library/user-event'; import * as Actions from '@/lib/actions'; import User from '@/lib/access/user'; import FeedbackForm from '@/app/feedback/_components/feedback-form'; +import { vi, describe, it, expect } from 'vitest'; -jest.mock('@/lib/actions'); +vi.mock('@/lib/actions'); const testUser: User = JSON.parse(process.env.TEST_USER_A as string); @@ -21,7 +22,7 @@ describe('FeedbackForm', () => { const user = userEvent.setup(); render(); - jest.spyOn(Actions, 'sendFeedback').mockResolvedValue({ + vi.spyOn(Actions, 'sendFeedback').mockResolvedValue({ resultCode: 'SUCCESS', recipient: 'recipient', from: 'from', @@ -48,7 +49,7 @@ describe('FeedbackForm', () => { const user = userEvent.setup(); render(); - jest.spyOn(Actions, 'sendFeedback').mockResolvedValue({ + vi.spyOn(Actions, 'sendFeedback').mockResolvedValue({ resultCode: 'FAILURE', recipient: 'recipient', from: 'from', @@ -73,21 +74,21 @@ describe('FeedbackForm', () => { const user = userEvent.setup(); render(); - jest.spyOn(Actions, 'sendFeedback').mockResolvedValueOnce({ - resultCode: 'FAILURE', - recipient: 'recipient', - from: 'from', - subject: 'subject', - text: 'text' - }); - - jest.spyOn(Actions, 'sendFeedback').mockResolvedValueOnce({ - resultCode: 'SUCCESS', - recipient: 'recipient', - from: 'from', - subject: 'subject', - text: 'text' - }); + vi.spyOn(Actions, 'sendFeedback') + .mockResolvedValueOnce({ + resultCode: 'FAILURE', + recipient: 'recipient', + from: 'from', + subject: 'subject', + text: 'text' + }) + .mockResolvedValueOnce({ + resultCode: 'SUCCESS', + recipient: 'recipient', + from: 'from', + subject: 'subject', + text: 'text' + }); await waitFor( async () => { diff --git a/ui/tests/app/feedback/_components/success-alert.test.tsx b/ui/tests/app/feedback/_components/success-alert.test.tsx index be62262b..88fbacb9 100644 --- a/ui/tests/app/feedback/_components/success-alert.test.tsx +++ b/ui/tests/app/feedback/_components/success-alert.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import SuccessAlert from '@/app/feedback/_components/success-alert'; import { render, screen } from '@testing-library/react'; diff --git a/ui/tests/app/feedback/page.test.tsx b/ui/tests/app/feedback/page.test.tsx index f9e9966c..e538f51e 100644 --- a/ui/tests/app/feedback/page.test.tsx +++ b/ui/tests/app/feedback/page.test.tsx @@ -1,15 +1,16 @@ +import { vi, describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import Feedback from '@/app/feedback/page'; import * as NextCasClient from 'next-cas-client/app'; import User from '@/lib/access/user'; -jest.mock('next-cas-client/app'); +vi.mock('next-cas-client/app'); const testUser: User = JSON.parse(process.env.TEST_USER_A as string); describe('Feedback', () => { it('should render the Feedback form', async () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); render(await Feedback()); diff --git a/ui/tests/app/groupings/[groupingPath]/_components/description-form.test.tsx b/ui/tests/app/groupings/[groupingPath]/_components/description-form.test.tsx index fa5db0ce..7fe0f9ed 100644 --- a/ui/tests/app/groupings/[groupingPath]/_components/description-form.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/_components/description-form.test.tsx @@ -2,13 +2,14 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import DescriptionForm from '@/app/groupings/[groupingPath]/_components/description-form'; import { updateDescription } from '@/lib/actions'; import { useRouter } from 'next/navigation'; +import { vi, describe, beforeEach, it, expect, Mock } from 'vitest'; -jest.mock('@/lib/actions', () => ({ - updateDescription: jest.fn() +vi.mock('@/lib/actions', () => ({ + updateDescription: vi.fn() })); -jest.mock('next/navigation', () => ({ - useRouter: jest.fn() +vi.mock('next/navigation', () => ({ + useRouter: vi.fn() })); describe('DescriptionForm', () => { @@ -16,8 +17,8 @@ describe('DescriptionForm', () => { const groupPath = 'test path'; beforeEach(() => { - const router = { refresh: jest.fn() }; - (useRouter as jest.Mock).mockReturnValue(router); + const router = { refresh: vi.fn() }; + (useRouter as Mock).mockReturnValue(router); }); it('should render description', () => { diff --git a/ui/tests/app/groupings/[groupingPath]/_components/export-dropdown.test.tsx b/ui/tests/app/groupings/[groupingPath]/_components/export-dropdown.test.tsx index 717563b5..4fa0035b 100644 --- a/ui/tests/app/groupings/[groupingPath]/_components/export-dropdown.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/_components/export-dropdown.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import ExportDropdown from '@/app/groupings/[groupingPath]/_components/export-dropdown'; diff --git a/ui/tests/app/groupings/[groupingPath]/_components/grouping-header.test.tsx b/ui/tests/app/groupings/[groupingPath]/_components/grouping-header.test.tsx index 334861b9..65d5eb34 100644 --- a/ui/tests/app/groupings/[groupingPath]/_components/grouping-header.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/_components/grouping-header.test.tsx @@ -1,8 +1,9 @@ +import { vi, describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import GroupingHeader from '@/app/groupings/[groupingPath]/_components/grouping-header'; import DescriptionForm from '@/app/groupings/[groupingPath]/_components/description-form'; -jest.mock('@/app/groupings/[groupingPath]/_components/description-form'); +vi.mock('@/app/groupings/[groupingPath]/_components/description-form'); describe('GroupingHeader Component', () => { const GroupName = 'Test Group'; diff --git a/ui/tests/app/groupings/[groupingPath]/_components/return-buttons.test.tsx b/ui/tests/app/groupings/[groupingPath]/_components/return-buttons.test.tsx index af083d53..dc177b4e 100644 --- a/ui/tests/app/groupings/[groupingPath]/_components/return-buttons.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/_components/return-buttons.test.tsx @@ -1,15 +1,16 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import ReturnButtons from '@/app/groupings/[groupingPath]/_components/return-buttons'; describe('ReturnButtons Component', () => { - test('renders button to return to Groupings List when fromManageSubject is false', () => { + it('renders button to return to Groupings List when fromManageSubject is false', () => { render(); expect(screen.getByText(/return to groupings list/i)).toBeInTheDocument(); expect(screen.queryByText(/return to manage person/i)).not.toBeInTheDocument(); }); - test('renders button to return to Manage Person when fromManageSubject is true', () => { + it('renders button to return to Manage Person when fromManageSubject is true', () => { render(); expect(screen.getByText(/return to manage person/i)).toBeInTheDocument(); diff --git a/ui/tests/app/groupings/[groupingPath]/_components/side-nav.test.tsx b/ui/tests/app/groupings/[groupingPath]/_components/side-nav.test.tsx index 49f8b805..256ae36c 100644 --- a/ui/tests/app/groupings/[groupingPath]/_components/side-nav.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/_components/side-nav.test.tsx @@ -1,14 +1,15 @@ +import { vi, describe, beforeEach, it, expect, Mock } from 'vitest'; import { render, screen } from '@testing-library/react'; import SideNav from '@/app/groupings/[groupingPath]/_components/side-nav'; import { usePathname } from 'next/navigation'; -jest.mock('next/navigation'); +vi.mock('next/navigation'); describe('SideNav Component', () => { const groupingPath = 'test-grouping'; beforeEach(() => { - (usePathname as jest.Mock).mockReturnValue(`/groupings/${groupingPath}/all-members`); + (usePathname as Mock).mockReturnValue(`/groupings/${groupingPath}/all-members`); }); it('renders link', () => { diff --git a/ui/tests/app/groupings/[groupingPath]/layout.test.tsx b/ui/tests/app/groupings/[groupingPath]/layout.test.tsx index 1691ad55..57a635bb 100644 --- a/ui/tests/app/groupings/[groupingPath]/layout.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/layout.test.tsx @@ -1,3 +1,4 @@ +import { vi, beforeEach, describe, it, expect, Mock } from 'vitest'; import { render, screen } from '@testing-library/react'; import GroupingPathLayout from '@/app/groupings/[groupingPath]/layout'; import { groupingDescription } from '@/lib/fetchers'; @@ -5,9 +6,9 @@ import { usePathname } from 'next/navigation'; import { GroupingDescription } from '@/lib/types'; import GroupingHeader from '@/app/groupings/[groupingPath]/_components/grouping-header'; -jest.mock('next/navigation'); -jest.mock('@/lib/fetchers'); -jest.mock('@/app/groupings/[groupingPath]/_components/grouping-header'); +vi.mock('next/navigation'); +vi.mock('@/lib/fetchers'); +vi.mock('@/app/groupings/[groupingPath]/_components/grouping-header'); const mockData: GroupingDescription = { groupPath: 'Test-path:Test-name', @@ -16,8 +17,8 @@ const mockData: GroupingDescription = { }; beforeEach(() => { - (groupingDescription as jest.Mock).mockResolvedValue(mockData); - (usePathname as jest.Mock).mockReturnValue('/groupings/Test-Path/Test-name'); + (groupingDescription as Mock).mockResolvedValue(mockData); + (usePathname as Mock).mockReturnValue('/groupings/Test-Path/Test-name'); }); describe('GroupingPathLayout', () => { diff --git a/ui/tests/app/groupings/[groupingPath]/tab/actions/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/actions/page.test.tsx index c9229acd..eb44f1b6 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/actions/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/actions/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import ActionsTab from '@/app/groupings/[groupingPath]/@tab/actions/page'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/all-members/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/all-members/page.test.tsx index c92034fe..dcb78200 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/all-members/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/all-members/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import AllMembersTab from '@/app/groupings/[groupingPath]/@tab/all-members/page'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/basis/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/basis/page.test.tsx index 2caa211f..ac784af2 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/basis/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/basis/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import BasisTab from '@/app/groupings/[groupingPath]/@tab/basis/page'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/exclude/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/exclude/page.test.tsx index e46918f4..e253d200 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/exclude/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/exclude/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import ExcludeTab from '@/app/groupings/[groupingPath]/@tab/exclude/page'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/include/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/include/page.test.tsx index 0a84735f..55df1862 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/include/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/include/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import IncludeTab from '@/app/groupings/[groupingPath]/@tab/include/page'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/layout.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/layout.test.tsx index cc1e2c40..14824c8f 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/layout.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/layout.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import TabLayout from '@/app/groupings/[groupingPath]/@tab/layout'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/owners/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/owners/page.test.tsx index 9a2d792c..2d38b131 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/owners/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/owners/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import OwnersTab from '@/app/groupings/[groupingPath]/@tab/owners/page'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/preferences/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/preferences/page.test.tsx index fa29ccaf..11f0d9ed 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/preferences/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/preferences/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import PreferencesTab from '@/app/groupings/[groupingPath]/@tab/preferences/page'; diff --git a/ui/tests/app/groupings/[groupingPath]/tab/sync-destinations/page.test.tsx b/ui/tests/app/groupings/[groupingPath]/tab/sync-destinations/page.test.tsx index 384e80b9..60c98564 100644 --- a/ui/tests/app/groupings/[groupingPath]/tab/sync-destinations/page.test.tsx +++ b/ui/tests/app/groupings/[groupingPath]/tab/sync-destinations/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import SyncDestinationsTab from '@/app/groupings/[groupingPath]/@tab/sync-destinations/page'; diff --git a/ui/tests/app/groupings/layout.test.tsx b/ui/tests/app/groupings/layout.test.tsx index bffd5dd9..56433cd2 100644 --- a/ui/tests/app/groupings/layout.test.tsx +++ b/ui/tests/app/groupings/layout.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import GroupingsLayout from '@/app/groupings/layout'; diff --git a/ui/tests/app/groupings/page.test.tsx b/ui/tests/app/groupings/page.test.tsx index 260a26de..fb456130 100644 --- a/ui/tests/app/groupings/page.test.tsx +++ b/ui/tests/app/groupings/page.test.tsx @@ -2,8 +2,9 @@ import { render, screen, waitFor } from '@testing-library/react'; import Groupings from '@/app/groupings/page'; import * as Fetchers from '@/lib/fetchers'; import { GroupingPaths } from '@/lib/types'; +import { vi, beforeEach, describe, it, expect } from 'vitest'; -jest.mock('@/lib/fetchers'); +vi.mock('@/lib/fetchers'); const mockData: GroupingPaths = { resultCode: 'SUCCESS', @@ -15,7 +16,7 @@ const mockData: GroupingPaths = { }; beforeEach(() => { - jest.spyOn(Fetchers, 'ownerGroupings').mockResolvedValue(mockData); + vi.spyOn(Fetchers, 'ownerGroupings').mockResolvedValue(mockData); }); describe('Groupings', () => { diff --git a/ui/tests/app/memberships/page.test.tsx b/ui/tests/app/memberships/page.test.tsx index 05e5aa86..b247657c 100644 --- a/ui/tests/app/memberships/page.test.tsx +++ b/ui/tests/app/memberships/page.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import MembershipsLayout from '@/app/memberships/layout'; import Memberships from '@/app/memberships/page'; import { render, screen } from '@testing-library/react'; diff --git a/ui/tests/components/layout/footer.test.tsx b/ui/tests/components/layout/footer.test.tsx index 339f6b67..47d71da4 100644 --- a/ui/tests/components/layout/footer.test.tsx +++ b/ui/tests/components/layout/footer.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import Footer from '@/components/layout/footer'; diff --git a/ui/tests/components/layout/heading.test.tsx b/ui/tests/components/layout/heading.test.tsx index 64debcfc..825a273f 100644 --- a/ui/tests/components/layout/heading.test.tsx +++ b/ui/tests/components/layout/heading.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import Heading from '@/components/layout/heading'; import { render, screen } from '@testing-library/react'; diff --git a/ui/tests/components/layout/navbar/dept-account-icon.test.tsx b/ui/tests/components/layout/navbar/dept-account-icon.test.tsx index 53610fb7..546676ea 100644 --- a/ui/tests/components/layout/navbar/dept-account-icon.test.tsx +++ b/ui/tests/components/layout/navbar/dept-account-icon.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, it, expect } from 'vitest'; import { fireEvent, render, screen } from '@testing-library/react'; import DeptAccountIcon from '@/components/layout/navbar/dept-account-icon'; import User, { AnonymousUser } from '@/lib/access/user'; @@ -5,7 +6,8 @@ import * as NextCasClient from 'next-cas-client/app'; import Role from '@/lib/access/role'; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client/app'); + +vi.mock('next-cas-client/app'); describe('Dept Account Icon', () => { it('should render the Departmental Account icon and open warning modal when clicked on', () => { @@ -21,7 +23,7 @@ describe('Dept Account Icon', () => { }); it('should not render the Departmental Account icon for other roles', () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(AnonymousUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(AnonymousUser); testUser.roles = [Role.ANONYMOUS, Role.ADMIN, Role.UH, Role.OWNER]; render(); diff --git a/ui/tests/components/layout/navbar/login-button.test.tsx b/ui/tests/components/layout/navbar/login-button.test.tsx index 78dc27b5..c3cf049a 100644 --- a/ui/tests/components/layout/navbar/login-button.test.tsx +++ b/ui/tests/components/layout/navbar/login-button.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, it, expect, beforeAll } from 'vitest'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Login from '@/components/layout/navbar/login-button'; @@ -7,7 +8,7 @@ import * as NextCasClient from 'next-cas-client'; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client'); +vi.mock('next-cas-client'); describe('Login', () => { describe('User is not logged in', () => { diff --git a/ui/tests/components/layout/navbar/navbar-menu.test.tsx b/ui/tests/components/layout/navbar/navbar-menu.test.tsx index 7ece65b4..4fa43271 100644 --- a/ui/tests/components/layout/navbar/navbar-menu.test.tsx +++ b/ui/tests/components/layout/navbar/navbar-menu.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect, beforeEach } from 'vitest'; import Role from '@/lib/access/role'; import User, { AnonymousUser } from '@/lib/access/user'; import NavbarMenu from '@/components/layout/navbar/navbar-menu'; diff --git a/ui/tests/components/layout/navbar/navbar.test.tsx b/ui/tests/components/layout/navbar/navbar.test.tsx index 2b49161c..d2fb6275 100644 --- a/ui/tests/components/layout/navbar/navbar.test.tsx +++ b/ui/tests/components/layout/navbar/navbar.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; import User, { AnonymousUser } from '@/lib/access/user'; import * as NextCasClient from 'next-cas-client/app'; import { render, screen } from '@testing-library/react'; @@ -6,12 +7,12 @@ import Role from '@/lib/access/role'; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client/app'); +vi.mock('next-cas-client/app'); describe('Navbar', () => { describe('User is logged-out', () => { it('should render the navbar with only the link to /about', async () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(AnonymousUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(AnonymousUser); render(await Navbar()); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -36,7 +37,7 @@ describe('Navbar', () => { it('should render only /memberships, /about, /feedback for the average user', async () => { testUser.roles.push(Role.UH); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); render(await Navbar()); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -55,7 +56,7 @@ describe('Navbar', () => { it('should render only /memberships, /groupings, /about, /feedback for an owner of a grouping', async () => { testUser.roles.push(Role.OWNER, Role.UH); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); render(await Navbar()); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -74,7 +75,7 @@ describe('Navbar', () => { it('should render all links for an Admin', async () => { testUser.roles.push(Role.ADMIN, Role.UH); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); render(await Navbar()); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -93,7 +94,7 @@ describe('Navbar', () => { it('should render the departmental icon for a Departmental Account without Admin or Groupings links', async () => { testUser.roles.push(Role.DEPARTMENTAL, Role.UH); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); render(await Navbar()); expect(screen.getByRole('navigation')).toBeInTheDocument(); diff --git a/ui/tests/components/modal/api-error-modal.test.tsx b/ui/tests/components/modal/api-error-modal.test.tsx index 6d4fbdbd..6abfaa61 100644 --- a/ui/tests/components/modal/api-error-modal.test.tsx +++ b/ui/tests/components/modal/api-error-modal.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { fireEvent, render, screen } from '@testing-library/react'; import ApiErrorModal from '@/components/modal/api-error-modal'; diff --git a/ui/tests/components/modal/dynamic-modal.test.tsx b/ui/tests/components/modal/dynamic-modal.test.tsx index 2df95ced..d5ee3483 100644 --- a/ui/tests/components/modal/dynamic-modal.test.tsx +++ b/ui/tests/components/modal/dynamic-modal.test.tsx @@ -1,11 +1,11 @@ +import { describe, it, vi, expect } from 'vitest'; import { fireEvent, render, screen } from '@testing-library/react'; import DynamicModal from '@/components/modal/dynamic-modal'; import Link from 'next/link'; -import { useState } from 'react'; describe('DynamicModal', () => { it('should open an informational modal with test contents and no extra buttons', () => { - const onClose = jest.fn(); + const onClose = vi.fn(); render( ); @@ -18,7 +18,7 @@ describe('DynamicModal', () => { }); it('should open an informational modal with test contents and extra buttons', () => { - const onClose = jest.fn(); + const onClose = vi.fn(); render( { }); it('should close the modal upon clicking the OK button', () => { - const onClose = jest.fn(); + const onClose = vi.fn(); render( { }); it('should close the modal and route to the provided link (Feedback)', () => { - const onClose = jest.fn(); + const onClose = vi.fn(); render( { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); testUser.roles = []; }); it('should not open the timeout modal when the user is not logged-in', () => { render(); - act(() => jest.advanceTimersByTime(1000 * 60 * 25)); + act(() => vi.advanceTimersByTime(1000 * 60 * 25)); fireEvent.focus(document); expect(screen.queryByRole('alertdialog', { name: 'Inactivity Warning' })).not.toBeInTheDocument(); @@ -31,7 +32,7 @@ describe('TimeoutModal', () => { testUser.roles.push(Role.UH); render(); - act(() => jest.advanceTimersByTime(1000 * 60 * 24)); + act(() => vi.advanceTimersByTime(1000 * 60 * 24)); fireEvent.focus(document); expect(screen.queryByRole('alertdialog', { name: 'Inactivity Warning' })).not.toBeInTheDocument(); @@ -41,7 +42,7 @@ describe('TimeoutModal', () => { testUser.roles.push(Role.UH); render(); - act(() => jest.advanceTimersByTime(1000 * 60 * 25)); + act(() => vi.advanceTimersByTime(1000 * 60 * 25)); fireEvent.focus(document); expect(screen.getByRole('alertdialog', { name: 'Inactivity Warning' })).toBeInTheDocument(); @@ -51,14 +52,14 @@ describe('TimeoutModal', () => { testUser.roles.push(Role.UH); render(); - act(() => jest.advanceTimersByTime(1000 * 60 * 25)); + act(() => vi.advanceTimersByTime(1000 * 60 * 25)); fireEvent.focus(document); expect(screen.getByRole('alertdialog', { name: 'Inactivity Warning' })).toBeInTheDocument(); for (let i = 5; i >= 0; i--) { expect(screen.getByText(i + ':00.')).toBeInTheDocument(); - act(() => jest.advanceTimersByTime(1000 * 60)); + act(() => vi.advanceTimersByTime(1000 * 60)); fireEvent.focus(document); } }); @@ -67,26 +68,26 @@ describe('TimeoutModal', () => { testUser.roles.push(Role.UH); render(); - act(() => jest.advanceTimersByTime(1000 * 60 * 25)); + act(() => vi.advanceTimersByTime(1000 * 60 * 25)); fireEvent.focus(document); expect(screen.getByRole('alertdialog', { name: 'Inactivity Warning' })).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Stay logged in' })); expect(screen.queryByRole('alertdialog', { name: 'Inactivity Warning' })).not.toBeInTheDocument(); - act(() => jest.advanceTimersByTime(1000 * 60 * 24)); + act(() => vi.advanceTimersByTime(1000 * 60 * 24)); fireEvent.focus(document); expect(screen.queryByRole('alertdialog', { name: 'Inactivity Warning' })).not.toBeInTheDocument(); }); it('should logout when "Log off now" is pressed', () => { - const logoutSpy = jest.spyOn(NextCasClient, 'logout'); + const logoutSpy = vi.spyOn(NextCasClient, 'logout'); testUser.roles.push(Role.UH); render(); - act(() => jest.advanceTimersByTime(1000 * 60 * 25)); + act(() => vi.advanceTimersByTime(1000 * 60 * 25)); fireEvent.focus(document); expect(screen.getByRole('alertdialog', { name: 'Inactivity Warning' })).toBeInTheDocument(); @@ -95,12 +96,12 @@ describe('TimeoutModal', () => { }); it('should logout after 30 minutes of idle', () => { - const logoutSpy = jest.spyOn(NextCasClient, 'logout'); + const logoutSpy = vi.spyOn(NextCasClient, 'logout'); testUser.roles.push(Role.UH); render(); - act(() => jest.advanceTimersByTime(1000 * 60 * 30 + 1)); + act(() => vi.advanceTimersByTime(1000 * 60 * 30 + 1)); fireEvent.focus(document); expect(logoutSpy).toHaveBeenCalled(); diff --git a/ui/tests/components/table/groupings-table-skeleton.test.tsx b/ui/tests/components/table/groupings-table/groupings-table-skeleton.test.tsx similarity index 100% rename from ui/tests/components/table/groupings-table-skeleton.test.tsx rename to ui/tests/components/table/groupings-table/groupings-table-skeleton.test.tsx diff --git a/ui/tests/components/table/groupings-table/groupings-table.test.tsx b/ui/tests/components/table/groupings-table/groupings-table.test.tsx index 54babd53..a0ac854b 100644 --- a/ui/tests/components/table/groupings-table/groupings-table.test.tsx +++ b/ui/tests/components/table/groupings-table/groupings-table.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import GroupingsTable from '@/components/table/groupings-table/groupings-table'; import userEvent from '@testing-library/user-event'; diff --git a/ui/tests/components/table/groupings-table/table-element/grouping-description-cell.test.tsx b/ui/tests/components/table/groupings-table/table-element/grouping-description-cell.test.tsx index 684acf8a..1c021002 100644 --- a/ui/tests/components/table/groupings-table/table-element/grouping-description-cell.test.tsx +++ b/ui/tests/components/table/groupings-table/table-element/grouping-description-cell.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import GroupingDescriptionCell from '@/components/table/groupings-table/table-element/grouping-description-cell'; diff --git a/ui/tests/components/table/groupings-table/table-element/grouping-name-cell.test.tsx b/ui/tests/components/table/groupings-table/table-element/grouping-name-cell.test.tsx index c6533cd3..fafc9259 100644 --- a/ui/tests/components/table/groupings-table/table-element/grouping-name-cell.test.tsx +++ b/ui/tests/components/table/groupings-table/table-element/grouping-name-cell.test.tsx @@ -1,4 +1,5 @@ -import { render, screen} from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; import GroupingNameCell from '@/components/table/groupings-table/table-element/grouping-name-cell'; describe('GroupingNameCell', () => { @@ -8,6 +9,6 @@ describe('GroupingNameCell', () => { render(); expect(screen.getByText(name)).toBeInTheDocument(); expect(screen.getByTestId('edit-icon')).toBeInTheDocument(); - expect(screen.getByRole('link')).toHaveAttribute('href', `/groupings/${path}/all-members`) + expect(screen.getByRole('link')).toHaveAttribute('href', `/groupings/${path}/all-members`); }); }); diff --git a/ui/tests/components/table/groupings-table/table-element/grouping-path-cell.test.tsx b/ui/tests/components/table/groupings-table/table-element/grouping-path-cell.test.tsx index 83351534..ef17e0cb 100644 --- a/ui/tests/components/table/groupings-table/table-element/grouping-path-cell.test.tsx +++ b/ui/tests/components/table/groupings-table/table-element/grouping-path-cell.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, it, expect } from 'vitest'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import GroupingPathCell from '@/components/table/groupings-table/table-element/grouping-path-cell'; import userEvent from '@testing-library/user-event'; @@ -28,11 +29,11 @@ describe('GroupingPathCell', () => { it('shows tooltip correctly when copying to clipboard', async () => { Object.defineProperty(navigator, 'clipboard', { value: { - writeText: jest.fn(() => Promise.resolve()) + writeText: vi.fn(() => Promise.resolve()) }, writable: true }); - jest.spyOn(navigator.clipboard, 'writeText').mockImplementation(() => Promise.resolve()); + vi.spyOn(navigator.clipboard, 'writeText').mockImplementation(() => Promise.resolve()); render(); const clipboardButton = screen.getByRole('button'); diff --git a/ui/tests/components/table/table-element/column-settings.test.tsx b/ui/tests/components/table/table-element/column-settings.test.tsx index aaab01cf..da3ad5e6 100644 --- a/ui/tests/components/table/table-element/column-settings.test.tsx +++ b/ui/tests/components/table/table-element/column-settings.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, beforeEach, it, expect } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import ColumnSettings from '@/components/table/table-element/column-settings'; @@ -15,7 +16,7 @@ const mockColumns = [ getCanHide: () => true, getIsVisible: () => mockColumnVisibility['description'], columnDef: { header: 'description' }, - toggleVisibility: jest.fn((isVisible: boolean) => { + toggleVisibility: vi.fn((isVisible: boolean) => { mockColumnVisibility['description'] = isVisible; }) }, @@ -24,13 +25,13 @@ const mockColumns = [ getCanHide: () => true, getIsVisible: () => mockColumnVisibility['path'], columnDef: { header: 'path' }, - toggleVisibility: jest.fn((isVisible: boolean) => { + toggleVisibility: vi.fn((isVisible: boolean) => { mockColumnVisibility['path'] = isVisible; }) } ]; -const mockGetAllColumns = jest.fn().mockReturnValue(mockColumns); +const mockGetAllColumns = vi.fn().mockReturnValue(mockColumns); const mockTable = { getAllColumns: mockGetAllColumns @@ -38,7 +39,7 @@ const mockTable = { describe('ColumnSettings', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); mockColumnVisibility.description = true; mockColumnVisibility.path = false; }); diff --git a/ui/tests/components/table/table-element/global-filter.test.tsx b/ui/tests/components/table/table-element/global-filter.test.tsx index 5de9623d..c589a4e3 100644 --- a/ui/tests/components/table/table-element/global-filter.test.tsx +++ b/ui/tests/components/table/table-element/global-filter.test.tsx @@ -1,8 +1,9 @@ +import { vi, describe, it, expect } from 'vitest'; import { fireEvent, render, screen } from '@testing-library/react'; import GlobalFilter from '@/components/table/table-element/global-filter'; describe('GlobalFilter', () => { - const mockSetFilter = jest.fn(); + const mockSetFilter = vi.fn(); it('renders the input with correct placeholder and value', () => { render(); diff --git a/ui/tests/components/table/table-element/pagination-bar.test.tsx b/ui/tests/components/table/table-element/pagination-bar.test.tsx index 360c5039..f1f6a33f 100644 --- a/ui/tests/components/table/table-element/pagination-bar.test.tsx +++ b/ui/tests/components/table/table-element/pagination-bar.test.tsx @@ -1,16 +1,17 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; import { fireEvent, render, screen } from '@testing-library/react'; import { Table } from '@tanstack/table-core'; import { GroupingPath } from '@/lib/types'; import PaginationBar from '@/components/table/table-element/pagination-bar'; -const mockGetPageCount = jest.fn(); -const mockGetCanPreviousPage = jest.fn(); -const mockGetCanNextPage = jest.fn(); -const mockFirstPage = jest.fn(); -const mockPreviousPage = jest.fn(); -const mockSetPageIndex = jest.fn(); -const mockNextPage = jest.fn(); -const mockLastPage = jest.fn(); +const mockGetPageCount = vi.fn(); +const mockGetCanPreviousPage = vi.fn(); +const mockGetCanNextPage = vi.fn(); +const mockFirstPage = vi.fn(); +const mockPreviousPage = vi.fn(); +const mockSetPageIndex = vi.fn(); +const mockNextPage = vi.fn(); +const mockLastPage = vi.fn(); const mockTable = { getPageCount: mockGetPageCount, @@ -24,6 +25,10 @@ const mockTable = { } as unknown as Table; describe('Pagination', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it('renders pagination Bar correctly', () => { mockGetPageCount.mockReturnValue(6); mockGetCanPreviousPage.mockReturnValue(true); diff --git a/ui/tests/components/table/table-element/sort-arrow.test.tsx b/ui/tests/components/table/table-element/sort-arrow.test.tsx index 2e04532e..3f2f8f34 100644 --- a/ui/tests/components/table/table-element/sort-arrow.test.tsx +++ b/ui/tests/components/table/table-element/sort-arrow.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import SortArrow from '@/components/table/table-element/sort-arrow'; diff --git a/ui/tests/components/table/table-element/tooltip-on-truncate.test.tsx b/ui/tests/components/table/table-element/tooltip-on-truncate.test.tsx index 61d00449..60e868fc 100644 --- a/ui/tests/components/table/table-element/tooltip-on-truncate.test.tsx +++ b/ui/tests/components/table/table-element/tooltip-on-truncate.test.tsx @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import TooltipOnTruncate from '@/components/table/table-element/tooltip-on-truncate'; diff --git a/ui/tests/components/uh-groupings-info.test.tsx b/ui/tests/components/uh-groupings-info.test.tsx index 99b561d0..64d9f83f 100644 --- a/ui/tests/components/uh-groupings-info.test.tsx +++ b/ui/tests/components/uh-groupings-info.test.tsx @@ -1,3 +1,4 @@ +import { describe, expect, it } from 'vitest'; import { render, screen } from '@testing-library/react'; import UHGroupingsInfo from '@/components/uh-groupings-info'; diff --git a/ui/tests/lib/access/authorization.test.ts b/ui/tests/lib/access/authorization.test.ts index cbc7da16..03c568b1 100644 --- a/ui/tests/lib/access/authorization.test.ts +++ b/ui/tests/lib/access/authorization.test.ts @@ -1,6 +1,10 @@ +import { vi, describe, afterEach, it, expect } from 'vitest'; import { setRoles } from '@/lib/access/authorization'; import Role from '@/lib/access/role'; import User, { AnonymousUser } from '@/lib/access/user'; +import * as Fetchers from '@/lib/fetchers'; + +vi.mock('@/lib/fetchers'); const testUser: User = JSON.parse(process.env.TEST_USER_A as string); @@ -12,9 +16,8 @@ describe('authorization', () => { }); it('should set the ANONYMOUS role', async () => { - fetchMock - .mockResponseOnce(JSON.stringify(false)) // isOwner - .mockResponseOnce(JSON.stringify(false)); // isAdmin + vi.spyOn(Fetchers, 'isOwner').mockResolvedValue(false); + vi.spyOn(Fetchers, 'isAdmin').mockResolvedValue(false); await setRoles(AnonymousUser); expect(AnonymousUser.roles.includes(Role.ADMIN)).toBeFalsy(); @@ -24,9 +27,8 @@ describe('authorization', () => { }); it('should set the UH role', async () => { - fetchMock - .mockResponseOnce(JSON.stringify(false)) // isOwner - .mockResponseOnce(JSON.stringify(false)); // isAdmin + vi.spyOn(Fetchers, 'isOwner').mockResolvedValue(false); + vi.spyOn(Fetchers, 'isAdmin').mockResolvedValue(false); await setRoles(testUser); expect(testUser.roles.includes(Role.ADMIN)).toBeFalsy(); @@ -36,9 +38,8 @@ describe('authorization', () => { }); it('should set the UH and ADMIN roles', async () => { - fetchMock - .mockResponseOnce(JSON.stringify(false)) // isOwner - .mockResponseOnce(JSON.stringify(true)); // isAdmin + vi.spyOn(Fetchers, 'isOwner').mockResolvedValue(false); + vi.spyOn(Fetchers, 'isAdmin').mockResolvedValue(true); await setRoles(testUser); expect(testUser.roles.includes(Role.ADMIN)).toBeTruthy(); @@ -48,9 +49,8 @@ describe('authorization', () => { }); it('should set the UH and OWNER roles', async () => { - fetchMock - .mockResponseOnce(JSON.stringify(true)) // isOwner - .mockResponseOnce(JSON.stringify(false)); // isAdmin + vi.spyOn(Fetchers, 'isOwner').mockResolvedValue(true); + vi.spyOn(Fetchers, 'isAdmin').mockResolvedValue(false); await setRoles(testUser); expect(testUser.roles.includes(Role.ADMIN)).toBeFalsy(); @@ -60,9 +60,8 @@ describe('authorization', () => { }); it('should set the UH, ADMIN, and OWNER roles', async () => { - fetchMock - .mockResponseOnce(JSON.stringify(true)) // isOwner - .mockResponseOnce(JSON.stringify(true)); // isAdmin + vi.spyOn(Fetchers, 'isOwner').mockResolvedValue(true); + vi.spyOn(Fetchers, 'isAdmin').mockResolvedValue(true); await setRoles(testUser); expect(testUser.roles.includes(Role.ADMIN)).toBeTruthy(); @@ -70,15 +69,5 @@ describe('authorization', () => { expect(testUser.roles.includes(Role.OWNER)).toBeTruthy(); expect(testUser.roles.includes(Role.UH)).toBeTruthy(); }); - - it('should catch Groupings API errors', async () => { - fetchMock.mockAbort(); - - await setRoles(testUser); - expect(testUser.roles.includes(Role.ADMIN)).toBeFalsy(); - expect(testUser.roles.includes(Role.ANONYMOUS)).toBeTruthy(); - expect(testUser.roles.includes(Role.OWNER)).toBeFalsy(); - expect(testUser.roles.includes(Role.UH)).toBeTruthy(); - }); }); }); diff --git a/ui/tests/lib/access/role.test.ts b/ui/tests/lib/access/role.test.ts index d43245b0..3136c9b1 100644 --- a/ui/tests/lib/access/role.test.ts +++ b/ui/tests/lib/access/role.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import Role from '@/lib/access/role'; describe('role', () => { diff --git a/ui/tests/lib/access/user.test.ts b/ui/tests/lib/access/user.test.ts index fc9f3645..0f91930a 100644 --- a/ui/tests/lib/access/user.test.ts +++ b/ui/tests/lib/access/user.test.ts @@ -1,10 +1,13 @@ +import { vi, describe, it, expect } from 'vitest'; import Role from '@/lib/access/role'; import User, { AnonymousUser, getUser, loadUser } from '@/lib/access/user'; import * as NextCasClient from 'next-cas-client/app'; +import * as Fetchers from '@/lib/fetchers'; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client/app'); +vi.mock('next-cas-client/app'); +vi.mock('@/lib/fetchers'); describe('user', () => { describe('loadUser', () => { @@ -21,9 +24,8 @@ describe('user', () => { }; it('should return a User', async () => { - fetchMock - .mockResponseOnce(JSON.stringify(false)) // isOwner - .mockResponseOnce(JSON.stringify(false)); // isAdmin + vi.spyOn(Fetchers, 'isOwner').mockResolvedValue(false); + vi.spyOn(Fetchers, 'isAdmin').mockResolvedValue(false); expect(await loadUser(casUser)).toEqual(testUser); }); @@ -31,14 +33,14 @@ describe('user', () => { describe('getUser', () => { it('should call getCurrentUser', async () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); expect(await getUser()).toEqual(testUser); expect(NextCasClient.getCurrentUser).toHaveBeenCalled(); }); it('should return an AnonymousUser if getCurrentUser is null', async () => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(null); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(null); expect(await getUser()).toEqual(AnonymousUser); expect(NextCasClient.getCurrentUser).toHaveBeenCalled(); diff --git a/ui/tests/lib/actions.test.ts b/ui/tests/lib/actions.test.ts index b2f9e3eb..969e5137 100644 --- a/ui/tests/lib/actions.test.ts +++ b/ui/tests/lib/actions.test.ts @@ -1,3 +1,4 @@ +import { vi, describe, beforeAll, it, expect, beforeEach, afterEach } from 'vitest'; import { addAdmin, addExcludeMembers, @@ -29,7 +30,7 @@ import { Feedback } from '@/lib/types'; const baseUrl = process.env.NEXT_PUBLIC_API_2_1_BASE_URL as string; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client/app'); +vi.mock('next-cas-client/app'); describe('actions', () => { const currentUser = testUser; @@ -61,7 +62,7 @@ describe('actions', () => { }; beforeAll(() => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); }); describe('updateDescription', () => { @@ -116,11 +117,11 @@ describe('actions', () => { describe('addIncludeMembersAsync', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should make a PUT request at the correct endpoint', async () => { @@ -142,7 +143,7 @@ describe('actions', () => { .mockResponseOnce(JSON.stringify(mockAsyncCompletedResponse)); const res = addIncludeMembersAsync(uhIdentifiers, groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockAsyncCompletedResponse.result); }); @@ -154,7 +155,7 @@ describe('actions', () => { fetchMock.mockResponseOnce(JSON.stringify(0)).mockRejectOnce(() => Promise.reject(mockError)); res = addIncludeMembersAsync(uhIdentifiers, groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockError); }); }); @@ -185,11 +186,11 @@ describe('actions', () => { describe('addExcludeMembersAsync', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should make a PUT request at the correct endpoint', async () => { @@ -211,7 +212,7 @@ describe('actions', () => { .mockResponseOnce(JSON.stringify(mockAsyncCompletedResponse)); const res = addExcludeMembersAsync(uhIdentifiers, groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockAsyncCompletedResponse.result); }); @@ -223,7 +224,7 @@ describe('actions', () => { fetchMock.mockResponseOnce(JSON.stringify(0)).mockRejectOnce(() => Promise.reject(mockError)); res = addExcludeMembersAsync(uhIdentifiers, groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockError); }); }); @@ -417,11 +418,11 @@ describe('actions', () => { describe('memberAttributeResultsAsync', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should make a POST request at the correct endpoint', async () => { @@ -443,7 +444,7 @@ describe('actions', () => { .mockResponseOnce(JSON.stringify(mockAsyncCompletedResponse)); const res = memberAttributeResultsAsync(uhIdentifiers); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockAsyncCompletedResponse.result); }); @@ -455,7 +456,7 @@ describe('actions', () => { fetchMock.mockResponseOnce(JSON.stringify(0)).mockRejectOnce(() => Promise.reject(mockError)); res = memberAttributeResultsAsync(uhIdentifiers); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockError); }); }); @@ -537,11 +538,11 @@ describe('actions', () => { describe('resetIncludeGroupAsync', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should make a DELETE request at the correct endpoint', async () => { @@ -562,7 +563,7 @@ describe('actions', () => { .mockResponseOnce(JSON.stringify(mockAsyncCompletedResponse)); const res = resetIncludeGroupAsync(groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockAsyncCompletedResponse.result); }); @@ -574,7 +575,7 @@ describe('actions', () => { fetchMock.mockResponseOnce(JSON.stringify(0)).mockRejectOnce(() => Promise.reject(mockError)); res = resetIncludeGroupAsync(groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockError); }); }); @@ -604,11 +605,11 @@ describe('actions', () => { describe('resetExcludeGroupAsync', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should make a DELETE request at the correct endpoint', async () => { @@ -629,7 +630,7 @@ describe('actions', () => { .mockResponseOnce(JSON.stringify(mockAsyncCompletedResponse)); const res = resetExcludeGroupAsync(groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockAsyncCompletedResponse.result); }); @@ -641,7 +642,7 @@ describe('actions', () => { fetchMock.mockResponseOnce(JSON.stringify(0)).mockRejectOnce(() => Promise.reject(mockError)); res = resetExcludeGroupAsync(groupingPath); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockError); }); }); diff --git a/ui/tests/lib/fetchers.test.ts b/ui/tests/lib/fetchers.test.ts index 2640c2ff..cd15810a 100644 --- a/ui/tests/lib/fetchers.test.ts +++ b/ui/tests/lib/fetchers.test.ts @@ -9,6 +9,8 @@ import { groupingOptAttributes, groupingOwners, groupingSyncDest, + isAdmin, + isOwner, isSoleOwner, managePersonResults, membershipResults, @@ -18,12 +20,13 @@ import { } from '@/lib/fetchers'; import * as NextCasClient from 'next-cas-client/app'; import * as Actions from '@/lib/actions'; +import { vi, describe, beforeAll, it, expect, beforeEach, afterEach } from 'vitest'; const baseUrl = process.env.NEXT_PUBLIC_API_2_1_BASE_URL as string; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client/app'); -jest.mock('@/lib/actions'); +vi.mock('next-cas-client/app'); +vi.mock('@/lib/actions'); describe('fetchers', () => { const currentUser = testUser; @@ -45,8 +48,8 @@ describe('fetchers', () => { }; beforeAll(() => { - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); - jest.spyOn(Actions, 'sendStackTrace'); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(Actions, 'sendStackTrace'); }); describe('getAllGroupings', () => { @@ -94,11 +97,11 @@ describe('fetchers', () => { const isAscending = true; beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); it('should make a POST request at the correct endpoint', async () => { @@ -126,7 +129,7 @@ describe('fetchers', () => { .mockResponseOnce(JSON.stringify(mockResponse), { status: 500 }) .mockResponseOnce(JSON.stringify(mockResponse)); let res = ownedGrouping(groupPaths, page, size, sortString, isAscending); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockResponse); fetchMock @@ -134,7 +137,7 @@ describe('fetchers', () => { .mockResponseOnce(JSON.stringify(mockResponse), { status: 500 }) .mockResponseOnce(JSON.stringify(mockResponse)); res = ownedGrouping(groupPaths, page, size, sortString, isAscending); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockResponse); fetchMock @@ -143,19 +146,19 @@ describe('fetchers', () => { .mockResponseOnce(JSON.stringify(mockResponse), { status: 500 }) .mockResponseOnce(JSON.stringify(mockResponse)); res = ownedGrouping(groupPaths, page, size, sortString, isAscending); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockResponse); }); it('should handle the error response', async () => { fetchMock.mockResponse(JSON.stringify(mockError), { status: 500 }); let res = ownedGrouping(groupPaths, page, size, sortString, isAscending); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockError); fetchMock.mockReject(() => Promise.reject(mockError)); res = ownedGrouping(groupPaths, page, size, sortString, isAscending); - await jest.advanceTimersByTimeAsync(5000); + await vi.advanceTimersByTimeAsync(5000); expect(await res).toEqual(mockError); }); }); @@ -387,4 +390,42 @@ describe('fetchers', () => { expect(await isSoleOwner(uhIdentifier, groupingPath)).toEqual(mockError); }); }); + + describe('isOwner', () => { + it('should make a GET request at the correct endpoint', async () => { + await isOwner(uhIdentifier); + expect(fetch).toHaveBeenCalledWith(`${baseUrl}/owners`, { + headers: { current_user: currentUser.uid } + }); + }); + + it('should handle the successful response', async () => { + fetchMock.mockResponse(JSON.stringify(mockResponse)); + expect(await isOwner(uhIdentifier)).toEqual(mockResponse); + }); + + it('should handle the error response', async () => { + fetchMock.mockReject(() => Promise.reject(mockError)); + expect(await isOwner(uhIdentifier)).toEqual(mockError); + }); + }); + + describe('isAdmin', () => { + it('should make a GET request at the correct endpoint', async () => { + await isAdmin(uhIdentifier); + expect(fetch).toHaveBeenCalledWith(`${baseUrl}/admins`, { + headers: { current_user: currentUser.uid } + }); + }); + + it('should handle the successful response', async () => { + fetchMock.mockResponse(JSON.stringify(mockResponse)); + expect(await isAdmin(uhIdentifier)).toEqual(mockResponse); + }); + + it('should handle the error response', async () => { + fetchMock.mockReject(() => Promise.reject(mockError)); + expect(await isAdmin(uhIdentifier)).toEqual(mockError); + }); + }); }); diff --git a/ui/tests/middleware.test.tsx b/ui/tests/middleware.test.tsx index 9c5830c9..462cd914 100644 --- a/ui/tests/middleware.test.tsx +++ b/ui/tests/middleware.test.tsx @@ -1,3 +1,4 @@ +import { vi, describe, it, expect, afterEach } from 'vitest'; import { config, middleware } from '@/middleware'; import { NextRequest, NextResponse } from 'next/server'; import User from '@/lib/access/user'; @@ -7,7 +8,7 @@ import * as NextCasClient from 'next-cas-client/app'; const baseUrl = process.env.NEXT_PUBLIC_BASE_URL as string; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -jest.mock('next-cas-client/app'); +vi.mock('next-cas-client/app'); describe('middleware', () => { it('should define the config with a list of matching paths', () => { @@ -17,13 +18,13 @@ describe('middleware', () => { describe('User is logged-out', () => { it('should redirect the user', async () => { - const redirectSpy = jest.spyOn(NextResponse, 'redirect'); + const redirectSpy = vi.spyOn(NextResponse, 'redirect'); for (const matcher of config.matcher) { const url = baseUrl + matcher; const req = new NextRequest(new Request(url)); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(null); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(null); await middleware(req); expect(redirectSpy).toHaveBeenCalledWith(new URL(baseUrl)); @@ -39,13 +40,13 @@ describe('middleware', () => { it('should redirect the average user at /admin and /groupings', async () => { testUser.roles.push(Role.UH); - const redirectSpy = jest.spyOn(NextResponse, 'redirect'); + const redirectSpy = vi.spyOn(NextResponse, 'redirect'); for (const matcher of config.matcher) { const url = baseUrl + matcher; const req = new NextRequest(new Request(url)); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); await middleware(req); if (matcher === '/admin' || matcher === '/groupings') { @@ -60,13 +61,13 @@ describe('middleware', () => { it('should redirect an owner of a grouping at /admin', async () => { testUser.roles.push(Role.OWNER, Role.UH); - const redirectSpy = jest.spyOn(NextResponse, 'redirect'); + const redirectSpy = vi.spyOn(NextResponse, 'redirect'); for (const matcher of config.matcher) { const url = baseUrl + matcher; const req = new NextRequest(new Request(url)); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); await middleware(req); if (matcher === '/admin') { @@ -81,13 +82,13 @@ describe('middleware', () => { it('should not redirect an admin', async () => { testUser.roles.push(Role.ADMIN); - const redirectSpy = jest.spyOn(NextResponse, 'redirect'); + const redirectSpy = vi.spyOn(NextResponse, 'redirect'); for (const matcher of config.matcher) { const url = baseUrl + matcher; const req = new NextRequest(new Request(url)); - jest.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); + vi.spyOn(NextCasClient, 'getCurrentUser').mockResolvedValue(testUser); await middleware(req); expect(redirectSpy).not.toHaveBeenCalled(); diff --git a/ui/tests/setup-jest.ts b/ui/tests/setup-jest.ts deleted file mode 100644 index cba98a72..00000000 --- a/ui/tests/setup-jest.ts +++ /dev/null @@ -1,14 +0,0 @@ -import '@testing-library/jest-dom'; -import { loadEnvConfig } from '@next/env'; -import { enableFetchMocks } from 'jest-fetch-mock'; -import User from '@/lib/access/user'; - -enableFetchMocks(); -loadEnvConfig(process.cwd()); - -export const createMockSession = (user: User | undefined) => ({ - user, - destroy: jest.fn(), - save: jest.fn(), - updateConfig: jest.fn() -}); diff --git a/ui/tests/vitest.setup.ts b/ui/tests/vitest.setup.ts new file mode 100644 index 00000000..89253ec4 --- /dev/null +++ b/ui/tests/vitest.setup.ts @@ -0,0 +1,15 @@ +import '@testing-library/jest-dom/vitest'; +import { loadEnvConfig } from '@next/env'; +import User from '@/lib/access/user'; +import { vi } from 'vitest'; +import createFetchMock from 'vitest-fetch-mock'; + +createFetchMock(vi).enableMocks(); +loadEnvConfig(process.cwd()); + +export const createMockSession = (user: User | undefined) => ({ + user, + destroy: vi.fn(), + save: vi.fn(), + updateConfig: vi.fn() +}); diff --git a/ui/vitest.config.mts b/ui/vitest.config.mts new file mode 100644 index 00000000..98978604 --- /dev/null +++ b/ui/vitest.config.mts @@ -0,0 +1,23 @@ +import { coverageConfigDefaults, defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [tsconfigPaths(), react()], + test: { + environment: 'jsdom', + globals: true, + setupFiles: './tests/vitest.setup.ts', + coverage: { + provider: 'istanbul', + enabled: true, + include: ['**/src/**/*.ts*'], + exclude: [ + ...coverageConfigDefaults.exclude, + '**/src/components/ui' // Ignore shadcn/ui components + ], + reporter: ['text', 'json-summary', 'html'], + reportsDirectory: './coverage' + } + } +});