Upgrade frontend to React 18 (#3353)

Co-authored-by: Kira Pilot <kira.pilot23@gmail.com>
This commit is contained in:
Ammar Bandukwala
2022-08-22 15:42:06 -05:00
committed by GitHub
parent 6fde537f9c
commit 2ee6acb2ad
121 changed files with 2465 additions and 2293 deletions

View File

@ -39,16 +39,17 @@
"cron-parser": "4.5.0",
"cronstrue": "2.11.0",
"dayjs": "1.11.4",
"formik": "2.2.9",
"formik": "^2.2.9",
"front-matter": "4.0.2",
"history": "5.3.0",
"just-debounce-it": "3.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-helmet": "6.1.0",
"react": "^18.2.0",
"react-dom": "18.2.0",
"react-helmet-async": "1.3.0",
"react-markdown": "8.0.3",
"react-router-dom": "6.3.0",
"sourcemapped-stacktrace": "1.1.11",
"swr": "1.3.0",
"tzdata": "1.0.30",
"uuid": "8.3.2",
"xstate": "4.32.1",
@ -70,13 +71,13 @@
"@storybook/addon-links": "6.5.9",
"@storybook/react": "6.4.22",
"@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "12.1.5",
"@testing-library/user-event": "14.3.0",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.4.3",
"@types/express": "4.17.13",
"@types/jest": "27.4.1",
"@types/node": "14.18.22",
"@types/react": "17.0.44",
"@types/react-dom": "17.0.16",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
"@types/react-helmet": "6.1.5",
"@types/superagent": "4.1.15",
"@types/uuid": "8.3.4",
@ -105,7 +106,7 @@
"jest-runner-eslint": "1.0.0",
"jest-websocket-mock": "2.3.0",
"mini-css-extract-plugin": "2.6.1",
"msw": "0.42.0",
"msw": "^0.44.2",
"prettier": "2.7.1",
"prettier-plugin-organize-imports": "3.0.0",
"react-hot-loader": "4.13.0",

View File

@ -35,7 +35,6 @@ const AuditPage = lazy(() => import("./pages/AuditPage/AuditPage"))
export const AppRouter: FC = () => {
const xServices = useContext(XServiceContext)
const permissions = useSelector(xServices.authXService, selectPermissions)
return (
<Suspense fallback={<></>}>
<Routes>

View File

@ -1,5 +1,5 @@
import { inspect } from "@xstate/inspect"
import ReactDOM from "react-dom"
import { createRoot } from "react-dom/client"
import { Interpreter } from "xstate"
import { App } from "./app"
@ -25,7 +25,11 @@ const main = () => {
██████▀▄█ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀
`)
const element = document.getElementById("root")
ReactDOM.render(<App />, element)
if (element === null) {
throw new Error("root element is null")
}
const root = createRoot(element)
root.render(<App />)
}
main()

View File

@ -1,6 +1,6 @@
import { FC } from "react"
import { FC, PropsWithChildren } from "react"
const ReactMarkdown: FC = ({ children }) => {
const ReactMarkdown: FC<PropsWithChildren<unknown>> = ({ children }) => {
return <div data-testid="markdown">{children}</div>
}

View File

@ -1,6 +1,7 @@
import CssBaseline from "@material-ui/core/CssBaseline"
import ThemeProvider from "@material-ui/styles/ThemeProvider"
import { FC } from "react"
import { HelmetProvider } from "react-helmet-async"
import { BrowserRouter as Router } from "react-router-dom"
import { AppRouter } from "./AppRouter"
import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary"
@ -12,6 +13,7 @@ import { XServiceProvider } from "./xServices/StateContext"
export const App: FC = () => {
return (
<Router>
<HelmetProvider>
<ThemeProvider theme={dark}>
<CssBaseline />
<ErrorBoundary>
@ -21,6 +23,7 @@ export const App: FC = () => {
</XServiceProvider>
</ErrorBoundary>
</ThemeProvider>
</HelmetProvider>
</Router>
)
}

View File

@ -2,7 +2,7 @@ import Button from "@material-ui/core/Button"
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
import ComputerIcon from "@material-ui/icons/Computer"
import { FC } from "react"
import { FC, PropsWithChildren } from "react"
import * as TypesGen from "../../api/typesGenerated"
import { generateRandomString } from "../../util/random"
@ -17,7 +17,12 @@ export interface AppLinkProps {
appIcon?: TypesGen.WorkspaceApp["icon"]
}
export const AppLink: FC<AppLinkProps> = ({ userName, workspaceName, appName, appIcon }) => {
export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
userName,
workspaceName,
appName,
appIcon,
}) => {
const styles = useStyles()
const href = `/@${userName}/${workspaceName}/apps/${appName}`

View File

@ -1,7 +1,7 @@
import Avatar from "@material-ui/core/Avatar"
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
import { FC } from "react"
import { FC, PropsWithChildren } from "react"
import { Link as RouterLink } from "react-router-dom"
import { firstLetter } from "../../util/firstLetter"
import {
@ -18,7 +18,7 @@ export interface AvatarDataProps {
avatar?: React.ReactNode
}
export const AvatarData: FC<AvatarDataProps> = ({
export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
title,
subtitle,
link,

View File

@ -1,6 +1,6 @@
import Popover, { PopoverProps } from "@material-ui/core/Popover"
import { fade, makeStyles } from "@material-ui/core/styles"
import { FC } from "react"
import { FC, PropsWithChildren } from "react"
type BorderedMenuVariant = "admin-dropdown" | "user-dropdown"
@ -8,7 +8,11 @@ export type BorderedMenuProps = Omit<PopoverProps, "variant"> & {
variant?: BorderedMenuVariant
}
export const BorderedMenu: FC<BorderedMenuProps> = ({ children, variant, ...rest }) => {
export const BorderedMenu: FC<PropsWithChildren<BorderedMenuProps>> = ({
children,
variant,
...rest
}) => {
const styles = useStyles()
return (

View File

@ -26,7 +26,7 @@ interface BorderedMenuRowProps {
onClick?: () => void
}
export const BorderedMenuRow: FC<BorderedMenuRowProps> = ({
export const BorderedMenuRow: FC<React.PropsWithChildren<BorderedMenuRowProps>> = ({
active,
description,
Icon,

View File

@ -30,7 +30,10 @@ export interface BuildsTableProps {
className?: string
}
export const BuildsTable: FC<BuildsTableProps> = ({ builds, className }) => {
export const BuildsTable: FC<React.PropsWithChildren<BuildsTableProps>> = ({
builds,
className,
}) => {
const { username, workspace: workspaceName } = useParams()
const isLoading = !builds
const theme: Theme = useTheme()

View File

@ -8,7 +8,7 @@ export interface CliAuthTokenProps {
sessionToken: string
}
export const CliAuthToken: FC<CliAuthTokenProps> = ({ sessionToken }) => {
export const CliAuthToken: FC<React.PropsWithChildren<CliAuthTokenProps>> = ({ sessionToken }) => {
const styles = useStyles()
return (
<Paper className={styles.container}>

View File

@ -9,7 +9,11 @@ export interface CodeBlockProps {
className?: string
}
export const CodeBlock: FC<CodeBlockProps> = ({ lines, ctas, className = "" }) => {
export const CodeBlock: FC<React.PropsWithChildren<CodeBlockProps>> = ({
lines,
ctas,
className = "",
}) => {
const styles = useStyles()
return (

View File

@ -14,7 +14,7 @@ export interface CodeExampleProps {
/**
* Component to show single-line code examples, with a copy button
*/
export const CodeExample: FC<CodeExampleProps> = ({
export const CodeExample: FC<React.PropsWithChildren<CodeExampleProps>> = ({
code,
className,
buttonClassName,

View File

@ -1,11 +1,12 @@
import { fireEvent, render } from "@testing-library/react"
import { FC } from "react"
import { act } from "react-dom/test-utils"
import { WrapperComponent } from "../../testHelpers/renderHelpers"
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog"
namespace Helpers {
export const Component: FC<ConfirmDialogProps> = (props: ConfirmDialogProps) => {
export const Component: FC<React.PropsWithChildren<ConfirmDialogProps>> = (
props: ConfirmDialogProps,
) => {
return (
<WrapperComponent>
<ConfirmDialog {...props} />
@ -116,9 +117,7 @@ describe("ConfirmDialog", () => {
// When
const { getByText } = render(<Helpers.Component {...props} />)
act(() => {
fireEvent.click(getByText("CANCEL"))
})
// Then
expect(onCloseMock).toBeCalledTimes(1)
@ -140,9 +139,7 @@ describe("ConfirmDialog", () => {
// When
const { getByText } = render(<Helpers.Component {...props} />)
act(() => {
fireEvent.click(getByText("CONFIRM"))
})
// Then
expect(onCloseMock).toBeCalledTimes(0)

View File

@ -78,7 +78,7 @@ const useStyles = makeStyles((theme) => ({
* Quick-use version of the Dialog component with slightly alternative styles,
* great to use for dialogs that don't have any interaction beyond yes / no.
*/
export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
export const ConfirmDialog: React.FC<React.PropsWithChildren<ConfirmDialogProps>> = ({
cancelText,
confirmLoading,
confirmText,

View File

@ -22,7 +22,7 @@ export const Language = {
/**
* Copy button used inside the CodeBlock component internally
*/
export const CopyButton: React.FC<CopyButtonProps> = ({
export const CopyButton: React.FC<React.PropsWithChildren<CopyButtonProps>> = ({
text,
ctaCopy,
wrapperClassName = "",

View File

@ -35,7 +35,7 @@ const validationSchema = Yup.object({
username: nameValidator(Language.usernameLabel),
})
export const CreateUserForm: FC<CreateUserFormProps> = ({
export const CreateUserForm: FC<React.PropsWithChildren<CreateUserFormProps>> = ({
onSubmit,
onCancel,
formErrors,

View File

@ -12,11 +12,9 @@ export interface DeleteWorkspaceDialogProps {
handleCancel: () => void
}
export const DeleteWorkspaceDialog: React.FC<DeleteWorkspaceDialogProps> = ({
isOpen,
handleCancel,
handleConfirm,
}) => (
export const DeleteWorkspaceDialog: React.FC<
React.PropsWithChildren<DeleteWorkspaceDialogProps>
> = ({ isOpen, handleCancel, handleConfirm }) => (
<ConfirmDialog
type="delete"
hideCancel={false}

View File

@ -23,7 +23,7 @@ export interface EmptyStateProps {
* EmptyState's props extend the [Material UI Box component](https://material-ui.com/components/box/)
* that you can directly pass props through to to customize the shape and layout of it.
*/
export const EmptyState: FC<EmptyStateProps> = (props) => {
export const EmptyState: FC<React.PropsWithChildren<EmptyStateProps>> = (props) => {
const { message, description, cta, descriptionClassName, className, ...boxProps } = props
const styles = useStyles()

View File

@ -25,7 +25,7 @@ export interface EnterpriseSnackbarProps extends MuiSnackbarProps {
*
* See original component's Material UI documentation here: https://material-ui.com/components/snackbars/
*/
export const EnterpriseSnackbar: FC<EnterpriseSnackbarProps> = ({
export const EnterpriseSnackbar: FC<React.PropsWithChildren<EnterpriseSnackbarProps>> = ({
onClose,
variant = "info",
ContentProps = {},

View File

@ -1,7 +1,7 @@
import { Component, ReactNode } from "react"
import React, { Component, ReactNode } from "react"
import { RuntimeErrorState } from "../RuntimeErrorState/RuntimeErrorState"
type ErrorBoundaryProps = Record<string, unknown>
type ErrorBoundaryProps = React.PropsWithChildren<unknown>
interface ErrorBoundaryState {
error: Error | null

View File

@ -23,7 +23,7 @@ export interface ErrorSummaryProps {
defaultMessage?: string
}
export const ErrorSummary: FC<ErrorSummaryProps> = ({
export const ErrorSummary: FC<React.PropsWithChildren<ErrorSummaryProps>> = ({
error,
retry,
dismissible,

View File

@ -18,7 +18,7 @@ export interface FooterProps {
buildInfo?: TypesGen.BuildInfoResponse
}
export const Footer: React.FC<FooterProps> = ({ buildInfo }) => {
export const Footer: React.FC<React.PropsWithChildren<FooterProps>> = ({ buildInfo }) => {
const styles = useFooterStyles()
const githubUrl = `https://github.com/coder/coder/issues/new?labels=needs+grooming&body=${encodeURIComponent(`Version: [\`${buildInfo?.version}\`](${buildInfo?.external_url})

View File

@ -8,7 +8,9 @@ export interface FormCloseButtonProps {
onClose: () => void
}
export const FormCloseButton: React.FC<FormCloseButtonProps> = ({ onClose }) => {
export const FormCloseButton: React.FC<React.PropsWithChildren<FormCloseButtonProps>> = ({
onClose,
}) => {
const styles = useStyles()
useEffect(() => {

View File

@ -28,7 +28,7 @@ const useStyles = makeStyles((theme) => ({
},
}))
export const FormFooter: FC<FormFooterProps> = ({
export const FormFooter: FC<React.PropsWithChildren<FormFooterProps>> = ({
onCancel,
isLoading,
submitLabel = Language.defaultSubmitLabel,

View File

@ -39,7 +39,11 @@ export const useStyles = makeStyles((theme) => ({
},
}))
export const FormSection: FC<FormSectionProps> = ({ title, description, children }) => {
export const FormSection: FC<React.PropsWithChildren<FormSectionProps>> = ({
title,
description,
children,
}) => {
const styles = useStyles()
return (

View File

@ -1,4 +1,4 @@
import { act, fireEvent, render, screen } from "@testing-library/react"
import { fireEvent, render, screen } from "@testing-library/react"
import { useFormik } from "formik"
import { FC } from "react"
import * as yup from "yup"
@ -11,9 +11,9 @@ namespace Helpers {
export const requiredValidationMsg = "required"
export const Component: FC<Omit<FormTextFieldProps<FormValues>, "form" | "formFieldName">> = (
props,
) => {
export const Component: FC<
React.PropsWithChildren<Omit<FormTextFieldProps<FormValues>, "form" | "formFieldName">>
> = (props) => {
const form = useFormik<FormValues>({
initialValues: {
name: "",
@ -58,17 +58,13 @@ describe("FormTextField", () => {
expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeNull()
// When
act(() => {
fireEvent.focus(el as Element)
})
// Then
expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeNull()
// When
act(() => {
fireEvent.blur(el as Element)
})
// Then
expect(screen.queryByText(Helpers.requiredValidationMsg)).toBeDefined()

View File

@ -134,7 +134,7 @@ export const FormTextField = <T,>({
variant={variant}
disabled={disabled || form.isSubmitting}
error={isError}
helperText={isError ? form.errors[formFieldName] : helperText}
helperText={isError ? form.errors[formFieldName]?.toString() : helperText}
id={fieldId}
InputProps={isPassword ? undefined : InputProps}
name={fieldId}

View File

@ -18,7 +18,7 @@ const useStyles = makeStyles((theme) => ({
},
}))
export const FormTitle: FC<FormTitleProps> = ({ title, detail }) => {
export const FormTitle: FC<React.PropsWithChildren<FormTitleProps>> = ({ title, detail }) => {
const styles = useStyles()
return (

View File

@ -19,7 +19,12 @@ const useStyles = makeStyles(() => ({
},
}))
export const FullPageForm: FC<FullPageFormProps> = ({ title, detail, onCancel, children }) => {
export const FullPageForm: FC<React.PropsWithChildren<FullPageFormProps>> = ({
title,
detail,
onCancel,
children,
}) => {
const styles = useStyles()
return (
<main className={styles.root}>

View File

@ -2,7 +2,7 @@ import Box from "@material-ui/core/Box"
import CircularProgress from "@material-ui/core/CircularProgress"
import { FC } from "react"
export const Loader: FC<{ size?: number }> = ({ size = 26 }) => {
export const Loader: FC<React.PropsWithChildren<{ size?: number }>> = ({ size = 26 }) => {
return (
<Box p={4} width="100%" display="flex" alignItems="center" justifyContent="center">
<CircularProgress size={size} />

View File

@ -17,7 +17,7 @@ export interface LoadingButtonProps extends ButtonProps {
* In Material-UI 5+ - this is built-in, but since we're on an earlier version,
* we have to roll our own.
*/
export const LoadingButton: FC<LoadingButtonProps> = ({
export const LoadingButton: FC<React.PropsWithChildren<LoadingButtonProps>> = ({
loading = false,
loadingLabel,
children,

View File

@ -14,7 +14,7 @@ export interface LogsProps {
className?: string
}
export const Logs: FC<LogsProps> = ({ lines, className = "" }) => {
export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({ lines, className = "" }) => {
const styles = useStyles()
return (

View File

@ -24,7 +24,10 @@ interface MarginsProps {
size?: Size
}
export const Margins: FC<MarginsProps> = ({ children, size = "regular" }) => {
export const Margins: FC<React.PropsWithChildren<MarginsProps>> = ({
children,
size = "regular",
}) => {
const styles = useStyles({ maxWidth: widthBySize[size] })
return <div className={styles.margins}>{children}</div>
}

View File

@ -25,10 +25,9 @@ export const Language = {
audit: "Audit",
}
const NavItems: React.FC<{ className?: string; canViewAuditLog: boolean }> = ({
className,
canViewAuditLog,
}) => {
const NavItems: React.FC<
React.PropsWithChildren<{ className?: string; canViewAuditLog: boolean }>
> = ({ className, canViewAuditLog }) => {
const styles = useStyles()
const location = useLocation()
@ -63,8 +62,11 @@ const NavItems: React.FC<{ className?: string; canViewAuditLog: boolean }> = ({
</List>
)
}
export const NavbarView: React.FC<NavbarViewProps> = ({ user, onSignOut, canViewAuditLog }) => {
export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
user,
onSignOut,
canViewAuditLog,
}) => {
const styles = useStyles()
const [isDrawerOpen, setIsDrawerOpen] = useState(false)

View File

@ -7,7 +7,11 @@ export interface PageHeaderProps {
className?: string
}
export const PageHeader: React.FC<PageHeaderProps> = ({ children, actions, className }) => {
export const PageHeader: React.FC<React.PropsWithChildren<PageHeaderProps>> = ({
children,
actions,
className,
}) => {
const styles = useStyles()
return (
@ -22,13 +26,13 @@ export const PageHeader: React.FC<PageHeaderProps> = ({ children, actions, class
)
}
export const PageHeaderTitle: React.FC = ({ children }) => {
export const PageHeaderTitle: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles()
return <h1 className={styles.title}>{children}</h1>
}
export const PageHeaderSubtitle: React.FC = ({ children }) => {
export const PageHeaderSubtitle: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles()
return <h2 className={styles.subtitle}>{children}</h2>

View File

@ -29,7 +29,11 @@ export interface ParameterInputProps {
onChange: (value: string) => void
}
export const ParameterInput: FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
export const ParameterInput: FC<React.PropsWithChildren<ParameterInputProps>> = ({
disabled,
onChange,
schema,
}) => {
const styles = useStyles()
return (
@ -42,7 +46,11 @@ export const ParameterInput: FC<ParameterInputProps> = ({ disabled, onChange, sc
)
}
const ParameterField: React.FC<ParameterInputProps> = ({ disabled, onChange, schema }) => {
const ParameterField: React.FC<React.PropsWithChildren<ParameterInputProps>> = ({
disabled,
onChange,
schema,
}) => {
if (schema.validation_contains && schema.validation_contains.length > 0) {
return (
<RadioGroup

View File

@ -8,7 +8,10 @@ import React, { useCallback, useState } from "react"
type PasswordFieldProps = Omit<TextFieldProps, "InputProps" | "type">
export const PasswordField: React.FC<PasswordFieldProps> = ({ variant = "outlined", ...rest }) => {
export const PasswordField: React.FC<React.PropsWithChildren<PasswordFieldProps>> = ({
variant = "outlined",
...rest
}) => {
const styles = useStyles()
const [showPassword, setShowPassword] = useState<boolean>(false)

View File

@ -9,7 +9,7 @@ export interface RequireAuthProps {
children: JSX.Element
}
export const RequireAuth: React.FC<RequireAuthProps> = ({ children }) => {
export const RequireAuth: React.FC<React.PropsWithChildren<RequireAuthProps>> = ({ children }) => {
const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService)
const location = useLocation()

View File

@ -24,7 +24,7 @@ export const Language = {
confirmText: "Reset password",
}
export const ResetPasswordDialog: FC<ResetPasswordDialogProps> = ({
export const ResetPasswordDialog: FC<React.PropsWithChildren<ResetPasswordDialogProps>> = ({
open,
onClose,
onConfirm,

View File

@ -33,7 +33,7 @@ interface ResourcesProps {
canUpdateWorkspace: boolean
}
export const Resources: FC<ResourcesProps> = ({
export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
resources,
getResourcesError,
workspace,

View File

@ -16,7 +16,7 @@ export interface RoleSelectProps {
open?: boolean
}
export const RoleSelect: FC<RoleSelectProps> = ({
export const RoleSelect: FC<React.PropsWithChildren<RoleSelectProps>> = ({
roles,
selectedRoles,
loading,

View File

@ -13,7 +13,7 @@ export interface SSHButtonProps {
defaultIsOpen?: boolean
}
export const SSHButton: React.FC<SSHButtonProps> = ({
export const SSHButton: React.FC<React.PropsWithChildren<SSHButtonProps>> = ({
workspaceName,
agentName,
defaultIsOpen = false,

View File

@ -36,7 +36,7 @@ interface FilterFormValues {
export type FilterFormErrors = FormikErrors<FilterFormValues>
export const SearchBarWithFilter: React.FC<SearchBarWithFilterProps> = ({
export const SearchBarWithFilter: React.FC<React.PropsWithChildren<SearchBarWithFilterProps>> = ({
filter,
onFilter,
presetFilters,

View File

@ -17,7 +17,7 @@ export interface SectionProps {
children?: React.ReactNode
}
type SectionFC = FC<SectionProps> & { Action: typeof SectionAction }
type SectionFC = FC<React.PropsWithChildren<SectionProps>> & { Action: typeof SectionAction }
export const Section: SectionFC = ({
title,

View File

@ -11,7 +11,7 @@ const useStyles = makeStyles((theme) => ({
* SectionAction is a content box that call to actions should be placed
* within
*/
export const SectionAction: FC = ({ children }) => {
export const SectionAction: FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles()
return <div className={styles.root}>{children}</div>
}

View File

@ -31,7 +31,7 @@ export interface AccountFormProps {
initialTouched?: FormikTouched<AccountFormValues>
}
export const AccountForm: FC<AccountFormProps> = ({
export const AccountForm: FC<React.PropsWithChildren<AccountFormProps>> = ({
email,
isLoading,
onSubmit,
@ -51,7 +51,7 @@ export const AccountForm: FC<AccountFormProps> = ({
<>
<form onSubmit={form.handleSubmit}>
<Stack>
{updateProfileError && <ErrorSummary error={updateProfileError} />}
{updateProfileError ? <ErrorSummary error={updateProfileError} /> : <></>}
<TextField
disabled
fullWidth

View File

@ -1,6 +1,6 @@
import Box from "@material-ui/core/Box"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { Helmet } from "react-helmet-async"
import { Outlet } from "react-router-dom"
import { pageTitle } from "../../util/page"
import { AuthAndFrame } from "../AuthAndFrame/AuthAndFrame"

View File

@ -68,7 +68,7 @@ export const SecurityForm: React.FC<SecurityFormProps> = ({
<>
<form onSubmit={form.handleSubmit}>
<Stack>
{updateSecurityError && <ErrorSummary error={updateSecurityError} />}
{updateSecurityError ? <ErrorSummary error={updateSecurityError} /> : <></>}
<TextField
{...getFieldHelpers("old_password")}
onChange={onChangeTrimmed(form)}

View File

@ -89,7 +89,7 @@ export interface SignInFormProps {
initialTouched?: FormikTouched<BuiltInAuthFormValues>
}
export const SignInForm: FC<SignInFormProps> = ({
export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
authMethods,
redirectTo,
isLoading,

View File

@ -1,5 +1,5 @@
import { makeStyles } from "@material-ui/core/styles"
import { FC } from "react"
import { FC, ReactNode } from "react"
import { Footer } from "../../components/Footer/Footer"
export const useStyles = makeStyles((theme) => ({
@ -21,7 +21,7 @@ export const useStyles = makeStyles((theme) => ({
},
}))
export const SignInLayout: FC = ({ children }) => {
export const SignInLayout: FC<{ children: ReactNode }> = ({ children }) => {
const styles = useStyles()
return (

View File

@ -1,7 +1,6 @@
import Button, { ButtonProps } from "@material-ui/core/Button"
import ButtonGroup from "@material-ui/core/ButtonGroup"
import ClickAwayListener from "@material-ui/core/ClickAwayListener"
import Grow from "@material-ui/core/Grow"
import MenuItem from "@material-ui/core/MenuItem"
import MenuList from "@material-ui/core/MenuList"
import Paper from "@material-ui/core/Paper"
@ -30,7 +29,7 @@ export interface SplitButtonProps<T> extends Pick<ButtonProps, "color" | "disabl
*/
options: SplitButtonOptions<T>[]
/**
* textTransform is applied to the primary button text. Defaults to
* textTransform is applied to the primary button text. Defaults PropsWithto
* uppercase
*/
textTransform?: React.CSSProperties["textTransform"]
@ -104,13 +103,7 @@ export const SplitButton = <T,>({
style={{ zIndex: 1 }}
transition
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: placement === "bottom" ? "center top" : "center bottom",
}}
>
{() => (
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu">
@ -122,7 +115,6 @@ export const SplitButton = <T,>({
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>

View File

@ -1,6 +1,7 @@
import { makeStyles } from "@material-ui/core/styles"
import { CSSProperties } from "@material-ui/core/styles/withStyles"
import { FC } from "react"
import { ReactNode } from "react-markdown/lib/react-markdown"
import { combineClasses } from "../../util/combineClasses"
type Direction = "column" | "row"
@ -29,7 +30,7 @@ const useStyles = makeStyles((theme) => ({
},
}))
export const Stack: FC<StackProps> = ({
export const Stack: FC<StackProps & { children: ReactNode | ReactNode[] }> = ({
children,
className,
direction = "column",
@ -37,7 +38,12 @@ export const Stack: FC<StackProps> = ({
alignItems,
justifyContent,
}) => {
const styles = useStyles({ spacing, direction, alignItems, justifyContent })
const styles = useStyles({
spacing,
direction,
alignItems,
justifyContent,
})
return <div className={combineClasses([styles.stack, className])}>{children}</div>
}

View File

@ -10,7 +10,11 @@ export interface TabPanelProps {
menuItems: TabSidebarItem[]
}
export const TabPanel: FC<TabPanelProps> = ({ children, title, menuItems }) => {
export const TabPanel: FC<React.PropsWithChildren<TabPanelProps>> = ({
children,
title,
menuItems,
}) => {
const styles = useStyles()
return (

View File

@ -16,7 +16,7 @@ export interface TabSidebarProps {
menuItems: TabSidebarItem[]
}
export const TabSidebar: FC<TabSidebarProps> = ({ menuItems }) => {
export const TabSidebar: FC<React.PropsWithChildren<TabSidebarProps>> = ({ menuItems }) => {
const styles = useStyles()
return (

View File

@ -1,16 +1,16 @@
import { makeStyles } from "@material-ui/core/styles"
import React from "react"
import React, { ReactNode } from "react"
import { Stack } from "../Stack/Stack"
interface StyleProps {
highlight?: boolean
}
export const TableCellData: React.FC = ({ children }) => {
export const TableCellData: React.FC<{ children: ReactNode }> = ({ children }) => {
return <Stack spacing={0}>{children}</Stack>
}
export const TableCellDataPrimary: React.FC<{ highlight?: boolean }> = ({
export const TableCellDataPrimary: React.FC<React.PropsWithChildren<{ highlight?: boolean }>> = ({
children,
highlight,
}) => {
@ -19,7 +19,9 @@ export const TableCellDataPrimary: React.FC<{ highlight?: boolean }> = ({
return <span className={styles.primary}>{children}</span>
}
export const TableCellDataSecondary: React.FC = ({ children }) => {
export const TableCellDataSecondary: React.FC<React.PropsWithChildren<unknown>> = ({
children,
}) => {
const styles = useStyles()
return <span className={styles.secondary}>{children}</span>

View File

@ -7,9 +7,11 @@ import { combineClasses } from "../../util/combineClasses"
// TableCellLink wraps a TableCell filling the entirety with a Link.
// This allows table rows to be clickable with browser-behavior like ctrl+click.
export const TableCellLink: React.FC<
React.PropsWithChildren<
TableCellProps & {
to: string
}
>
> = (props) => {
const styles = useStyles()

View File

@ -1,26 +1,6 @@
import TableCell from "@material-ui/core/TableCell"
import TableRow from "@material-ui/core/TableRow"
import { FC } from "react"
import { FC, ReactNode } from "react"
export interface TableHeadersProps {
columns: string[]
hasMenu?: boolean
}
export const TableHeaderRow: FC = ({ children }) => {
export const TableHeaderRow: FC<{ children: ReactNode }> = ({ children }) => {
return <TableRow>{children}</TableRow>
}
export const TableHeaders: FC<TableHeadersProps> = ({ columns, hasMenu }) => {
return (
<TableHeaderRow>
{columns.map((c, idx) => (
<TableCell key={idx} size="small">
{c}
</TableCell>
))}
{/* 1% is a trick to make the table cell width fit the content */}
{hasMenu && <TableCell width="1%" />}
</TableHeaderRow>
)
}

View File

@ -5,7 +5,7 @@ import { Language as AgentTooltipLanguage } from "../Tooltips/AgentHelpTooltip"
import { Language as ResourceTooltipLanguage } from "../Tooltips/ResourcesHelpTooltip"
import { TemplateResourcesProps, TemplateResourcesTable } from "./TemplateResourcesTable"
const Component: FC<TemplateResourcesProps> = (props) => (
const Component: FC<React.PropsWithChildren<TemplateResourcesProps>> = (props) => (
<WrapperComponent>
<TemplateResourcesTable {...props} />
</WrapperComponent>

View File

@ -23,7 +23,9 @@ export interface TemplateResourcesProps {
resources: WorkspaceResource[]
}
export const TemplateResourcesTable: FC<TemplateResourcesProps> = ({ resources }) => {
export const TemplateResourcesTable: FC<React.PropsWithChildren<TemplateResourcesProps>> = ({
resources,
}) => {
const styles = useStyles()
return (

View File

@ -26,7 +26,7 @@ export interface TerminalLinkProps {
* If no user name is provided "me" is used however it makes the link not
* shareable.
*/
export const TerminalLink: FC<TerminalLinkProps> = ({
export const TerminalLink: FC<React.PropsWithChildren<TerminalLinkProps>> = ({
agentName,
userName = "me",
workspaceName,

View File

@ -33,7 +33,11 @@ const useHelpTooltip = () => {
return helpTooltipContext
}
export const HelpTooltip: React.FC<HelpTooltipProps> = ({ children, open, size = "medium" }) => {
export const HelpTooltip: React.FC<React.PropsWithChildren<HelpTooltipProps>> = ({
children,
open,
size = "medium",
}) => {
const styles = useStyles({ size })
const anchorRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(!!open)
@ -92,19 +96,22 @@ export const HelpTooltip: React.FC<HelpTooltipProps> = ({ children, open, size =
)
}
export const HelpTooltipTitle: React.FC = ({ children }) => {
export const HelpTooltipTitle: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles()
return <h4 className={styles.title}>{children}</h4>
}
export const HelpTooltipText: React.FC = ({ children }) => {
export const HelpTooltipText: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles()
return <p className={styles.text}>{children}</p>
}
export const HelpTooltipLink: React.FC<{ href: string }> = ({ children, href }) => {
export const HelpTooltipLink: React.FC<React.PropsWithChildren<{ href: string }>> = ({
children,
href,
}) => {
const styles = useStyles()
return (
@ -115,11 +122,13 @@ export const HelpTooltipLink: React.FC<{ href: string }> = ({ children, href })
)
}
export const HelpTooltipAction: React.FC<{
export const HelpTooltipAction: React.FC<
React.PropsWithChildren<{
icon: Icon
onClick: () => void
ariaLabel?: string
}> = ({ children, icon: Icon, onClick, ariaLabel }) => {
}>
> = ({ children, icon: Icon, onClick, ariaLabel }) => {
const styles = useStyles()
const tooltip = useHelpTooltip()
@ -139,7 +148,7 @@ export const HelpTooltipAction: React.FC<{
)
}
export const HelpTooltipLinksGroup: React.FC = ({ children }) => {
export const HelpTooltipLinksGroup: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const styles = useStyles()
return (

View File

@ -19,7 +19,10 @@ interface TooltipProps {
ariaLabel?: string
}
export const OutdatedHelpTooltip: FC<TooltipProps> = ({ onUpdateVersion, ariaLabel }) => {
export const OutdatedHelpTooltip: FC<React.PropsWithChildren<TooltipProps>> = ({
onUpdateVersion,
ariaLabel,
}) => {
return (
<HelpTooltip size="small">
<HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle>

View File

@ -13,7 +13,7 @@ namespace Helpers {
onPrimaryTextSelect: jest.fn(),
}
export const Component: FC<UserCellProps> = (props) => (
export const Component: FC<React.PropsWithChildren<UserCellProps>> = (props) => (
<WrapperComponent>
<UserCell {...props} />
</WrapperComponent>

View File

@ -35,7 +35,7 @@ const useStyles = makeStyles((theme) => ({
* UserCell is a single cell in an audit log table row that contains user-level
* information
*/
export const UserCell: FC<UserCellProps> = ({
export const UserCell: FC<React.PropsWithChildren<UserCellProps>> = ({
Avatar,
caption,
primaryText,

View File

@ -1,4 +1,4 @@
import { screen } from "@testing-library/react"
import { fireEvent, screen } from "@testing-library/react"
import { MockUser } from "../../testHelpers/entities"
import { render } from "../../testHelpers/renderHelpers"
import { Language } from "../UserDropdownContent/UserDropdownContent"
@ -7,7 +7,7 @@ import { UserDropdown, UserDropdownProps } from "./UsersDropdown"
const renderAndClick = async (props: Partial<UserDropdownProps> = {}) => {
render(<UserDropdown user={props.user ?? MockUser} onSignOut={props.onSignOut ?? jest.fn()} />)
const trigger = await screen.findByTestId("user-dropdown-trigger")
trigger.click()
fireEvent.click(trigger)
}
describe("UserDropdown", () => {

View File

@ -14,7 +14,7 @@ export interface UserDropdownProps {
onSignOut: () => void
}
export const UserDropdown: React.FC<UserDropdownProps> = ({
export const UserDropdown: React.FC<React.PropsWithChildren<UserDropdownProps>> = ({
user,
onSignOut,
}: UserDropdownProps) => {

View File

@ -28,7 +28,7 @@ export interface UsersTableProps {
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
}
export const UsersTable: FC<UsersTableProps> = ({
export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
users,
roles,
onSuspendUser,

View File

@ -30,7 +30,7 @@ interface UsersTableBodyProps {
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void
}
export const UsersTableBody: FC<UsersTableBodyProps> = ({
export const UsersTableBody: FC<React.PropsWithChildren<UsersTableBodyProps>> = ({
users,
roles,
onSuspendUser,

View File

@ -23,7 +23,7 @@ export interface VersionsTableProps {
versions?: TypesGen.TemplateVersion[]
}
export const VersionsTable: FC<VersionsTableProps> = ({ versions }) => {
export const VersionsTable: FC<React.PropsWithChildren<VersionsTableProps>> = ({ versions }) => {
const isLoading = !versions
const theme: Theme = useTheme()

View File

@ -1,6 +1,6 @@
import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import { FC } from "react"
import { FC, PropsWithChildren } from "react"
import { CoderIcon } from "../Icons/CoderIcon"
const Language = {
@ -11,7 +11,9 @@ const Language = {
),
}
export const Welcome: FC<{ message?: JSX.Element }> = ({ message = Language.defaultMessage }) => {
export const Welcome: FC<PropsWithChildren<{ message?: JSX.Element }>> = ({
message = Language.defaultMessage,
}) => {
const styles = useStyles()
return (

View File

@ -49,7 +49,7 @@ export interface WorkspaceProps {
/**
* Workspace is the top-level component for viewing an individual workspace
*/
export const Workspace: FC<WorkspaceProps> = ({
export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
bannerProps,
scheduleProps,
handleStart,
@ -69,11 +69,15 @@ export const Workspace: FC<WorkspaceProps> = ({
return (
<Margins>
<Stack spacing={1}>
{workspaceErrors[WorkspaceErrors.BUILD_ERROR] && (
{workspaceErrors[WorkspaceErrors.BUILD_ERROR] ? (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]} dismissible />
) : (
<></>
)}
{workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] && (
{workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] ? (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]} dismissible />
) : (
<></>
)}
</Stack>
<PageHeader

View File

@ -9,7 +9,7 @@ export interface WorkspaceActionButtonProps {
ariaLabel?: string
}
export const WorkspaceActionButton: FC<WorkspaceActionButtonProps> = ({
export const WorkspaceActionButton: FC<React.PropsWithChildren<WorkspaceActionButtonProps>> = ({
label,
icon,
onClick,

View File

@ -27,7 +27,7 @@ interface WorkspaceAction {
handleAction: () => void
}
export const UpdateButton: FC<WorkspaceAction> = ({ handleAction }) => {
export const UpdateButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles()
return (
@ -37,7 +37,7 @@ export const UpdateButton: FC<WorkspaceAction> = ({ handleAction }) => {
)
}
export const StartButton: FC<WorkspaceAction> = ({ handleAction }) => {
export const StartButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles()
return (
@ -50,7 +50,7 @@ export const StartButton: FC<WorkspaceAction> = ({ handleAction }) => {
)
}
export const StopButton: FC<WorkspaceAction> = ({ handleAction }) => {
export const StopButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles()
return (
@ -63,7 +63,7 @@ export const StopButton: FC<WorkspaceAction> = ({ handleAction }) => {
)
}
export const DeleteButton: FC<WorkspaceAction> = ({ handleAction }) => {
export const DeleteButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles()
return (
@ -76,7 +76,7 @@ export const DeleteButton: FC<WorkspaceAction> = ({ handleAction }) => {
)
}
export const CancelButton: FC<WorkspaceAction> = ({ handleAction }) => {
export const CancelButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({ handleAction }) => {
const styles = useStyles()
// this is an icon button, so it's important to include an aria label
@ -94,7 +94,7 @@ interface DisabledProps {
workspaceState: WorkspaceStateEnum
}
export const DisabledButton: FC<DisabledProps> = ({ workspaceState }) => {
export const DisabledButton: FC<React.PropsWithChildren<DisabledProps>> = ({ workspaceState }) => {
const styles = useStyles()
return (
@ -108,7 +108,7 @@ interface LoadingProps {
label: string
}
export const ActionLoadingButton: FC<LoadingProps> = ({ label }) => {
export const ActionLoadingButton: FC<React.PropsWithChildren<LoadingProps>> = ({ label }) => {
const styles = useStyles()
return (
<LoadingButton

View File

@ -8,7 +8,10 @@ export interface DropdownContentProps {
}
/* secondary workspace CTAs */
export const DropdownContent: FC<DropdownContentProps> = ({ secondaryActions, buttonMapping }) => {
export const DropdownContent: FC<React.PropsWithChildren<DropdownContentProps>> = ({
secondaryActions,
buttonMapping,
}) => {
const styles = useStyles()
return (

View File

@ -1,4 +1,4 @@
import { screen } from "@testing-library/react"
import { fireEvent, screen } from "@testing-library/react"
import { WorkspaceStateEnum } from "util/workspace"
import * as Mocks from "../../testHelpers/entities"
import { render } from "../../testHelpers/renderHelpers"
@ -30,7 +30,7 @@ const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
/>,
)
const trigger = await screen.findByTestId("workspace-actions-button")
trigger.click()
fireEvent.click(trigger)
}
describe("WorkspaceActions", () => {

View File

@ -1,7 +1,7 @@
import Button from "@material-ui/core/Button"
import Popover from "@material-ui/core/Popover"
import { makeStyles } from "@material-ui/core/styles"
import { FC, useEffect, useMemo, useRef, useState } from "react"
import { FC, ReactNode, useEffect, useMemo, useRef, useState } from "react"
import { getWorkspaceStatus, WorkspaceStateEnum, WorkspaceStatus } from "util/workspace"
import { Workspace } from "../../api/typesGenerated"
import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows"
@ -32,6 +32,7 @@ export interface WorkspaceActionsProps {
handleDelete: () => void
handleUpdate: () => void
handleCancel: () => void
children?: ReactNode
}
export const WorkspaceActions: FC<WorkspaceActionsProps> = ({

View File

@ -16,7 +16,7 @@ export interface WorkspaceDeletedBannerProps {
handleClick: () => void
}
export const WorkspaceDeletedBanner: FC<WorkspaceDeletedBannerProps> = ({
export const WorkspaceDeletedBanner: FC<React.PropsWithChildren<WorkspaceDeletedBannerProps>> = ({
workspace,
handleClick,
}) => {

View File

@ -36,7 +36,7 @@ export interface WorkspaceScheduleProps {
canUpdateWorkspace: boolean
}
export const WorkspaceSchedule: FC<WorkspaceScheduleProps> = ({
export const WorkspaceSchedule: FC<React.PropsWithChildren<WorkspaceScheduleProps>> = ({
workspace,
canUpdateWorkspace,
}) => {

View File

@ -35,7 +35,7 @@ export const shouldDisplay = (workspace: TypesGen.Workspace): boolean => {
}
}
export const WorkspaceScheduleBanner: FC<WorkspaceScheduleBannerProps> = ({
export const WorkspaceScheduleBanner: FC<React.PropsWithChildren<WorkspaceScheduleBannerProps>> = ({
isLoading,
onExtend,
workspace,

View File

@ -170,7 +170,7 @@ export const validationSchema = Yup.object({
}),
})
export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
export const WorkspaceScheduleForm: FC<React.PropsWithChildren<WorkspaceScheduleFormProps>> = ({
submitScheduleError,
initialValues,
isLoading,
@ -221,7 +221,7 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
<FullPageForm onCancel={onCancel} title={Language.formTitle}>
<form onSubmit={form.handleSubmit} className={styles.form}>
<Stack>
{submitScheduleError && <ErrorSummary error={submitScheduleError} />}
{submitScheduleError ? <ErrorSummary error={submitScheduleError} /> : <></>}
<Section title={Language.startSection}>
<FormControlLabel
control={

View File

@ -14,7 +14,7 @@ export interface WorkspaceSectionProps {
title?: string
}
export const WorkspaceSection: React.FC<WorkspaceSectionProps> = ({
export const WorkspaceSection: React.FC<React.PropsWithChildren<WorkspaceSectionProps>> = ({
action,
children,
contentsProps,

View File

@ -124,7 +124,10 @@ export type WorkspaceStatusBadgeProps = {
className?: string
}
export const WorkspaceStatusBadge: React.FC<WorkspaceStatusBadgeProps> = ({ build, className }) => {
export const WorkspaceStatusBadge: React.FC<React.PropsWithChildren<WorkspaceStatusBadgeProps>> = ({
build,
className,
}) => {
const styles = useStyles()
const theme = useTheme()
const { text, icon, ...colorStyles } = getStatus(theme, build)

View File

@ -21,7 +21,9 @@ const Language = {
outdatedLabel: "Outdated",
}
export const WorkspacesRow: FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ workspaceRef }) => {
export const WorkspacesRow: FC<
React.PropsWithChildren<{ workspaceRef: WorkspaceItemMachineRef }>
> = ({ workspaceRef }) => {
const styles = useStyles()
const navigate = useNavigate()
const theme: Theme = useTheme()

View File

@ -22,7 +22,11 @@ export interface WorkspacesTableProps {
filter?: string
}
export const WorkspacesTable: FC<WorkspacesTableProps> = ({ isLoading, workspaceRefs, filter }) => {
export const WorkspacesTable: FC<React.PropsWithChildren<WorkspacesTableProps>> = ({
isLoading,
workspaceRefs,
filter,
}) => {
return (
<TableContainer>
<Table>

View File

@ -24,7 +24,11 @@ interface TableBodyProps {
filter?: string
}
export const WorkspacesTableBody: FC<TableBodyProps> = ({ isLoading, workspaceRefs, filter }) => {
export const WorkspacesTableBody: FC<React.PropsWithChildren<TableBodyProps>> = ({
isLoading,
workspaceRefs,
filter,
}) => {
if (isLoading) {
return <TableLoader />
}

View File

@ -1,5 +1,4 @@
import { waitFor } from "@testing-library/react"
import { renderHook } from "@testing-library/react-hooks"
import { renderHook, waitFor } from "@testing-library/react"
import { dispatchCustomEvent } from "../util/events"
import { useCustomEvent } from "./events"

View File

@ -1,14 +1,14 @@
import { makeStyles } from "@material-ui/core/styles"
import { useActor } from "@xstate/react"
import React, { useContext, useEffect, useState } from "react"
import { Helmet } from "react-helmet"
import { Helmet } from "react-helmet-async"
import { getApiKey } from "../../api/api"
import { CliAuthToken } from "../../components/CliAuthToken/CliAuthToken"
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
import { pageTitle } from "../../util/page"
import { XServiceContext } from "../../xServices/StateContext"
export const CliAuthenticationPage: React.FC = () => {
export const CliAuthenticationPage: React.FC<React.PropsWithChildren<unknown>> = () => {
const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService)
const { me } = authState.context

View File

@ -1,6 +1,7 @@
import { screen, waitFor } from "@testing-library/react"
/* eslint-disable @typescript-eslint/no-floating-promises */
import { screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import * as API from "../../api/api"
import * as API from "api/api"
import { Language as FooterLanguage } from "../../components/FormFooter/FormFooter"
import { MockTemplate, MockWorkspace } from "../../testHelpers/entities"
import { renderWithAuth } from "../../testHelpers/renderHelpers"
@ -14,13 +15,6 @@ const renderCreateWorkspacePage = () => {
})
}
const fillForm = async ({ name = "example" }: { name?: string }) => {
const nameField = await screen.findByLabelText(Language.nameLabel)
await userEvent.type(nameField, name)
const submitButton = await screen.findByText(FooterLanguage.defaultSubmitLabel)
await userEvent.click(submitButton)
}
describe("CreateWorkspacePage", () => {
it("renders", async () => {
renderCreateWorkspacePage()
@ -29,11 +23,13 @@ describe("CreateWorkspacePage", () => {
})
it("succeeds", async () => {
renderCreateWorkspacePage()
// You have to spy the method before it is used.
jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace)
await fillForm({ name: "test" })
// Check if the request was made
await waitFor(() => expect(API.createWorkspace).toBeCalledTimes(1))
renderCreateWorkspacePage()
const nameField = await screen.findByLabelText(Language.nameLabel)
userEvent.type(nameField, "test")
const submitButton = screen.getByText(FooterLanguage.defaultSubmitLabel)
userEvent.click(submitButton)
})
})

View File

@ -1,6 +1,6 @@
import { useMachine } from "@xstate/react"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { Helmet } from "react-helmet-async"
import { useNavigate, useParams } from "react-router-dom"
import { useOrganizationId } from "../../hooks/useOrganizationId"
import { pageTitle } from "../../util/page"

View File

@ -43,7 +43,9 @@ export const validationSchema = Yup.object({
name: nameValidator(Language.nameLabel),
})
export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props) => {
export const CreateWorkspacePageView: FC<React.PropsWithChildren<CreateWorkspacePageViewProps>> = (
props,
) => {
const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
useStyles()
@ -90,15 +92,19 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
if (props.hasTemplateErrors) {
return (
<Stack>
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] && (
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] ? (
<ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]}
/>
) : (
<></>
)}
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] && (
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] ? (
<ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]}
/>
) : (
<></>
)}
</Stack>
)
@ -108,10 +114,12 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
<FullPageForm title="Create workspace" onCancel={props.onCancel}>
<form onSubmit={form.handleSubmit}>
<Stack>
{props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] && (
{props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR] ? (
<ErrorSummary
error={props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]}
/>
) : (
<></>
)}
<TextField
disabled

View File

@ -1,4 +1,4 @@
import { act, screen, waitFor } from "@testing-library/react"
import { fireEvent, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { rest } from "msw"
import { Language } from "../../components/SignInForm/SignInForm"
@ -42,7 +42,7 @@ describe("LoginPage", () => {
await userEvent.type(password, "password")
// Click sign-in
const signInButton = await screen.findByText(Language.passwordSignIn)
act(() => signInButton.click())
fireEvent.click(signInButton)
// Then
const errorMessage = await screen.findByText(Language.errorMessages.authError)

View File

@ -1,7 +1,7 @@
import { useActor } from "@xstate/react"
import { SignInLayout } from "components/SignInLayout/SignInLayout"
import React, { useContext } from "react"
import { Helmet } from "react-helmet"
import { Helmet } from "react-helmet-async"
import { Navigate, useLocation } from "react-router-dom"
import { LoginErrors, SignInForm } from "../../components/SignInForm/SignInForm"
import { pageTitle } from "../../util/page"

View File

@ -1,4 +1,4 @@
import { screen, waitFor } from "@testing-library/react"
import { fireEvent, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import * as API from "api/api"
import { rest } from "msw"
@ -28,7 +28,7 @@ const fillForm = async ({
await userEvent.type(emailField, email)
await userEvent.type(passwordField, password)
const submitButton = screen.getByRole("button", { name: PageViewLanguage.create })
submitButton.click()
fireEvent.click(submitButton)
}
describe("Setup Page", () => {

View File

@ -1,6 +1,6 @@
import { useActor, useMachine } from "@xstate/react"
import { FC, useContext, useEffect } from "react"
import { Helmet } from "react-helmet"
import { Helmet } from "react-helmet-async"
import { useNavigate } from "react-router-dom"
import { pageTitle } from "util/page"
import { setupMachine } from "xServices/setup/setupXService"

View File

@ -21,6 +21,6 @@ describe("TemplatePage", () => {
await screen.findByText(MockTemplate.name)
screen.getByTestId("markdown")
screen.getByText(MockWorkspaceResource.name)
screen.getByTestId(`version-${MockTemplateVersion.id}`)
screen.queryAllByText(`${MockTemplateVersion.name}`).length
})
})

View File

@ -1,6 +1,6 @@
import { useMachine } from "@xstate/react"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { Helmet } from "react-helmet-async"
import { useParams } from "react-router-dom"
import { Loader } from "../../components/Loader/Loader"
import { useOrganizationId } from "../../hooks/useOrganizationId"
@ -18,7 +18,7 @@ const useTemplateName = () => {
return template
}
export const TemplatePage: FC = () => {
export const TemplatePage: FC<React.PropsWithChildren<unknown>> = () => {
const organizationId = useOrganizationId()
const templateName = useTemplateName()
const [templateState] = useMachine(templateMachine, {

View File

@ -38,7 +38,7 @@ export interface TemplatePageViewProps {
templateVersions?: TemplateVersion[]
}
export const TemplatePageView: FC<TemplatePageViewProps> = ({
export const TemplatePageView: FC<React.PropsWithChildren<TemplatePageViewProps>> = ({
template,
activeTemplateVersion,
templateResources,

View File

@ -1,7 +1,7 @@
import { useMachine } from "@xstate/react"
import { useOrganizationId } from "hooks/useOrganizationId"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { Helmet } from "react-helmet-async"
import { useNavigate, useParams } from "react-router-dom"
import { pageTitle } from "util/page"
import { templateSettingsMachine } from "xServices/templateSettings/templateSettingsXService"

View File

@ -33,7 +33,7 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
return (
<FullPageForm title={Language.title} onCancel={onCancel}>
{errors.getTemplateError && <ErrorSummary error={errors.getTemplateError} />}
{!!errors.getTemplateError && <ErrorSummary error={errors.getTemplateError} />}
{isLoading && <Loader />}
{template && (
<TemplateSettingsForm

Some files were not shown because too many files have changed in this diff Show More