618 lines
21 KiB
JavaScript
618 lines
21 KiB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
request: vi.fn(),
|
|
parseErrorMessage: vi.fn((error) => error?.message || String(error || "Unknown error")),
|
|
}));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
request: mocks.request,
|
|
functions: {
|
|
parseErrorMessage: mocks.parseErrorMessage,
|
|
},
|
|
},
|
|
}));
|
|
|
|
import {
|
|
__resetMachineConnectivityStoreForTests,
|
|
parseRelayStatus,
|
|
relayKinds,
|
|
useMachineConnectivity,
|
|
} from "@/views/dashboards/superUserDashboard/selfserve/components/SelfServeMachineConnectivity.vue";
|
|
|
|
const inProgressEndpoint = "/modules/self-serve/lane/wash/in-progress";
|
|
const batchStatusEndpoint = "/modules/self-serve/lane/hardware/batch/status";
|
|
const batchSetEndpoint = "/modules/self-serve/lane/hardware/batch/set";
|
|
const machineStatusEndpoint = "/modules/self-serve/lane/relay/machine/status";
|
|
const programPickerStatusEndpoint = "/modules/self-serve/lane/relay/machine_program_picker/status";
|
|
const cleanerStatusEndpoint = "/modules/self-serve/lane/relay/machine_cleaner/status";
|
|
const machineSetEndpoint = "/modules/self-serve/lane/relay/machine/set";
|
|
const gateOpenEndpoint = "/modules/self-serve/lane/gate/open";
|
|
const activeBatchStoragePrefix = "truckwash.selfserve.hardwareBatch.";
|
|
const cloudShellyBatchUnsupportedError = "Cloud Shelly transport does not support asynchronous relay batches";
|
|
|
|
const buildRelayStatus = (
|
|
laneId,
|
|
relay,
|
|
{ relayId = `${relay.toLowerCase()}-${laneId}`, online = true, on = false } = {}
|
|
) => ({
|
|
lane_id: laneId,
|
|
relay,
|
|
relay_id: relayId,
|
|
online,
|
|
on,
|
|
});
|
|
|
|
const buildInProgressDetails = (
|
|
laneId,
|
|
{ inProgress = false, session = null, customer = null, vehicle = null } = {}
|
|
) => ({
|
|
lane_id: laneId,
|
|
in_progress: inProgress,
|
|
session,
|
|
customer,
|
|
vehicle,
|
|
});
|
|
|
|
const buildBatch = (items, status = "COMPLETED") => ({
|
|
batch_id: "batch-test-id",
|
|
status,
|
|
items,
|
|
});
|
|
|
|
const buildBatchItem = (laneId, relay, options = {}) => ({
|
|
target: relay,
|
|
relay_id: options.relayId || `${relay.toLowerCase()}-${laneId}`,
|
|
ok: options.ok !== false,
|
|
error: options.error || null,
|
|
payload: buildRelayStatus(laneId, relay, options),
|
|
});
|
|
|
|
const createDeferred = () => {
|
|
let resolve;
|
|
let reject;
|
|
const promise = new Promise((res, rej) => {
|
|
resolve = res;
|
|
reject = rej;
|
|
});
|
|
return { promise, resolve, reject };
|
|
};
|
|
|
|
const createMemoryStorage = () => {
|
|
const entries = new Map();
|
|
return {
|
|
get length() {
|
|
return entries.size;
|
|
},
|
|
key: (index) => Array.from(entries.keys())[index] || null,
|
|
getItem: (key) => entries.get(String(key)) ?? null,
|
|
setItem: (key, value) => {
|
|
entries.set(String(key), String(value));
|
|
},
|
|
removeItem: (key) => {
|
|
entries.delete(String(key));
|
|
},
|
|
clear: () => {
|
|
entries.clear();
|
|
},
|
|
};
|
|
};
|
|
|
|
describe("self-serve machine connectivity store", () => {
|
|
beforeEach(() => {
|
|
vi.useRealTimers();
|
|
mocks.request.mockReset();
|
|
mocks.parseErrorMessage.mockReset();
|
|
mocks.parseErrorMessage.mockImplementation((error) => error?.message || String(error || "Unknown error"));
|
|
globalThis.window = {
|
|
localStorage: createMemoryStorage(),
|
|
sessionStorage: createMemoryStorage(),
|
|
};
|
|
window.localStorage.clear();
|
|
__resetMachineConnectivityStoreForTests();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
window.localStorage.clear();
|
|
delete globalThis.window;
|
|
__resetMachineConnectivityStoreForTests();
|
|
});
|
|
|
|
it("hydrates all relay statuses for a lane via shared store", async () => {
|
|
const laneId = 7;
|
|
|
|
mocks.request.mockImplementation(async (endpoint) => {
|
|
if (endpoint === batchStatusEndpoint) {
|
|
return {
|
|
data: {
|
|
data: buildBatch([
|
|
buildBatchItem(laneId, relayKinds.MACHINE, { on: true }),
|
|
buildBatchItem(laneId, relayKinds.PROGRAM_PICKER, { on: false }),
|
|
buildBatchItem(laneId, relayKinds.CLEANER, { online: false, on: false }),
|
|
]),
|
|
},
|
|
};
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.fetchAllRelayStatuses();
|
|
|
|
expect(connectivity.statuses.value.MACHINE).toMatchObject({ lane_id: laneId, on: true, online: true });
|
|
expect(connectivity.statuses.value.PROGRAM_PICKER).toMatchObject({ lane_id: laneId, on: false, online: true });
|
|
expect(connectivity.statuses.value.CLEANER).toMatchObject({ lane_id: laneId, on: false, online: false });
|
|
expect(connectivity.error.value).toBeNull();
|
|
});
|
|
|
|
it("toggles a relay and refreshes relay status when set response is envelope-only", async () => {
|
|
const laneId = 11;
|
|
let machineOn = false;
|
|
|
|
mocks.request.mockImplementation(async (endpoint, method, payload) => {
|
|
if (endpoint === batchSetEndpoint && method === "POST") {
|
|
machineOn = Boolean(payload?.commands?.[0]?.on);
|
|
return {
|
|
data: {
|
|
data: buildBatch([buildBatchItem(laneId, relayKinds.MACHINE, { on: Boolean(payload?.commands?.[0]?.on) })]),
|
|
},
|
|
};
|
|
}
|
|
if (endpoint === batchStatusEndpoint && method === "POST") {
|
|
return {
|
|
data: {
|
|
data: buildBatch([
|
|
buildBatchItem(laneId, relayKinds.MACHINE, { on: machineOn }),
|
|
buildBatchItem(laneId, relayKinds.PROGRAM_PICKER),
|
|
buildBatchItem(laneId, relayKinds.CLEANER),
|
|
]),
|
|
},
|
|
};
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
|
|
await connectivity.fetchAllRelayStatuses();
|
|
expect(connectivity.statuses.value.MACHINE?.on).toBe(false);
|
|
|
|
await connectivity.toggleRelay(relayKinds.MACHINE, true);
|
|
expect(connectivity.statuses.value.MACHINE?.on).toBe(true);
|
|
});
|
|
|
|
it("waits long enough for slow edge batches to complete", async () => {
|
|
vi.useFakeTimers();
|
|
const laneId = 12;
|
|
let pollCount = 0;
|
|
|
|
mocks.request.mockImplementation(async (endpoint, method, payload) => {
|
|
if (endpoint === batchSetEndpoint && method === "POST") {
|
|
return {
|
|
data: {
|
|
data: buildBatch(
|
|
[buildBatchItem(laneId, relayKinds.MACHINE, { on: Boolean(payload?.commands?.[0]?.on) })],
|
|
"PENDING"
|
|
),
|
|
},
|
|
};
|
|
}
|
|
if (endpoint === "/modules/self-serve/lane/hardware/batch/batch-test-id" && method === "GET") {
|
|
pollCount += 1;
|
|
return {
|
|
data: {
|
|
data:
|
|
pollCount < 45
|
|
? buildBatch([buildBatchItem(laneId, relayKinds.MACHINE, { on: true })], "PENDING")
|
|
: buildBatch([buildBatchItem(laneId, relayKinds.MACHINE, { on: true })]),
|
|
},
|
|
};
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
const togglePromise = connectivity.toggleRelay(relayKinds.MACHINE, true);
|
|
|
|
await vi.advanceTimersByTimeAsync(12_000);
|
|
await togglePromise;
|
|
|
|
expect(pollCount).toBeGreaterThan(40);
|
|
expect(connectivity.statuses.value.MACHINE?.on).toBe(true);
|
|
expect(connectivity.error.value).toBeNull();
|
|
});
|
|
|
|
it("runs force and command actions through shared connectivity helpers", async () => {
|
|
const laneId = 15;
|
|
|
|
mocks.request.mockResolvedValue({ data: { success: true } });
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.forceEnableMachine({ duration: 120, licensePlate: "ab12345" });
|
|
await connectivity.forceDisableMachine("cd67890");
|
|
await connectivity.forceStopLane({ sessionId: 91, bill: true, reason: "clear app runtime" });
|
|
await connectivity.stopWash();
|
|
|
|
expect(mocks.request).toHaveBeenNthCalledWith(1, "/modules/self-serve/lane/force/machine/enable", "POST", {
|
|
lane_id: laneId,
|
|
duration: 120,
|
|
license_plate: "AB12345",
|
|
});
|
|
expect(mocks.request).toHaveBeenNthCalledWith(2, "/modules/self-serve/lane/force/machine/disable", "POST", {
|
|
lane_id: laneId,
|
|
license_plate: "CD67890",
|
|
});
|
|
expect(mocks.request).toHaveBeenNthCalledWith(3, "/modules/self-serve/lane/force/stop", "POST", {
|
|
lane_id: laneId,
|
|
session_id: 91,
|
|
bill: true,
|
|
reason: "clear app runtime",
|
|
});
|
|
expect(mocks.request).toHaveBeenNthCalledWith(4, "/modules/self-serve/lane/command", "POST", {
|
|
lane_id: laneId,
|
|
command: "STOP",
|
|
});
|
|
});
|
|
|
|
it("runs entrance/exit gate actions through shared connectivity helpers", async () => {
|
|
const laneId = 18;
|
|
|
|
mocks.request.mockResolvedValue({ data: { success: true } });
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.openEntranceGate();
|
|
await connectivity.openExitGate();
|
|
|
|
expect(mocks.request).toHaveBeenNthCalledWith(1, batchSetEndpoint, "POST", {
|
|
lane_id: laneId,
|
|
commands: [{ target: "ENTRANCE", action: "OPEN" }],
|
|
});
|
|
expect(mocks.request).toHaveBeenNthCalledWith(2, batchSetEndpoint, "POST", {
|
|
lane_id: laneId,
|
|
commands: [{ target: "EXIT", action: "OPEN" }],
|
|
});
|
|
});
|
|
|
|
it("surfaces unknown gate command outcomes as operator errors", async () => {
|
|
const laneId = 21;
|
|
|
|
mocks.request.mockResolvedValue({
|
|
data: {
|
|
data: buildBatch(
|
|
[
|
|
{
|
|
target: "ENTRANCE",
|
|
relay_id: "entrance-relay",
|
|
ok: false,
|
|
outcome_unknown: true,
|
|
error: "Edge gateway command outcome is unknown after dispatch interruption",
|
|
},
|
|
],
|
|
"UNKNOWN_OUTCOME"
|
|
),
|
|
},
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
|
|
await expect(connectivity.openEntranceGate()).rejects.toThrow("outcome is unknown");
|
|
expect(connectivity.error.value).toContain("outcome is unknown");
|
|
});
|
|
|
|
it("fetches in-progress wash details through shared connectivity helpers", async () => {
|
|
const laneId = 19;
|
|
|
|
mocks.request.mockResolvedValue({
|
|
data: {
|
|
data: buildInProgressDetails(laneId, {
|
|
inProgress: true,
|
|
session: { id: 55, reg: "AB12345", customer_number: 9001 },
|
|
customer: { id: 2, customer_number: 9001, display_name: "Jane Doe" },
|
|
vehicle: { id: 3, reg: "AB12345" },
|
|
}),
|
|
},
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
const details = await connectivity.fetchInProgressDetails();
|
|
|
|
expect(mocks.request).toHaveBeenCalledWith(inProgressEndpoint, "GET", { lane_id: laneId });
|
|
expect(details).toMatchObject({
|
|
lane_id: laneId,
|
|
in_progress: true,
|
|
customer: { display_name: "Jane Doe" },
|
|
vehicle: { reg: "AB12345" },
|
|
});
|
|
expect(connectivity.inProgressDetails.value).toMatchObject({
|
|
lane_id: laneId,
|
|
in_progress: true,
|
|
});
|
|
expect(connectivity.inProgressLoading.value).toBe(false);
|
|
});
|
|
|
|
it("sets lane error when in-progress details request fails", async () => {
|
|
const laneId = 20;
|
|
|
|
mocks.request.mockRejectedValue(new Error("in-progress unavailable"));
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await expect(connectivity.fetchInProgressDetails()).rejects.toThrow("in-progress unavailable");
|
|
expect(connectivity.error.value).toContain("in-progress unavailable");
|
|
expect(connectivity.inProgressLoading.value).toBe(false);
|
|
});
|
|
|
|
it("starts and stops polling without leaking background status requests", async () => {
|
|
vi.useFakeTimers();
|
|
const laneId = 4;
|
|
|
|
mocks.request.mockImplementation(async (endpoint) => {
|
|
if (endpoint === inProgressEndpoint) {
|
|
return { data: { data: buildInProgressDetails(laneId) } };
|
|
}
|
|
|
|
if (endpoint !== batchStatusEndpoint) {
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
}
|
|
return {
|
|
data: {
|
|
data: buildBatch([
|
|
buildBatchItem(laneId, relayKinds.MACHINE, { on: false }),
|
|
buildBatchItem(laneId, relayKinds.PROGRAM_PICKER, { on: false }),
|
|
buildBatchItem(laneId, relayKinds.CLEANER, { on: false }),
|
|
]),
|
|
},
|
|
};
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.startPolling(1000);
|
|
|
|
expect(connectivity.isPolling.value).toBe(true);
|
|
await vi.advanceTimersByTimeAsync(2200);
|
|
|
|
const statusCallCountBeforeStop = mocks.request.mock.calls.length;
|
|
expect(statusCallCountBeforeStop).toBeGreaterThanOrEqual(6);
|
|
|
|
connectivity.stopPolling();
|
|
expect(connectivity.isPolling.value).toBe(false);
|
|
|
|
await vi.advanceTimersByTimeAsync(2200);
|
|
expect(mocks.request.mock.calls.length).toBe(statusCallCountBeforeStop);
|
|
});
|
|
|
|
it("keeps successful relay statuses and surfaces an error when one relay fetch fails", async () => {
|
|
const laneId = 22;
|
|
|
|
mocks.request.mockImplementation(async (endpoint) => {
|
|
if (endpoint === batchStatusEndpoint) {
|
|
return {
|
|
data: {
|
|
data: buildBatch(
|
|
[
|
|
buildBatchItem(laneId, relayKinds.MACHINE, { on: true }),
|
|
buildBatchItem(laneId, relayKinds.PROGRAM_PICKER, { ok: false, error: "program picker unavailable" }),
|
|
buildBatchItem(laneId, relayKinds.CLEANER, { on: false }),
|
|
],
|
|
"PARTIAL"
|
|
),
|
|
},
|
|
};
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.fetchAllRelayStatuses();
|
|
|
|
expect(connectivity.statuses.value.MACHINE?.on).toBe(true);
|
|
expect(connectivity.statuses.value.CLEANER?.on).toBe(false);
|
|
expect(connectivity.statuses.value.PROGRAM_PICKER).toBeNull();
|
|
expect(connectivity.error.value).toContain("program picker unavailable");
|
|
});
|
|
|
|
it("falls back to individual Cloud Shelly relay status requests when async batches are unavailable", async () => {
|
|
const laneId = 23;
|
|
|
|
mocks.request.mockImplementation(async (endpoint, method) => {
|
|
if (endpoint === batchStatusEndpoint && method === "POST") {
|
|
throw new Error(cloudShellyBatchUnsupportedError);
|
|
}
|
|
if (endpoint === machineStatusEndpoint && method === "GET") {
|
|
return { data: { data: buildRelayStatus(laneId, relayKinds.MACHINE, { on: true }) } };
|
|
}
|
|
if (endpoint === programPickerStatusEndpoint && method === "GET") {
|
|
return { data: { data: buildRelayStatus(laneId, relayKinds.PROGRAM_PICKER, { on: false }) } };
|
|
}
|
|
if (endpoint === cleanerStatusEndpoint && method === "GET") {
|
|
return { data: { data: buildRelayStatus(laneId, relayKinds.CLEANER, { online: false }) } };
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.fetchAllRelayStatuses();
|
|
|
|
expect(connectivity.statuses.value.MACHINE).toMatchObject({ on: true, online: true });
|
|
expect(connectivity.statuses.value.PROGRAM_PICKER).toMatchObject({ on: false, online: true });
|
|
expect(connectivity.statuses.value.CLEANER).toMatchObject({ on: false, online: false });
|
|
expect(connectivity.error.value).toBeNull();
|
|
expect(mocks.request).toHaveBeenCalledWith(batchStatusEndpoint, "POST", {
|
|
lane_id: laneId,
|
|
targets: ["MACHINE", "PROGRAM_PICKER", "CLEANER"],
|
|
});
|
|
expect(mocks.request).toHaveBeenCalledWith(machineStatusEndpoint, "GET", { lane_id: laneId });
|
|
expect(mocks.request).toHaveBeenCalledWith(programPickerStatusEndpoint, "GET", { lane_id: laneId });
|
|
expect(mocks.request).toHaveBeenCalledWith(cleanerStatusEndpoint, "GET", { lane_id: laneId });
|
|
});
|
|
|
|
it("falls back to individual Cloud Shelly relay set requests when async batches are unavailable", async () => {
|
|
const laneId = 25;
|
|
|
|
mocks.request.mockImplementation(async (endpoint, method, payload) => {
|
|
if (endpoint === batchSetEndpoint && method === "POST") {
|
|
throw new Error(cloudShellyBatchUnsupportedError);
|
|
}
|
|
if (endpoint === machineSetEndpoint && method === "POST") {
|
|
return {
|
|
data: {
|
|
data: buildRelayStatus(laneId, relayKinds.MACHINE, { on: Boolean(payload?.on) }),
|
|
},
|
|
};
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.toggleRelay(relayKinds.MACHINE, true);
|
|
|
|
expect(connectivity.statuses.value.MACHINE).toMatchObject({ on: true, online: true });
|
|
expect(connectivity.error.value).toBeNull();
|
|
expect(mocks.request).toHaveBeenCalledWith(batchSetEndpoint, "POST", {
|
|
lane_id: laneId,
|
|
commands: [{ target: "MACHINE", on: true }],
|
|
});
|
|
expect(mocks.request).toHaveBeenCalledWith(machineSetEndpoint, "POST", {
|
|
lane_id: laneId,
|
|
on: true,
|
|
});
|
|
});
|
|
|
|
it("falls back to the synchronous Cloud Shelly gate endpoint when async batches are unavailable", async () => {
|
|
const laneId = 27;
|
|
|
|
mocks.request.mockImplementation(async (endpoint, method, payload) => {
|
|
if (endpoint === batchSetEndpoint && method === "POST") {
|
|
throw new Error(cloudShellyBatchUnsupportedError);
|
|
}
|
|
if (endpoint === gateOpenEndpoint && method === "POST") {
|
|
return { data: { data: { lane_id: payload?.lane_id, gate: payload?.gate, queued: false } } };
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await connectivity.openEntranceGate();
|
|
|
|
expect(connectivity.error.value).toBeNull();
|
|
expect(mocks.request).toHaveBeenCalledWith(batchSetEndpoint, "POST", {
|
|
lane_id: laneId,
|
|
commands: [{ target: "ENTRANCE", action: "OPEN" }],
|
|
});
|
|
expect(mocks.request).toHaveBeenCalledWith(gateOpenEndpoint, "POST", {
|
|
lane_id: laneId,
|
|
gate: "ENTRANCE",
|
|
});
|
|
});
|
|
|
|
it("resumes a remembered hardware batch for the lane", async () => {
|
|
const laneId = 26;
|
|
const batchId = "batch-resume-id";
|
|
window.localStorage.setItem(
|
|
`${activeBatchStoragePrefix}${batchId}`,
|
|
JSON.stringify({
|
|
batch_id: batchId,
|
|
lane_id: laneId,
|
|
kind: "STATUS",
|
|
status: "PENDING",
|
|
remembered_at: Date.now(),
|
|
})
|
|
);
|
|
|
|
mocks.request.mockImplementation(async (endpoint, method) => {
|
|
if (endpoint === `/modules/self-serve/lane/hardware/batch/${batchId}` && method === "GET") {
|
|
return { data: { data: buildBatch([buildBatchItem(laneId, relayKinds.MACHINE, { on: true })]) } };
|
|
}
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
});
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
|
|
await vi.waitFor(() => {
|
|
expect(connectivity.statuses.value.MACHINE?.on).toBe(true);
|
|
});
|
|
expect(window.localStorage.getItem(`${activeBatchStoragePrefix}${batchId}`)).toBeNull();
|
|
});
|
|
|
|
it("throws and stores lane error when all relay status requests fail", async () => {
|
|
const laneId = 24;
|
|
|
|
mocks.request.mockRejectedValue(new Error("all relays offline"));
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
await expect(connectivity.fetchAllRelayStatuses()).rejects.toThrow("all relays offline");
|
|
expect(connectivity.error.value).toContain("all relays offline");
|
|
});
|
|
|
|
it("tracks command loading while lane commands are in-flight and resets after completion", async () => {
|
|
const laneId = 28;
|
|
const deferred = createDeferred();
|
|
|
|
mocks.request.mockReturnValue(deferred.promise);
|
|
|
|
const connectivity = useMachineConnectivity(laneId);
|
|
const stopPromise = connectivity.openEntranceGate();
|
|
|
|
expect(connectivity.commandLoading.value.openEntranceGate).toBe(true);
|
|
|
|
deferred.resolve({ data: { success: true } });
|
|
await stopPromise;
|
|
|
|
expect(connectivity.commandLoading.value.openEntranceGate).toBe(false);
|
|
expect(connectivity.error.value).toBeNull();
|
|
});
|
|
|
|
it("keeps polling active until all subscribers for the same lane have unsubscribed", async () => {
|
|
vi.useFakeTimers();
|
|
const laneId = 31;
|
|
|
|
mocks.request.mockImplementation(async (endpoint) => {
|
|
if (endpoint === inProgressEndpoint) {
|
|
return { data: { data: buildInProgressDetails(laneId) } };
|
|
}
|
|
|
|
if (endpoint !== batchStatusEndpoint) {
|
|
throw new Error(`Unexpected endpoint: ${endpoint}`);
|
|
}
|
|
return {
|
|
data: {
|
|
data: buildBatch([
|
|
buildBatchItem(laneId, relayKinds.MACHINE),
|
|
buildBatchItem(laneId, relayKinds.PROGRAM_PICKER),
|
|
buildBatchItem(laneId, relayKinds.CLEANER),
|
|
]),
|
|
},
|
|
};
|
|
});
|
|
|
|
const first = useMachineConnectivity(laneId);
|
|
const second = useMachineConnectivity(laneId);
|
|
|
|
await first.startPolling(1000);
|
|
await second.startPolling(1000);
|
|
|
|
const callsAfterStart = mocks.request.mock.calls.length;
|
|
|
|
first.stopPolling();
|
|
expect(first.isPolling.value).toBe(true);
|
|
|
|
await vi.advanceTimersByTimeAsync(1100);
|
|
expect(mocks.request.mock.calls.length).toBeGreaterThan(callsAfterStart);
|
|
|
|
second.stopPolling();
|
|
expect(first.isPolling.value).toBe(false);
|
|
|
|
const callsAfterFinalStop = mocks.request.mock.calls.length;
|
|
await vi.advanceTimersByTimeAsync(1100);
|
|
expect(mocks.request.mock.calls.length).toBe(callsAfterFinalStop);
|
|
});
|
|
|
|
it("maps relay status payloads to display labels", () => {
|
|
expect(parseRelayStatus(null)).toBe("MAINTENANCE");
|
|
expect(parseRelayStatus(buildRelayStatus(1, relayKinds.MACHINE, { online: false }))).toBe("OFFLINE");
|
|
expect(parseRelayStatus(buildRelayStatus(1, relayKinds.MACHINE, { online: true, on: true }))).toBe("ON");
|
|
expect(parseRelayStatus(buildRelayStatus(1, relayKinds.MACHINE, { online: true, on: false }))).toBe("OFF");
|
|
});
|
|
});
|