418 lines
12 KiB
JavaScript
418 lines
12 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { ref } from "vue";
|
|
import { mount } from "@vue/test-utils";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
openGate: vi.fn(() => Promise.resolve()),
|
|
openOutsideEntranceGate: vi.fn(() => Promise.resolve()),
|
|
openOutsideExitGate: vi.fn(() => Promise.resolve()),
|
|
fetchAllRelayStatuses: vi.fn(() => Promise.resolve()),
|
|
startPolling: vi.fn(() => Promise.resolve()),
|
|
stopPolling: vi.fn(),
|
|
toggleRelay: vi.fn(() => Promise.resolve()),
|
|
useMachineConnectivity: vi.fn(),
|
|
getDepartmentOutsideGateCapabilities: vi.fn(),
|
|
}));
|
|
|
|
const buildConnectivity = () => ({
|
|
statuses: ref({
|
|
MACHINE: null,
|
|
PROGRAM_PICKER: null,
|
|
CLEANER: null,
|
|
}),
|
|
loading: ref({
|
|
MACHINE: false,
|
|
PROGRAM_PICKER: false,
|
|
CLEANER: false,
|
|
}),
|
|
commandLoading: ref({
|
|
command: false,
|
|
forceEnable: false,
|
|
forceDisable: false,
|
|
openEntranceGate: false,
|
|
openExitGate: false,
|
|
openOutsideEntranceGate: false,
|
|
openOutsideExitGate: false,
|
|
}),
|
|
error: ref(null),
|
|
inProgressDetails: ref(null),
|
|
inProgressLoading: ref(false),
|
|
toggleRelay: mocks.toggleRelay,
|
|
openGate: mocks.openGate,
|
|
openOutsideEntranceGate: mocks.openOutsideEntranceGate,
|
|
openOutsideExitGate: mocks.openOutsideExitGate,
|
|
fetchAllRelayStatuses: mocks.fetchAllRelayStatuses,
|
|
startPolling: mocks.startPolling,
|
|
stopPolling: mocks.stopPolling,
|
|
});
|
|
|
|
vi.mock("@/views/dashboards/superUserDashboard/selfserve/components/SelfServeMachineConnectivity.vue", () => ({
|
|
parseRelayStatus: () => "MAINTENANCE",
|
|
relayKinds: {
|
|
MACHINE: "MACHINE",
|
|
PROGRAM_PICKER: "PROGRAM_PICKER",
|
|
CLEANER: "CLEANER",
|
|
},
|
|
useMachineConnectivity: mocks.useMachineConnectivity,
|
|
}));
|
|
|
|
vi.mock("@/composables/departmentOutsideGateCapabilities.js", () => ({
|
|
getDepartmentOutsideGateCapabilities: mocks.getDepartmentOutsideGateCapabilities,
|
|
}));
|
|
|
|
import SelfServeMachineRelayControls from "@/views/dashboards/superUserDashboard/selfserve/displays/machine/SelfServeMachineRelayControls.vue";
|
|
|
|
const flushMicrotasks = async () => {
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
};
|
|
|
|
describe("SelfServeMachineRelayControls gate controls", () => {
|
|
beforeEach(() => {
|
|
mocks.openGate.mockReset();
|
|
mocks.openOutsideEntranceGate.mockReset();
|
|
mocks.openOutsideExitGate.mockReset();
|
|
mocks.fetchAllRelayStatuses.mockReset();
|
|
mocks.startPolling.mockReset();
|
|
mocks.stopPolling.mockReset();
|
|
mocks.toggleRelay.mockReset();
|
|
mocks.useMachineConnectivity.mockReset();
|
|
mocks.getDepartmentOutsideGateCapabilities.mockReset();
|
|
mocks.openGate.mockResolvedValue(undefined);
|
|
mocks.openOutsideEntranceGate.mockResolvedValue(undefined);
|
|
mocks.openOutsideExitGate.mockResolvedValue(undefined);
|
|
mocks.fetchAllRelayStatuses.mockResolvedValue(undefined);
|
|
mocks.startPolling.mockResolvedValue(undefined);
|
|
mocks.useMachineConnectivity.mockImplementation(() => buildConnectivity());
|
|
mocks.getDepartmentOutsideGateCapabilities.mockResolvedValue({
|
|
hasOutsideEntranceGate: true,
|
|
hasOutsideExitGate: true,
|
|
});
|
|
});
|
|
|
|
it("shows a single full-width Open Gate button when entrance/exit share the same relay id", async () => {
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 1,
|
|
name: "Lane 1",
|
|
status: "ONLINE",
|
|
relay_in_id: "gate-a",
|
|
relay_out_id: "gate-a",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("Open Gate");
|
|
expect(wrapper.text()).not.toContain("Open entrance gate");
|
|
expect(wrapper.text()).not.toContain("Open exit gate");
|
|
|
|
const button = wrapper.get("button.is-primary");
|
|
await button.trigger("click");
|
|
|
|
expect(mocks.openGate).toHaveBeenCalledWith("ENTRANCE");
|
|
expect(mocks.fetchAllRelayStatuses).toHaveBeenCalledTimes(1);
|
|
expect(mocks.startPolling).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("shows separate entrance/exit controls when gate relay ids differ", async () => {
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 2,
|
|
name: "Lane 2",
|
|
status: "ONLINE",
|
|
relay_in_id: "gate-entrance",
|
|
relay_out_id: "gate-exit",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).not.toContain("Open Gate");
|
|
expect(wrapper.text()).toContain("Open entrance gate");
|
|
expect(wrapper.text()).toContain("Open exit gate");
|
|
|
|
await wrapper.get("button.is-info").trigger("click");
|
|
await wrapper.get("button.is-link").trigger("click");
|
|
|
|
expect(mocks.openGate).toHaveBeenNthCalledWith(1, "ENTRANCE");
|
|
expect(mocks.openGate).toHaveBeenNthCalledWith(2, "EXIT");
|
|
});
|
|
|
|
it("shows no in-progress wash details when lane is idle", async () => {
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 3,
|
|
name: "Lane 3",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
expect(wrapper.text()).toContain("No wash in progress.");
|
|
});
|
|
|
|
it("renders customer and vehicle details when wash is in progress", async () => {
|
|
mocks.useMachineConnectivity.mockImplementation(() => ({
|
|
...buildConnectivity(),
|
|
inProgressDetails: ref({
|
|
lane_id: 4,
|
|
in_progress: true,
|
|
session: { reg: "AB12345", customer_number: 1001 },
|
|
customer: { display_name: "John Doe", customer_number: 1001 },
|
|
vehicle: { reg: "AB12345" },
|
|
}),
|
|
}));
|
|
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 4,
|
|
name: "Lane 4",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("Customer");
|
|
expect(wrapper.text()).toContain("John Doe");
|
|
expect(wrapper.text()).toContain("Vehicle");
|
|
expect(wrapper.text()).toContain("AB12345");
|
|
});
|
|
|
|
it("keeps existing in-progress content visible while refresh is loading", async () => {
|
|
mocks.useMachineConnectivity.mockImplementation(() => ({
|
|
...buildConnectivity(),
|
|
inProgressDetails: ref({
|
|
lane_id: 5,
|
|
in_progress: true,
|
|
session: { reg: "CD67890", customer_number: 2002 },
|
|
customer: { display_name: "Jane Doe", customer_number: 2002 },
|
|
vehicle: { reg: "CD67890" },
|
|
}),
|
|
inProgressLoading: ref(true),
|
|
}));
|
|
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 5,
|
|
name: "Lane 5",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).toContain("Customer");
|
|
expect(wrapper.text()).toContain("Jane Doe");
|
|
expect(wrapper.text()).not.toContain("Loading in-progress details...");
|
|
});
|
|
|
|
it("shows and wires both outside gate buttons when department has outside entrance and exit gates", async () => {
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 6,
|
|
department: 12,
|
|
name: "Lane 6",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
const outsideEntranceButton = wrapper.get("[data-testid='outside-entrance-gate-button']");
|
|
const outsideExitButton = wrapper.get("[data-testid='outside-exit-gate-button']");
|
|
|
|
expect(outsideEntranceButton.exists()).toBe(true);
|
|
expect(outsideExitButton.exists()).toBe(true);
|
|
|
|
await outsideEntranceButton.trigger("click");
|
|
await outsideExitButton.trigger("click");
|
|
|
|
expect(mocks.openOutsideEntranceGate).toHaveBeenCalledTimes(1);
|
|
expect(mocks.openOutsideExitGate).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("shows only the outside entrance button when department only has an outside entrance gate", async () => {
|
|
mocks.getDepartmentOutsideGateCapabilities.mockResolvedValueOnce({
|
|
hasOutsideEntranceGate: true,
|
|
hasOutsideExitGate: false,
|
|
});
|
|
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 7,
|
|
department: 13,
|
|
name: "Lane 7",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.find("[data-testid='outside-entrance-gate-button']").exists()).toBe(true);
|
|
expect(wrapper.find("[data-testid='outside-exit-gate-button']").exists()).toBe(false);
|
|
});
|
|
|
|
it("shows only the outside exit button when department only has an outside exit gate", async () => {
|
|
mocks.getDepartmentOutsideGateCapabilities.mockResolvedValueOnce({
|
|
hasOutsideEntranceGate: false,
|
|
hasOutsideExitGate: true,
|
|
});
|
|
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 8,
|
|
department: 14,
|
|
name: "Lane 8",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.find("[data-testid='outside-entrance-gate-button']").exists()).toBe(false);
|
|
expect(wrapper.find("[data-testid='outside-exit-gate-button']").exists()).toBe(true);
|
|
});
|
|
|
|
it("hides outside gate controls when department has no outside gates", async () => {
|
|
mocks.getDepartmentOutsideGateCapabilities.mockResolvedValueOnce({
|
|
hasOutsideEntranceGate: false,
|
|
hasOutsideExitGate: false,
|
|
});
|
|
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 9,
|
|
department: 15,
|
|
name: "Lane 9",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.find("[data-testid='outside-entrance-gate-button']").exists()).toBe(false);
|
|
expect(wrapper.find("[data-testid='outside-exit-gate-button']").exists()).toBe(false);
|
|
expect(wrapper.text()).not.toContain("Open outside entrance gate");
|
|
expect(wrapper.text()).not.toContain("Open outside exit gate");
|
|
});
|
|
|
|
it("does not render the shared lane error inside the relay and gate panel", async () => {
|
|
const connectivity = buildConnectivity();
|
|
connectivity.error.value = "Edge gateway command timed out";
|
|
mocks.useMachineConnectivity.mockReturnValueOnce(connectivity);
|
|
|
|
const wrapper = mount(SelfServeMachineRelayControls, {
|
|
props: {
|
|
machine: {
|
|
id: 10,
|
|
department: 16,
|
|
name: "Lane 10",
|
|
status: "ONLINE",
|
|
relay_in_id: "in",
|
|
relay_out_id: "out",
|
|
},
|
|
},
|
|
global: {
|
|
stubs: {
|
|
SelfServeMachineStatus: { template: "<div />" },
|
|
BSwitch: { template: "<div />" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await flushMicrotasks();
|
|
|
|
expect(wrapper.text()).not.toContain("Edge gateway command timed out");
|
|
expect(wrapper.find(".help.is-danger").exists()).toBe(false);
|
|
});
|
|
});
|