Files
calendar/apps/web/modules/shell/user-dropdown/UserDropdown.test.tsx
T
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ab21c7f805 refactor: Cal.diy (#28903)
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com

This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main:

- Rebrand Cal.com to Cal.diy across the entire codebase
- Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions
- Switch license from AGPL-3.0 to MIT
- Remove docs/ directory (migrated to Nextra at cal.diy)
- Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc.
- Clean up .env.example for self-hosted Cal.diy
- Update Docker image references to calcom/cal.diy
- Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork
- Add PR welcome bot for Cal.diy contributors
- Fix API v2 breaking changes oasdiff ignore entries
- Replace Blacksmith CI runners with default GitHub Actions

3893 files changed, 20789 insertions(+), 411020 deletions(-)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* rip out org related comments in api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-15 09:52:36 -03:00

265 lines
7.8 KiB
TypeScript

import { render, waitFor } from "@testing-library/react";
import React from "react";
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
vi.mock("next-auth/react", () => ({
signOut: vi.fn(),
}));
vi.mock("next/navigation", () => ({
usePathname: () => "/settings",
}));
vi.mock("@calcom/lib/hooks/useLocale", () => ({
useLocale: () => ({
t: (key: string) => key,
}),
}));
vi.mock("@calcom/lib/hooks/useUserAgentData", () => ({
useUserAgentData: () => ({
os: "linux",
browser: "chrome",
isMobile: false,
}),
}));
const mockUseMeQuery = vi.fn();
vi.mock("@calcom/trpc/react/hooks/useMeQuery", () => ({
default: () => mockUseMeQuery(),
}));
vi.mock("@calcom/web/components/settings/platform/hooks/useGetUserAttributes", () => ({
useGetUserAttributes: () => ({
isPlatformUser: false,
}),
}));
vi.mock("@calcom/web/modules/api-keys/support/lib/freshchat/FreshChatProvider", () => ({
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock("@calcom/ui/components/avatar", () => ({
Avatar: () => <div data-testid="avatar">Avatar</div>,
}));
vi.mock("@calcom/ui/components/icon", () => ({
Icon: ({ name }: { name: string }) => <span data-testid={`icon-${name}`}>{name}</span>,
}));
vi.mock("@coss/ui/components/menu", () => ({
Menu: ({ children }: { children: React.ReactNode }) => <div data-testid="menu">{children}</div>,
MenuTrigger: ({ children, render }: { children: React.ReactNode; render?: React.ReactElement }) => {
if (render) {
return React.cloneElement(render, {}, children);
}
return <div>{children}</div>;
},
MenuPopup: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
MenuItem: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
MenuSeparator: () => <hr />,
MenuSub: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
MenuSubTrigger: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
MenuSubPopup: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}));
vi.mock("@calcom/ui/classNames", () => ({
default: (...args: string[]) => args.filter(Boolean).join(" "),
}));
describe("UserDropdown", () => {
let mockBeacon: ReturnType<typeof vi.fn>;
let mockSupportOpen: ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.clearAllMocks();
mockBeacon = vi.fn();
mockSupportOpen = vi.fn();
Object.defineProperty(window, "Beacon", {
value: mockBeacon,
writable: true,
configurable: true,
});
Object.defineProperty(window, "Support", {
value: { open: mockSupportOpen, shouldShowTriggerButton: vi.fn() },
writable: true,
configurable: true,
});
Object.defineProperty(window, "screen", {
value: { width: 1920, height: 1080 },
writable: true,
configurable: true,
});
});
afterEach(() => {
delete window.Beacon;
delete window.Support;
});
describe("Beacon session-data functionality", () => {
it("should call Beacon with session-data when Beacon is available and user has username", async () => {
mockUseMeQuery.mockReturnValue({
data: { username: "testuser", name: "Test User", avatarUrl: null, avatar: null },
isPending: false,
});
const { UserDropdown } = await import("./UserDropdown");
render(<UserDropdown />);
await waitFor(() => {
expect(mockBeacon).toHaveBeenCalledWith("session-data", {
username: "testuser",
screenResolution: "1920x1080",
});
});
});
it("should call Beacon with 'Unknown' username when user has no username", async () => {
mockUseMeQuery.mockReturnValue({
data: { username: null, name: "Test User", avatarUrl: null, avatar: null },
isPending: false,
});
const { UserDropdown } = await import("./UserDropdown");
render(<UserDropdown />);
await waitFor(() => {
expect(mockBeacon).toHaveBeenCalledWith("session-data", {
username: "Unknown",
screenResolution: "1920x1080",
});
});
});
it("should not throw error when Beacon is undefined", async () => {
delete window.Beacon;
mockUseMeQuery.mockReturnValue({
data: { username: "testuser", name: "Test User", avatarUrl: null, avatar: null },
isPending: false,
});
const { UserDropdown } = await import("./UserDropdown");
// Should not throw
expect(() => render(<UserDropdown />)).not.toThrow();
});
it("should call Beacon when it loads lazily after mount", async () => {
// Start with Beacon undefined (simulating lazy load)
delete window.Beacon;
mockUseMeQuery.mockReturnValue({
data: { username: "testuser", name: "Test User", avatarUrl: null, avatar: null },
isPending: false,
});
const { UserDropdown } = await import("./UserDropdown");
render(<UserDropdown />);
// Beacon should not have been called yet
expect(mockBeacon).not.toHaveBeenCalled();
// Simulate Beacon loading after mount
Object.defineProperty(window, "Beacon", {
value: mockBeacon,
writable: true,
configurable: true,
});
// Wait for the polling interval to detect Beacon
await waitFor(
() => {
expect(mockBeacon).toHaveBeenCalledWith("session-data", {
username: "testuser",
screenResolution: "1920x1080",
});
},
{ timeout: 2000 }
);
});
it("should update Beacon session-data when username changes", async () => {
const { rerender } = render(<div />);
// First render with initial username
mockUseMeQuery.mockReturnValue({
data: { username: "user1", name: "User One", avatarUrl: null, avatar: null },
isPending: false,
});
const { UserDropdown } = await import("./UserDropdown");
const { rerender: rerenderComponent } = render(<UserDropdown />);
await waitFor(() => {
expect(mockBeacon).toHaveBeenCalledWith("session-data", {
username: "user1",
screenResolution: "1920x1080",
});
});
// Clear mock to track new calls
mockBeacon.mockClear();
// Update username
mockUseMeQuery.mockReturnValue({
data: { username: "user2", name: "User Two", avatarUrl: null, avatar: null },
isPending: false,
});
rerenderComponent(<UserDropdown />);
await waitFor(() => {
expect(mockBeacon).toHaveBeenCalledWith("session-data", {
username: "user2",
screenResolution: "1920x1080",
});
});
});
});
describe("Component rendering", () => {
it("should return null when user is not available and not pending", async () => {
mockUseMeQuery.mockReturnValue({
data: null,
isPending: false,
});
const { UserDropdown } = await import("./UserDropdown");
const { container } = render(<UserDropdown />);
// Component should return null
expect(container.firstChild).toBeNull();
});
it("should render dropdown when user is available", async () => {
mockUseMeQuery.mockReturnValue({
data: { username: "testuser", name: "Test User", avatarUrl: null, avatar: null },
isPending: false,
});
const { UserDropdown } = await import("./UserDropdown");
const { getByTestId } = render(<UserDropdown />);
expect(getByTestId("menu")).toBeInTheDocument();
});
it("should render dropdown when isPending is true (loading state)", async () => {
mockUseMeQuery.mockReturnValue({
data: null,
isPending: true,
});
const { UserDropdown } = await import("./UserDropdown");
const { getByTestId } = render(<UserDropdown />);
expect(getByTestId("menu")).toBeInTheDocument();
});
});
});