Files
calendar/apps/web/modules/ee/teams/components/EditMemberSheet.test.tsx
T
Benny JooGitHubbenny@cal.com <sldisek783@gmail.com>benny@cal.com <sldisek783@gmail.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
f5d345b133 refactor: remove @calcom/web imports from @calcom/features and add @calcom/testing package (#26480)
* fix: remove @calcom/web imports from packages/features to eliminate circular dependency

- Migrate UserTableUser and MemberPermissions types to packages/features/users/types/user-table.ts
- Migrate useGeo hook to packages/features/geo/GeoContext.tsx
- Migrate buildLegacyRequest to packages/lib/buildLegacyCtx.ts
- Migrate Calendar component to packages/features/calendars/weeklyview/components/
- Move test utilities (bookingScenario, fixtures) to packages/features/test/
- Update all imports in packages/features to use new locations
- Add re-exports in apps/web for backward compatibility

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

* fix: delete original implementation files and fix type issues

- Delete original calendar component files in apps/web (keep only re-export stubs)
- Migrate OutOfOfficeInSlots to packages/features/bookings/components
- Convert apps/web OutOfOfficeInSlots to re-export stub
- Fix className vs class issue in Calendar.tsx
- Fix @calcom/trpc import violation in user-table.ts by using structural type

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

* fix: add missing isGroup and contains fields to UserTableUser attributes type

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

* fix: update customRole type to match actual Prisma Role model

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

* fix build

* fix

* fix

* cleanup weeklyview

* fix

* refactor to mv MemberPermissions to types package

* add types dependency to features

* fix

* fix

* fix

* fix

* fix

* fix

* rename

* rename

* migrate

* migrate

* migrate

* fix

* fix

* fix

* refactor: move test utilities from packages/features/test to tests/libs

- Move bookingScenario utilities to tests/libs/bookingScenario
- Move fixtures to tests/libs/fixtures
- Update all imports in packages/features test files to use new location
- Update all imports in apps/web test files to use new location
- Eliminates duplication of test utilities between packages/features and apps/web

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

* fix: correct relative import paths for tests/libs in test files

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

* refactor: replace test utility implementations with re-exports to tests/libs

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

* fix: fix test import paths and move signup handler tests to apps/web

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

* fix: move buildLegacyCtx to packages/lib and restore handlers to packages/features

- Move buildLegacyCtx from apps/web/lib to packages/lib to break circular dependency
- Update apps/web/lib/buildLegacyCtx.ts to re-export from @calcom/lib
- Restore signup handlers and tests to packages/features/auth/signup/handlers
- Update handler imports to use @calcom/lib/buildLegacyCtx instead of @calcom/web/lib/buildLegacyCtx

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

* fix: update recurring-event.test.ts imports to use tests/libs path

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

* refactor: delete test re-export files and update imports to use tests/libs directly

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

* fix: update remaining test imports to use tests/libs directly

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

* fix: update handleRecurringEventBooking calls to match function signature (1 arg)

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

* remove

* migrate tests

* migrate tests

* refactor: update test mock imports by removing  and using async  for mock creators.

* fix type errors

* fix: add type assertion for MockUser in p2002.test-suite.ts

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

* fix: restore locale import in compareReminderBodyToTemplate.test.ts using relative path

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

* feat: create @calcom/testing package and migrate tests from /tests directory

- Created new @calcom/testing package in /packages/testing
- Moved all files from /tests to /packages/testing
- Updated all imports across the codebase to use @calcom/testing alias
- Removed /tests directory at root level

This allows other packages like @calcom/features and @calcom/web to import
testing utilities using the @calcom/testing alias instead of relative paths.

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

* fix

* fix

* fix: add missing useBookings export to @calcom/atoms package

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

* fix: add missing useCalendarsBusyTimes and useConnectedCalendars exports to @calcom/atoms

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

* chore: add @calcom/testing as explicit devDependency to packages that use it

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

* refactor: move setupVitest.ts into @calcom/testing package

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

* fix

* chore: add biome rules to restrict @calcom/testing imports

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

* fix

* rename libs to lib

* rename libs to lib

* add rule

* add rule

* refactor: remove @calcom/features imports from @calcom/testing

- Move mockPaymentSuccessWebhookFromStripe to fresh-booking.test.ts
- Replace ProfileRepository.generateProfileUid() with uuidv4()
- Clone Tracking type into @calcom/testing/src/lib/types.ts
- Update imports in expects.ts and getMockRequestDataForBooking.ts
- Move source files into src/ folder
- Move CalendarManager mock to @calcom/features

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

* fix: add explicit exports for nested paths in @calcom/testing

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

* improve

* improve

* fix

* fix

* fix type checks

* fix type checks

* fix type checks

* fix tests

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-08 16:59:11 +09:00

335 lines
9.7 KiB
TypeScript

import { render } from "@testing-library/react";
import React, { type ReactNode } from "react";
import { vi } from "vitest";
import { MembershipRole } from "@calcom/prisma/enums";
import { EditMemberSheet } from "@calcom/web/modules/ee/teams/components/EditMemberSheet";
import type { MemberPermissions } from "@calcom/features/pbac/lib/team-member-permissions";
import type { State, User } from "./MemberList";
// Mock dependencies
vi.mock("@calcom/lib/hooks/useLocale", () => ({
useLocale: () => ({
t: (key: string) => key,
}),
}));
vi.mock("@calcom/trpc/react", () => ({
trpc: {
viewer: {
pbac: {
getTeamRoles: {
useQuery: () => ({
data: undefined,
isPending: false,
}),
},
},
teams: {
getUserConnectedApps: {
useQuery: () => ({
data: {},
isPending: false,
}),
},
changeMemberRole: {
useMutation: () => ({
mutate: vi.fn(),
mutateAsync: vi.fn(),
}),
},
listMembers: {
cancel: vi.fn(),
getInfiniteData: vi.fn(),
invalidate: vi.fn(),
},
get: {
setData: vi.fn(),
invalidate: vi.fn(),
},
},
},
useUtils: () => ({
viewer: {
teams: {
listMembers: {
cancel: vi.fn(),
getInfiniteData: vi.fn(),
invalidate: vi.fn(),
},
get: {
setData: vi.fn(),
invalidate: vi.fn(),
},
},
},
}),
},
}));
const mockSetEditMode = vi.fn();
const mockSetMutationLoading = vi.fn();
vi.mock("@calcom/web/modules/users/components/UserTable/EditSheet/store", () => ({
useEditMode: (
selector: (state: {
editMode: boolean;
setEditMode: typeof mockSetEditMode;
mutationLoading: boolean;
setMutationLoading: typeof mockSetMutationLoading;
}) => unknown
) => {
const state = {
editMode: false,
setEditMode: mockSetEditMode,
mutationLoading: false,
setMutationLoading: mockSetMutationLoading,
};
if (typeof selector === "function") {
return selector(state);
}
return state;
},
}));
// Mock SheetFooterControls to verify props
let capturedProps: { canChangeMemberRole?: boolean; canEditAttributesForUser?: boolean }[] = [];
vi.mock("@calcom/web/modules/users/components/UserTable/EditSheet/SheetFooterControls", () => ({
SheetFooterControls: (props: { canChangeMemberRole?: boolean; canEditAttributesForUser?: boolean }) => {
capturedProps.push(props);
return React.createElement("div", { "data-testid": "sheet-footer-controls" }, "SheetFooterControls");
},
}));
// Mock other UI components
vi.mock("@calcom/ui/components/sheet", () => ({
Sheet: ({ children }: { children: ReactNode }) =>
React.createElement("div", { "data-testid": "sheet" }, children),
SheetContent: ({ children }: { children: ReactNode }) =>
React.createElement("div", { "data-testid": "sheet-content" }, children),
SheetHeader: ({ children }: { children: ReactNode }) =>
React.createElement("div", { "data-testid": "sheet-header" }, children),
SheetBody: ({ children }: { children: ReactNode }) =>
React.createElement("div", { "data-testid": "sheet-body" }, children),
SheetFooter: ({ children }: { children: ReactNode }) =>
React.createElement("div", { "data-testid": "sheet-footer" }, children),
}));
vi.mock("@calcom/ui/components/form", () => ({
Form: ({ children }: { children: ReactNode }) => React.createElement("form", null, children),
ToggleGroup: () => React.createElement("div", { "data-testid": "toggle-group" }, "ToggleGroup"),
Select: () => React.createElement("div", { "data-testid": "select" }, "Select"),
}));
vi.mock("@calcom/ui/components/avatar", () => ({
Avatar: () => React.createElement("div", { "data-testid": "avatar" }, "Avatar"),
}));
vi.mock("@calcom/ui/components/skeleton", () => ({
Skeleton: ({ children }: { children: ReactNode }) => React.createElement("div", null, children),
Loader: () => React.createElement("div", { "data-testid": "loader" }, "Loading..."),
}));
vi.mock("@calcom/web/modules/users/components/UserTable/EditSheet/DisplayInfo", () => ({
DisplayInfo: () => <div data-testid="display-info">DisplayInfo</div>,
}));
describe("EditMemberSheet", () => {
const mockDispatch = vi.fn();
// Create a minimal mock user that satisfies the User type
const mockUser = {
id: 1,
name: "Test User",
email: "test@example.com",
username: "testuser",
role: MembershipRole.MEMBER,
accepted: true,
avatarUrl: "",
bookerUrl: "https://cal.com",
lastActiveAt: "2024-01-01",
customRoleId: null,
} as User;
const mockState: State = {
editSheet: {
user: mockUser,
showModal: true,
},
deleteMember: {
showModal: false,
},
impersonateMember: {
showModal: false,
},
teamAvailability: {
showModal: false,
},
};
beforeEach(() => {
vi.clearAllMocks();
mockSetEditMode.mockClear();
mockSetMutationLoading.mockClear();
capturedProps = [];
});
describe("Fix verification: canChangeMemberRole prop passing", () => {
it("should pass canChangeMemberRole=true to SheetFooterControls when permissions.canChangeMemberRole is true", () => {
const permissions: MemberPermissions = {
canListMembers: false,
canInvite: false,
canChangeMemberRole: true,
canRemove: false,
canImpersonate: false,
};
render(
<EditMemberSheet
state={mockState}
dispatch={mockDispatch}
currentMember={MembershipRole.OWNER}
teamId={1}
permissions={permissions}
/>
);
// Verify SheetFooterControls was called with canChangeMemberRole=true
expect(capturedProps).toContainEqual(
expect.objectContaining({
canChangeMemberRole: true,
})
);
});
it("should pass canChangeMemberRole=false to SheetFooterControls when permissions.canChangeMemberRole is false", () => {
const permissions: MemberPermissions = {
canListMembers: false,
canInvite: false,
canChangeMemberRole: false,
canRemove: false,
canImpersonate: false,
};
render(
<EditMemberSheet
state={mockState}
dispatch={mockDispatch}
currentMember={MembershipRole.OWNER}
teamId={1}
permissions={permissions}
/>
);
// Verify SheetFooterControls was called with canChangeMemberRole=false
expect(capturedProps).toContainEqual(
expect.objectContaining({
canChangeMemberRole: false,
})
);
});
it("should pass canChangeMemberRole=undefined to SheetFooterControls when permissions is undefined", () => {
render(
<EditMemberSheet
state={mockState}
dispatch={mockDispatch}
currentMember={MembershipRole.OWNER}
teamId={1}
permissions={undefined}
/>
);
// Verify SheetFooterControls was called with canChangeMemberRole=undefined
expect(capturedProps).toContainEqual(
expect.objectContaining({
canChangeMemberRole: undefined,
})
);
});
it("should pass canChangeMemberRole=undefined to SheetFooterControls when permissions object exists but canChangeMemberRole is undefined", () => {
const permissions: Partial<MemberPermissions> = {
canListMembers: true,
canInvite: false,
// canChangeMemberRole is intentionally omitted
};
render(
<EditMemberSheet
state={mockState}
dispatch={mockDispatch}
currentMember={MembershipRole.OWNER}
teamId={1}
permissions={permissions as MemberPermissions}
/>
);
// Verify SheetFooterControls was called with canChangeMemberRole=undefined
expect(capturedProps).toContainEqual(
expect.objectContaining({
canChangeMemberRole: undefined,
})
);
});
it("should pass canEditAttributesForUser from permissions to SheetFooterControls", () => {
const permissions: MemberPermissions = {
canListMembers: false,
canInvite: false,
canChangeMemberRole: true,
canRemove: false,
canImpersonate: false,
canEditAttributesForUser: true,
};
render(
<EditMemberSheet
state={mockState}
dispatch={mockDispatch}
currentMember={MembershipRole.OWNER}
teamId={1}
permissions={permissions}
/>
);
// Verify SheetFooterControls was called with both props
expect(capturedProps).toContainEqual(
expect.objectContaining({
canChangeMemberRole: true,
canEditAttributesForUser: true,
})
);
});
it("should correctly extract canChangeMemberRole from permissions object (verifies fix)", () => {
// This test specifically verifies the fix where permissions?.canChangeMemberRole
// is now correctly passed to SheetFooterControls
const permissions: MemberPermissions = {
canListMembers: false,
canInvite: false,
canChangeMemberRole: true, // This should be passed to SheetFooterControls
canRemove: false,
canImpersonate: false,
};
render(
<EditMemberSheet
state={mockState}
dispatch={mockDispatch}
currentMember={MembershipRole.OWNER}
teamId={1}
permissions={permissions}
/>
);
expect(capturedProps).toContainEqual(
expect.objectContaining({
canChangeMemberRole: true,
})
);
});
});
});