248 lines
7.0 KiB
JavaScript
248 lines
7.0 KiB
JavaScript
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 = [] }) => (
|
|
<select
|
|
aria-label="Employee"
|
|
id={id}
|
|
value={value ?? ""}
|
|
onChange={(event) => onChange?.(event.target.value || undefined)}
|
|
>
|
|
<option value="">Select Employee</option>
|
|
{options.map((option) => (
|
|
<option key={option.id} value={option.id}>
|
|
{[option.first_name, option.last_name].filter(Boolean).join(" ")}
|
|
</option>
|
|
))}
|
|
</select>
|
|
)
|
|
}));
|
|
|
|
vi.mock("../form-items-formatted/currency-form-item.component", () => ({
|
|
default: ({ id, value, onChange }) => (
|
|
<input
|
|
data-testid="currency-input"
|
|
id={id}
|
|
type="text"
|
|
value={value ?? ""}
|
|
onChange={(event) => onChange?.(event.target.value === "" ? null : Number(event.target.value))}
|
|
/>
|
|
)
|
|
}));
|
|
|
|
vi.mock("../layout-form-row/layout-form-row.component", () => ({
|
|
default: ({ title, extra, children }) => (
|
|
<div>
|
|
{title}
|
|
{extra}
|
|
{children}
|
|
</div>
|
|
)
|
|
}));
|
|
|
|
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(<ShopEmployeeTeamsFormComponent bodyshop={bodyshop} />);
|
|
|
|
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(<ShopEmployeeTeamsFormComponent bodyshop={bodyshop} />);
|
|
|
|
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"
|
|
});
|
|
});
|
|
});
|