fix: Remove the right date override on removal (#13988)

Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
Alex van Andelandzomars authored and GitHub committed 2024-03-13 20:30:28 +00:00
1 parent 502a3241e4
commit 2803b5072c
11 files changed
+470 -201

No files matched your search

+165 -112
View File
@@ -1,6 +1,6 @@
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { useMemo, useState } from "react";
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import { DateOverrideInputDialog, DateOverrideList } from "@calcom/features/schedules";
@@ -8,6 +8,7 @@ import Schedule from "@calcom/features/schedules/components/Schedule";
import Shell from "@calcom/features/shell/Shell";
import { classNames } from "@calcom/lib";
import { availabilityAsString } from "@calcom/lib/availability";
import { withErrorFromUnknown } from "@calcom/lib/getClientErrorFromUnknown";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
@@ -29,13 +30,13 @@ import {
Tooltip,
VerticalDivider,
} from "@calcom/ui";
import { Info, MoreVertical, ArrowLeft, Plus, Trash } from "@calcom/ui/components/icon";
import { ArrowLeft, Info, MoreVertical, Plus, Trash } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
import EditableHeading from "@components/ui/EditableHeading";
type AvailabilityFormValues = {
export type AvailabilityFormValues = {
name: string;
schedule: ScheduleType;
dateOverrides: { ranges: TimeRange[] }[];
@@ -43,11 +44,29 @@ type AvailabilityFormValues = {
isDefault: boolean;
};
const useExcludedDates = () => {
const watchValues = useWatch<AvailabilityFormValues>({ name: "dateOverrides" }) as {
ranges: TimeRange[];
}[];
return useMemo(() => {
return watchValues?.map((field) => dayjs(field.ranges[0].start).utc().format("YYYY-MM-DD"));
}, [watchValues]);
};
const useSettings = () => {
const { data } = useMeQuery();
return {
hour12: data?.timeFormat === 12,
timeZone: data?.timeZone,
};
};
const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
const { remove, append, replace, fields } = useFieldArray<AvailabilityFormValues, "dateOverrides">({
const { hour12 } = useSettings();
const { append, replace, fields } = useFieldArray<AvailabilityFormValues, "dateOverrides">({
name: "dateOverrides",
});
const excludedDates = fields.map((field) => dayjs(field.ranges[0].start).utc().format("YYYY-MM-DD"));
const excludedDates = useExcludedDates();
const { t } = useLocale();
return (
<div className="p-6">
@@ -62,10 +81,10 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
<p className="text-subtle mb-4 text-sm">{t("date_overrides_subtitle")}</p>
<div className="space-y-2">
<DateOverrideList
excludedDates={excludedDates}
remove={remove}
hour12={hour12}
replace={replace}
items={fields}
fields={fields}
excludedDates={excludedDates}
workingHours={workingHours}
/>
<DateOverrideInputDialog
@@ -83,11 +102,85 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
);
};
const DeleteDialogButton = ({
disabled,
scheduleId,
buttonClassName,
onDeleteConfirmed,
}: {
disabled?: boolean;
onDeleteConfirmed?: () => void;
buttonClassName: string;
scheduleId: number;
}) => {
const { t } = useLocale();
const router = useRouter();
const utils = trpc.useUtils();
const { isPending, mutate } = trpc.viewer.availability.schedule.delete.useMutation({
onError: withErrorFromUnknown((err) => {
showToast(err.message, "error");
}),
onSettled: () => {
utils.viewer.availability.list.invalidate();
},
onSuccess: () => {
showToast(t("schedule_deleted_successfully"), "success");
router.push("/availability");
},
});
return (
<Dialog>
<DialogTrigger asChild>
<Button
StartIcon={Trash}
variant="icon"
color="destructive"
aria-label={t("delete")}
className={buttonClassName}
disabled={disabled}
tooltip={disabled ? t("requires_at_least_one_schedule") : t("delete")}
/>
</DialogTrigger>
<ConfirmationDialogContent
isPending={isPending}
variety="danger"
title={t("delete_schedule")}
confirmBtnText={t("delete")}
loadingText={t("delete")}
onConfirm={() => {
scheduleId && mutate({ scheduleId });
onDeleteConfirmed?.();
}}>
{t("delete_schedule_description")}
</ConfirmationDialogContent>
</Dialog>
);
};
// Simplify logic by assuming this will never be opened on a large screen
const SmallScreenSideBar = ({ open, children }: { open: boolean; children: JSX.Element }) => {
return (
<div
className={classNames(
open
? "fadeIn fixed inset-0 z-50 bg-neutral-800 bg-opacity-70 transition-opacity dark:bg-opacity-70 sm:hidden"
: ""
)}>
<div
className={classNames(
"bg-default fixed right-0 z-20 flex h-screen w-80 flex-col space-y-2 overflow-x-hidden rounded-md px-2 pb-3 transition-transform",
open ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"
)}>
{open ? children : null}
</div>
</div>
);
};
export default function Availability() {
const searchParams = useCompatSearchParams();
const { t, i18n } = useLocale();
const router = useRouter();
const utils = trpc.useContext();
const utils = trpc.useUtils();
const me = useMeQuery();
const scheduleId = searchParams?.get("schedule") ? Number(searchParams.get("schedule")) : -1;
const fromEventType = searchParams?.get("fromEventType");
@@ -133,22 +226,6 @@ export default function Availability() {
},
});
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
onSettled: () => {
utils.viewer.availability.list.invalidate();
},
onSuccess: () => {
showToast(t("schedule_deleted_successfully"), "success");
router.push("/availability");
},
});
return (
<Shell
backPath={fromEventType ? true : "/availability"}
@@ -179,88 +256,58 @@ export default function Availability() {
CTA={
<div className="flex items-center justify-end">
<div className="sm:hover:bg-muted hidden items-center rounded-md px-2 sm:flex">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
className="mt-2 cursor-pointer self-center pe-2"
loadingClassName="me-4">
{t("set_to_default")}
</Skeleton>
<Switch
id="hiddenSwitch"
disabled={isPending || schedule?.isDefault}
checked={form.watch("isDefault")}
onCheckedChange={(e) => {
form.setValue("isDefault", e);
}}
/>
{!openSidebar ? (
<>
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
className="mt-2 cursor-pointer self-center pe-2"
loadingClassName="me-4">
{t("set_to_default")}
</Skeleton>
<Controller
control={form.control}
name="isDefault"
render={({ field: { value, onChange } }) => (
<Switch
id="hiddenSwitch"
disabled={isPending || schedule?.isDefault}
checked={value}
onCheckedChange={onChange}
/>
)}
/>
</>
) : null}
</div>
<VerticalDivider className="hidden sm:inline" />
<Dialog>
<DialogTrigger asChild>
<Button
StartIcon={Trash}
variant="icon"
color="destructive"
aria-label={t("delete")}
className="hidden sm:inline"
disabled={schedule?.isLastSchedule}
tooltip={schedule?.isLastSchedule ? t("requires_at_least_one_schedule") : t("delete")}
/>
</DialogTrigger>
<ConfirmationDialogContent
isPending={deleteMutation.isPending}
variety="danger"
title={t("delete_schedule")}
confirmBtnText={t("delete")}
loadingText={t("delete")}
onConfirm={() => {
scheduleId && deleteMutation.mutate({ scheduleId });
}}>
{t("delete_schedule_description")}
</ConfirmationDialogContent>
</Dialog>
<DeleteDialogButton
buttonClassName="hidden sm:inline"
scheduleId={scheduleId}
disabled={schedule?.isLastSchedule}
/>
<VerticalDivider className="hidden sm:inline" />
<div
className={classNames(
openSidebar
? "fadeIn fixed inset-0 z-50 bg-neutral-800 bg-opacity-70 transition-opacity dark:bg-opacity-70 sm:hidden"
: ""
)}>
<div
className={classNames(
"bg-default fixed right-0 z-20 flex h-screen w-80 flex-col space-y-2 overflow-x-hidden rounded-md px-2 pb-3 transition-transform",
openSidebar ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"
)}>
<SmallScreenSideBar open={openSidebar}>
<>
<div className="flex flex-row items-center pt-5">
<Button StartIcon={ArrowLeft} color="minimal" onClick={() => setOpenSidebar(false)} />
<Button
StartIcon={ArrowLeft}
color="minimal"
onClick={() => {
setOpenSidebar(false);
}}
/>
<p className="-ml-2">{t("availability_settings")}</p>
<Dialog>
<DialogTrigger asChild>
<Button
StartIcon={Trash}
variant="icon"
color="destructive"
aria-label={t("delete")}
className="ml-16 inline"
disabled={schedule?.isLastSchedule}
tooltip={schedule?.isLastSchedule ? t("requires_at_least_one_schedule") : t("delete")}
/>
</DialogTrigger>
<ConfirmationDialogContent
isPending={deleteMutation.isPending}
variety="danger"
title={t("delete_schedule")}
confirmBtnText={t("delete")}
loadingText={t("delete")}
onConfirm={() => {
scheduleId && deleteMutation.mutate({ scheduleId });
setOpenSidebar(false);
}}>
{t("delete_schedule_description")}
</ConfirmationDialogContent>
</Dialog>
<DeleteDialogButton
buttonClassName="ml-16 inline"
scheduleId={scheduleId}
disabled={schedule?.isLastSchedule}
onDeleteConfirmed={() => {
setOpenSidebar(false);
}}
/>
</div>
<div className="flex flex-col px-2 py-2">
<Skeleton as={Label}>{t("name")}</Skeleton>
@@ -275,6 +322,7 @@ export default function Availability() {
)}
/>
</div>
<div className="flex h-9 flex-row-reverse items-center justify-end gap-3 px-2">
<Skeleton
as={Label}
@@ -282,13 +330,17 @@ export default function Availability() {
className="mt-2 cursor-pointer self-center pr-2 sm:inline">
{t("set_to_default")}
</Skeleton>
<Switch
id="hiddenSwitch"
disabled={isPending || schedule?.isDefault}
checked={form.watch("isDefault")}
onCheckedChange={(e) => {
form.setValue("isDefault", e);
}}
<Controller
control={form.control}
name="isDefault"
render={({ field: { value, onChange } }) => (
<Switch
id="hiddenSwitch"
disabled={isPending || value}
checked={value}
onCheckedChange={onChange}
/>
)}
/>
</div>
@@ -331,8 +383,9 @@ export default function Availability() {
</div>
</div>
</div>
</div>
</div>
</>
</SmallScreenSideBar>
<div className="border-default border-l-2" />
<Button
className="ml-4 lg:ml-0"
+46 -1
View File
@@ -7,7 +7,7 @@ import { localize } from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
test.describe("Availablity tests", () => {
test.describe("Availablity", () => {
test.beforeEach(async ({ page, users }) => {
const user = await users.create();
await user.apiLogin();
@@ -40,6 +40,51 @@ test.describe("Availablity tests", () => {
await expect(page.locator('[data-testid="troubleshooter-busy-time"]')).toHaveCount(1);
});
test("it can delete date overrides", async ({ page }) => {
await page.getByTestId("schedules").first().click();
await page.getByTestId("add-override").click();
await page.locator('[id="modal-title"]').waitFor();
// always go to the next month so there's enough slots regardless of current time.
await page.getByTestId("incrementMonth").click();
await page.locator('[data-testid="day"][data-disabled="false"]').first().click();
await page.locator('[data-testid="day"][data-disabled="false"]').nth(4).click();
await page.locator('[data-testid="day"][data-disabled="false"]').nth(12).click();
await page.getByTestId("date-override-mark-unavailable").click();
await page.getByTestId("add-override-submit-btn").click();
await page.getByTestId("dialog-rejection").click();
await expect(page.locator('[data-testid="date-overrides-list"] > li')).toHaveCount(3);
await page.locator('[form="availability-form"][type="submit"]').click();
await page.getByTestId("add-override").click();
await page.locator('[id="modal-title"]').waitFor();
// always go to the next month so there's enough slots regardless of current time.
await page.getByTestId("incrementMonth").click();
await page.locator('[data-testid="day"][data-disabled="false"]').nth(2).click();
await page.getByTestId("date-override-mark-unavailable").click();
await page.getByTestId("add-override-submit-btn").click();
await page.getByTestId("dialog-rejection").click();
const dateOverrideList = page.locator('[data-testid="date-overrides-list"] > li');
await expect(dateOverrideList).toHaveCount(4);
await page.locator('[form="availability-form"][type="submit"]').click();
const deleteButton = dateOverrideList.nth(1).getByTestId("delete-button");
// we cannot easily predict the title, as this changes throughout the year.
const deleteButtonTitle = (await deleteButton.getAttribute("title")) as string;
// press the delete button (should remove the .nth 1 element & trigger reorder)
await deleteButton.click();
await page.locator('[form="availability-form"][type="submit"]').click();
await expect(dateOverrideList).toHaveCount(3);
await expect(await page.getByTitle(deleteButtonTitle).isVisible()).toBe(false);
});
test("Schedule listing", async ({ page }) => {
await test.step("Can add a new schedule", async () => {
await page.getByTestId("new-schedule").click();
@@ -1681,6 +1681,7 @@
"meeting_url_variable": "Meeting url",
"meeting_url_info": "The event meeting conference url",
"date_overrides": "Date overrides",
"date_overrides_delete_on_date": "Delete date overrides on {{date}}",
"date_overrides_subtitle": "Add dates when your availability changes from your daily hours.",
"date_overrides_info": "Date overrides are archived automatically after the date has passed",
"date_overrides_dialog_which_hours": "Which hours are you free?",
+2
View File
@@ -93,9 +93,11 @@
"jsdom": "^22.0.0",
"lint-staged": "^12.5.0",
"mailhog": "^4.16.0",
"next-router-mock": "^0.9.12",
"node-ical": "^0.16.1",
"prettier": "^2.8.6",
"prismock": "^1.21.1",
"resize-observer-polyfill": "^1.5.1",
"tsc-absolute": "^1.0.0",
"typescript": "^4.9.4",
"vitest": "^0.34.6",
@@ -0,0 +1,112 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { render, renderHook, screen } from "@testing-library/react";
import { useFieldArray, useForm, FormProvider } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import type { TimeRange } from "@calcom/types/schedule";
import DateOverrideList from "./DateOverrideList";
type FormValues = {
dateOverrides: { ranges: TimeRange[] }[];
};
vi.mock("next/navigation", () => ({
...require("next-router-mock"),
useSearchParams: () => vi.fn(),
usePathname: () => "",
useParams: () => ({}),
ReadonlyURLSearchParams: URLSearchParams,
}));
describe("DateOverrideList", () => {
it("renders DateOverrideList component in military time", () => {
const { result } = renderHook(() =>
useForm<FormValues>({
values: {
dateOverrides: [
{
ranges: [
{
start: dayjs.utc().startOf("day").add(540, "minute").toDate(),
end: dayjs.utc().startOf("day").add(1020, "minute").toDate(),
},
],
},
],
},
})
);
const methods = result.current;
const { control } = methods;
const { result: fieldArrayResult } = renderHook(() => useFieldArray({ control, name: "dateOverrides" }));
render(
<TooltipProvider>
<FormProvider {...methods}>
<DateOverrideList
hour12={false}
fields={fieldArrayResult.current.fields}
replace={fieldArrayResult.current.replace}
workingHours={[]}
/>
</FormProvider>
</TooltipProvider>
);
const dateString = new Intl.DateTimeFormat("en", {
weekday: "long",
month: "long",
day: "numeric",
timeZone: "UTC",
}).format(new Date());
expect(screen.getByText(dateString)).toBeInTheDocument();
expect(screen.getByText(`09:00 - 17:00`)).toBeInTheDocument();
});
it("renders DateOverrideList component in AM/PM", () => {
const { result } = renderHook(() =>
useForm<FormValues>({
values: {
dateOverrides: [
{
ranges: [
{
start: dayjs.utc().startOf("day").add(540, "minute").toDate(),
end: dayjs.utc().startOf("day").add(1020, "minute").toDate(),
},
],
},
],
},
})
);
const methods = result.current;
const { control } = methods;
const { result: fieldArrayResult } = renderHook(() => useFieldArray({ control, name: "dateOverrides" }));
render(
<TooltipProvider>
<FormProvider {...methods}>
<DateOverrideList
hour12={true}
fields={fieldArrayResult.current.fields}
replace={fieldArrayResult.current.replace}
workingHours={[]}
/>
</FormProvider>
</TooltipProvider>
);
const dateString = new Intl.DateTimeFormat("en", {
weekday: "long",
month: "long",
day: "numeric",
timeZone: "UTC",
}).format(new Date());
expect(screen.getByText(dateString)).toBeInTheDocument();
expect(screen.getByText(`9:00 AM - 5:00 PM`)).toBeInTheDocument();
});
});
@@ -1,7 +1,4 @@
import type { UseFieldArrayRemove } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import type { TimeRange, WorkingHours } from "@calcom/types/schedule";
import { Button, DialogTrigger, Tooltip } from "@calcom/ui";
import { Edit2, Trash2 } from "@calcom/ui/components/icon";
@@ -12,37 +9,29 @@ const sortByDate = (a: { ranges: TimeRange[]; id: string }, b: { ranges: TimeRan
return a.ranges[0].start > b.ranges[0].start ? 1 : -1;
};
const useSettings = () => {
const { data } = useMeQuery();
return {
hour12: data?.timeFormat === 12,
timeZone: data?.timeZone,
};
};
// I would like this to be decoupled, but RHF really doesn't support this.
const DateOverrideList = ({
items,
remove,
replace,
workingHours,
excludedDates = [],
hour12,
replace,
fields,
}: {
remove: UseFieldArrayRemove;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
replace: any;
items: { ranges: TimeRange[]; id: string }[];
fields: { ranges: TimeRange[]; id: string }[];
workingHours: WorkingHours[];
excludedDates?: string[];
hour12: boolean;
}) => {
const { t, i18n } = useLocale();
const { hour12 } = useSettings();
const unsortedFieldArrayMap = items.reduce(
const unsortedFieldArrayMap = fields.reduce(
(map: { [id: string]: number }, { id }, index) => ({ ...map, [id]: index }),
{}
);
if (!items.length) {
if (!fields.length) {
return <></>;
}
@@ -56,12 +45,12 @@ const DateOverrideList = ({
return (
<ul className="border-subtle rounded border" data-testid="date-overrides-list">
{items.sort(sortByDate).map((item) => (
{fields.sort(sortByDate).map((item) => (
<li key={item.id} className="border-subtle flex justify-between border-b px-5 py-4 last:border-b-0">
<div>
<h3 className="text-emphasis text-sm">
{new Intl.DateTimeFormat("en-GB", {
weekday: "short",
{new Intl.DateTimeFormat(i18n.language, {
weekday: "long",
month: "long",
day: "numeric",
timeZone: "UTC",
@@ -84,7 +73,7 @@ const DateOverrideList = ({
value={item.ranges}
onChange={(ranges) => {
// update has very weird side-effects with sorting.
replace([...items.filter((currentItem) => currentItem.id !== item.id), { ranges }]);
replace([...fields.filter((currentItem) => currentItem.id !== item.id), { ranges }]);
delete unsortedFieldArrayMap[item.id];
}}
Trigger={
@@ -102,10 +91,21 @@ const DateOverrideList = ({
<Tooltip content="Delete">
<Button
className="text-default"
data-testid="delete-button"
title={t("date_overrides_delete_on_date", {
date: new Intl.DateTimeFormat(i18n.language, {
weekday: "long",
month: "long",
day: "numeric",
timeZone: "UTC",
}).format(item.ranges[0].start),
})}
color="destructive"
variant="icon"
StartIcon={Trash2}
onClick={() => remove(unsortedFieldArrayMap[item.id])}
onClick={() => {
replace([...fields.filter((currentItem) => currentItem.id !== item.id)]);
}}
/>
</Tooltip>
</div>
@@ -6,6 +6,7 @@ import Schedule from "@calcom/features/schedules/components/Schedule";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import type { Schedule as ScheduleType, TimeRange, WorkingHours } from "@calcom/types/schedule";
import {
Button,
@@ -37,8 +38,18 @@ type AvailabilityFormValues = {
isDefault: boolean;
};
const useSettings = () => {
const { data } = useMeQuery();
return {
hour12: data?.timeFormat === 12,
timeZone: data?.timeZone,
};
};
const DateOverride = ({ workingHours, disabled }: { workingHours: WorkingHours[]; disabled?: boolean }) => {
const { remove, append, replace, fields } = useFieldArray<AvailabilityFormValues, "dateOverrides">({
const { hour12 } = useSettings();
const { append, replace, fields } = useFieldArray<AvailabilityFormValues, "dateOverrides">({
name: "dateOverrides",
});
const excludedDates = fields.map((field) => dayjs(field.ranges[0].start).utc().format("YYYY-MM-DD"));
@@ -49,9 +60,9 @@ const DateOverride = ({ workingHours, disabled }: { workingHours: WorkingHours[]
<div className="space-y-2">
<DateOverrideList
excludedDates={excludedDates}
remove={remove}
replace={replace}
items={fields}
fields={fields}
hour12={hour12}
workingHours={workingHours}
/>
<DateOverrideInputDialog
@@ -73,7 +84,7 @@ export function AvailabilityEditSheet(props: Props) {
// This sheet will not be rendered without a selected user
const userId = props.selectedUser?.id as number;
const { t } = useLocale();
const utils = trpc.useContext();
const utils = trpc.useUtils();
const { data: hasEditPermission, isPending: loadingPermissions } =
trpc.viewer.teams.hasEditPermissionForUser.useQuery({
+24
View File
@@ -0,0 +1,24 @@
import { TRPCClientError } from "@trpc/client";
import { HttpError } from "./http-error";
export function getClientErrorFromUnknown(cause: unknown): Error {
if (cause instanceof HttpError) {
const message = `${cause.statusCode}: ${cause.message}`;
return new Error(message);
}
if (cause instanceof TRPCClientError) {
return new Error(cause.message);
}
if (cause instanceof Error) {
return new Error(cause.message);
}
if (typeof cause === "string") {
return new Error(cause);
}
return new Error(`Unhandled error of type '${typeof cause}'. Please reach out for our customer support.`);
}
export const withErrorFromUnknown = (a: (b: Error) => void) => (b: unknown) =>
a(getClientErrorFromUnknown(b));
+6 -1
View File
@@ -1,7 +1,12 @@
import { vi } from "vitest";
import matchers from "@testing-library/jest-dom/matchers";
import ResizeObserver from "resize-observer-polyfill";
import { vi, expect } from "vitest";
import createFetchMock from "vitest-fetch-mock";
global.ResizeObserver = ResizeObserver;
const fetchMocker = createFetchMock(vi);
// sets globalThis.fetch and globalThis.fetchMock to our mocked version
fetchMocker.enableMocks();
expect.extend(matchers);
+7 -10
View File
@@ -34,13 +34,10 @@ const workspaces = packagedEmbedTestsOnly
: [
{
test: {
include: ["packages/**/*.{test,spec}.{ts,js}", "apps/**/*.{test,spec}.{ts,js}"],
exclude: [
"**/node_modules/**/*",
"**/.next/**/*",
"packages/embeds/**/*",
"packages/lib/hooks/**/*",
],
globals: true,
name: "@calcom/features",
include: ["packages/features/**/*.{test,spec}.tsx"],
environment: "jsdom",
setupFiles: ["setupVitest.ts"],
},
},
@@ -56,8 +53,8 @@ const workspaces = packagedEmbedTestsOnly
{
test: {
globals: true,
name: "ui/components",
include: ["packages/ui/components/**/*.{test,spec}.{ts,js,tsx}"],
name: "@calcom/ui",
include: ["packages/ui/components/**/*.{test,spec}.[jt]s?(x)"],
environment: "jsdom",
setupFiles: ["packages/ui/components/test-setup.ts"],
},
@@ -66,7 +63,7 @@ const workspaces = packagedEmbedTestsOnly
test: {
globals: true,
name: "EventTypeAppCardInterface components",
include: ["packages/app-store/_components/**/*.{test,spec}.{ts,js,tsx}"],
include: ["packages/app-store/_components/**/*.{test,spec}.[jt]s?(x)"],
environment: "jsdom",
setupFiles: ["packages/app-store/test-setup.ts"],
},
+68 -49
View File
@@ -3546,7 +3546,7 @@ __metadata:
"@types/react-dom": ^18.0.9
eslint: ^8.34.0
eslint-config-next: ^13.2.1
next: ^14.1.0
next: ^13.5.4
next-auth: ^4.22.1
postcss: ^8.4.18
react: ^18.2.0
@@ -8296,10 +8296,10 @@ __metadata:
languageName: node
linkType: hard
"@next/env@npm:14.1.1":
version: 14.1.1
resolution: "@next/env@npm:14.1.1"
checksum: 9714e716ca26dd024b1680ca9c17be60adf4228a2bd7a1a9f71752cfd8989f005e805477453200cad0a5ed85439161563a248235fc897dc42f762cba742ec62a
"@next/env@npm:14.1.3":
version: 14.1.3
resolution: "@next/env@npm:14.1.3"
checksum: 1645d801acc5c642c57205c354f39d0b95d5641613a0d6e2aff1ab3e7bd34402953e10246484b405086961b91baf044baf0b7a510f020a68427b63f6093bca90
languageName: node
linkType: hard
@@ -8319,9 +8319,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-darwin-arm64@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-darwin-arm64@npm:14.1.1"
"@next/swc-darwin-arm64@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-darwin-arm64@npm:14.1.3"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
@@ -8333,9 +8333,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-darwin-x64@npm:14.1.1"
"@next/swc-darwin-x64@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-darwin-x64@npm:14.1.3"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
@@ -8347,9 +8347,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-linux-arm64-gnu@npm:14.1.1"
"@next/swc-linux-arm64-gnu@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-linux-arm64-gnu@npm:14.1.3"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
@@ -8361,9 +8361,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-linux-arm64-musl@npm:14.1.1"
"@next/swc-linux-arm64-musl@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-linux-arm64-musl@npm:14.1.3"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
@@ -8375,9 +8375,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-linux-x64-gnu@npm:14.1.1"
"@next/swc-linux-x64-gnu@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-linux-x64-gnu@npm:14.1.3"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
@@ -8389,9 +8389,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-linux-x64-musl@npm:14.1.1"
"@next/swc-linux-x64-musl@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-linux-x64-musl@npm:14.1.3"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
@@ -8403,9 +8403,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-win32-arm64-msvc@npm:14.1.1"
"@next/swc-win32-arm64-msvc@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-win32-arm64-msvc@npm:14.1.3"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
@@ -8417,9 +8417,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-win32-ia32-msvc@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-win32-ia32-msvc@npm:14.1.1"
"@next/swc-win32-ia32-msvc@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-win32-ia32-msvc@npm:14.1.3"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
@@ -8431,9 +8431,9 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:14.1.1":
version: 14.1.1
resolution: "@next/swc-win32-x64-msvc@npm:14.1.1"
"@next/swc-win32-x64-msvc@npm:14.1.3":
version: 14.1.3
resolution: "@next/swc-win32-x64-msvc@npm:14.1.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -17356,9 +17356,11 @@ __metadata:
lint-staged: ^12.5.0
lucide-react: ^0.171.0
mailhog: ^4.16.0
next-router-mock: ^0.9.12
node-ical: ^0.16.1
prettier: ^2.8.6
prismock: ^1.21.1
resize-observer-polyfill: ^1.5.1
tsc-absolute: ^1.0.0
turbo: ^1.10.1
typescript: ^4.9.4
@@ -17515,9 +17517,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001579":
version: 1.0.30001591
resolution: "caniuse-lite@npm:1.0.30001591"
checksum: e48f924cdefff86d29d38ee1bffe2cdb1ef55e179d08ae2f1f5546d9d563e030f13755a0096ea87a09498daffd18666d1fe0b2759aea8421bbf4c214b47d410d
version: 1.0.30001597
resolution: "caniuse-lite@npm:1.0.30001597"
checksum: ec6a2cf0fd49f37d16732e6595939fc80a125dcd188a950bc936c61b4ad53becc0fe51bf2d9a625415de7b1cb23bd835f220e8b68d8ab951a940edeea65476fd
languageName: node
linkType: hard
@@ -29894,6 +29896,16 @@ __metadata:
languageName: node
linkType: hard
"next-router-mock@npm:^0.9.12":
version: 0.9.12
resolution: "next-router-mock@npm:0.9.12"
peerDependencies:
next: ">=10.0.0"
react: ">=17.0.0"
checksum: f630a4308686b2f6eeb1138d05ccb40dd2e6312216de1e94811403222967fd9d1ee723b2595d20bffbb1eaffce0d8e8da81983346528c6ac8334d4968338dd96
languageName: node
linkType: hard
"next-seo@npm:^6.0.0":
version: 6.1.0
resolution: "next-seo@npm:6.1.0"
@@ -30005,20 +30017,20 @@ __metadata:
languageName: node
linkType: hard
"next@npm:^14.1, next@npm:^14.1.0":
version: 14.1.1
resolution: "next@npm:14.1.1"
"next@npm:^14.1":
version: 14.1.3
resolution: "next@npm:14.1.3"
dependencies:
"@next/env": 14.1.1
"@next/swc-darwin-arm64": 14.1.1
"@next/swc-darwin-x64": 14.1.1
"@next/swc-linux-arm64-gnu": 14.1.1
"@next/swc-linux-arm64-musl": 14.1.1
"@next/swc-linux-x64-gnu": 14.1.1
"@next/swc-linux-x64-musl": 14.1.1
"@next/swc-win32-arm64-msvc": 14.1.1
"@next/swc-win32-ia32-msvc": 14.1.1
"@next/swc-win32-x64-msvc": 14.1.1
"@next/env": 14.1.3
"@next/swc-darwin-arm64": 14.1.3
"@next/swc-darwin-x64": 14.1.3
"@next/swc-linux-arm64-gnu": 14.1.3
"@next/swc-linux-arm64-musl": 14.1.3
"@next/swc-linux-x64-gnu": 14.1.3
"@next/swc-linux-x64-musl": 14.1.3
"@next/swc-win32-arm64-msvc": 14.1.3
"@next/swc-win32-ia32-msvc": 14.1.3
"@next/swc-win32-x64-msvc": 14.1.3
"@swc/helpers": 0.5.2
busboy: 1.6.0
caniuse-lite: ^1.0.30001579
@@ -30056,7 +30068,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
checksum: 58c17bf9520648cb79c3341a0a011ce19e8e5368f23be7415b4a35787b3562519cfd0dd8e008a1ce0b49dfe79e4ab89127f412cf039cb854b35f79a457a9be22
checksum: 179a5dfd27a3ac900c0c229c523793cf28c3972cf87ec9ce7b68ead8fc2b8cd3b9c0538bba1b42d3351462ffe890b964c869e081da76c3bed29826e7eae4d7a4
languageName: node
linkType: hard
@@ -34749,6 +34761,13 @@ __metadata:
languageName: node
linkType: hard
"resize-observer-polyfill@npm:^1.5.1":
version: 1.5.1
resolution: "resize-observer-polyfill@npm:1.5.1"
checksum: 57e7f79489867b00ba43c9c051524a5c8f162a61d5547e99333549afc23e15c44fd43f2f318ea0261ea98c0eb3158cca261e6f48d66e1ed1cd1f340a43977094
languageName: node
linkType: hard
"resolve-alpn@npm:^1.0.0":
version: 1.2.1
resolution: "resolve-alpn@npm:1.2.1"