162 lines
9.5 KiB
JavaScript
162 lines
9.5 KiB
JavaScript
import fs from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const source = fs.readFileSync("src/views/dashboards/userDashboard/wash/MyWashStart.vue", "utf8");
|
|
const myWashSource = fs.readFileSync("src/views/dashboards/userDashboard/wash/MyWash.vue", "utf8");
|
|
|
|
describe("MyWashStart.vue production recovery contracts", () => {
|
|
it("requires the operational self-serve permission on customer entry points", () => {
|
|
expect(source).toContain('required-feature="selfserve"');
|
|
expect(source).toContain('required-action="add"');
|
|
expect(myWashSource).toContain('required-feature="selfserve"');
|
|
expect(myWashSource).toContain('required-action="add"');
|
|
});
|
|
|
|
it("restores local progress before scheduling a server active-wash restore", () => {
|
|
expect(source).toContain("const restoredProgress = restoreProgress();");
|
|
expect(source).toContain("shouldRestoreServerActiveWash.value = !restoredProgress?.washInProgress;");
|
|
expect(source).toContain("scheduleServerActiveWashRestore(restoredProgress ? 1600 : 0);");
|
|
expect(source.indexOf("const restoredProgress = restoreProgress();")).toBeLessThan(
|
|
source.indexOf("scheduleServerActiveWashRestore(restoredProgress ? 1600 : 0);")
|
|
);
|
|
});
|
|
|
|
it("restores authenticated server active washes into in-progress state and summary data", () => {
|
|
expect(source).toContain('const SERVER_ACTIVE_WASH_ENDPOINT = "/modules/self-serve/lane/wash/my-active-wash";');
|
|
expect(source).toContain("if (!isAuthenticatedCustomerActiveWash(details, customerNumber))");
|
|
expect(source).toContain("await applyServerActiveWash(activeWash);");
|
|
expect(source).toContain("washInProgress.value = true;");
|
|
expect(source).toContain("currentStep.value = shouldRestoreTaskStep ? steps.TASKS : steps.WASH_IN_PROGRESS;");
|
|
expect(source).toContain("await fetchWashSummary(summaryParams, false);");
|
|
expect(source).toContain('saveProgress("serverActiveWash");');
|
|
});
|
|
|
|
it("restores relay-enabled server sessions to the task checklist", () => {
|
|
expect(source).toContain('const SERVER_TASK_RESTORE_STATUSES = new Set(["MACHINE_RELAY_ENABLED"]);');
|
|
expect(source).toContain(
|
|
'const SERVER_MACHINE_WASH_STATUSES = new Set(["MACHINE_RELAY_ENABLED", "MACHINE_STARTED"]);'
|
|
);
|
|
expect(source).toContain("const shouldRestoreTaskStep = SERVER_TASK_RESTORE_STATUSES.has(sessionStatus);");
|
|
expect(source).toContain("if (shouldRestoreTaskStep && hasMachineTasks.value)");
|
|
expect(source).toContain(
|
|
"currentStep.value = displayedActiveTasks.value.length > 0 ? steps.TASKS : steps.WASH_IN_PROGRESS;"
|
|
);
|
|
});
|
|
|
|
it("does not restore preview-only ready sessions without start evidence", () => {
|
|
expect(source).toContain("const hasServerActiveWashStartEvidence = (details: any) =>");
|
|
expect(source).toContain("SERVER_MACHINE_WASH_STATUSES.has(sessionStatus)");
|
|
expect(source).toContain("session?.machine_relay_enabled === true");
|
|
expect(source).toContain("session?.machine_start_triggered === true");
|
|
expect(source).toContain("parseServerDateTimeMs(session?.wash_started_at) !== null");
|
|
expect(source).toContain("if (!hasServerActiveWashStartEvidence(details))");
|
|
});
|
|
|
|
it("suppresses recent completions so polling and restore do not resurrect a just-finished wash", () => {
|
|
expect(source).toContain('const RECENT_COMPLETED_WASH_KEY = "mywash_recent_completed_v1";');
|
|
expect(source).toContain("const RECENT_COMPLETED_WASH_SUPPRESSION_MS = 10 * 60 * 1000;");
|
|
expect(source).toContain("const finalizeGuidedWashCompletion = (laneId: number | string | null) =>");
|
|
expect(source).toContain("markRecentlyCompletedWash(laneId, licensePlateInput.value, getNumericCustomerNumber());");
|
|
expect(source).toContain("if (isRecentlyCompletedActiveWash(activeWash))");
|
|
expect(source).toContain(
|
|
"const recentlyCompletedCurrentWash = isRecentlyCompletedActiveWash({ details, laneId });"
|
|
);
|
|
expect(source).toContain("if (recentlyCompletedCurrentWash) {");
|
|
expect(source).toContain("if (!serverStillMatchesCurrentWash) {");
|
|
expect(source).toContain("if (isRestoring.value || isCompletedWashState.value || !nearestDepartment.value)");
|
|
});
|
|
|
|
it("verifies server completion before recovering from a failed stop command", () => {
|
|
expect(source).toContain("const waitForStopFailureServerCompletion = async () =>");
|
|
expect(source).toContain("const serverCompleted = await waitForStopFailureServerCompletion();");
|
|
expect(source).toContain("if (serverCompleted) {");
|
|
expect(source).toContain("finalizeGuidedWashCompletion(completingLaneId);");
|
|
});
|
|
|
|
it("persists and restores the stopping wash view across refreshes", () => {
|
|
expect(source).toContain('const STOPPING_WASH_KEY = "mywash_stopping_v1";');
|
|
expect(source).toContain("const STOPPING_WASH_TTL_MS = 2 * 60 * 1000;");
|
|
expect(source).toContain("const markStoppingWash = (laneId: number | string | null) =>");
|
|
expect(source).toContain("markStoppingWash(completingLaneId);");
|
|
expect(source).toContain("const stopSucceeded = await onStopWash(completingLaneId);");
|
|
expect(source.indexOf("markStoppingWash(completingLaneId);")).toBeLessThan(
|
|
source.indexOf("const stopSucceeded = await onStopWash(completingLaneId);")
|
|
);
|
|
expect(source).toContain("const restoreStoppingWashIfMatched = (");
|
|
expect(source).toContain("if (restoredProgress?.washInProgress) {");
|
|
expect(source).toContain("restoreStoppingWashIfMatched({");
|
|
expect(source).toContain("clearStoppingWashMarker();");
|
|
});
|
|
|
|
it("keeps active and completed washes visible even if department availability changes", () => {
|
|
expect(source).toContain("const shouldShowWashFlow = computed(");
|
|
expect(source).toContain("const isCompletedWashState = computed(");
|
|
expect(source).toContain("const hasCompletedGuidedWash = ref(false);");
|
|
expect(source).toContain("const hasRecentlyCompletedWash = ref(false);");
|
|
expect(source).toContain("washInProgress.value ||");
|
|
expect(source).toContain("isCompletingWash.value ||");
|
|
expect(source).toContain("isCompletedWashState.value");
|
|
expect(source).toContain("hasRecentlyCompletedWash.value");
|
|
expect(source).toContain('data-testid="self-serve-completed-fallback"');
|
|
expect(source).toContain('v-show="isCompletedWashState"');
|
|
expect(source).toContain('<template v-if="shouldShowWashFlow && !isCompletedWashState">');
|
|
expect(source).toContain('v-model="displayedStep"');
|
|
expect(source).toContain("currentStep.value < steps.COMPLETED");
|
|
});
|
|
|
|
it("does not treat the customer's active occupied lane as unavailable", () => {
|
|
expect(source).toContain("session: selfServeSession,");
|
|
expect(source).toContain("const isCurrentActiveWashLane = (lane: any) =>");
|
|
expect(source).toContain("const isSelfServeSessionAllowed = () =>");
|
|
expect(source).toContain("const isCurrentAllowedSelfServeLane = (lane: any) =>");
|
|
expect(source).toContain("isAllowedSelfServeValue(selfServeSession.value?.allowed)");
|
|
expect(source).toContain("selfServeSession.value?.lane_id");
|
|
expect(source).toContain("getAllowedSelfServeLaneIds().has(laneId)");
|
|
expect(source).toContain("if (isCurrentActiveWashLane(lane) || isCurrentAllowedSelfServeLane(lane))");
|
|
expect(source).toContain("return isLaneSelfServeEnabled(lane);");
|
|
expect(source).toContain('Object.prototype.hasOwnProperty.call(lane || {}, "selfserve_available")');
|
|
expect(source).toContain('return lane?.status === "AVAILABLE" && isLaneSelfServeEnabled(lane);');
|
|
});
|
|
|
|
it("does not auto-complete active washes from polling before the customer completes them", () => {
|
|
expect(source).toContain(
|
|
"const recentlyCompletedCurrentWash = isRecentlyCompletedActiveWash({ details, laneId });"
|
|
);
|
|
expect(source).toContain("if (recentlyCompletedCurrentWash) {");
|
|
expect(source).toContain("!isCompletingWash.value");
|
|
expect(source.indexOf("!isCompletingWash.value")).toBeLessThan(
|
|
source.indexOf(
|
|
"clearLocalActiveWashFromServer({ preserveActionError: isCompletingWash.value });",
|
|
source.indexOf("if (!serverStillMatchesCurrentWash)")
|
|
)
|
|
);
|
|
});
|
|
|
|
it("defaults to machine wash when configured tasks are machine-only and the customer has not chosen manually", () => {
|
|
expect(source).toContain("const hasExplicitWashTypeSelection = ref(false);");
|
|
expect(source).toContain(
|
|
"const hasMachineTasks = computed(() => activeTasks.value.some((task: any) => isMachineTask(task)));"
|
|
);
|
|
expect(source).toContain("const shouldDefaultToMachineWash = computed(");
|
|
expect(source).toContain('radioWashType.value = "Machine";');
|
|
});
|
|
|
|
it("cleans up unload handlers, department refresh, active-wash polling, and footer state on unmount", () => {
|
|
expect(source).toContain("unregisterBeforeUnload.value();");
|
|
expect(source).toContain("stopAutoRefresh();");
|
|
expect(source).toContain("stopActiveWashRefresh();");
|
|
expect(source).toContain("markDestroying();");
|
|
expect(source).toContain("setShowFooterInContent(true);");
|
|
});
|
|
|
|
it("prevents duplicate fetches and overlapping active-wash sync requests", () => {
|
|
expect(source).toContain("if (isVehicleStepNextLoading.value)");
|
|
expect(source).toContain("isVehicleStepNextLoading.value = true;");
|
|
expect(source).toContain("if (isSyncingActiveWash.value || !washInProgress.value || !washLaneId.value)");
|
|
expect(source).toContain("isSyncingActiveWash.value = true;");
|
|
expect(source).toContain("isSyncingActiveWash.value = false;");
|
|
expect(source).toContain("stopActiveWashRefresh();");
|
|
expect(source).toContain("activeWashRefreshInterval.value = window.setInterval");
|
|
});
|
|
});
|