diff --git a/site/.storybook/preview.jsx b/site/.storybook/preview.jsx index 5297d730d9c7a..840e232076a3c 100644 --- a/site/.storybook/preview.jsx +++ b/site/.storybook/preview.jsx @@ -1,14 +1,12 @@ import CssBaseline from "@mui/material/CssBaseline" import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles" -import { createMemoryHistory } from "history" -import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom" + +import { MemoryRouter } from "react-router-dom" import { HelmetProvider } from "react-helmet-async" import { dark } from "../src/theme" import "../src/theme/globalFonts" import "../src/i18n" -const history = createMemoryHistory() - export const decorators = [ (Story) => ( @@ -20,9 +18,9 @@ export const decorators = [ ), (Story) => { return ( - + - + ) }, (Story) => { diff --git a/site/package.json b/site/package.json index aa687dc61ec79..5cd8d328e3951 100644 --- a/site/package.json +++ b/site/package.json @@ -64,7 +64,6 @@ "eventsourcemock": "2.0.0", "formik": "2.4.1", "front-matter": "4.0.2", - "history": "5.3.0", "i18next": "22.5.0", "jest-environment-jsdom": "29.5.0", "jest-location-mock": "1.0.9", @@ -80,7 +79,7 @@ "react-helmet-async": "1.3.0", "react-i18next": "12.2.2", "react-markdown": "8.0.3", - "react-router-dom": "6.4.1", + "react-router-dom": "6.13.0", "react-syntax-highlighter": "15.5.0", "react-use": "17.4.0", "react-virtualized-auto-sizer": "1.0.7", diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index b477e2ffc74f9..1a8fa700ae77c 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -292,7 +292,7 @@ export const AppRouter: FC = () => { /> - + } /> { {/* Terminal and CLI auth pages don't have the dashboard layout */} } /> } /> diff --git a/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx b/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx index 03aa74c68fa4e..642029afdfb8c 100644 --- a/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx +++ b/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx @@ -1,19 +1,6 @@ -import { fireEvent, render, screen } from "@testing-library/react" -import { FC } from "react" -import { WrapperComponent } from "../../../testHelpers/renderHelpers" +import { fireEvent, screen } from "@testing-library/react" import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog" - -namespace Helpers { - export const Component: FC> = ( - props: ConfirmDialogProps, - ) => { - return ( - - - - ) - } -} +import { render } from "testHelpers/renderHelpers" describe("ConfirmDialog", () => { it("renders", () => { @@ -26,7 +13,7 @@ describe("ConfirmDialog", () => { } // When - render() + render() // Then expect(screen.getByRole("dialog")).toBeDefined() @@ -43,7 +30,7 @@ describe("ConfirmDialog", () => { } // When - render() + render() // Then expect(screen.queryByText("CANCEL")).toBeNull() @@ -61,7 +48,7 @@ describe("ConfirmDialog", () => { } // When - render() + render() // Then expect(screen.getByText("CANCEL")).toBeDefined() @@ -79,7 +66,7 @@ describe("ConfirmDialog", () => { } // When - render() + render() // Then expect(screen.getByText("CANCEL")).toBeDefined() @@ -98,7 +85,7 @@ describe("ConfirmDialog", () => { } // When - render() + render() // Then expect(screen.queryByText("CANCEL")).toBeNull() @@ -116,7 +103,7 @@ describe("ConfirmDialog", () => { } // When - render() + render() fireEvent.click(screen.getByText("CANCEL")) // Then @@ -138,7 +125,7 @@ describe("ConfirmDialog", () => { } // When - render() + render() fireEvent.click(screen.getByText("CONFIRM")) // Then diff --git a/site/src/components/UserCell/UserCell.stories.tsx b/site/src/components/UserCell/UserCell.stories.tsx deleted file mode 100644 index feb2002697970..0000000000000 --- a/site/src/components/UserCell/UserCell.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ComponentMeta, Story } from "@storybook/react" -import { MockUser, MockUserAgent } from "testHelpers/entities" -import { UserCell, UserCellProps } from "./UserCell" - -export default { - title: "components/UserCell", - component: UserCell, -} as ComponentMeta - -const Template: Story = (args) => - -export const AuditLogExample = Template.bind({}) -AuditLogExample.args = { - Avatar: { - username: MockUser.username, - avatarURL: "", - }, - caption: MockUserAgent.ip_address, - primaryText: MockUser.email, - onPrimaryTextSelect: () => { - return - }, -} - -export const AuditLogEmptyUserExample = Template.bind({}) -AuditLogEmptyUserExample.args = { - Avatar: { - username: MockUser.username, - avatarURL: "", - }, - caption: MockUserAgent.ip_address, - primaryText: "Deleted User", - onPrimaryTextSelect: undefined, -} diff --git a/site/src/components/UserCell/UserCell.test.tsx b/site/src/components/UserCell/UserCell.test.tsx deleted file mode 100644 index 95c1278f12988..0000000000000 --- a/site/src/components/UserCell/UserCell.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { fireEvent, render, screen } from "@testing-library/react" -import { FC } from "react" -import { MockUser, MockUserAgent } from "testHelpers/entities" -import { WrapperComponent } from "../../testHelpers/renderHelpers" -import { UserCell, UserCellProps } from "./UserCell" - -namespace Helpers { - export const Props: UserCellProps = { - Avatar: { - username: MockUser.username, - avatarURL: "", - }, - caption: MockUserAgent.ip_address, - primaryText: MockUser.username, - onPrimaryTextSelect: jest.fn(), - } - - export const Component: FC> = ( - props, - ) => ( - - - - ) -} - -describe("UserCell", () => { - // callbacks - it("calls onPrimaryTextSelect when primaryText is clicked", () => { - // Given - const onPrimaryTextSelectMock = jest.fn() - const props: UserCellProps = { - ...Helpers.Props, - onPrimaryTextSelect: onPrimaryTextSelectMock, - } - - // When - click the user's email address - render() - fireEvent.click(screen.getByText(props.primaryText)) - - // Then - callback was fired once - expect(onPrimaryTextSelectMock).toHaveBeenCalledTimes(1) - }) - - // primaryText - it("renders primaryText as a link when onPrimaryTextSelect is defined", () => { - // Given - const props: UserCellProps = Helpers.Props - - // When - render() - const primaryTextNode = screen.getByText(props.primaryText) - - // Then - expect(primaryTextNode.tagName).toBe("A") - }) - it("renders primaryText without a link when onPrimaryTextSelect is undefined", () => { - // Given - const props: UserCellProps = { - ...Helpers.Props, - onPrimaryTextSelect: undefined, - } - - // When - render() - const primaryTextNode = screen.getByText(props.primaryText) - - // Then - expect(primaryTextNode.tagName).toBe("P") - }) - - // caption - it("renders caption", () => { - // Given - const caption = "definitely a caption" - const props: UserCellProps = { - ...Helpers.Props, - caption, - } - - // When - render() - - // Then - expect(screen.getByText(caption)).toBeDefined() - }) -}) diff --git a/site/src/components/UserCell/UserCell.tsx b/site/src/components/UserCell/UserCell.tsx deleted file mode 100644 index 7260bb2c139ad..0000000000000 --- a/site/src/components/UserCell/UserCell.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import Box from "@mui/material/Box" -import Link from "@mui/material/Link" -import { makeStyles } from "@mui/styles" -import Typography from "@mui/material/Typography" -import { FC } from "react" -import { UserAvatar, UserAvatarProps } from "../UserAvatar/UserAvatar" - -export interface UserCellProps { - Avatar: UserAvatarProps - /** - * primaryText is rendered beside the avatar - */ - primaryText: string /* | React.ReactNode <-- if needed */ - /** - * caption is rendered beneath the avatar and primaryText - */ - caption?: string /* | React.ReactNode <-- if needed */ - /** - * onPrimaryTextSelect, if defined, is called when the primaryText is clicked - */ - onPrimaryTextSelect?: () => void -} - -const useStyles = makeStyles((theme) => ({ - primaryText: { - color: theme.palette.text.primary, - fontFamily: theme.typography.fontFamily, - fontSize: "16px", - lineHeight: "15px", - marginBottom: "5px", - }, -})) - -/** - * UserCell is a single cell in an audit log table row that contains user-level - * information - */ -export const UserCell: FC> = ({ - Avatar, - caption, - primaryText, - onPrimaryTextSelect, -}) => { - const styles = useStyles() - - return ( - - - - - - - {onPrimaryTextSelect ? ( - - {primaryText} - - ) : ( - {primaryText} - )} - - {caption && ( - - {caption} - - )} - - - ) -} diff --git a/site/src/pages/LoginPage/LoginPage.test.tsx b/site/src/pages/LoginPage/LoginPage.test.tsx index 2054e6513643a..e7dcd782fcfa5 100644 --- a/site/src/pages/LoginPage/LoginPage.test.tsx +++ b/site/src/pages/LoginPage/LoginPage.test.tsx @@ -1,11 +1,11 @@ import { fireEvent, screen } from "@testing-library/react" import userEvent from "@testing-library/user-event" import { rest } from "msw" -import { Route, Routes } from "react-router-dom" +import { createMemoryRouter } from "react-router-dom" import { Language } from "../../components/SignInForm/SignInForm" import { - history, render, + renderWithRouter, waitForLoaderToBeRemoved, } from "../../testHelpers/renderHelpers" import { server } from "../../testHelpers/server" @@ -17,7 +17,6 @@ const { t } = i18n describe("LoginPage", () => { beforeEach(() => { - history.replace("/login") // appear logged out server.use( rest.get("/api/v2/users/me", (req, res, ctx) => { @@ -58,7 +57,6 @@ describe("LoginPage", () => { // Then const errorMessage = await screen.findByText(apiErrorMessage) expect(errorMessage).toBeDefined() - expect(history.location.pathname).toEqual("/login") }) it("shows github authentication when enabled", async () => { @@ -92,11 +90,20 @@ describe("LoginPage", () => { ) // When - render( - - }> - Setup}> - , + renderWithRouter( + createMemoryRouter( + [ + { + path: "/login", + element: , + }, + { + path: "/setup", + element:

Setup

, + }, + ], + { initialEntries: ["/login"] }, + ), ) // Then diff --git a/site/src/pages/SetupPage/SetupPage.test.tsx b/site/src/pages/SetupPage/SetupPage.test.tsx index 616e3439372f6..7d53fe1dce8c2 100644 --- a/site/src/pages/SetupPage/SetupPage.test.tsx +++ b/site/src/pages/SetupPage/SetupPage.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, screen, waitFor } from "@testing-library/react" import userEvent from "@testing-library/user-event" import { rest } from "msw" -import { history, render } from "testHelpers/renderHelpers" +import { render } from "testHelpers/renderHelpers" import { server } from "testHelpers/server" import { SetupPage } from "./SetupPage" import { Language as PageViewLanguage } from "./SetupPageView" @@ -30,7 +30,6 @@ const fillForm = async ({ describe("Setup Page", () => { beforeEach(() => { - history.replace("/setup") // appear logged out server.use( rest.get("/api/v2/users/me", (req, res, ctx) => { diff --git a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx index 56b69d75a246d..d342ace9bdf75 100644 --- a/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/UsersPage/CreateUserPage/CreateUserPage.test.tsx @@ -4,7 +4,6 @@ import { rest } from "msw" import { Language as FormLanguage } from "../../../components/CreateUserForm/CreateUserForm" import { Language as FooterLanguage } from "../../../components/FormFooter/FormFooter" import { - history, renderWithAuth, waitForLoaderToBeRemoved, } from "../../../testHelpers/renderHelpers" @@ -41,10 +40,6 @@ const fillForm = async ({ } describe("Create User Page", () => { - beforeEach(() => { - history.replace("/users/create") - }) - it("shows validation error message", async () => { await renderCreateUserPage() await fillForm({ email: "test" }) diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx index 72a68bc6ffff4..47011b6266779 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx @@ -25,7 +25,7 @@ describe("WorkspaceBuildPage", () => { client.onmessage = async () => { renderWithAuth(, { route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}/builds/${MockWorkspace.latest_build.build_number}`, - path: "/@:username/:workspace/builds/:buildNumber", + path: "/:username/:workspace/builds/:buildNumber", }) await screen.findByText(MockWorkspaceBuild.workspace_name) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 43554667a3868..06b451a1ac6e1 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -45,7 +45,7 @@ const renderWorkspacePage = async () => { }) renderWithAuth(, { route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`, - path: "/@:username/:workspace", + path: "/:username/:workspace", }) await waitForLoaderToBeRemoved() diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 8d8a23a2e18f0..f0c0c2844afae 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -33,10 +33,12 @@ const useFailedBuildLogs = (workspace: Workspace | undefined) => { } export const WorkspacePage: FC = () => { - const { username, workspace: workspaceName } = useParams() as { + const params = useParams() as { username: string workspace: string } + const workspaceName = params.workspace + const username = params.username.replace("@", "") const orgId = useOrganizationId() const [workspaceState, workspaceSend] = useMachine(workspaceMachine, { context: { diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.test.tsx index aae32a16c175d..b100c5bda56f8 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.test.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.test.tsx @@ -40,9 +40,9 @@ test("Submit the workspace settings page successfully", async () => { const user = userEvent.setup() renderWithWorkspaceSettingsLayout(, { route: "/@test-user/test-workspace/settings", - path: "/@:username/:workspace/settings", + path: "/:username/:workspace/settings", // Need this because after submit the user is redirected - extraRoutes: [{ path: "/@:username/:workspace", element:
}], + extraRoutes: [{ path: "/:username/:workspace", element:
}], }) await waitForLoaderToBeRemoved() // Fill the form and submit diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 5e4b36a02ce9e..85d0d0b6b6787 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -258,11 +258,44 @@ describe("WorkspaceSchedulePage", () => { }) }) + describe("autostop", () => { + it("uses template default ttl when first enabled", async () => { + // have autostop disabled + server.use( + rest.get( + "/api/v2/users/:userId/workspace/:workspaceName", + (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ ...MockWorkspace, ttl_ms: 0 }), + ) + }, + ), + ) + renderWithWorkspaceSettingsLayout(, { + route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`, + path: "/:username/:workspace/schedule", + }) + const user = userEvent.setup() + const autostopToggle = await screen.findByLabelText( + FormLanguage.stopSwitch, + ) + // enable autostop + await user.click(autostopToggle) + // find helper text that describes the mock template's 24 hour default + const autostopHelperText = await screen.findByText( + "Your workspace will shut down a day after", + { exact: false }, + ) + expect(autostopHelperText).toBeDefined() + }) + }) + describe("autostop change dialog", () => { it("shows if autostop is changed", async () => { renderWithWorkspaceSettingsLayout(, { route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`, - path: "/@:username/:workspace/schedule", + path: "/:username/:workspace/schedule", }) const user = userEvent.setup() const autostopToggle = await screen.findByLabelText( @@ -281,7 +314,10 @@ describe("WorkspaceSchedulePage", () => { it("doesn't show if autostop is not changed", async () => { renderWithWorkspaceSettingsLayout(, { route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`, - path: "/@:username/:workspace/schedule", + path: "/:username/:workspace/schedule", + extraRoutes: [ + { path: "/:username/:workspace", element:
Workspace
}, + ], }) const user = userEvent.setup() const autostartToggle = await screen.findByLabelText( @@ -297,37 +333,4 @@ describe("WorkspaceSchedulePage", () => { expect(dialog).not.toBeInTheDocument() }) }) - - describe("autostop", () => { - it("uses template default ttl when first enabled", async () => { - // have autostop disabled - server.use( - rest.get( - "/api/v2/users/:userId/workspace/:workspaceName", - (req, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ ...MockWorkspace, ttl_ms: 0 }), - ) - }, - ), - ) - renderWithWorkspaceSettingsLayout(, { - route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`, - path: "/@:username/:workspace/schedule", - }) - const user = userEvent.setup() - const autostopToggle = await screen.findByLabelText( - FormLanguage.stopSwitch, - ) - // enable autostop - await user.click(autostopToggle) - // find helper text that describes the mock template's 24 hour default - const autostopHelperText = await screen.findByText( - "Your workspace will shut down a day after", - { exact: false }, - ) - expect(autostopHelperText).toBeDefined() - }) - }) }) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index 710266930228c..778384fe8e7b6 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -16,7 +16,6 @@ import { pageTitle } from "utils/page" import { scheduleChanged } from "utils/schedule" import * as TypesGen from "../../../api/typesGenerated" import { WorkspaceScheduleForm } from "../../../components/WorkspaceScheduleForm/WorkspaceScheduleForm" -import { firstOrItem } from "../../../utils/array" import { workspaceSchedule } from "../../../xServices/workspaceSchedule/workspaceScheduleXService" import { formValuesToAutostartRequest, @@ -41,11 +40,10 @@ const useStyles = makeStyles((theme) => ({ export const WorkspaceSchedulePage: FC = () => { const { t } = useTranslation("workspaceSchedulePage") const styles = useStyles() - const { username: usernameQueryParam, workspace: workspaceQueryParam } = - useParams() + const params = useParams() as { username: string; workspace: string } const navigate = useNavigate() - const username = firstOrItem(usernameQueryParam, null) - const workspaceName = firstOrItem(workspaceQueryParam, null) + const username = params.username.replace("@", "") + const workspaceName = params.workspace const { workspace } = useWorkspaceSettingsContext() const [scheduleState, scheduleSend] = useMachine(workspaceSchedule, { context: { workspace }, diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx index 7f864abce1e5e..0abecb68a2354 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx @@ -43,10 +43,12 @@ export const useWorkspaceSettingsContext = () => { export const WorkspaceSettingsLayout: FC = () => { const styles = useStyles() - const { workspace: workspaceName, username } = useParams() as { + const params = useParams() as { workspace: string username: string } + const workspaceName = params.workspace + const username = params.username.replace("@", "") const { data: settings } = useWorkspace(username, workspaceName) return ( diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.test.tsx index 049fc97f131d7..eaf5afaeb14be 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.test.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.test.tsx @@ -21,9 +21,9 @@ test("Submit the workspace settings page successfully", async () => { const user = userEvent.setup() renderWithWorkspaceSettingsLayout(, { route: "/@test-user/test-workspace/settings", - path: "/@:username/:workspace/settings", + path: "/:username/:workspace/settings", // Need this because after submit the user is redirected - extraRoutes: [{ path: "/@:username/:workspace", element:
}], + extraRoutes: [{ path: "/:username/:workspace", element:
}], }) await waitForLoaderToBeRemoved() // Fill the form and submit diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx index 0e3fc4f06dec4..89b927e526e8d 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.tsx @@ -9,10 +9,12 @@ import { patchWorkspace } from "api/api" import { WorkspaceSettingsFormValues } from "./WorkspaceSettingsForm" const WorkspaceSettingsPage = () => { - const { username, workspace: workspaceName } = useParams() as { - username: string + const params = useParams() as { workspace: string + username: string } + const workspaceName = params.workspace + const username = params.username.replace("@", "") const { workspace } = useWorkspaceSettingsContext() const navigate = useNavigate() const mutation = useMutation({ diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx index 27381f62fc729..d030f0bd8901d 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx @@ -2,7 +2,7 @@ import { screen } from "@testing-library/react" import { rest } from "msw" import * as CreateDayString from "utils/createDayString" import { MockWorkspace, MockWorkspacesResponse } from "testHelpers/entities" -import { history, renderWithAuth } from "testHelpers/renderHelpers" +import { renderWithAuth } from "testHelpers/renderHelpers" import { server } from "testHelpers/server" import WorkspacesPage from "./WorkspacesPage" import { i18n } from "i18n" @@ -11,7 +11,6 @@ const { t } = i18n describe("WorkspacesPage", () => { beforeEach(() => { - history.replace("/workspaces") // Mocking the dayjs module within the createDayString file const mock = jest.spyOn(CreateDayString, "createDayString") mock.mockImplementation(() => "a minute ago") diff --git a/site/src/testHelpers/renderHelpers.tsx b/site/src/testHelpers/renderHelpers.tsx index 0110d207df079..47862b5d29b07 100644 --- a/site/src/testHelpers/renderHelpers.tsx +++ b/site/src/testHelpers/renderHelpers.tsx @@ -1,40 +1,52 @@ import { - render as wrappedRender, - RenderResult, + render as tlRender, screen, waitForElementToBeRemoved, } from "@testing-library/react" import { AppProviders } from "app" import { DashboardLayout } from "components/Dashboard/DashboardLayout" -import { createMemoryHistory } from "history" import { i18n } from "i18n" import { TemplateSettingsLayout } from "pages/TemplateSettingsPage/TemplateSettingsLayout" import { WorkspaceSettingsLayout } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout" -import { FC, ReactElement } from "react" import { I18nextProvider } from "react-i18next" import { - unstable_HistoryRouter as HistoryRouter, RouterProvider, createMemoryRouter, RouteObject, } from "react-router-dom" import { RequireAuth } from "../components/RequireAuth/RequireAuth" import { MockUser } from "./entities" +import { ReactNode } from "react" -export const history = createMemoryHistory() - -export const WrapperComponent: FC> = ({ - children, -}) => { - return ( - - {children} - +const baseRender = (element: ReactNode) => { + return tlRender( + + {element} + , ) } -export const render = (component: ReactElement): RenderResult => { - return wrappedRender({component}) +export const renderWithRouter = ( + router: ReturnType, +) => { + return { + ...baseRender(), + router, + } +} + +export const render = (element: ReactNode) => { + return renderWithRouter( + createMemoryRouter( + [ + { + path: "/", + element, + }, + ], + { initialEntries: ["/"] }, + ), + ) } type RenderWithAuthOptions = { @@ -69,19 +81,12 @@ export function renderWithAuth( ...nonAuthenticatedRoutes, ] - const router = createMemoryRouter(routes, { initialEntries: [route] }) - - const renderResult = wrappedRender( - - - - - , + const renderResult = renderWithRouter( + createMemoryRouter(routes, { initialEntries: [route] }), ) return { user: MockUser, - router, ...renderResult, } } @@ -113,19 +118,12 @@ export function renderWithTemplateSettingsLayout( ...nonAuthenticatedRoutes, ] - const router = createMemoryRouter(routes, { initialEntries: [route] }) - - const renderResult = wrappedRender( - - - - - , + const renderResult = renderWithRouter( + createMemoryRouter(routes, { initialEntries: [route] }), ) return { user: MockUser, - router, ...renderResult, } } @@ -148,7 +146,7 @@ export function renderWithWorkspaceSettingsLayout( children: [ { element: , - children: [{ path, element }, ...extraRoutes], + children: [{ element, path }, ...extraRoutes], }, ], }, @@ -157,19 +155,12 @@ export function renderWithWorkspaceSettingsLayout( ...nonAuthenticatedRoutes, ] - const router = createMemoryRouter(routes, { initialEntries: [route] }) - - const renderResult = wrappedRender( - - - - - , + const renderResult = renderWithRouter( + createMemoryRouter(routes, { initialEntries: [route] }), ) return { user: MockUser, - router, ...renderResult, } } diff --git a/site/src/utils/array.test.ts b/site/src/utils/array.test.ts deleted file mode 100644 index d6060eb859eb0..0000000000000 --- a/site/src/utils/array.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { firstOrItem } from "./array" - -describe("array", () => { - describe("firstOrItem", () => { - it("returns null if empty array", () => { - expect(firstOrItem([], null)).toBeNull() - }) - - it("returns first item if array with more one item", () => { - expect(firstOrItem(["a", "b"], "c")).toEqual("a") - }) - - it("returns item if single item", () => { - expect(firstOrItem("c", "d")).toEqual("c") - }) - }) -}) diff --git a/site/src/utils/array.ts b/site/src/utils/array.ts deleted file mode 100644 index fdd76789c8af9..0000000000000 --- a/site/src/utils/array.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Helper function that, given an array or a single item: - * - If an array with no elements, returns null - * - If an array with 1 or more elements, returns the first element - * - If a single item, returns that item - */ -export const firstOrItem = ( - itemOrItems: undefined | T | T[], - defaults: T, -): T => { - if (Array.isArray(itemOrItems)) { - return itemOrItems.length > 0 ? itemOrItems[0] : defaults - } - - if (typeof itemOrItems === "undefined") { - return defaults - } - - return itemOrItems -} diff --git a/site/yarn.lock b/site/yarn.lock index b2eed654b998b..212b518ef76ce 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -1004,7 +1004,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== @@ -1932,10 +1932,10 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7" integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw== -"@remix-run/router@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.1.tgz#88d7ac31811ab0cef14aaaeae2a0474923b278bc" - integrity sha512-eBV5rvW4dRFOU1eajN7FmYxjAIVz/mRHgUE9En9mBn6m3mulK3WTR5C3iQhL9MZ14rWAq+xOlEaCkDiW0/heOg== +"@remix-run/router@1.6.3": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.3.tgz#8205baf6e17ef93be35bf62c37d2d594e9be0dad" + integrity sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q== "@rollup/pluginutils@^4.2.0": version "4.2.1" @@ -6703,13 +6703,6 @@ highlight.js@^10.4.1, highlight.js@~10.7.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== -history@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" - integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== - dependencies: - "@babel/runtime" "^7.7.6" - hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -10005,20 +9998,20 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-router-dom@6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.4.1.tgz#99c9b7c4967890701c888517475aa5d54d25760e" - integrity sha512-MY7NJCrGNVJtGp8ODMOBHu20UaIkmwD2V3YsAOUQoCXFk7Ppdwf55RdcGyrSj+ycSL9Uiwrb3gTLYSnzcRoXww== +react-router-dom@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.13.0.tgz#6651f456bb2af42ef14f6880123b1f575539e81f" + integrity sha512-6Nqoqd7fgwxxVGdbiMHTpDHCYPq62d7Wk1Of7B82vH7ZPwwsRaIa22zRZKPPg413R5REVNiyuQPKDG1bubcOFA== dependencies: - "@remix-run/router" "1.0.1" - react-router "6.4.1" + "@remix-run/router" "1.6.3" + react-router "6.13.0" -react-router@6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.4.1.tgz#dd9cc4dfa264751d143a4b6c9d4faa60ab3ce26c" - integrity sha512-OJASKp5AykDWFewgWUim1vlLr7yfD4vO/h+bSgcP/ix8Md+LMHuAjovA74MQfsfhQJGGN1nHRhwS5qQQbbBt3A== +react-router@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.13.0.tgz#7e4427a271dae0cafbdb88c56ccbd9b1434ee93f" + integrity sha512-Si6KnfEnJw7gUQkNa70dlpI1bul46FuSxX5t5WwlUBxE25DAz2BjVkwaK8Y2s242bQrZPXCpmwLPtIO5pv4tXg== dependencies: - "@remix-run/router" "1.0.1" + "@remix-run/router" "1.6.3" react-syntax-highlighter@15.5.0: version "15.5.0"