Files
calendar/apps/web/modules/videos/__tests__/cal-video-premium-features.test.tsx
10bf982028 feat: add setting to automatic start recording (#21853)
* feat: disable transcription setting in cal video

* feat: add enable automatic recording feature

* chore: only when organizer joins

* chore: update name of variable

* fix: improvements

* chore: improvements

* chore: add badge

* refactor: cal video premium

* tests: add tests for cal video

* fix: use isOrganizer

* Simplify type to remove 'as SessionUser' from new code

---------

Co-authored-by: Benny Joo <[email protected]>
Co-authored-by: Alex van Andel <[email protected]>
2025-07-15 16:54:23 +01:00

493 lines
18 KiB
TypeScript

import type { DailyCall } from "@daily-co/daily-js";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { BUTTONS } from "../button-states";
import { createCalVideoCallbacks } from "../cal-video-premium-features";
vi.mock("@calcom/lib/constants", () => ({
TRANSCRIPTION_STARTED_ICON: "/transcription-started-icon.svg",
RECORDING_IN_PROGRESS_ICON: "/recording-in-progress-icon.svg",
TRANSCRIPTION_STOPPED_ICON: "/transcription-stopped-icon.svg",
RECORDING_DEFAULT_ICON: "/recording-default-icon.svg",
}));
const mockDaily: DailyCall = {
startRecording: vi.fn(),
stopRecording: vi.fn(),
startTranscription: vi.fn(),
stopTranscription: vi.fn(),
updateCustomTrayButtons: vi.fn(),
} as unknown as DailyCall;
const createMockRecording = (isRecording = false) => ({
isRecording,
});
const createMockTranscription = (isTranscribing = false) => ({
isTranscribing,
});
describe("CalVideoPremiumFeatures - End-to-End Callback Tests", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("onMeetingJoined", () => {
it("should start transcription automatically when enabled and not already transcribing", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(false), // not transcribing
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: true,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.onMeetingJoined();
expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).not.toHaveBeenCalled();
});
it("should start recording automatically when enabled and not already recording", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(false), // not recording
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: true,
});
callbacks.onMeetingJoined();
expect(mockDaily.startRecording).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).toHaveBeenCalledWith({
videoBitrate: 2000,
});
expect(mockDaily.startTranscription).not.toHaveBeenCalled();
});
it("should start both transcription and recording when both are enabled", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(false),
transcription: createMockTranscription(false),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: true,
enableAutomaticRecordingForOrganizer: true,
});
callbacks.onMeetingJoined();
expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).toHaveBeenCalledTimes(1);
});
it("should not start transcription when already transcribing", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(true), // already transcribing
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: true,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.onMeetingJoined();
expect(mockDaily.startTranscription).not.toHaveBeenCalled();
});
it("should not start recording when already recording", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(true), // already recording
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: true,
});
callbacks.onMeetingJoined();
expect(mockDaily.startRecording).not.toHaveBeenCalled();
});
});
describe("onCustomButtonClick - Recording Button", () => {
it("should start recording when click occurs on Recording button and recording is stopped", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(false), // not recording
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.onCustomButtonClick({ button_id: "recording" });
expect(mockDaily.startRecording).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).toHaveBeenCalledWith({
videoBitrate: 2000,
});
expect(mockDaily.stopRecording).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.WAIT_FOR_RECORDING_TO_START,
transcription: BUTTONS.START_TRANSCRIPTION, // current state included
});
});
it("should stop recording when click occurs on Recording button and recording is started", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(true), // currently recording
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.onCustomButtonClick({ button_id: "recording" });
expect(mockDaily.stopRecording).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.WAIT_FOR_RECORDING_TO_STOP,
transcription: BUTTONS.START_TRANSCRIPTION, // current state included
});
});
});
describe("onCustomButtonClick - Transcription Button", () => {
it("should start transcription when click occurs on Transcription button and transcription is stopped", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(false), // not transcribing
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.onCustomButtonClick({ button_id: "transcription" });
expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1);
expect(mockDaily.stopTranscription).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.START_RECORDING, // current state included
transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_START,
});
});
it("should stop transcription when click occurs on Transcription button and transcription is started", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(true), // currently transcribing
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.onCustomButtonClick({ button_id: "transcription" });
expect(mockDaily.stopTranscription).toHaveBeenCalledTimes(1);
expect(mockDaily.startTranscription).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.START_RECORDING, // current state included
transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_STOP,
});
});
});
describe("onRecordingStarted", () => {
it("should update custom tray buttons to show stop recording button", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.onRecordingStarted();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.STOP_RECORDING,
transcription: BUTTONS.START_TRANSCRIPTION, // current state included
});
});
});
describe("onRecordingStopped", () => {
it("should update custom tray buttons to show start recording button", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.onRecordingStopped();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.START_RECORDING,
transcription: BUTTONS.START_TRANSCRIPTION, // current state included
});
});
});
describe("onTranscriptionStarted", () => {
it("should update custom tray buttons to show stop transcription button", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.onTranscriptionStarted();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.START_RECORDING, // current state included
transcription: BUTTONS.STOP_TRANSCRIPTION,
});
});
});
describe("onTranscriptionStopped", () => {
it("should update custom tray buttons to show start transcription button", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.onTranscriptionStopped();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.START_RECORDING, // current state included
transcription: BUTTONS.START_TRANSCRIPTION,
});
});
});
describe("toggleRecording", () => {
it("should start recording when not currently recording", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(false), // not recording
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.toggleRecording();
expect(mockDaily.startRecording).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).toHaveBeenCalledWith({
videoBitrate: 2000,
});
expect(mockDaily.stopRecording).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.WAIT_FOR_RECORDING_TO_START,
transcription: BUTTONS.START_TRANSCRIPTION, // current state included
});
});
it("should stop recording when currently recording", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(true), // currently recording
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.toggleRecording();
expect(mockDaily.stopRecording).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.WAIT_FOR_RECORDING_TO_STOP,
transcription: BUTTONS.START_TRANSCRIPTION, // current state included
});
});
});
describe("toggleTranscription", () => {
it("should start transcription when not currently transcribing", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(false), // not transcribing
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.toggleTranscription();
expect(mockDaily.startTranscription).toHaveBeenCalledTimes(1);
expect(mockDaily.stopTranscription).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.START_RECORDING, // current state included
transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_START,
});
});
it("should stop transcription when currently transcribing", async () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(true), // currently transcribing
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
await callbacks.toggleTranscription();
expect(mockDaily.stopTranscription).toHaveBeenCalledTimes(1);
expect(mockDaily.startTranscription).not.toHaveBeenCalled();
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.START_RECORDING, // current state included
transcription: BUTTONS.WAIT_FOR_TRANSCRIPTION_TO_STOP,
});
});
});
describe("updateCustomTrayButtons", () => {
it("should update both recording and transcription buttons when both are shown", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(false),
transcription: createMockTranscription(false),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.updateCustomTrayButtons({
recording: BUTTONS.STOP_RECORDING,
transcription: BUTTONS.STOP_TRANSCRIPTION,
});
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.STOP_RECORDING,
transcription: BUTTONS.STOP_TRANSCRIPTION,
});
});
it("should update only recording button when transcription button is not shown", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(false),
transcription: createMockTranscription(false),
showRecordingButton: true,
showTranscriptionButton: false,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.updateCustomTrayButtons({
recording: BUTTONS.STOP_RECORDING,
});
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.STOP_RECORDING,
});
});
it("should update only transcription button when recording button is not shown", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(false),
transcription: createMockTranscription(false),
showRecordingButton: false,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.updateCustomTrayButtons({
transcription: BUTTONS.STOP_TRANSCRIPTION,
});
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
transcription: BUTTONS.STOP_TRANSCRIPTION,
});
});
it("should use current state when no override is provided", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(true), // currently recording
transcription: createMockTranscription(false), // not transcribing
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.updateCustomTrayButtons({});
expect(mockDaily.updateCustomTrayButtons).toHaveBeenCalledWith({
recording: BUTTONS.STOP_RECORDING, // because isRecording is true
transcription: BUTTONS.START_TRANSCRIPTION, // because isTranscribing is false
});
});
});
describe("startRecording", () => {
it("should call daily.startRecording with correct parameters", () => {
const callbacks = createCalVideoCallbacks({
daily: mockDaily,
recording: createMockRecording(),
transcription: createMockTranscription(),
showRecordingButton: true,
showTranscriptionButton: true,
enableAutomaticTranscription: false,
enableAutomaticRecordingForOrganizer: false,
});
callbacks.startRecording();
expect(mockDaily.startRecording).toHaveBeenCalledTimes(1);
expect(mockDaily.startRecording).toHaveBeenCalledWith({
videoBitrate: 2000,
});
});
});
});