chore: bump react-router-dom from 6.4.1 to 6.12.1 in /site (#7950)

* chore: bump react-router-dom from 6.4.1 to 6.12.1 in /site

Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.4.1 to 6.12.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.12.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix what needs to be fixed

* fix storybook

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: BrunoQuaresma <bruno_nonato_quaresma@hotmail.com>
This commit is contained in:
dependabot[bot]
2023-06-15 10:34:49 -03:00
committed by GitHub
co-authored by BrunoQuaresma
parent 6e598234b6
commit 33c0674d76
24 changed files with 142 additions and 394 deletions
+2 -2
View File
@@ -292,7 +292,7 @@ export const AppRouter: FC = () => {
/>
</Route>
<Route path="/@:username">
<Route path="/:username">
<Route path=":workspace">
<Route index element={<WorkspacePage />} />
<Route
@@ -316,7 +316,7 @@ export const AppRouter: FC = () => {
{/* Terminal and CLI auth pages don't have the dashboard layout */}
<Route
path="/@:username/:workspace/terminal"
path="/:username/:workspace/terminal"
element={<TerminalPage />}
/>
<Route path="cli-auth" element={<CliAuthenticationPage />} />
@@ -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<React.PropsWithChildren<ConfirmDialogProps>> = (
props: ConfirmDialogProps,
) => {
return (
<WrapperComponent>
<ConfirmDialog {...props} />
</WrapperComponent>
)
}
}
import { render } from "testHelpers/renderHelpers"
describe("ConfirmDialog", () => {
it("renders", () => {
@@ -26,7 +13,7 @@ describe("ConfirmDialog", () => {
}
// When
render(<Helpers.Component {...props} />)
render(<ConfirmDialog {...props} />)
// Then
expect(screen.getByRole("dialog")).toBeDefined()
@@ -43,7 +30,7 @@ describe("ConfirmDialog", () => {
}
// When
render(<Helpers.Component {...props} />)
render(<ConfirmDialog {...props} />)
// Then
expect(screen.queryByText("CANCEL")).toBeNull()
@@ -61,7 +48,7 @@ describe("ConfirmDialog", () => {
}
// When
render(<Helpers.Component {...props} />)
render(<ConfirmDialog {...props} />)
// Then
expect(screen.getByText("CANCEL")).toBeDefined()
@@ -79,7 +66,7 @@ describe("ConfirmDialog", () => {
}
// When
render(<Helpers.Component {...props} />)
render(<ConfirmDialog {...props} />)
// Then
expect(screen.getByText("CANCEL")).toBeDefined()
@@ -98,7 +85,7 @@ describe("ConfirmDialog", () => {
}
// When
render(<Helpers.Component {...props} />)
render(<ConfirmDialog {...props} />)
// Then
expect(screen.queryByText("CANCEL")).toBeNull()
@@ -116,7 +103,7 @@ describe("ConfirmDialog", () => {
}
// When
render(<Helpers.Component {...props} />)
render(<ConfirmDialog {...props} />)
fireEvent.click(screen.getByText("CANCEL"))
// Then
@@ -138,7 +125,7 @@ describe("ConfirmDialog", () => {
}
// When
render(<Helpers.Component {...props} />)
render(<ConfirmDialog {...props} />)
fireEvent.click(screen.getByText("CONFIRM"))
// Then
@@ -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<typeof UserCell>
const Template: Story<UserCellProps> = (args) => <UserCell {...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,
}
@@ -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<React.PropsWithChildren<UserCellProps>> = (
props,
) => (
<WrapperComponent>
<UserCell {...props} />
</WrapperComponent>
)
}
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(<Helpers.Component {...props} />)
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(<Helpers.Component {...props} />)
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(<Helpers.Component {...props} />)
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(<Helpers.Component {...props} />)
// Then
expect(screen.getByText(caption)).toBeDefined()
})
})
-69
View File
@@ -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<React.PropsWithChildren<UserCellProps>> = ({
Avatar,
caption,
primaryText,
onPrimaryTextSelect,
}) => {
const styles = useStyles()
return (
<Box alignItems="center" display="flex" flexDirection="row">
<Box display="flex" margin="auto 14px auto 0">
<UserAvatar {...Avatar} />
</Box>
<Box display="flex" flexDirection="column">
{onPrimaryTextSelect ? (
<Link className={styles.primaryText} onClick={onPrimaryTextSelect}>
{primaryText}
</Link>
) : (
<Typography className={styles.primaryText}>{primaryText}</Typography>
)}
{caption && (
<Typography color="textSecondary" variant="caption">
{caption}
</Typography>
)}
</Box>
</Box>
)
}
+16 -9
View File
@@ -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(
<Routes>
<Route path="/login" element={<LoginPage />}></Route>
<Route path="/setup" element={<h1>Setup</h1>}></Route>
</Routes>,
renderWithRouter(
createMemoryRouter(
[
{
path: "/login",
element: <LoginPage />,
},
{
path: "/setup",
element: <h1>Setup</h1>,
},
],
{ initialEntries: ["/login"] },
),
)
// Then
+1 -2
View File
@@ -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) => {
@@ -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" })
@@ -25,7 +25,7 @@ describe("WorkspaceBuildPage", () => {
client.onmessage = async () => {
renderWithAuth(<WorkspaceBuildPage />, {
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)
@@ -45,7 +45,7 @@ const renderWorkspacePage = async () => {
})
renderWithAuth(<WorkspacePage />, {
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
path: "/@:username/:workspace",
path: "/:username/:workspace",
})
await waitForLoaderToBeRemoved()
@@ -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: {
@@ -40,9 +40,9 @@ test("Submit the workspace settings page successfully", async () => {
const user = userEvent.setup()
renderWithWorkspaceSettingsLayout(<WorkspaceParametersPage />, {
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: <div /> }],
extraRoutes: [{ path: "/:username/:workspace", element: <div /> }],
})
await waitForLoaderToBeRemoved()
// Fill the form and submit
@@ -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(<WorkspaceSchedulePage />, {
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(<WorkspaceSchedulePage />, {
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(<WorkspaceSchedulePage />, {
route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`,
path: "/@:username/:workspace/schedule",
path: "/:username/:workspace/schedule",
extraRoutes: [
{ path: "/:username/:workspace", element: <div>Workspace</div> },
],
})
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(<WorkspaceSchedulePage />, {
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()
})
})
})
@@ -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 },
@@ -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 (
@@ -21,9 +21,9 @@ test("Submit the workspace settings page successfully", async () => {
const user = userEvent.setup()
renderWithWorkspaceSettingsLayout(<WorkspaceSettingsPage />, {
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: <div /> }],
extraRoutes: [{ path: "/:username/:workspace", element: <div /> }],
})
await waitForLoaderToBeRemoved()
// Fill the form and submit
@@ -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({
@@ -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")
+35 -44
View File
@@ -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<React.PropsWithChildren<unknown>> = ({
children,
}) => {
return (
<AppProviders>
<HistoryRouter history={history}>{children}</HistoryRouter>
</AppProviders>
const baseRender = (element: ReactNode) => {
return tlRender(
<I18nextProvider i18n={i18n}>
<AppProviders>{element}</AppProviders>
</I18nextProvider>,
)
}
export const render = (component: ReactElement): RenderResult => {
return wrappedRender(<WrapperComponent>{component}</WrapperComponent>)
export const renderWithRouter = (
router: ReturnType<typeof createMemoryRouter>,
) => {
return {
...baseRender(<RouterProvider router={router} />),
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(
<I18nextProvider i18n={i18n}>
<AppProviders>
<RouterProvider router={router} />
</AppProviders>
</I18nextProvider>,
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(
<I18nextProvider i18n={i18n}>
<AppProviders>
<RouterProvider router={router} />
</AppProviders>
</I18nextProvider>,
const renderResult = renderWithRouter(
createMemoryRouter(routes, { initialEntries: [route] }),
)
return {
user: MockUser,
router,
...renderResult,
}
}
@@ -148,7 +146,7 @@ export function renderWithWorkspaceSettingsLayout(
children: [
{
element: <WorkspaceSettingsLayout />,
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(
<I18nextProvider i18n={i18n}>
<AppProviders>
<RouterProvider router={router} />
</AppProviders>
</I18nextProvider>,
const renderResult = renderWithRouter(
createMemoryRouter(routes, { initialEntries: [route] }),
)
return {
user: MockUser,
router,
...renderResult,
}
}
-17
View File
@@ -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")
})
})
})
-20
View File
@@ -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 = <T>(
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
}