Files
pleno-vue/tests/unit/my-wash-start.spec.js
T

1524 lines
50 KiB
JavaScript

// @vitest-environment jsdom
import { flushPromises } from "@vue/test-utils";
import { nextTick } from "vue";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => {
const asMockRef = (value) => ({ value, __v_isRef: true });
const nearestDepartment = {
value: {
id: 6,
name: "Roskilde",
address: "Industrivej 45, 4000 Roskilde",
self_serve_enabled: true,
lanes: [{ id: 7, name: "7", status: "AVAILABLE", products: [2], machine_available: true }],
},
};
const guestDepartments = { value: [nearestDepartment.value] };
const doesCurrentDepartmentSelectionHaveSelfServeEnabled = asMockRef(true);
const answers = { value: {} };
const completedTasks = { value: {} };
const visibleQuestions = { value: [{ id: 11, question: "Question 11" }] };
const activeTasks = { value: [] };
const conditions = { value: [] };
const rules = { value: [] };
const allowedServices = { value: ["MACHINE"] };
return {
nearestDepartment,
guestDepartments,
doesCurrentDepartmentSelectionHaveSelfServeEnabled,
answers,
completedTasks,
visibleQuestions,
activeTasks,
conditions,
rules,
allowedServices,
fetchDepartments: vi.fn(async () => guestDepartments.value),
startAutoRefresh: vi.fn(),
stopAutoRefresh: vi.fn(),
startDepartmentSearch: vi.fn(),
selectDepartment: vi.fn(),
clearForcedDepartment: vi.fn(),
evaluateLocationDepartments: vi.fn(),
fetchSelfServeDataInternal: vi.fn(),
fetchWashSummary: vi.fn(),
syncVehicleAnswer: vi.fn(),
updateLaneAllowedServices: vi.fn(),
enableMachineRelay: vi.fn(),
downloadAttachment: vi.fn(),
answerQuestion: vi.fn((questionId, value) => {
answers.value = { ...answers.value, [questionId]: value };
}),
restoredProgressPayload: null,
saveProgress: vi.fn(),
clearProgress: vi.fn(),
restoreProgress: vi.fn(),
startElapsedTimer: vi.fn(),
stopElapsedTimer: vi.fn(),
registerBeforeUnload: vi.fn(() => vi.fn()),
markDestroying: vi.fn(),
handleConfirmNext: vi.fn(),
isNextButtonDisabled: vi.fn(() => false),
onStartWash: vi.fn(),
onStopWash: vi.fn(),
openPropertyAccessGate: vi.fn(),
openPropertyExitGate: vi.fn(),
setShowFooterInContent: vi.fn(),
fetchCustomerVehicles: vi.fn(),
fetchVehicleTypeOptions: vi.fn(),
addVehicle: vi.fn(),
sessionRequest: vi.fn(),
sessionCustomerNumber: null,
};
});
vi.mock("@/components/session/token/SessionUser.vue", async () => {
const { ref } = await import("vue");
mocks.sessionCustomerNumber = ref(12345679);
return {
SessionUser: {
user: {
customer_number: mocks.sessionCustomerNumber,
},
canAccessSuperUser: () => true,
canAccessDeveloper: () => false,
request: vi.fn(async (...args) => {
const response = await mocks.sessionRequest(...args);
return response ?? { status: 200, data: { data: {} } };
}),
functions: {
contact: {
onClickCallPhoneNumber: vi.fn(),
},
},
objects: {
vehicles: {
add: vi.fn(async (...args) => {
mocks.addVehicle(...args);
return { status: 200, data: { data: {} } };
}),
get: {
all: vi.fn(async () => {
mocks.fetchCustomerVehicles();
return [{ reg: "AB12345", type: 2 }];
}),
},
columns: {
type: {
options: vi.fn(async () => {
mocks.fetchVehicleTypeOptions();
return [{ id: 2, name: "Truck", product: { id: 2, description: "Truck" } }];
}),
},
},
},
},
},
};
});
vi.mock("@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue", () => ({
locations: {
location: {
value: {
coords: {
latitude: 55.6,
longitude: 12.5,
},
},
},
},
}));
vi.mock("@/components/viewport/conditions/ViewPortFooterOptions.vue", () => ({
setShowFooterInContent: mocks.setShowFooterInContent,
}));
vi.mock("@/composables/useWashDepartments", () => ({
useWashDepartments: () => ({
guestDepartments: mocks.guestDepartments,
nearestDepartment: mocks.nearestDepartment,
isForcingNearestDepartment: { value: false },
forceNearestDepartmentEvaluationId: { value: 0 },
isSearchingDepartments: { value: false },
departmentFetchError: { value: null },
isDepartmentSelectionFallbackBased: { value: false },
availableProductIds: { value: [2] },
doesCurrentDepartmentSelectionHaveSelfServeEnabled: mocks.doesCurrentDepartmentSelectionHaveSelfServeEnabled,
fetchDepartments: mocks.fetchDepartments,
evaluateLocationDepartments: mocks.evaluateLocationDepartments,
startDepartmentSearch: mocks.startDepartmentSearch,
selectDepartment: mocks.selectDepartment,
clearForcedDepartment: mocks.clearForcedDepartment,
startAutoRefresh: mocks.startAutoRefresh,
stopAutoRefresh: mocks.stopAutoRefresh,
}),
}));
vi.mock("@/composables/useSelfServeLogic", () => ({
useSelfServeLogic: () => ({
loading: { value: false },
error: { value: null },
lane: { value: { id: 7 } },
questions: { value: [] },
conditions: mocks.conditions,
rules: mocks.rules,
tasks: { value: [] },
answers: mocks.answers,
completedTasks: mocks.completedTasks,
allowedServices: mocks.allowedServices,
visibleQuestions: mocks.visibleQuestions,
activeTasks: mocks.activeTasks,
allVisibleQuestionsAnswered: { value: false },
machineAvailable: { value: true },
fetchSelfServeData: mocks.fetchSelfServeDataInternal,
fetchWashSummary: mocks.fetchWashSummary,
syncVehicleAnswer: mocks.syncVehicleAnswer,
evaluateRule: () => true,
evaluateCondition: () => true,
isQuestionVisible: () => true,
isServiceAllowed: () => true,
updateLaneAllowedServices: mocks.updateLaneAllowedServices,
enableMachineRelay: mocks.enableMachineRelay,
downloadAttachment: mocks.downloadAttachment,
answerQuestion: mocks.answerQuestion,
}),
}));
vi.mock("@/composables/useWashProgress", () => ({
useWashProgress: (options = {}) => ({
completedDurationMs: { value: null },
now: { value: Date.now() },
isRestoring: { value: false },
formattedElapsed: { value: "00:00" },
saveProgress: mocks.saveProgress,
clearProgress: mocks.clearProgress,
restoreProgress: vi.fn(() => {
mocks.restoreProgress();
if (mocks.restoredProgressPayload && typeof options.applyRestoredState === "function") {
options.applyRestoredState(mocks.restoredProgressPayload);
}
return mocks.restoredProgressPayload || null;
}),
startElapsedTimer: mocks.startElapsedTimer,
stopElapsedTimer: mocks.stopElapsedTimer,
registerBeforeUnload: mocks.registerBeforeUnload,
markDestroying: mocks.markDestroying,
}),
}));
vi.mock("@/composables/useWashFlowState", () => ({
useWashFlowState: () => ({
steps: {
VEHICLE: 0,
QUESTIONS: 1,
SELECT_LANE: 2,
TASKS: 3,
WASH_IN_PROGRESS: 4,
COMPLETED: 5,
},
clickableSteps: {
0: () => true,
1: () => true,
2: () => true,
3: () => true,
4: () => false,
},
isNextButtonDisabled: mocks.isNextButtonDisabled,
handleConfirmNext: mocks.handleConfirmNext,
targetStepForStart: () => 3,
}),
}));
vi.mock("@/composables/useWashSessionActions", async () => {
const { ref } = await import("vue");
return {
useWashSessionActions: () => ({
dynamicImageUrl: ref("https://cdn.example.test/dynamic.png"),
onStartWash: mocks.onStartWash,
onStopWash: mocks.onStopWash,
openPropertyAccessGate: mocks.openPropertyAccessGate,
openPropertyExitGate: mocks.openPropertyExitGate,
openingPropertyAccessGate: ref(false),
openingPropertyExitGate: ref(false),
isStartingWash: ref(false),
}),
};
});
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import MyWashStart from "@/views/dashboards/userDashboard/wash/MyWashStart.vue";
import { mountWithApp } from "./helpers/mountWithApp.js";
const BStepsStub = {
template: `
<div class="b-steps-stub">
<slot />
<slot
name="navigation"
:previous="{ disabled: false, action: () => {} }"
:next="{ disabled: false, action: () => {} }"
/>
</div>
`,
};
const BStepItemStub = {
template: "<div class='b-step-item-stub'><slot /></div>",
};
const BButtonStub = {
emits: ["click"],
template: "<button v-bind='$attrs' @click=\"$emit('click', $event)\"><slot /></button>",
};
const stubComponents = {
PosDepartmentStepMobile1Location: {
template: "<div data-testid='location-stub' />",
},
SelfServeDepartmentHeader: {
template: "<div data-testid='department-header-stub' />",
},
SelfServeVehicleStep: {
emits: ["update:registration-number", "update:customer-number", "select-vehicle-type"],
template: `
<div data-testid="vehicle-step-stub">
<button data-testid="emit-registration" @click="$emit('update:registration-number', 'AB12345')">registration</button>
<button data-testid="emit-registration-unknown" @click="$emit('update:registration-number', 'ZZ99999')">registration-unknown</button>
<button data-testid="emit-customer-number" @click="$emit('update:customer-number', '12345679')">customer</button>
<button data-testid="emit-vehicle-type" @click="$emit('select-vehicle-type', { id: 2, name: 'Truck' })">vehicle</button>
</div>
`,
},
SelfServeQuestionsStep: {
emits: ["answer-question"],
template: `
<div>
<button data-testid="emit-answer" @click="$emit('answer-question', 11, true)">answer</button>
<button data-testid="emit-answer-21" @click="$emit('answer-question', 21, false)">answer 21</button>
</div>
`,
},
SelfServeLaneStep: {
emits: ["update:selected-lane-id", "update:washType"],
template: `
<div>
<button data-testid="emit-lane" @click="$emit('update:selected-lane-id', 7)">lane</button>
<button data-testid="emit-machine-wash" @click="$emit('update:washType', 'Machine')">machine wash</button>
</div>
`,
},
SelfServeTasksStep: {
props: ["activeTasks", "dynamicImageUrl"],
template: `
<div data-testid="tasks-step-stub">
<img v-if="dynamicImageUrl" data-testid="tasks-dynamic-image-stub" :src="dynamicImageUrl" alt="" />
<span
v-for="task in activeTasks"
:key="task.id"
:data-testid="\`rendered-task-\${task.id}\`"
>
{{ task.task }}
</span>
</div>
`,
},
SelfServeGuidedInstructions: {
props: ["currentStep"],
template: "<div data-testid='guided-step-stub'>{{ currentStep }}</div>",
},
SelfServeCompletedStep: {
template: "<div data-testid='completed-step-stub' />",
},
BSteps: BStepsStub,
BStepItem: BStepItemStub,
BButton: BButtonStub,
BMessage: {
template: "<div><slot /></div>",
},
};
describe("MyWashStart", () => {
afterEach(() => {
vi.useRealTimers();
localStorage.clear();
});
beforeEach(() => {
localStorage.clear();
mocks.nearestDepartment.value = {
id: 6,
name: "Roskilde",
address: "Industrivej 45, 4000 Roskilde",
self_serve_enabled: true,
lanes: [{ id: 7, name: "7", status: "AVAILABLE", products: [2], machine_available: true }],
};
mocks.guestDepartments.value = [mocks.nearestDepartment.value];
mocks.doesCurrentDepartmentSelectionHaveSelfServeEnabled.value = true;
mocks.answers.value = {};
mocks.completedTasks.value = {};
mocks.visibleQuestions.value = [{ id: 11, question: "Question 11" }];
mocks.activeTasks.value = [];
mocks.allowedServices.value = ["MACHINE"];
mocks.conditions.value = [];
mocks.rules.value = [];
mocks.sessionCustomerNumber.value = 12345679;
mocks.fetchDepartments.mockClear();
mocks.startAutoRefresh.mockClear();
mocks.stopAutoRefresh.mockClear();
mocks.restoreProgress.mockClear();
mocks.registerBeforeUnload.mockClear();
mocks.markDestroying.mockClear();
mocks.fetchCustomerVehicles.mockClear();
mocks.fetchVehicleTypeOptions.mockClear();
mocks.fetchSelfServeDataInternal.mockClear();
mocks.fetchWashSummary.mockClear();
mocks.saveProgress.mockClear();
mocks.clearProgress.mockClear();
mocks.startElapsedTimer.mockClear();
mocks.stopElapsedTimer.mockClear();
mocks.restoredProgressPayload = null;
mocks.syncVehicleAnswer.mockClear();
mocks.answerQuestion.mockClear();
mocks.setShowFooterInContent.mockClear();
mocks.addVehicle.mockClear();
mocks.sessionRequest.mockClear();
mocks.sessionRequest.mockResolvedValue(undefined);
mocks.handleConfirmNext.mockClear();
mocks.isNextButtonDisabled.mockClear();
mocks.isNextButtonDisabled.mockReturnValue(false);
mocks.onStartWash.mockClear();
mocks.onStopWash.mockReset();
mocks.onStopWash.mockResolvedValue(true);
mocks.openPropertyAccessGate.mockClear();
mocks.openPropertyExitGate.mockClear();
});
it("loads initial wash context and tears down refresh state on unmount", async () => {
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
expect(mocks.setShowFooterInContent).toHaveBeenCalledWith(false);
expect(mocks.fetchDepartments).toHaveBeenCalledTimes(1);
expect(mocks.startAutoRefresh).toHaveBeenCalledTimes(1);
expect(mocks.fetchCustomerVehicles).toHaveBeenCalledTimes(1);
expect(mocks.fetchVehicleTypeOptions).toHaveBeenCalledTimes(1);
expect(mocks.restoreProgress).toHaveBeenCalledTimes(1);
expect(mocks.registerBeforeUnload).toHaveBeenCalledTimes(1);
wrapper.unmount();
expect(mocks.stopAutoRefresh).toHaveBeenCalledTimes(1);
expect(mocks.markDestroying).toHaveBeenCalledTimes(1);
expect(mocks.setShowFooterInContent).toHaveBeenLastCalledWith(true);
});
it("wires child updates back into the self-serve runtime", async () => {
vi.useFakeTimers();
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await vi.advanceTimersByTimeAsync(300);
await flushPromises();
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
await wrapper.get('[data-testid="emit-answer"]').trigger("click");
await flushPromises();
expect(mocks.answerQuestion).toHaveBeenCalledWith(11, true);
expect(mocks.syncVehicleAnswer).toHaveBeenCalledWith({
departmentId: 6,
laneId: 7,
customerNumber: 12345679,
reg: "AB12345",
questionId: 11,
value: true,
vehicleTypeId: 2,
});
});
it("debounces registration, lane, and vehicle type updates into one self-serve fetch", async () => {
vi.useFakeTimers();
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
mocks.fetchSelfServeDataInternal.mockClear();
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await vi.advanceTimersByTimeAsync(299);
await flushPromises();
expect(mocks.fetchSelfServeDataInternal).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(1);
await flushPromises();
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledTimes(1);
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
});
it("hides machine tasks while manual wash is selected", async () => {
mocks.activeTasks.value = [
{ id: 31, task: "Machine checklist", services: ["MACHINE"] },
{ id: 32, task: "Manual bay prep", services: ["GATE"] },
{ id: 33, task: "General instruction", services: [] },
];
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
expect(wrapper.find('[data-testid="rendered-task-31"]').exists()).toBe(false);
expect(wrapper.get('[data-testid="rendered-task-32"]').text()).toContain("Manual bay prep");
expect(wrapper.get('[data-testid="rendered-task-33"]').text()).toContain("General instruction");
expect(wrapper.find('[data-testid="tasks-dynamic-image-stub"]').exists()).toBe(false);
});
it("shows machine tasks after machine wash is selected", async () => {
mocks.activeTasks.value = [
{ id: 31, task: "Machine checklist", services: ["MACHINE"] },
{ id: 32, task: "Manual bay prep", services: ["GATE"] },
];
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
expect(wrapper.find('[data-testid="rendered-task-31"]').exists()).toBe(false);
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
await wrapper.get('[data-testid="emit-machine-wash"]').trigger("click");
await nextTick();
expect(wrapper.get('[data-testid="rendered-task-31"]').text()).toContain("Machine checklist");
expect(wrapper.get('[data-testid="rendered-task-32"]').text()).toContain("Manual bay prep");
expect(wrapper.find('[data-testid="tasks-dynamic-image-stub"]').exists()).toBe(true);
});
it("accepts another question answer while the previous answer sync is still pending", async () => {
let resolveFirstSync;
mocks.syncVehicleAnswer.mockImplementationOnce(
() =>
new Promise((resolve) => {
resolveFirstSync = resolve;
})
);
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await wrapper.get('[data-testid="emit-answer"]').trigger("click");
await nextTick();
await wrapper.get('[data-testid="emit-answer-21"]').trigger("click");
await flushPromises();
expect(mocks.answerQuestion).toHaveBeenCalledWith(11, true);
expect(mocks.answerQuestion).toHaveBeenCalledWith(21, false);
expect(mocks.syncVehicleAnswer).toHaveBeenCalledTimes(2);
expect(mocks.syncVehicleAnswer).toHaveBeenNthCalledWith(2, {
departmentId: 6,
laneId: 7,
customerNumber: 12345679,
reg: "AB12345",
questionId: 21,
value: false,
vehicleTypeId: 2,
});
resolveFirstSync?.();
await flushPromises();
});
it("surfaces failed question answer syncs and blocks question confirmation until retry succeeds", async () => {
mocks.syncVehicleAnswer.mockRejectedValueOnce(new Error("Question sync failed")).mockResolvedValueOnce(undefined);
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await wrapper.get('[data-testid="self-serve-nav-next"]').trigger("click");
await flushPromises();
await wrapper.get('[data-testid="emit-answer"]').trigger("click");
await flushPromises();
expect(wrapper.get('[data-testid="self-serve-question-sync-error"]').text()).toContain("Question sync failed");
const confirmButton = wrapper.get('[data-testid="self-serve-nav-confirm"]');
expect(confirmButton.attributes("disabled")).toBeDefined();
await confirmButton.trigger("click");
expect(mocks.handleConfirmNext).not.toHaveBeenCalled();
await wrapper.get('[data-testid="self-serve-question-sync-retry"]').trigger("click");
await flushPromises();
expect(mocks.syncVehicleAnswer).toHaveBeenCalledTimes(2);
expect(mocks.syncVehicleAnswer).toHaveBeenNthCalledWith(2, {
departmentId: 6,
laneId: 7,
customerNumber: 12345679,
reg: "AB12345",
questionId: 11,
value: true,
vehicleTypeId: 2,
});
expect(wrapper.find('[data-testid="self-serve-question-sync-error"]').exists()).toBe(false);
expect(wrapper.get('[data-testid="self-serve-nav-confirm"]').attributes("disabled")).toBeUndefined();
});
it("shows disabled warning without showing loading state when department self-serve is unavailable", async () => {
mocks.nearestDepartment.value = {
...mocks.nearestDepartment.value,
self_serve_enabled: false,
};
mocks.guestDepartments.value = [mocks.nearestDepartment.value];
mocks.doesCurrentDepartmentSelectionHaveSelfServeEnabled.value = false;
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
expect(wrapper.find('[data-testid="self-serve-disabled-warning"]').exists()).toBe(true);
expect(wrapper.text()).not.toContain("self_wash.loading_data");
});
it("adds an unknown registration as customer vehicle before continuing from vehicle step", async () => {
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
await wrapper.get('[data-testid="emit-registration-unknown"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await wrapper.get('[data-testid="self-serve-nav-next"]').trigger("click");
await flushPromises();
expect(mocks.addVehicle).toHaveBeenCalledWith(2, "ZZ99999", false, 12345679);
expect(mocks.fetchCustomerVehicles.mock.calls.length).toBeGreaterThan(1);
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "ZZ99999");
});
it("uses an effective lane when moving from vehicle to questions without manual lane selection", async () => {
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await wrapper.get('[data-testid="self-serve-nav-next"]').trigger("click");
await flushPromises();
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
});
it("falls back to available lane when restored lane is stale for the selected department", async () => {
vi.useFakeTimers();
mocks.restoredProgressPayload = {
washInProgress: false,
washLaneId: null,
washStartTime: null,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 999,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 1,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await vi.advanceTimersByTimeAsync(300);
await flushPromises();
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
wrapper.unmount();
});
it("overrides a restored customer number with the authenticated customer's number", async () => {
mocks.restoredProgressPayload = {
washInProgress: false,
washLaneId: null,
washStartTime: null,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 777,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 1,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-answer"]').trigger("click");
await flushPromises();
expect(mocks.syncVehicleAnswer).toHaveBeenCalledWith({
departmentId: 6,
laneId: 7,
customerNumber: 12345679,
reg: "AB12345",
questionId: 11,
value: true,
vehicleTypeId: 2,
});
wrapper.unmount();
});
it("does not auto-start wash when only the first visible question is answered", async () => {
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-lane"]').trigger("click");
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await wrapper.get('[data-testid="self-serve-nav-next"]').trigger("click");
await flushPromises();
expect(wrapper.get('[data-testid="self-serve-nav-confirm"]').text()).toContain("common.next");
await wrapper.get('[data-testid="emit-answer"]').trigger("click");
await flushPromises();
expect(mocks.onStartWash).not.toHaveBeenCalled();
});
it("skips directly to lane selection when no questions are visible after vehicle setup", async () => {
mocks.visibleQuestions.value = [];
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-registration"]').trigger("click");
await wrapper.get('[data-testid="emit-vehicle-type"]').trigger("click");
await nextTick();
await wrapper.get('[data-testid="self-serve-nav-next"]').trigger("click");
await flushPromises();
expect(mocks.fetchSelfServeDataInternal).toHaveBeenCalledWith(6, 2, 7, "AB12345");
expect(wrapper.get('[data-testid="self-serve-nav-confirm"]').text()).toContain("self_wash.confirm_and_start");
expect(mocks.onStartWash).not.toHaveBeenCalled();
});
it("returns restored prewash progress to answered question review when questions exist", async () => {
mocks.answers.value = { 11: true };
mocks.restoredProgressPayload = {
washInProgress: false,
washLaneId: null,
washStartTime: null,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: { 11: true },
completedTasks: {},
currentStep: 2,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await nextTick();
const prewashActions = wrapper.get('[data-testid="self-serve-prewash-bottom-actions"]');
expect(prewashActions.classes()).toContain("self-serve-prewash-actions--question-review");
expect(wrapper.get('[data-testid="self-serve-nav-confirm"]').text()).toContain("common.next");
expect(mocks.onStartWash).not.toHaveBeenCalled();
});
it("places lane setup navigation in the pre-wash bottom action row", async () => {
mocks.visibleQuestions.value = [];
mocks.restoredProgressPayload = {
washInProgress: false,
washLaneId: null,
washStartTime: null,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 2,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
const prewashActions = wrapper.get('[data-testid="self-serve-prewash-bottom-actions"]');
const navigationRow = prewashActions.get('[data-testid="self-serve-prewash-navigation-actions"]');
const previousButton = navigationRow.get('[data-testid="self-serve-nav-previous"]');
const confirmButton = navigationRow.get('[data-testid="self-serve-nav-confirm"]');
const visibleActionIds = Array.from(navigationRow.element.children)
.filter((element) => !(element.getAttribute("style") || "").includes("display: none"))
.map((element) => element.getAttribute("data-testid"));
expect(prewashActions.classes()).toContain("self-serve-prewash-actions");
expect(navigationRow.classes()).toContain("self-serve-bottom-actions__row--prewash");
expect(previousButton.classes()).toContain("self-serve-bottom-actions__button");
expect(confirmButton.classes()).toContain("self-serve-bottom-actions__button");
expect(visibleActionIds).toEqual(["self-serve-nav-previous", "self-serve-nav-confirm"]);
});
it("does not restore a delayed server wash after the user edits start form input", async () => {
vi.useFakeTimers();
mocks.restoredProgressPayload = {
washInProgress: false,
washLaneId: null,
washStartTime: null,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 0,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await wrapper.get('[data-testid="emit-registration-unknown"]').trigger("click");
await vi.advanceTimersByTimeAsync(1600);
await flushPromises();
expect(mocks.sessionRequest).not.toHaveBeenCalledWith(
"/modules/self-serve/lane/wash/in-progress",
"GET",
expect.anything()
);
wrapper.unmount();
});
it("does not call SessionUser.request for delayed server restore after unmount", async () => {
vi.useFakeTimers();
mocks.restoredProgressPayload = {
washInProgress: false,
washLaneId: null,
washStartTime: null,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 0,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
expect(SessionUser.request).not.toHaveBeenCalled();
wrapper.unmount();
await vi.advanceTimersByTimeAsync(1600);
await flushPromises();
expect(SessionUser.request).not.toHaveBeenCalled();
});
it("retries server active wash restore when the authenticated customer number arrives after mount", async () => {
mocks.sessionCustomerNumber.value = null;
mocks.sessionRequest.mockImplementation(async (path, method, payload) => {
if (path === "/modules/self-serve/lane/wash/my-active-wash" && method === "GET") {
return {
data: {
data: {
lane_id: 7,
in_progress: true,
session: {
id: 805,
reg: "AB12345",
customer_number: 12345679,
vehicle_type_id: 2,
machine_relay_enabled: false,
wash_started_at: "2026-04-28 10:15:00",
},
customer: { customer_number: 12345679 },
vehicle: { reg: "AB12345", type: 2 },
},
},
};
}
return {
data: {
data: {
lane_id: payload?.lane_id,
in_progress: false,
session: null,
customer: null,
vehicle: null,
},
},
};
});
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await new Promise((resolve) => setTimeout(resolve, 0));
await flushPromises();
expect(mocks.sessionRequest).not.toHaveBeenCalledWith(
"/modules/self-serve/lane/wash/in-progress",
"GET",
expect.anything()
);
mocks.sessionCustomerNumber.value = 12345679;
await nextTick();
await flushPromises();
await new Promise((resolve) => setTimeout(resolve, 0));
await flushPromises();
expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/my-active-wash", "GET");
expect(mocks.fetchWashSummary).toHaveBeenCalledWith({ session_id: 805, vehicle_type: 2 }, false);
expect(mocks.startElapsedTimer).toHaveBeenCalled();
expect(
wrapper.get('[data-testid="self-serve-nav-open-property-access-gate"]').attributes("style") || ""
).not.toContain("display: none");
wrapper.unmount();
});
it("wires property gate buttons in the in-progress step", async () => {
mocks.restoredProgressPayload = {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 20_000,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 4,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
const bottomActions = wrapper.get('[data-testid="self-serve-bottom-actions"]');
const guidedActions = bottomActions.get('[data-testid="self-serve-guided-bottom-actions"]');
const sessionActions = bottomActions.get('[data-testid="self-serve-session-bottom-actions"]');
const gateActions = bottomActions.get('[data-testid="self-serve-property-gate-actions"]');
const navigationActions = wrapper.get(".buttons");
const guidedPreviousButton = guidedActions.get('[data-testid="self-serve-guided-prev"]');
const guidedNextButton = guidedActions.get('[data-testid="self-serve-guided-next"]');
const helpButton = sessionActions.get('[data-testid="self-serve-nav-help"]');
const accessGateButton = gateActions.get('[data-testid="self-serve-nav-open-property-access-gate"]');
const exitGateButton = gateActions.get('[data-testid="self-serve-nav-open-property-exit-gate"]');
expect(bottomActions.classes()).toContain("self-serve-bottom-actions");
expect(bottomActions.element.children[0]).toBe(sessionActions.element);
expect(bottomActions.element.children[1]).toBe(gateActions.element);
expect(bottomActions.element.children[2]).toBe(guidedActions.element);
expect(guidedPreviousButton.classes()).toContain("self-serve-bottom-actions__button");
expect(guidedNextButton.classes()).toContain("self-serve-bottom-actions__button");
expect(helpButton.classes()).toContain("self-serve-bottom-actions__button");
expect(sessionActions.find('[data-testid="self-serve-nav-complete"]').exists()).toBe(false);
expect(guidedActions.find('[data-testid="self-serve-nav-complete"]').exists()).toBe(false);
expect(accessGateButton.classes()).toContain("self-serve-bottom-actions__button");
expect(exitGateButton.classes()).toContain("self-serve-bottom-actions__button");
expect(navigationActions.find('[data-testid="self-serve-guided-prev"]').exists()).toBe(false);
expect(navigationActions.find('[data-testid="self-serve-guided-next"]').exists()).toBe(false);
expect(navigationActions.find('[data-testid="self-serve-nav-help"]').exists()).toBe(false);
expect(navigationActions.find('[data-testid="self-serve-nav-complete"]').exists()).toBe(false);
expect(navigationActions.find('[data-testid="self-serve-nav-open-property-access-gate"]').exists()).toBe(false);
expect(navigationActions.find('[data-testid="self-serve-nav-open-property-exit-gate"]').exists()).toBe(false);
await accessGateButton.trigger("click");
await exitGateButton.trigger("click");
expect(mocks.openPropertyAccessGate).toHaveBeenCalledWith(7);
expect(mocks.openPropertyExitGate).toHaveBeenCalledWith(7);
for (let index = 0; index < 5; index += 1) {
await guidedActions.get('[data-testid="self-serve-guided-next"]').trigger("click");
}
expect(guidedActions.find('[data-testid="self-serve-guided-next"]').exists()).toBe(false);
const completeButton = guidedActions.get('[data-testid="self-serve-nav-complete"]');
expect(completeButton.classes()).toContain("self-serve-bottom-actions__button");
await completeButton.trigger("click");
await new Promise((resolve) => setTimeout(resolve, 260));
await flushPromises();
expect(mocks.onStopWash).toHaveBeenCalledWith(7);
expect(mocks.openPropertyExitGate).toHaveBeenLastCalledWith(7, { suppressAlert: true });
});
it("resets guided wash steps after completing so the next wash starts from the first instruction", async () => {
mocks.restoredProgressPayload = {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 20_000,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 4,
};
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
const guidedStepText = () => wrapper.get('[data-testid="guided-step-stub"]').text();
const guidedActions = wrapper
.get('[data-testid="self-serve-bottom-actions"]')
.get('[data-testid="self-serve-guided-bottom-actions"]');
expect(guidedStepText()).toBe("0");
for (let index = 0; index < 5; index += 1) {
await guidedActions.get('[data-testid="self-serve-guided-next"]').trigger("click");
}
expect(guidedStepText()).toBe("5");
await guidedActions.get('[data-testid="self-serve-nav-complete"]').trigger("click");
await new Promise((resolve) => setTimeout(resolve, 260));
await flushPromises();
expect(mocks.onStopWash).toHaveBeenCalledWith(7);
await vi.waitFor(() => {
expect(wrapper.find('[data-testid="self-serve-nav-close"]').exists()).toBe(true);
});
await wrapper.get('[data-testid="self-serve-nav-close"]').trigger("click");
await nextTick();
expect(guidedStepText()).toBe("0");
expect(mocks.clearProgress).toHaveBeenCalled();
});
it("shows a finishing screen while completing and then opens the property exit gate silently", async () => {
mocks.restoredProgressPayload = {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 20_000,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 4,
};
let resolveStop;
mocks.onStopWash.mockReturnValueOnce(
new Promise((resolve) => {
resolveStop = resolve;
})
);
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
messages: {
en: {
common: {
done: "Done",
next: "Next",
previous: "Previous",
},
self_wash: {
finishing_wash_exit_opening: "Afslutter vask, porten åbnes automatisk",
},
user_dashboard: {
wash: {
title: "Wash",
subtitle: "Wash",
},
},
},
},
});
await flushPromises();
const guidedActions = wrapper
.get('[data-testid="self-serve-bottom-actions"]')
.get('[data-testid="self-serve-guided-bottom-actions"]');
for (let index = 0; index < 5; index += 1) {
await guidedActions.get('[data-testid="self-serve-guided-next"]').trigger("click");
}
await guidedActions.get('[data-testid="self-serve-nav-complete"]').trigger("click");
await nextTick();
expect(wrapper.get('[data-testid="self-serve-finishing-wash"]').text()).toContain(
"Afslutter vask, porten åbnes automatisk"
);
expect(mocks.onStopWash).toHaveBeenCalledWith(7);
expect(mocks.openPropertyExitGate).not.toHaveBeenCalled();
resolveStop(true);
await new Promise((resolve) => setTimeout(resolve, 260));
await flushPromises();
expect(mocks.openPropertyExitGate).toHaveBeenCalledWith(7, { suppressAlert: true });
});
it("keeps the guided wash in progress when stop fails", async () => {
mocks.restoredProgressPayload = {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 20_000,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 4,
};
mocks.onStopWash.mockResolvedValue(false);
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
const bottomActions = wrapper.get('[data-testid="self-serve-bottom-actions"]');
const guidedActions = bottomActions.get('[data-testid="self-serve-guided-bottom-actions"]');
for (let index = 0; index < 5; index += 1) {
await guidedActions.get('[data-testid="self-serve-guided-next"]').trigger("click");
}
await guidedActions.get('[data-testid="self-serve-nav-complete"]').trigger("click");
await new Promise((resolve) => setTimeout(resolve, 260));
await flushPromises();
expect(mocks.onStopWash).toHaveBeenCalledWith(7);
expect(wrapper.get('[data-testid="guided-step-stub"]').text()).toBe("5");
expect(bottomActions.isVisible()).toBe(true);
});
it("restores an active wash from the server when local progress is missing", async () => {
mocks.nearestDepartment.value = {
id: 6,
name: "Roskilde",
address: "Industrivej 45, 4000 Roskilde",
self_serve_enabled: true,
lanes: [{ id: 7, name: "7", status: "AVAILABLE", products: [2], machine_available: true }],
};
const odense = {
id: 2,
name: "Odense",
address: "Beta 2",
self_serve_enabled: true,
lanes: [{ id: 9, name: "9", status: "OCCUPIED", products: [2], machine_available: true }],
};
mocks.guestDepartments.value = [mocks.nearestDepartment.value, odense];
mocks.sessionRequest.mockImplementation(async (path, method, payload) => {
if (
(path === "/modules/self-serve/lane/wash/my-active-wash" && method === "GET") ||
(path === "/modules/self-serve/lane/wash/in-progress" && method === "GET" && payload?.lane_id === 9)
) {
return {
data: {
data: {
lane_id: 9,
in_progress: true,
session: {
id: 704,
reg: "CD67890",
customer_number: 12345679,
vehicle_type_id: 2,
machine_relay_enabled: true,
wash_started_at: "2026-04-28 10:15:00",
},
customer: { customer_number: 12345679 },
vehicle: { reg: "CD67890", type: 2 },
},
},
};
}
return {
data: {
data: {
lane_id: payload?.lane_id,
in_progress: false,
session: null,
customer: null,
vehicle: null,
},
},
};
});
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await new Promise((resolve) => setTimeout(resolve, 0));
await flushPromises();
expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/my-active-wash", "GET");
expect(mocks.sessionRequest).not.toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", {
lane_id: 7,
});
expect(mocks.fetchWashSummary).toHaveBeenCalledWith({ session_id: 704, vehicle_type: 2 }, false);
expect(mocks.startElapsedTimer).toHaveBeenCalled();
expect(mocks.saveProgress).toHaveBeenCalledWith("serverActiveWash");
const accessGateButton = wrapper.get('[data-testid="self-serve-nav-open-property-access-gate"]');
expect(accessGateButton.attributes("style") || "").not.toContain("display: none");
await accessGateButton.trigger("click");
expect(mocks.openPropertyAccessGate).toHaveBeenCalledWith(9);
});
it("does not restore a recently completed wash from the server on revisit", async () => {
localStorage.setItem(
"mywash_recent_completed_v1",
JSON.stringify({
laneId: 7,
reg: "AB12345",
customerNumber: 12345679,
completedAt: Date.now(),
})
);
mocks.sessionRequest.mockImplementation(async (path, method, payload) => {
if (path === "/modules/self-serve/lane/wash/my-active-wash" && method === "GET") {
return {
data: {
data: {
lane_id: 7,
in_progress: true,
session: {
id: 805,
reg: "AB12345",
customer_number: 12345679,
vehicle_type_id: 2,
machine_relay_enabled: false,
wash_started_at: "2026-04-28 10:15:00",
},
customer: { customer_number: 12345679 },
vehicle: { reg: "AB12345", type: 2 },
},
},
};
}
return {
data: {
data: {
lane_id: payload?.lane_id,
in_progress: false,
session: null,
customer: null,
vehicle: null,
},
},
};
});
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await new Promise((resolve) => setTimeout(resolve, 0));
await flushPromises();
expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/my-active-wash", "GET");
expect(mocks.fetchWashSummary).not.toHaveBeenCalled();
expect(mocks.startElapsedTimer).not.toHaveBeenCalled();
expect(mocks.saveProgress).not.toHaveBeenCalledWith("serverActiveWash");
expect(wrapper.get('[data-testid="self-serve-bottom-actions"]').attributes("style") || "").toContain(
"display: none"
);
});
it("clears restored in-progress state when active wash refresh cannot verify authenticated ownership", async () => {
mocks.restoredProgressPayload = {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 30_000,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 4,
};
mocks.sessionRequest.mockImplementation(async (path, method, payload) => {
if (path === "/modules/self-serve/lane/wash/in-progress" && method === "GET" && payload?.lane_id === 7) {
return {
data: {
data: {
lane_id: 7,
in_progress: true,
session: {
id: 909,
reg: "VICTIM42",
customer_number: null,
vehicle_type_id: 2,
machine_relay_enabled: true,
wash_started_at: "2026-04-28 10:15:00",
},
customer: {},
vehicle: { reg: "VICTIM42", type: 2 },
},
},
};
}
return undefined;
});
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await nextTick();
await flushPromises();
expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", {
lane_id: 7,
});
expect(mocks.fetchWashSummary).not.toHaveBeenCalledWith({ session_id: 909, vehicle_type: 2 }, false);
expect(mocks.saveProgress).not.toHaveBeenCalledWith("serverActiveWashRefresh");
expect(mocks.stopElapsedTimer).toHaveBeenCalled();
expect(mocks.clearProgress).toHaveBeenCalled();
expect(wrapper.get('[data-testid="self-serve-bottom-actions"]').attributes("style") || "").toContain(
"display: none"
);
wrapper.unmount();
});
it("clears restored in-progress state when active wash refresh says the lane is no longer in progress", async () => {
mocks.restoredProgressPayload = {
washInProgress: true,
washLaneId: 7,
washStartTime: Date.now() - 30_000,
licensePlateInput: "AB12345",
vehicleTypeSelect: 2,
radioWashType: "Manual",
radioLaneOption: 7,
customerNumberInput: 12345679,
isForcingNearestDepartment: false,
forceNearestDepartmentEvaluationId: 0,
answers: {},
completedTasks: {},
currentStep: 4,
};
mocks.sessionRequest.mockImplementation(async (path, method, payload) => {
if (path === "/modules/self-serve/lane/wash/in-progress" && method === "GET" && payload?.lane_id === 7) {
return {
data: {
data: {
lane_id: 7,
in_progress: false,
session: null,
customer: null,
vehicle: null,
},
},
};
}
return undefined;
});
const wrapper = mountWithApp(MyWashStart, {
global: {
stubs: stubComponents,
},
});
await flushPromises();
await nextTick();
await flushPromises();
expect(mocks.sessionRequest).toHaveBeenCalledWith("/modules/self-serve/lane/wash/in-progress", "GET", {
lane_id: 7,
});
expect(mocks.stopElapsedTimer).toHaveBeenCalled();
expect(mocks.clearProgress).toHaveBeenCalled();
expect(mocks.fetchWashSummary).not.toHaveBeenCalled();
expect(wrapper.get('[data-testid="self-serve-bottom-actions"]').attributes("style") || "").toContain(
"display: none"
);
wrapper.unmount();
});
});