Files
calendar/packages/features/bookings/lib/getCalendarLinks.test.ts
T
Benny JooandGitHub d908403a99 refactor: circular deps between app store and lib [1] (#23653)
* move dailyApiFetcher.ts to app store package

* update imports

* bookingLocationService

* revert

* move findFieldValueByIdentifier

* wip

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* move to features package

* update imports

* update imports

* move

* fix typechecks

* move getCalendarLinks

* mv event test
2025-09-09 14:38:29 +00:00

215 lines
8.0 KiB
TypeScript

import type { TFunction } from "i18next";
import { createEvent } from "ics";
import { RRule } from "rrule";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import dayjs from "@calcom/dayjs";
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
import { getCalendarLinks, CalendarLinkType } from "./getCalendarLinks";
// Mock dependencies
vi.mock("ics", () => ({
createEvent: vi.fn(),
}));
vi.mock("@calcom/lib/isRecurringEvent", () => ({
parseRecurringEvent: vi.fn(),
}));
describe("getCalendarLinks", () => {
// Mock data for tests
const mockStartTime = new Date("2023-01-15T10:00:00Z");
const mockEndTime = new Date("2023-01-15T11:00:00Z");
const mockDayjsStartTime = dayjs(mockStartTime);
const mockDayjsEndTime = dayjs(mockEndTime);
// Mock translation function
const mockT = ((key: string) => key) as TFunction;
// Mock createEvent implementation
const mockIcsValue = "BEGIN:VCALENDAR\nVERSION:2.0\nEND:VCALENDAR";
// Baseline mock objects that can be reused across tests
const baseMockBooking = {
startTime: mockStartTime,
endTime: mockEndTime,
location: "Test Location",
title: "Test Title",
responses: {},
metadata: {},
};
const baseMockEventType = {
recurringEvent: {},
description: "Test Description",
eventName: "Test Event",
isDynamic: false,
length: 60,
team: null,
users: [{ name: "Test User" }],
title: "Test Title",
};
beforeEach(() => {
// Reset mocks before each test
vi.resetAllMocks();
// Default mock implementations
(createEvent as jest.Mock).mockReturnValue({ value: mockIcsValue });
(parseRecurringEvent as jest.Mock).mockReturnValue(null);
});
afterEach(() => {
vi.clearAllMocks();
});
it("should return all calendar links", async () => {
const result = getCalendarLinks({ booking: baseMockBooking, eventType: baseMockEventType, t: mockT });
const googleLink = result.find((link) => link.id === CalendarLinkType.GOOGLE_CALENDAR);
const microsoftOfficeLink = result.find((link) => link.id === CalendarLinkType.MICROSOFT_OFFICE);
const microsoftOutlookLink = result.find((link) => link.id === CalendarLinkType.MICROSOFT_OUTLOOK);
const icsLink = result.find((link) => link.id === CalendarLinkType.ICS);
expect(result).toHaveLength(4);
expect(googleLink).toBeDefined();
expect(microsoftOfficeLink).toBeDefined();
expect(microsoftOutlookLink).toBeDefined();
expect(icsLink).toBeDefined();
(function verifyGoogleLink() {
expect(googleLink?.link).toContain("https://calendar.google.com/calendar/r/eventedit");
expect(googleLink?.link).toContain("dates=");
expect(googleLink?.link).toContain(
`${mockDayjsStartTime.utc().format("YYYYMMDDTHHmmss[Z]")}/${mockDayjsEndTime
.utc()
.format("YYYYMMDDTHHmmss[Z]")}`
);
expect(googleLink?.link).toContain(`details=${encodeURIComponent(baseMockEventType.description)}`);
})();
(function verifyMicrosoftOfficeLink() {
expect(microsoftOfficeLink?.link).toContain("https://outlook.office.com/calendar/0/deeplink/compose");
expect(microsoftOfficeLink?.link).toContain("enddt=");
expect(microsoftOfficeLink?.link).toContain("startdt=");
expect(microsoftOfficeLink?.link).toContain("body=Test%20Description");
})();
(function verifymicrosoftOutlookLink() {
expect(microsoftOutlookLink?.link).toContain("https://outlook.live.com/calendar/0/deeplink/compose");
expect(microsoftOutlookLink?.link).toContain("enddt=");
expect(microsoftOutlookLink?.link).toContain("startdt=");
expect(microsoftOutlookLink?.link).toContain("body=Test%20Description");
})();
(function verifyIcsLink() {
expect(icsLink).toBeDefined();
expect(icsLink?.link).toContain("data:text/calendar");
expect(icsLink?.link).toContain(encodeURIComponent(mockIcsValue));
expect(createEvent).toHaveBeenCalled();
expect(createEvent).toHaveBeenCalledWith(
expect.objectContaining({
description: baseMockEventType.description,
})
);
})();
});
it("should use videoCallUrl from metadata when available", async () => {
const videoCallUrl = "https://zoom.com/test";
const booking = {
...baseMockBooking,
metadata: { videoCallUrl },
};
const result = getCalendarLinks({ booking, eventType: baseMockEventType, t: mockT });
// Check that all links contain the videoCallUrl
const googleLink = result.find((link) => link.id === CalendarLinkType.GOOGLE_CALENDAR);
const microsoftOfficeLink = result.find((link) => link.id === CalendarLinkType.MICROSOFT_OFFICE);
const microsoftOutlookLink = result.find((link) => link.id === CalendarLinkType.MICROSOFT_OUTLOOK);
expect(googleLink?.link).toContain(`location=${encodeURIComponent(videoCallUrl)}`);
expect(microsoftOfficeLink?.link).toContain(`location=${encodeURIComponent(videoCallUrl)}`);
expect(microsoftOutlookLink?.link).toContain(`location=${encodeURIComponent(videoCallUrl)}`);
});
it("should handle custom title from dynamic event", async () => {
const customTitle = "Custom Dynamic Title";
const booking = {
...baseMockBooking,
responses: { title: customTitle, name: "Test Attendee" },
};
const eventType = {
...baseMockEventType,
isDynamic: true, // This makes it use the custom title
};
const result = getCalendarLinks({ booking, eventType, t: mockT });
const googleLink = result.find((link) => link.id === CalendarLinkType.GOOGLE_CALENDAR);
expect(googleLink?.link).toContain(`details=${encodeURIComponent(eventType.description)}`);
expect(googleLink?.link).toContain(`text=${customTitle}`);
// Check Office 365 link
const microsoftOfficeLink = result.find((link) => link.id === CalendarLinkType.MICROSOFT_OFFICE);
expect(microsoftOfficeLink?.link).toContain("body=Test%20Description");
expect(microsoftOfficeLink?.link).toContain(`subject=${customTitle}`);
// Check Outlook link
const microsoftOutlookLink = result.find((link) => link.id === CalendarLinkType.MICROSOFT_OUTLOOK);
expect(microsoftOutlookLink?.link).toContain("body=Test%20Description");
expect(microsoftOutlookLink?.link).toContain(`subject=${encodeURIComponent(customTitle)}`);
});
it("should handle recurring events - Only Google Calendar supports at the moment", async () => {
// Mock a recurring event rule
const mockRecurringRule = {
freq: 2, // WEEKLY
interval: 1,
count: 5,
};
(parseRecurringEvent as jest.Mock).mockReturnValue(mockRecurringRule);
const eventType = {
...baseMockEventType,
recurringEvent: { count: 5, freq: 2, interval: 1 },
};
const mockRRuleString = "FREQ=WEEKLY;INTERVAL=1;COUNT=5";
vi.spyOn(RRule.prototype, "toString").mockReturnValue(mockRRuleString);
const result = getCalendarLinks({ booking: baseMockBooking, eventType, t: mockT });
const googleLink = result.find((link) => link.id === "googleCalendar");
expect(googleLink).toBeDefined();
expect(googleLink?.link).toContain(`recur=${encodeURIComponent(mockRRuleString)}`);
});
it("should handle error in ICS generation", async () => {
// Mock createEvent to return an error
(createEvent as jest.Mock).mockReturnValue({ error: new Error("ICS generation failed") });
// Mock console.error to avoid test output noise
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
const result = getCalendarLinks({ booking: baseMockBooking, eventType: baseMockEventType, t: mockT });
const icsLink = result.find((link) => link.id === "ics");
expect(icsLink).toBeDefined();
expect(icsLink?.link).toBe("");
expect(consoleErrorSpy).toHaveBeenCalled();
// Verify createEvent was called with the correct description
expect(createEvent).toHaveBeenCalledWith(
expect.objectContaining({
description: baseMockEventType.description,
})
);
consoleErrorSpy.mockRestore();
});
});