import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { INSERT_EMPLOYEE_TEAM, UPDATE_EMPLOYEE_TEAM } from "../../graphql/employee_teams.queries"; import { LABOR_TYPES } from "./shop-employee-teams.form.utils.js"; import { ShopEmployeeTeamsFormComponent } from "./shop-employee-teams.form.component.jsx"; const insertEmployeeTeamMock = vi.fn(); const updateEmployeeTeamMock = vi.fn(); const useQueryMock = vi.fn(); const useMutationMock = vi.fn(); const navigateMock = vi.fn(); const notification = { error: vi.fn(), success: vi.fn() }; vi.mock("@apollo/client/react", () => ({ useQuery: (...args) => useQueryMock(...args), useMutation: (...args) => useMutationMock(...args) })); vi.mock("react-router-dom", () => ({ useLocation: () => ({ search: "?employeeTeamId=new" }), useNavigate: () => navigateMock })); vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (key, values = {}) => { const translations = { "employee_teams.fields.name": "Team Name", "employee_teams.fields.active": "Active", "employee_teams.fields.max_load": "Max Load", "employee_teams.fields.employeeid": "Employee", "employee_teams.fields.allocation_percentage": "Allocation %", "employee_teams.fields.payout_method": "Payout Method", "employee_teams.fields.allocation": "Allocation", "employee_teams.fields.employeeid_label": "Employee", "employee_teams.options.hourly": "Hourly", "employee_teams.options.commission": "Commission", "employee_teams.options.commission_percentage": "Commission", "employee_teams.actions.newmember": "New Team Member", "employee_teams.errors.minimum_one_member": "Add at least one team member.", "employee_teams.errors.duplicate_member": "Team members must be unique.", "employee_teams.errors.allocation_total_exact": "Allocation must total exactly 100%.", "general.actions.save": "Save", "employees.successes.save": "Saved" }; if (key === "employee_teams.labels.allocation_total") { return `Allocation Total: ${values.total}%`; } if (key.startsWith("joblines.fields.lbr_types.")) { return key.split(".").pop(); } return translations[key] || key; } }) })); vi.mock("../../contexts/Notifications/notificationContext.jsx", () => ({ useNotification: () => notification })); vi.mock("../../firebase/firebase.utils", () => ({ logImEXEvent: vi.fn() })); vi.mock("../employee-search-select/employee-search-select.component", () => ({ default: ({ id, value, onChange, options = [] }) => ( ) })); vi.mock("../form-items-formatted/currency-form-item.component", () => ({ default: ({ id, value, onChange }) => ( onChange?.(event.target.value === "" ? null : Number(event.target.value))} /> ) })); vi.mock("../layout-form-row/layout-form-row.component", () => ({ default: ({ title, extra, children }) => (
{title} {extra} {children}
) })); vi.mock("../form-list-move-arrows/form-list-move-arrows.component", () => ({ default: () => null })); const bodyshop = { id: "shop-1", employees: [ { id: "emp-1", first_name: "Avery", last_name: "Johnson" }, { id: "emp-2", first_name: "Morgan", last_name: "Lee" } ] }; const fillHourlyRates = (value) => { LABOR_TYPES.forEach((laborType) => { fireEvent.change(screen.getByLabelText(laborType), { target: { value: String(value) } }); }); }; const addBaseTeamMember = ({ employeeId = "emp-1", percentage = 100, rate = 25 } = {}) => { fireEvent.click(screen.getByRole("button", { name: "New Team Member" })); fireEvent.change(screen.getByLabelText("Employee"), { target: { value: employeeId } }); fireEvent.change(screen.getByRole("spinbutton", { name: "Allocation %" }), { target: { value: String(percentage) } }); fillHourlyRates(rate); }; describe("ShopEmployeeTeamsFormComponent", () => { beforeEach(() => { vi.clearAllMocks(); useQueryMock.mockReturnValue({ error: null, data: null, loading: false }); useMutationMock.mockImplementation((mutation) => { if (mutation === UPDATE_EMPLOYEE_TEAM) { return [updateEmployeeTeamMock]; } if (mutation === INSERT_EMPLOYEE_TEAM) { return [insertEmployeeTeamMock]; } return [vi.fn()]; }); insertEmployeeTeamMock.mockResolvedValue({ data: { insert_employee_teams_one: { id: "team-1" } } }); }); it("switches a new team member from hourly rates to commission percentages", async () => { render(); addBaseTeamMember(); expect(screen.getAllByTestId("currency-input")).toHaveLength(LABOR_TYPES.length); fireEvent.mouseDown(screen.getByRole("combobox", { name: "Payout Method" })); fireEvent.click(screen.getByText("Commission")); await waitFor(() => { expect(screen.queryAllByTestId("currency-input")).toHaveLength(0); }); }); it("submits a valid new hourly team with normalized member data", async () => { render(); fireEvent.change(screen.getByRole("textbox", { name: "Team Name" }), { target: { value: "Commission Crew" } }); fireEvent.change(screen.getByRole("spinbutton", { name: "Max Load" }), { target: { value: "8" } }); addBaseTeamMember({ employeeId: "emp-1", percentage: 100, rate: 27.5 }); fireEvent.click(screen.getByRole("button", { name: "Save" })); await waitFor(() => { expect(insertEmployeeTeamMock).toHaveBeenCalledWith({ variables: { employeeTeam: { name: "Commission Crew", max_load: 8, employee_team_members: { data: [ { employeeid: "emp-1", percentage: 100, payout_method: "hourly", labor_rates: Object.fromEntries(LABOR_TYPES.map((laborType) => [laborType, 27.5])), commission_rates: {} } ] }, bodyshopid: "shop-1" } }, refetchQueries: ["QUERY_TEAMS"] }); }); expect(notification.success).toHaveBeenCalledWith({ title: "Saved" }); expect(navigateMock).toHaveBeenCalledWith({ search: "employeeTeamId=team-1" }); }); });