diff --git a/frontend/actions/review.ts b/frontend/actions/review.ts index 91db44849..fe7f1372a 100644 --- a/frontend/actions/review.ts +++ b/frontend/actions/review.ts @@ -2,8 +2,9 @@ import qs from 'querystring' import { ResponseTypeKeys } from 'src/common/ReviewWithComment' import useSWR from 'swr' import { ModelInterface, ReleaseInterface } from 'types/types' +import { ReviewRequestInterface } from 'types/v2/types' -import { AccessRequestInterface, ReviewRequestInterface } from '../types/interfaces' +import { AccessRequestInterface } from '../types/interfaces' import { ErrorInfo, fetcher } from '../utils/fetcher' export function useGetReviewRequestsForUser() { @@ -54,10 +55,10 @@ export function useGetReviewRequestsForModel({ modelId, semver, accessRequestId ) return { - mutateReviews: mutate, reviews: data ? data.reviews : [], isReviewsLoading: !error && !data, isReviewsError: error, + mutateReviews: mutate, } } diff --git a/frontend/pages/_app.tsx b/frontend/pages/_app.tsx index 24b2bd5cd..1ca4d2c27 100644 --- a/frontend/pages/_app.tsx +++ b/frontend/pages/_app.tsx @@ -16,8 +16,8 @@ import createEmotionCache from 'utils/createEmotionCache' import ThemeModeContext from '../src/contexts/themeModeContext' import UnsavedChangesContext from '../src/contexts/unsavedChangesContext' -import useThemeMode from '../utils/hooks/useThemeMode' -import useUnsavedChanges from '../utils/hooks/useUnsavedChanges' +import useThemeMode from '../src/hooks/useThemeMode' +import useUnsavedChanges from '../src/hooks/useUnsavedChanges' // Client-side cache, shared for the whole session of the user in the browser. const clientSideEmotionCache = createEmotionCache() diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index b1e205344..7481a6571 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -22,8 +22,8 @@ import React, { ChangeEvent, Fragment, useCallback, useState } from 'react' import ChipSelector from 'src/common/ChipSelector' import EmptyBlob from 'src/common/EmptyBlob' import MultipleErrorWrapper from 'src/errors/MultipleErrorWrapper' +import useDebounce from 'src/hooks/useDebounce' import Wrapper from 'src/Wrapper' -import useDebounce from 'utils/hooks/useDebounce' interface KeyAndLabel { key: string diff --git a/frontend/src/MessageAlert.tsx b/frontend/src/MessageAlert.tsx index b89a544d6..4f00c7b83 100644 --- a/frontend/src/MessageAlert.tsx +++ b/frontend/src/MessageAlert.tsx @@ -6,7 +6,7 @@ import Alert, { AlertProps } from '@mui/material/Alert' import Stack from '@mui/material/Stack' import Link from 'next/link' import { useEffect, useRef, useState } from 'react' -import useNotification from 'utils/hooks/useNotification' +import useNotification from 'src/hooks/useNotification' type PartialMessageAlertProps = | { @@ -21,9 +21,16 @@ type PartialMessageAlertProps = type MessageAlertProps = { message?: string severity?: AlertProps['severity'] + 'data-test'?: string } & PartialMessageAlertProps -export default function MessageAlert({ message = '', severity, linkText, href }: MessageAlertProps) { +export default function MessageAlert({ + message = '', + severity, + linkText, + href, + 'data-test': dataTest, +}: MessageAlertProps) { const alertRef = useRef(null) const [showContactMessage, setShowContactMessage] = useState(false) const sendNotification = useNotification() @@ -48,7 +55,7 @@ export default function MessageAlert({ message = '', severity, linkText, href }: if (!message) return null return ( - + {message} diff --git a/frontend/src/MuiForms/CustomTextInput.tsx b/frontend/src/MuiForms/CustomTextInput.tsx index 9bfae2c83..aa1e614d2 100644 --- a/frontend/src/MuiForms/CustomTextInput.tsx +++ b/frontend/src/MuiForms/CustomTextInput.tsx @@ -62,6 +62,7 @@ export default function CustomTextInput(props: CustomTextInputProps) { InputProps={{ ...props.InputProps, ...(!formContext.editMode && { disableUnderline: true }), + 'data-test': id, }} /> diff --git a/frontend/src/common/ConfirmationDialogue.spec.tsx b/frontend/src/common/ConfirmationDialogue.spec.tsx deleted file mode 100644 index 765588d89..000000000 --- a/frontend/src/common/ConfirmationDialogue.spec.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react' -import { describe, expect, it } from 'vitest' - -import { doNothing } from '../../utils/test/testUtils' -import ConfirmationDialogue from './ConfirmationDialogue' - -describe('ConfirmationDialogue', () => { - const testTitle = 'Test dialogue title' - const testError = 'Test dialogue text' - - it('renders a ConfirmationDialogue component', async () => { - render( - , - ) - - await waitFor(async () => { - expect(await screen.findByText(testTitle)).toBeDefined() - expect(await screen.findByText(testError)).toBeDefined() - }) - }) - - it('does not render a ConfirmationDialogue component', async () => { - render( - , - ) - - await waitFor(async () => { - expect(await screen.queryByText(testTitle)).toBeNull() - expect(await screen.queryByText(testError)).toBeNull() - }) - }) -}) diff --git a/frontend/src/common/EmptyBlob.spec.tsx b/frontend/src/common/EmptyBlob.spec.tsx deleted file mode 100644 index b57baf4aa..000000000 --- a/frontend/src/common/EmptyBlob.spec.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react' -import { describe, expect, it } from 'vitest' - -import EmptyBlob from './EmptyBlob' - -describe('EmptyBlob', () => { - it('renders an EmptyBlob component with the text of string', async () => { - render() - - await waitFor(async () => { - expect(await screen.findByText('string')).not.toBeUndefined() - }) - }) -}) diff --git a/frontend/src/common/EmptyBlob.tsx b/frontend/src/common/EmptyBlob.tsx index cba426fef..11f5764ca 100644 --- a/frontend/src/common/EmptyBlob.tsx +++ b/frontend/src/common/EmptyBlob.tsx @@ -9,7 +9,7 @@ type EmptyBlobProps = { export default function EmptyBlob({ text }: EmptyBlobProps) { return ( - Empty blob + Empty blob {text} ) diff --git a/frontend/src/common/ExpandableButton.spec.tsx b/frontend/src/common/ExpandableButton.spec.tsx deleted file mode 100644 index a3b47b872..000000000 --- a/frontend/src/common/ExpandableButton.spec.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import Add from '@mui/icons-material/Add' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import { describe, expect } from 'vitest' - -import { doNothing } from '../../utils/test/testUtils' -import ExpandableButton from './ExpandableButton' - -describe('ExpandableButton', () => { - it('renders an ExpandableButton component where you cannot view the label text', async () => { - render(} onClick={doNothing} />) - - await waitFor(async () => { - const expandableButton = screen.queryByTestId('expandableButton') - expect(expandableButton).not.toBeUndefined() - expect(screen.queryByText('Click me')).toBeNull() - }) - }) - - it('renders an ExpandableButton component that shows the label on hover', async () => { - render(} onClick={doNothing} />) - - fireEvent.mouseEnter(await screen.findByTestId('expandableButton')) - - await waitFor(async () => { - const expandableButton = screen.queryByTestId('expandableButton') - expect(expandableButton).not.toBeUndefined() - expect(await screen.findByText('Click me')).not.toBeUndefined() - }) - }) -}) diff --git a/frontend/src/common/ExpandableButton.tsx b/frontend/src/common/ExpandableButton.tsx index fa23d7bce..2b7955db3 100644 --- a/frontend/src/common/ExpandableButton.tsx +++ b/frontend/src/common/ExpandableButton.tsx @@ -60,9 +60,11 @@ export default function ExpandableButton({ label, icon, onClick, ariaLabel, heig > {icon} {hover ? ( - {label} + + {label} + ) : ( - + )} diff --git a/frontend/src/common/MarkdownDisplay.tsx b/frontend/src/common/MarkdownDisplay.tsx index d23d12abc..799e9acaf 100644 --- a/frontend/src/common/MarkdownDisplay.tsx +++ b/frontend/src/common/MarkdownDisplay.tsx @@ -5,7 +5,7 @@ import Typography from '@mui/material/Typography' import ReactMarkdown from 'markdown-to-jsx' import { useMemo } from 'react' -type MarkdownDisplayProps = { +export type MarkdownDisplayProps = { children: string } diff --git a/frontend/src/common/ReviewWithComment.tsx b/frontend/src/common/ReviewWithComment.tsx index 0f39244ae..b849e9c54 100644 --- a/frontend/src/common/ReviewWithComment.tsx +++ b/frontend/src/common/ReviewWithComment.tsx @@ -12,10 +12,11 @@ import { } from '@mui/material' import { useTheme } from '@mui/material/styles' import { SyntheticEvent, useMemo, useState } from 'react' +import { ReviewRequestInterface } from 'types/v2/types' import { useGetModelRoles } from '../../actions/model' import { useGetReviewRequestsForModel } from '../../actions/review' -import { AccessRequestInterface, ReviewRequestInterface } from '../../types/interfaces' +import { AccessRequestInterface } from '../../types/interfaces' import { ReleaseInterface } from '../../types/types' import { getRoleDisplay } from '../../utils/roles' import MessageAlert from '../MessageAlert' diff --git a/frontend/src/common/UserAvatar.tsx b/frontend/src/common/UserAvatar.tsx index 4d71b7fde..1c2efad0a 100644 --- a/frontend/src/common/UserAvatar.tsx +++ b/frontend/src/common/UserAvatar.tsx @@ -56,6 +56,7 @@ export default function UserAvatar({ width: avatarSize, fontSize, }} + data-test='userAvatar' > {entity.id.charAt(0).toUpperCase()} diff --git a/frontend/src/common/UserDisplay.tsx b/frontend/src/common/UserDisplay.tsx index c1e2d15d1..199982068 100644 --- a/frontend/src/common/UserDisplay.tsx +++ b/frontend/src/common/UserDisplay.tsx @@ -14,7 +14,7 @@ export interface UserInformation { email?: string } -type UserDisplayProps = { +export type UserDisplayProps = { dn: string hidePopover?: boolean } diff --git a/frontend/src/contexts/themeModeContext.ts b/frontend/src/contexts/themeModeContext.ts index 1035a625d..a08f4b914 100644 --- a/frontend/src/contexts/themeModeContext.ts +++ b/frontend/src/contexts/themeModeContext.ts @@ -1,6 +1,6 @@ import { createContext } from 'react' -import { ThemeModeHook } from '../../utils/hooks/useThemeMode' +import { ThemeModeHook } from '../hooks/useThemeMode' import { lightTheme } from '../theme' const ThemeModeContext = createContext({ diff --git a/frontend/src/contexts/unsavedChangesContext.ts b/frontend/src/contexts/unsavedChangesContext.ts index 139cf115f..0aa25a721 100644 --- a/frontend/src/contexts/unsavedChangesContext.ts +++ b/frontend/src/contexts/unsavedChangesContext.ts @@ -1,6 +1,6 @@ import { createContext } from 'react' -import { UnsavedChangesHook } from '../../utils/hooks/useUnsavedChanges' +import { UnsavedChangesHook } from '../hooks/useUnsavedChanges' const UnsavedChangesContext = createContext({ unsavedChanges: false, diff --git a/frontend/src/errors/ErrorWrapper.spec.tsx b/frontend/src/errors/ErrorWrapper.spec.tsx deleted file mode 100644 index b83f9dd39..000000000 --- a/frontend/src/errors/ErrorWrapper.spec.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react' -import mockRouter from 'next-router-mock' -import { WrapperProps } from 'src/Wrapper' -import { beforeAll, describe, expect, it, vi } from 'vitest' - -import ErrorWrapper from './ErrorWrapper' - -vi.mock('src/Wrapper.tsx', () => ({ - default: ({ children, ..._other }: WrapperProps) => <>{children}, -})) - -describe('ErrorWrapper', () => { - beforeAll(() => { - mockRouter.push('/') - }) - it('renders an ErrorWrapper component', async () => { - render() - - await waitFor(async () => { - expect(await screen.findByText('error!')).not.toBeUndefined() - }) - }) -}) diff --git a/frontend/src/errors/ErrorWrapper.tsx b/frontend/src/errors/ErrorWrapper.tsx index 95391f2e2..901ec1934 100644 --- a/frontend/src/errors/ErrorWrapper.tsx +++ b/frontend/src/errors/ErrorWrapper.tsx @@ -9,7 +9,11 @@ type ErrorWrapperProps = { export default function ErrorWrapper({ message }: ErrorWrapperProps) { return ( - + ) } diff --git a/frontend/src/errors/MultipleErrorWrapper.spec.tsx b/frontend/src/errors/MultipleErrorWrapper.spec.tsx deleted file mode 100644 index ad4339a2b..000000000 --- a/frontend/src/errors/MultipleErrorWrapper.spec.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react' -import mockRouter from 'next-router-mock' -import { WrapperProps } from 'src/Wrapper' -import { describe, expect, it, vi } from 'vitest' - -import MultipleErrorWrapper from './MultipleErrorWrapper' - -vi.mock('src/Wrapper.tsx', () => ({ - default: ({ children, ..._other }: WrapperProps) => <>{children}, -})) - -describe('MultipleErrorWrapper', () => { - const error1 = {} - const error2 = {} - - const errorWrapper = MultipleErrorWrapper(`There was an error!`, { - error1, - error2, - }) - - it('renders an MultipleErrorWrapper component', async () => { - mockRouter.push('/') - if (errorWrapper) { - render(errorWrapper) - } - - await waitFor(async () => { - expect(await screen.findByText('There was an error!')).not.toBeUndefined() - }) - }) -}) diff --git a/frontend/utils/hooks/useDebounce.ts b/frontend/src/hooks/useDebounce.ts similarity index 100% rename from frontend/utils/hooks/useDebounce.ts rename to frontend/src/hooks/useDebounce.ts diff --git a/frontend/utils/hooks/useNotification.tsx b/frontend/src/hooks/useNotification.tsx similarity index 100% rename from frontend/utils/hooks/useNotification.tsx rename to frontend/src/hooks/useNotification.tsx diff --git a/frontend/utils/hooks/useThemeMode.ts b/frontend/src/hooks/useThemeMode.ts similarity index 92% rename from frontend/utils/hooks/useThemeMode.ts rename to frontend/src/hooks/useThemeMode.ts index 20fe9dd23..8e068b58b 100644 --- a/frontend/utils/hooks/useThemeMode.ts +++ b/frontend/src/hooks/useThemeMode.ts @@ -1,7 +1,7 @@ import { Theme } from '@mui/material/styles' import { ChangeEvent, useCallback, useState } from 'react' -import { darkTheme, lightTheme } from '../../src/theme' +import { darkTheme, lightTheme } from '../theme' export type ThemeModeHook = { theme: Theme diff --git a/frontend/utils/hooks/useUnsavedChanges.ts b/frontend/src/hooks/useUnsavedChanges.ts similarity index 100% rename from frontend/utils/hooks/useUnsavedChanges.ts rename to frontend/src/hooks/useUnsavedChanges.ts diff --git a/frontend/src/model/accessRequests/AccessRequestDisplay.tsx b/frontend/src/model/accessRequests/AccessRequestDisplay.tsx index 3bad32f24..e1e71a219 100644 --- a/frontend/src/model/accessRequests/AccessRequestDisplay.tsx +++ b/frontend/src/model/accessRequests/AccessRequestDisplay.tsx @@ -1,11 +1,13 @@ import CommentIcon from '@mui/icons-material/ChatBubble' -import { Card, Divider, Grid, Stack, Tooltip, Typography } from '@mui/material' +import { Card, Divider, Grid, Stack, Typography } from '@mui/material' import { groupBy } from 'lodash-es' import { useEffect, useState } from 'react' import UserDisplay from 'src/common/UserDisplay' import Link from 'src/Link' -import { AccessRequestInterface, ReviewRequestInterface, ReviewResponse } from 'types/interfaces' +import { AccessRequestInterface } from 'types/interfaces' +import { ReviewRequestInterface, ReviewResponse } from 'types/v2/types' import { formatDateString, sortByCreatedAtAscending } from 'utils/dateUtils' +import { plural } from 'utils/stringUtils' import { useGetReviewRequestsForModel } from '../../../actions/review' import Loading from '../../common/Loading' @@ -68,7 +70,7 @@ export default function AccessRequestDisplay({ accessRequest }: AccessRequestDis {accessRequest.metadata.overview.endDate && ( End Date: - + {` ${formatDateString(accessRequest.metadata.overview.endDate)}`} @@ -104,12 +106,12 @@ export default function AccessRequestDisplay({ accessRequest }: AccessRequestDis {accessRequest.comments.length > 0 && ( - - - - {accessRequest.comments.length} - - + + + + {plural(accessRequest.comments.length, 'comment')} + + )} diff --git a/frontend/src/model/common/ValidationErrorIcon.tsx b/frontend/src/model/common/ValidationErrorIcon.tsx index 2c8ccb257..1ae26d3b6 100644 --- a/frontend/src/model/common/ValidationErrorIcon.tsx +++ b/frontend/src/model/common/ValidationErrorIcon.tsx @@ -11,7 +11,7 @@ export default function ValidationErrorIcon({ step }: ValidationErrorIconProps) const theme = useTheme() return !step.isComplete(step) ? ( - + ) : ( diff --git a/frontend/src/model/registry/CodeLine.tsx b/frontend/src/model/registry/CodeLine.tsx index ed09d88f0..ad6f67b06 100644 --- a/frontend/src/model/registry/CodeLine.tsx +++ b/frontend/src/model/registry/CodeLine.tsx @@ -3,7 +3,7 @@ import CodeIcon from '@mui/icons-material/Code' import { Box, IconButton, Stack, Tooltip, Typography } from '@mui/material' import { useTheme } from '@mui/material/styles' import { ReactElement } from 'react' -import useNotification from 'utils/hooks/useNotification' +import useNotification from 'src/hooks/useNotification' interface CodeLineProps { line: string diff --git a/frontend/src/model/releases/ReleaseDisplay.tsx b/frontend/src/model/releases/ReleaseDisplay.tsx index 2e00755f6..139ee1da1 100644 --- a/frontend/src/model/releases/ReleaseDisplay.tsx +++ b/frontend/src/model/releases/ReleaseDisplay.tsx @@ -6,7 +6,7 @@ import { useRouter } from 'next/router' import prettyBytes from 'pretty-bytes' import { useEffect, useState } from 'react' import UserDisplay from 'src/common/UserDisplay' -import { ReviewRequestInterface, ReviewResponse } from 'types/interfaces' +import { ReviewRequestInterface, ReviewResponse } from 'types/v2/types' import { formatDateString, sortByCreatedAtAscending } from 'utils/dateUtils' import { useGetReviewRequestsForModel } from '../../../actions/review' diff --git a/frontend/src/model/reviews/ApprovalsDisplay.tsx b/frontend/src/model/reviews/ApprovalsDisplay.tsx index de632a4d1..bd3f3a5ee 100644 --- a/frontend/src/model/reviews/ApprovalsDisplay.tsx +++ b/frontend/src/model/reviews/ApprovalsDisplay.tsx @@ -5,7 +5,7 @@ import { useGetModelRoles } from 'actions/model' import { useMemo } from 'react' import Loading from 'src/common/Loading' import MessageAlert from 'src/MessageAlert' -import { ReviewResponseWithRole } from 'types/interfaces' +import { ReviewResponseWithRole } from 'types/v2/types' import { plural } from 'utils/stringUtils' interface ApprovalsDisplayProps { diff --git a/frontend/src/model/reviews/ReviewBanner.tsx b/frontend/src/model/reviews/ReviewBanner.tsx index c8756c925..d2be03c3c 100644 --- a/frontend/src/model/reviews/ReviewBanner.tsx +++ b/frontend/src/model/reviews/ReviewBanner.tsx @@ -16,7 +16,7 @@ import { postReviewResponse } from '../../../actions/review' import { ReleaseInterface } from '../../../types/types' import ReviewWithComment, { ResponseTypeKeys } from '../../common/ReviewWithComment' -type ReviewBannerProps = +export type ReviewBannerProps = | { release: ReleaseInterface accessRequest?: never diff --git a/frontend/src/model/reviews/ReviewDisplay.tsx b/frontend/src/model/reviews/ReviewDisplay.tsx index 3c2d39947..5c8f1f15e 100644 --- a/frontend/src/model/reviews/ReviewDisplay.tsx +++ b/frontend/src/model/reviews/ReviewDisplay.tsx @@ -2,11 +2,10 @@ import HourglassEmpty from '@mui/icons-material/HourglassEmpty' import { Stack, Tooltip, Typography } from '@mui/material' import { useEffect, useState } from 'react' import ApprovalsDisplay from 'src/model/reviews/ApprovalsDisplay' +import { ReviewRequestInterface, ReviewResponseWithRole } from 'types/v2/types' import { plural } from 'utils/stringUtils' -import { ReviewRequestInterface, ReviewResponseWithRole } from '../../../types/interfaces' - -interface ReviewDisplayProps { +export interface ReviewDisplayProps { reviews: ReviewRequestInterface[] } diff --git a/frontend/src/model/settings/AccessRequestSettings.tsx b/frontend/src/model/settings/AccessRequestSettings.tsx index ab13357d2..43b349051 100644 --- a/frontend/src/model/settings/AccessRequestSettings.tsx +++ b/frontend/src/model/settings/AccessRequestSettings.tsx @@ -5,7 +5,7 @@ import { useState } from 'react' import { ModelInterface } from '../../../types/v2/types' import { getErrorMessage } from '../../../utils/fetcher' -import useNotification from '../../../utils/hooks/useNotification' +import useNotification from '../../hooks/useNotification' import MessageAlert from '../../MessageAlert' type ModelAccessProps = { diff --git a/frontend/src/model/settings/ModelAccess.tsx b/frontend/src/model/settings/ModelAccess.tsx index 12c15ee54..6b88835c2 100644 --- a/frontend/src/model/settings/ModelAccess.tsx +++ b/frontend/src/model/settings/ModelAccess.tsx @@ -17,7 +17,7 @@ import { patchModel, useGetModel } from '../../../actions/model' import { useListUsers } from '../../../actions/user' import { CollaboratorEntry, EntityObject, ModelInterface } from '../../../types/v2/types' import { getErrorMessage } from '../../../utils/fetcher' -import useNotification from '../../../utils/hooks/useNotification' +import useNotification from '../../hooks/useNotification' import MessageAlert from '../../MessageAlert' import EntityItem from './EntityItem' diff --git a/frontend/src/model/settings/ModelDetails.tsx b/frontend/src/model/settings/ModelDetails.tsx index cebae154a..7793d66a4 100644 --- a/frontend/src/model/settings/ModelDetails.tsx +++ b/frontend/src/model/settings/ModelDetails.tsx @@ -11,7 +11,7 @@ import { TeamInterface } from 'types/interfaces' import { patchModel } from '../../../actions/model' import { ModelForm, ModelInterface } from '../../../types/v2/types' import { getErrorMessage } from '../../../utils/fetcher' -import useNotification from '../../../utils/hooks/useNotification' +import useNotification from '../../hooks/useNotification' import MessageAlert from '../../MessageAlert' type ModelAccessProps = { diff --git a/frontend/src/reviews/ReviewComments.tsx b/frontend/src/reviews/ReviewComments.tsx index 5c93bee79..8475441a0 100644 --- a/frontend/src/reviews/ReviewComments.tsx +++ b/frontend/src/reviews/ReviewComments.tsx @@ -9,8 +9,9 @@ import RichTextEditor from 'src/common/RichTextEditor' import MessageAlert from 'src/MessageAlert' import ReviewCommentDisplay from 'src/reviews/ReviewCommentDisplay' import ReviewDecisionDisplay from 'src/reviews/ReviewDecisionDisplay' -import { AccessRequestInterface, ReviewResponse } from 'types/interfaces' +import { AccessRequestInterface } from 'types/interfaces' import { isReviewResponse, ReleaseInterface, ReviewComment, ReviewResponseKind } from 'types/types' +import { ReviewResponse } from 'types/v2/types' import { sortByCreatedAtAscending } from 'utils/dateUtils' import { getErrorMessage } from 'utils/fetcher' diff --git a/frontend/src/reviews/ReviewDecisionDisplay.tsx b/frontend/src/reviews/ReviewDecisionDisplay.tsx index db71d0fae..1ef8176fb 100644 --- a/frontend/src/reviews/ReviewDecisionDisplay.tsx +++ b/frontend/src/reviews/ReviewDecisionDisplay.tsx @@ -5,8 +5,8 @@ import { useMemo } from 'react' import MarkdownDisplay from 'src/common/MarkdownDisplay' import UserAvatar from 'src/common/UserAvatar' import UserDisplay from 'src/common/UserDisplay' -import { ReviewResponse } from 'types/interfaces' import { EntityKind } from 'types/types' +import { ReviewResponse } from 'types/v2/types' import { formatDateString } from 'utils/dateUtils' type ReviewDecisionDisplayProps = { diff --git a/frontend/src/reviews/ReviewItem.tsx b/frontend/src/reviews/ReviewItem.tsx index ec4e4cb05..00db7aa52 100644 --- a/frontend/src/reviews/ReviewItem.tsx +++ b/frontend/src/reviews/ReviewItem.tsx @@ -2,7 +2,7 @@ import { ListItem, ListItemButton, Stack, Typography } from '@mui/material' import { useRouter } from 'next/router' import ReviewDisplay from 'src/model/reviews/ReviewDisplay' import ReviewRoleDisplay from 'src/reviews/ReviewRoleDisplay' -import { ReviewRequestInterface } from 'types/interfaces' +import { ReviewRequestInterface } from 'types/v2/types' import { timeDifference } from 'utils/dateUtils' type ReviewItemProps = { diff --git a/frontend/src/reviews/ReviewRoleDisplay.tsx b/frontend/src/reviews/ReviewRoleDisplay.tsx index 3c8cde1e0..c9e1fbbf7 100644 --- a/frontend/src/reviews/ReviewRoleDisplay.tsx +++ b/frontend/src/reviews/ReviewRoleDisplay.tsx @@ -1,8 +1,8 @@ import NotificationsNoneOutlinedIcon from '@mui/icons-material/NotificationsNoneOutlined' import { Stack, Typography } from '@mui/material' +import { ReviewRequestInterface } from 'types/v2/types' import { useGetModelRoles } from '../../actions/model' -import { ReviewRequestInterface } from '../../types/interfaces' import { getRoleDisplay } from '../../utils/roles' import Loading from '../common/Loading' import MessageAlert from '../MessageAlert' diff --git a/frontend/src/reviews/ReviewsList.tsx b/frontend/src/reviews/ReviewsList.tsx index 6e0207e65..0e1037b21 100644 --- a/frontend/src/reviews/ReviewsList.tsx +++ b/frontend/src/reviews/ReviewsList.tsx @@ -6,7 +6,7 @@ import EmptyBlob from 'src/common/EmptyBlob' import Loading from 'src/common/Loading' import MessageAlert from 'src/MessageAlert' import ReviewItem from 'src/reviews/ReviewItem' -import { ReviewRequestInterface } from 'types/interfaces' +import { ReviewRequestInterface } from 'types/v2/types' type ReviewsListProps = { kind: 'release' | 'access' | 'archived' diff --git a/frontend/src/wrapper/ModelSearchField.tsx b/frontend/src/wrapper/ModelSearchField.tsx index f485e3662..5968f0a68 100644 --- a/frontend/src/wrapper/ModelSearchField.tsx +++ b/frontend/src/wrapper/ModelSearchField.tsx @@ -4,9 +4,9 @@ import { alpha, styled, useTheme } from '@mui/material/styles' import { useListModels } from 'actions/model' import { ChangeEvent, useMemo, useState } from 'react' import Loading from 'src/common/Loading' +import useDebounce from 'src/hooks/useDebounce' import Link from 'src/Link' import MessageAlert from 'src/MessageAlert' -import useDebounce from 'utils/hooks/useDebounce' const Search = styled('div')(({ theme }) => ({ borderRadius: theme.shape.borderRadius, diff --git a/frontend/src/wrapper/SideNavigation.tsx b/frontend/src/wrapper/SideNavigation.tsx index d653ffb6c..002b01d19 100644 --- a/frontend/src/wrapper/SideNavigation.tsx +++ b/frontend/src/wrapper/SideNavigation.tsx @@ -13,9 +13,8 @@ import { CSSProperties, useCallback, useEffect, useState } from 'react' import Loading from 'src/common/Loading' import MessageAlert from 'src/MessageAlert' import { NavMenuItem } from 'src/wrapper/NavMenuItem' -import { ReviewRequestInterface } from 'types/interfaces' +import { ReviewRequestInterface, User } from 'types/v2/types' -import { User } from '../../types/v2/types' import { DRAWER_WIDTH } from '../../utils/constants' const StyledList = styled(List)(({ theme }) => ({ diff --git a/frontend/src/Banner.spec.tsx b/frontend/test/Banner.spec.tsx similarity index 95% rename from frontend/src/Banner.spec.tsx rename to frontend/test/Banner.spec.tsx index 475a33fe1..0bfc05ce8 100644 --- a/frontend/src/Banner.spec.tsx +++ b/frontend/test/Banner.spec.tsx @@ -2,7 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react' import { useGetUiConfig } from 'actions/uiConfig' import { describe, expect, it, vi } from 'vitest' -import Banner from './Banner' +import Banner from '../src/Banner' vi.mock('../actions/uiConfig', () => ({ useGetUiConfig: vi.fn(), diff --git a/frontend/src/Copyright.spec.tsx b/frontend/test/Copyright.spec.tsx similarity index 70% rename from frontend/src/Copyright.spec.tsx rename to frontend/test/Copyright.spec.tsx index 70cf1289e..d76c7f794 100644 --- a/frontend/src/Copyright.spec.tsx +++ b/frontend/test/Copyright.spec.tsx @@ -1,14 +1,14 @@ import { render, screen, waitFor } from '@testing-library/react' import { describe, expect, it } from 'vitest' -import Copyright from './Copyright' +import Copyright from '../src/Copyright' describe('Copyright', () => { it('renders a Copyright component', async () => { render() await waitFor(async () => { - expect(await screen.findByText('Crown Copyright')).not.toBeUndefined() + expect(await screen.findByText('Crown Copyright')).toBeDefined() }) }) }) diff --git a/frontend/test/common/EmptyBlob.spec.tsx b/frontend/test/common/EmptyBlob.spec.tsx new file mode 100644 index 000000000..73eb7dfbf --- /dev/null +++ b/frontend/test/common/EmptyBlob.spec.tsx @@ -0,0 +1,15 @@ +import { render, screen, waitFor } from '@testing-library/react' +import { describe, expect, it } from 'vitest' + +import EmptyBlob from '../../src/common/EmptyBlob' + +describe('EmptyBlob', () => { + it('displays an image with text when rendered', async () => { + render() + + await waitFor(async () => { + expect(await screen.findByTestId('emptyBlobImage')).toBeDefined() + expect(await screen.findByText('Test')).toBeDefined() + }) + }) +}) diff --git a/frontend/test/common/ExpandableButton.spec.tsx b/frontend/test/common/ExpandableButton.spec.tsx new file mode 100644 index 000000000..87464bfc5 --- /dev/null +++ b/frontend/test/common/ExpandableButton.spec.tsx @@ -0,0 +1,32 @@ +import Add from '@mui/icons-material/Add' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { describe, expect } from 'vitest' + +import ExpandableButton from '../../src/common/ExpandableButton' +import { doNothing } from '../../utils/test/testUtils' + +describe('ExpandableButton', () => { + it('button collapses when not hovered over', async () => { + render(} onClick={doNothing} />) + + await waitFor(async () => { + expect(await screen.findByTestId('expandableButton')).toBeDefined() + expect(screen.queryByTestId('expandedButtonContent')).toBeNull() + expect(await screen.findByTestId('collapsedButtonContent')).toBeDefined() + expect(screen.queryByText('Click me')).toBeNull() + }) + }) + + it('button expands when hovered over', async () => { + render(} onClick={doNothing} />) + + fireEvent.mouseEnter(await screen.findByTestId('expandableButton')) + + await waitFor(async () => { + expect(await screen.findByTestId('expandableButton')).toBeDefined() + expect(await screen.findByTestId('expandedButtonContent')).toBeDefined() + expect(screen.queryByTestId('collapsedButtonContent')).toBeNull() + expect(await screen.findByText('Click me')).toBeDefined() + }) + }) +}) diff --git a/frontend/src/common/HelpPopover.spec.tsx b/frontend/test/common/HelpPopover.spec.tsx similarity index 78% rename from frontend/src/common/HelpPopover.spec.tsx rename to frontend/test/common/HelpPopover.spec.tsx index cf0b40ff9..3e96a62bb 100644 --- a/frontend/src/common/HelpPopover.spec.tsx +++ b/frontend/test/common/HelpPopover.spec.tsx @@ -2,17 +2,17 @@ import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, expect } from 'vitest' -import HelpPopover from './HelpPopover' +import HelpPopover from '../../src/common/HelpPopover' describe('HelpPopover', () => { const testMessage = 'This is a test message' - it('the popover should initially be closed', () => { + it('does not display popover when help icon is not hovered over', () => { render({testMessage}) expect(screen.queryByText(testMessage)).toBeNull() }) - it('the popover should open when help icon is hovered over', async () => { + it('displays popover when help icon is hovered over', async () => { const user = userEvent.setup() render({testMessage}) @@ -22,7 +22,7 @@ describe('HelpPopover', () => { expect(await screen.findByText(testMessage)).toBeDefined() }) }) - it('the popver should close when help icon is unhovered', async () => { + it('popover closes when help icon is unhovered', async () => { const user = userEvent.setup() render({testMessage}) await waitFor(async () => { diff --git a/frontend/src/common/UserAvatar.spec.tsx b/frontend/test/common/UserAvatar.spec.tsx similarity index 56% rename from frontend/src/common/UserAvatar.spec.tsx rename to frontend/test/common/UserAvatar.spec.tsx index 778f98b52..2df7864f3 100644 --- a/frontend/src/common/UserAvatar.spec.tsx +++ b/frontend/test/common/UserAvatar.spec.tsx @@ -1,15 +1,16 @@ import { render, screen, waitFor } from '@testing-library/react' import { describe, expect, it } from 'vitest' +import UserAvatar from '../../src/common/UserAvatar' import { EntityKind } from '../../types/types' -import UserAvatar from './UserAvatar' describe('UserAvatar', () => { - it('renders an UserAvatar component', async () => { + it(`displays an avatar with user's initial`, async () => { render() await waitFor(async () => { - expect(await screen.findByText('Z')).not.toBeUndefined() + expect(await screen.findByTestId('userAvatar')).toBeDefined() + expect(await screen.findByText('Z')).toBeDefined() }) }) }) diff --git a/frontend/test/errors/ErrorWrapper.spec.tsx b/frontend/test/errors/ErrorWrapper.spec.tsx new file mode 100644 index 000000000..537be1710 --- /dev/null +++ b/frontend/test/errors/ErrorWrapper.spec.tsx @@ -0,0 +1,24 @@ +import { render, screen, waitFor } from '@testing-library/react' +import mockRouter from 'next-router-mock' +import { WrapperProps } from 'src/Wrapper' +import { beforeAll, describe, expect, it, vi } from 'vitest' + +import ErrorWrapper from '../../src/errors/ErrorWrapper' + +vi.mock('src/Wrapper.tsx', () => ({ + default: ({ children, ..._other }: WrapperProps) =>
{children}
, +})) + +describe('ErrorWrapper', () => { + beforeAll(() => { + mockRouter.push('/') + }) + it('displays an error message with wrapper when rendered', async () => { + render() + + await waitFor(async () => { + expect(await screen.findByTestId('errorWrapperWrapper')).toBeDefined() + expect(await screen.findByTestId('errorWrapperMessage')).toBeDefined() + }) + }) +}) diff --git a/frontend/test/errors/MultipleErrorWrapper.spec.tsx b/frontend/test/errors/MultipleErrorWrapper.spec.tsx new file mode 100644 index 000000000..ee37519a5 --- /dev/null +++ b/frontend/test/errors/MultipleErrorWrapper.spec.tsx @@ -0,0 +1,66 @@ +import { render, screen, waitFor } from '@testing-library/react' +import mockRouter from 'next-router-mock' +import { WrapperProps } from 'src/Wrapper' +import { describe, expect, it, vi } from 'vitest' + +import MultipleErrorWrapper from '../../src/errors/MultipleErrorWrapper' + +vi.mock('src/Wrapper.tsx', () => ({ + default: ({ children, ..._other }: WrapperProps) => <>{children}, +})) + +const noError = undefined +const error1 = {} +const error2 = {} + +describe('MultipleErrorWrapper', () => { + it('does not display an error message when no errors are present', async () => { + mockRouter.push('/') + + const errorWrapper = MultipleErrorWrapper('Test error message', { + noError, + }) + + if (errorWrapper) { + render(errorWrapper) + } + + await waitFor(async () => { + expect(screen.queryByText('Test error message')).toBeNull() + }) + }) + + it('displays an error message when one error is present', async () => { + mockRouter.push('/') + + const errorWrapper = MultipleErrorWrapper('Test error message', { + error1, + noError, + }) + + if (errorWrapper) { + render(errorWrapper) + } + + await waitFor(async () => { + expect(await screen.findByText('Test error message')).toBeDefined() + }) + }) + + it('displays an error message when multiple errors are present', async () => { + mockRouter.push('/') + + const errorWrapper = MultipleErrorWrapper('Test error message', { + error1, + error2, + }) + + if (errorWrapper) { + render(errorWrapper) + } + + await waitFor(async () => { + expect(await screen.findByText('Test error message')).toBeDefined() + }) + }) +}) diff --git a/frontend/utils/hooks/useNotification.spec.tsx b/frontend/test/hooks/useNotification.spec.tsx similarity index 75% rename from frontend/utils/hooks/useNotification.spec.tsx rename to frontend/test/hooks/useNotification.spec.tsx index 77e8509fb..cb02f9e0b 100644 --- a/frontend/utils/hooks/useNotification.spec.tsx +++ b/frontend/test/hooks/useNotification.spec.tsx @@ -4,7 +4,7 @@ import { SnackbarProvider } from 'notistack' import React from 'react' import { describe, expect, it } from 'vitest' -import useNotification from './useNotification' +import useNotification from '../../src/hooks/useNotification' describe('Snackbar', () => { function TestComponent() { @@ -16,7 +16,7 @@ describe('Snackbar', () => { return test } - it('renders a Snackbar component', async () => { + it('displays a snackbar when a notification is sent', async () => { render( @@ -24,7 +24,7 @@ describe('Snackbar', () => { ) await waitFor(async () => { - expect(await screen.findByText('Notification message')).not.toBeUndefined() + expect(await screen.findByText('Notification message')).toBeDefined() }) }) }) diff --git a/frontend/test/model/accessRequests/AccessRequestDisplay.spec.tsx b/frontend/test/model/accessRequests/AccessRequestDisplay.spec.tsx new file mode 100644 index 000000000..9ade19521 --- /dev/null +++ b/frontend/test/model/accessRequests/AccessRequestDisplay.spec.tsx @@ -0,0 +1,80 @@ +import { ThemeProvider } from '@mui/material/styles' +import { render, screen, waitFor } from '@testing-library/react' +import { UserDisplayProps } from 'src/common/UserDisplay' +import AccessRequestDisplay from 'src/model/accessRequests/AccessRequestDisplay' +import { ReviewBannerProps } from 'src/model/reviews/ReviewBanner' +import { ReviewDisplayProps } from 'src/model/reviews/ReviewDisplay' +import { lightTheme } from 'src/theme' +import { formatDateString } from 'utils/dateUtils' +import { testAccessRequest, testAccessRequestReview, testAccessRequestWithComments } from 'utils/test/testModels' +import { describe, expect, it, vi } from 'vitest' + +import { useGetReviewRequestsForModel } from '../../../actions/review' + +vi.mock('../../../actions/review', () => ({ + useGetReviewRequestsForModel: vi.fn(), +})) +vi.mock('src/model/reviews/ReviewBanner.tsx', () => ({ default: (_props: ReviewBannerProps) => <> })) +vi.mock('src/common/UserDisplay.tsx', () => ({ default: (_props: UserDisplayProps) => <> })) +vi.mock('src/model/reviews/ReviewDisplay.tsx', () => ({ default: (_props: ReviewDisplayProps) => <> })) + +describe('AccessRequestDisplay', () => { + it('displays access request metadata when not loading and no errors', async () => { + vi.mocked(useGetReviewRequestsForModel).mockReturnValue({ + reviews: [testAccessRequestReview], + isReviewsLoading: false, + isReviewsError: undefined, + mutateReviews: vi.fn(), + }) + render( + + + , + ) + + await waitFor(async () => { + const accessRequestEndDate = await screen.findByTestId('accessRequestEndDate') + expect(await screen.findByText(testAccessRequest.metadata.overview.name)).toBeDefined() + expect(accessRequestEndDate.innerHTML).toBe( + ` ${formatDateString(testAccessRequest.metadata.overview.endDate as string)}`, + ) + }) + }) + + it('displays comment icon when access request has comments', async () => { + vi.mocked(useGetReviewRequestsForModel).mockReturnValue({ + reviews: [testAccessRequestReview], + isReviewsLoading: false, + isReviewsError: undefined, + mutateReviews: vi.fn(), + }) + render( + + + , + ) + await waitFor(async () => { + expect(await screen.findByTestId('commentCount')).toBeDefined() + }) + }) + + it('does not display comment icon when access request does not have comments', async () => { + vi.mocked(useGetReviewRequestsForModel).mockReturnValue({ + reviews: [testAccessRequestReview], + isReviewsLoading: false, + isReviewsError: undefined, + mutateReviews: vi.fn(), + }) + render( + + + , + ) + await waitFor(async () => { + const commentsIcon = screen.queryByTestId('commentIcon') + const commentCount = screen.queryByTestId('commentCount') + expect(commentsIcon).toBeNull() + expect(commentCount).toBeNull() + }) + }) +}) diff --git a/frontend/test/model/common/SchemaButton.spec.tsx b/frontend/test/model/common/SchemaButton.spec.tsx new file mode 100644 index 000000000..861e63270 --- /dev/null +++ b/frontend/test/model/common/SchemaButton.spec.tsx @@ -0,0 +1,51 @@ +import { ThemeProvider } from '@mui/material/styles' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { MarkdownDisplayProps } from 'src/common/MarkdownDisplay' +import SchemaButton from 'src/model/common/SchemaButton' +import { lightTheme } from 'src/theme' +import { testAccessRequestSchema } from 'utils/test/testModels' +import { describe, expect, it, vi } from 'vitest' + +vi.mock('src/common/MarkdownDisplay.tsx', () => ({ default: (_props: MarkdownDisplayProps) => <> })) + +describe('SchemaButton', () => { + it('displays a loading spinner when loading prop is true', async () => { + render( + + undefined} loading /> + , + ) + + await waitFor(async () => { + expect(await screen.findByRole('progressbar')).toBeDefined() + }) + }) + + it('does not display a loading spinner when loading prop is false', async () => { + render( + + undefined} /> + , + ) + + await waitFor(async () => { + const loadingSpinner = await screen.queryByRole('progressbar') + expect(loadingSpinner).toBeNull() + }) + }) + + it('fires the onClick event when the schema button is clicked', async () => { + const handleClickSpy = vi.fn() + render( + + + , + ) + + await waitFor(async () => { + const schemaButton = screen.getByTestId('selectSchemaButton-access-request-schema') + fireEvent.click(schemaButton) + expect(handleClickSpy).toHaveBeenCalledOnce() + }) + }) +}) diff --git a/frontend/test/model/common/ValidationErrorIcon.spec.tsx b/frontend/test/model/common/ValidationErrorIcon.spec.tsx new file mode 100644 index 000000000..70e4357b0 --- /dev/null +++ b/frontend/test/model/common/ValidationErrorIcon.spec.tsx @@ -0,0 +1,39 @@ +import { ThemeProvider } from '@mui/material/styles' +import { render, screen, waitFor } from '@testing-library/react' +import { MarkdownDisplayProps } from 'src/common/MarkdownDisplay' +import ValidationErrorIcon from 'src/model/common/ValidationErrorIcon' +import { lightTheme } from 'src/theme' +import { testAccessRequestSchemaStepNoRender } from 'utils/test/testModels' +import { describe, expect, it, vi } from 'vitest' + +vi.mock('src/common/MarkdownDisplay.tsx', () => ({ default: (_props: MarkdownDisplayProps) => <> })) + +describe('ValidationErrorIcon', () => { + it('displays a validation warning message when the form step is marked as incomplete', async () => { + render( + + + , + ) + + await waitFor(async () => { + expect(await screen.findByTestId('formStepValidationWarning')).toBeDefined() + }) + }) + + it('does not display a validation warning message when the form step is marked as complete', async () => { + const step = { + ...testAccessRequestSchemaStepNoRender, + isComplete: () => true, + } + const { container } = render( + + + , + ) + + await waitFor(async () => { + expect(container.childElementCount).toEqual(0) + }) + }) +}) diff --git a/frontend/test/reviews/ReviewRoleDisplay.spec.tsx b/frontend/test/reviews/ReviewRoleDisplay.spec.tsx new file mode 100644 index 000000000..0f1cb7463 --- /dev/null +++ b/frontend/test/reviews/ReviewRoleDisplay.spec.tsx @@ -0,0 +1,85 @@ +import { render, screen, waitFor } from '@testing-library/react' +import { useGetModelRoles } from 'actions/model' +import ReviewRoleDisplay from 'src/reviews/ReviewRoleDisplay' +import { + testAccessRequestReview, + testAccessRequestReviewNoResponses, + testManagerRole, + testReleaseReview, + testReleaseReviewNoResponses, +} from 'utils/test/testModels' +import { describe, expect, vi } from 'vitest' + +const mockRoleUtils = vi.hoisted(() => { + return { + getRoleDisplay: vi.fn(), + } +}) +vi.mock('utils/roles.ts', () => mockRoleUtils) + +vi.mock('actions/model', () => ({ + useGetModelRoles: vi.fn(), +})) + +describe('ReviewRoleDisplay', () => { + const testMessageAccess = 'This access needs to be reviewed by the Manager.' + const testMessageRelease = 'This release needs to be reviewed by the Manager.' + + it('shows a message when an access request has no responses', async () => { + vi.mocked(useGetModelRoles).mockReturnValue({ + modelRoles: [testManagerRole], + isModelRolesLoading: false, + isModelRolesError: undefined, + mutateModelRoles: vi.fn(), + }) + mockRoleUtils.getRoleDisplay.mockReturnValue('Manager') + render() + await waitFor(async () => { + expect(await screen.findByText(testMessageAccess)).toBeDefined() + }) + }) + + it('shows a message when an release has no responses', async () => { + vi.mocked(useGetModelRoles).mockReturnValue({ + modelRoles: [testManagerRole], + isModelRolesLoading: false, + isModelRolesError: undefined, + mutateModelRoles: vi.fn(), + }) + mockRoleUtils.getRoleDisplay.mockReturnValue('Manager') + render() + await waitFor(async () => { + expect(await screen.findByText(testMessageRelease)).toBeDefined() + }) + }) + + it('does not show a message when an access request has responses', async () => { + vi.mocked(useGetModelRoles).mockReturnValue({ + modelRoles: [testManagerRole], + isModelRolesLoading: false, + isModelRolesError: undefined, + mutateModelRoles: vi.fn(), + }) + mockRoleUtils.getRoleDisplay.mockReturnValue('Manager') + render() + + await waitFor(async () => { + expect(screen.queryByText(testMessageAccess)).toBeNull() + }) + }) + + it('does not show a message when an release has responses', async () => { + vi.mocked(useGetModelRoles).mockReturnValue({ + modelRoles: [testManagerRole], + isModelRolesLoading: false, + isModelRolesError: undefined, + mutateModelRoles: vi.fn(), + }) + mockRoleUtils.getRoleDisplay.mockReturnValue('Manager') + render() + + await waitFor(async () => { + expect(screen.queryByText(testMessageRelease)).toBeNull() + }) + }) +}) diff --git a/frontend/types/interfaces.ts b/frontend/types/interfaces.ts index c8448bcb7..33839677b 100644 --- a/frontend/types/interfaces.ts +++ b/frontend/types/interfaces.ts @@ -92,39 +92,6 @@ export interface ModelInterface { updatedAt: Date } -export const Decision = { - RequestChanges: 'request_changes', - Approve: 'approve', -} as const -export type DecisionKeys = (typeof Decision)[keyof typeof Decision] - -export interface ReviewResponse { - user: string - decision: DecisionKeys - comment?: string - createdAt: string - updatedAt: string -} - -type PartialReviewRequestInterface = - | { - accessRequestId: string - semver?: never - } - | { - accessRequestId?: never - semver: string - } - -export type ReviewRequestInterface = { - model: ModelInterface - role: string - kind: 'release' | 'access' - responses: ReviewResponse[] - createdAt: string - updatedAt: string -} & PartialReviewRequestInterface - export interface AccessRequestMetadata { overview: { name: string @@ -163,7 +130,3 @@ export interface FileWithMetadata { fileName: string metadata?: string } - -export interface ReviewResponseWithRole extends ReviewResponse { - role: string -} diff --git a/frontend/types/types.ts b/frontend/types/types.ts index d25d53c90..9ad13aa57 100644 --- a/frontend/types/types.ts +++ b/frontend/types/types.ts @@ -1,6 +1,7 @@ import { Document, Types } from 'mongoose' +import { ReviewResponse } from 'types/v2/types' -import { FlattenedModelImage, ReviewResponse } from './interfaces' +import { FlattenedModelImage } from './interfaces' import { SchemaKindKeys } from './v2/types' export enum ModelUploadType { diff --git a/frontend/types/v2/types.ts b/frontend/types/v2/types.ts index 308908a0d..938cf4c2b 100644 --- a/frontend/types/v2/types.ts +++ b/frontend/types/v2/types.ts @@ -125,3 +125,40 @@ export interface TokenInterface { updatedAt: string compareToken: (candidateToken: string) => Promise } + +export const Decision = { + RequestChanges: 'request_changes', + Approve: 'approve', +} as const +export type DecisionKeys = (typeof Decision)[keyof typeof Decision] + +export interface ReviewResponse { + user: string + decision: DecisionKeys + comment?: string + createdAt: string + updatedAt: string +} + +export interface ReviewResponseWithRole extends ReviewResponse { + role: string +} + +type PartialReviewRequestInterface = + | { + accessRequestId: string + semver?: never + } + | { + accessRequestId?: never + semver: string + } + +export type ReviewRequestInterface = { + model: ModelInterface + role: string + kind: 'release' | 'access' + responses: ReviewResponse[] + createdAt: string + updatedAt: string +} & PartialReviewRequestInterface diff --git a/frontend/utils/createEmotionCache.spec.ts b/frontend/utils/createEmotionCache.spec.ts deleted file mode 100644 index e52f09558..000000000 --- a/frontend/utils/createEmotionCache.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import createEmotionCache from 'utils/createEmotionCache' -import { describe, expect, it } from 'vitest' - -describe('createEmotionCache', () => { - it('createEmotionCache', async () => { - const emotionCache = createEmotionCache() - expect(emotionCache.key).toBe('css') - }) -}) diff --git a/frontend/utils/test/testModels.ts b/frontend/utils/test/testModels.ts index 318d51fc5..716a700e5 100644 --- a/frontend/utils/test/testModels.ts +++ b/frontend/utils/test/testModels.ts @@ -1,4 +1,13 @@ -import { ApprovalCategory, ApprovalStates, ApprovalTypes, EntityKind } from '../../types/types' +import { AccessRequestInterface, ModelVisibility, StepNoRender } from 'types/interfaces' +import { + ApprovalCategory, + ApprovalStates, + ApprovalTypes, + EntityKind, + ReviewComment, + SchemaInterface, +} from 'types/types' +import { ModelCardInterface, ModelInterface, ReviewRequestInterface, ReviewResponse, Role } from 'types/v2/types' export const testId = 'testId' @@ -97,3 +106,174 @@ export const testApproval4: any = { _id: 'testApprovalId4', status: ApprovalStates.Accepted, } + +/******** V2 ********/ + +const testEntity = 'user:user1' +const modelId = 'my-test-model' +const accessRequestSchemaId = 'my-request-schema' +const modelcardSchemaId = 'my-model-schema' + +export const testAccessRequest: AccessRequestInterface = { + id: 'my-access-request', + modelId: modelId, + schemaId: accessRequestSchemaId, + deleted: false, + comments: [], + createdBy: testEntity, + metadata: { + overview: { + name: 'My Access Request', + endDate: new Date().toISOString(), + entities: [testEntity], + }, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toDateString(), +} + +const testComment: ReviewComment = { + message: 'This is a comment', + user: 'Joe Blogs', + createdAt: new Date().toISOString(), +} + +export const testAccessRequestWithComments: AccessRequestInterface = { + id: 'my-access-request', + modelId: modelId, + schemaId: accessRequestSchemaId, + deleted: false, + comments: [testComment], + createdBy: testEntity, + metadata: { + overview: { + name: 'My Access Request', + entities: [testEntity], + }, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toDateString(), +} + +export const testModelCard: ModelCardInterface = { + schemaId: modelcardSchemaId, + metadata: {}, + version: 1, + createdBy: testEntity, +} + +export const testV2Model: ModelInterface = { + id: modelId, + name: 'My Model', + description: 'This is a test model', + visibility: ModelVisibility.Public, + collaborators: [ + { + entity: testEntity, + roles: ['owner', 'msro', 'mtr'], + }, + ], + settings: { + ungovernedAccess: false, + }, + teamId: 'test-team', + card: testModelCard, +} + +export const testReviewResponse: ReviewResponse = { + user: testEntity, + decision: 'approve', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), +} + +export const testAccessRequestReview: ReviewRequestInterface = { + model: testV2Model, + role: 'mrso', + semver: '1.0.0', + kind: 'access', + responses: [testReviewResponse], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), +} + +export const testReleaseReview: ReviewRequestInterface = { + model: testV2Model, + role: 'mrso', + semver: '1.0.0', + kind: 'release', + responses: [testReviewResponse], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), +} + +export const testAccessRequestReviewNoResponses: ReviewRequestInterface = { + model: testV2Model, + role: 'mrso', + kind: 'access', + responses: [], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + accessRequestId: 'my-access-request', +} +export const testReleaseReviewNoResponses: ReviewRequestInterface = { + model: testV2Model, + role: 'mrso', + kind: 'release', + responses: [], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + accessRequestId: 'my-release', +} + +export const testAccessRequestSchema: SchemaInterface = { + id: 'access-request-schema', + name: 'Access request schema', + description: '', + active: true, + hidden: false, + kind: 'accessRequest', + meta: {}, + uiSchema: {}, + schema: { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + overview: { + title: 'Details', + type: 'object', + properties: { + name: { + title: 'What is the name of the access request?', + description: + 'This will be used to distinguish your access request from other access requests of this model', + type: 'string', + }, + }, + required: ['name'], + additionalProperties: false, + }, + }, + required: ['overview'], + additionalProperties: false, + }, + createdAt: new Date(), + updatedAt: new Date(), +} + +export const testAccessRequestSchemaStepNoRender: StepNoRender = { + schema: testAccessRequestSchema.schema, + state: {}, + index: 0, + steps: [], + type: 'Form', + section: 'First Page', + schemaRef: testAccessRequestSchema.id, + shouldValidate: false, + isComplete: () => false, +} + +export const testManagerRole: Role = { + id: 'mngr', + name: 'Manager', +}