Add Danish self-serve labels and guided wash flow steps. Update UI with guided step actions and bottom control integration. Fix translations and refine E2E/unit test coverage for self-serve flow behavior.
This commit is contained in:
@@ -1116,6 +1116,7 @@ function buildPathOutcomesResponse(request = {}) {
|
||||
question_count: 2,
|
||||
question_ids: [11, 12],
|
||||
max_states: 2048,
|
||||
path_sample_count: 2,
|
||||
},
|
||||
outcomes: [
|
||||
{
|
||||
@@ -1220,6 +1221,103 @@ function buildPathOutcomesResponse(request = {}) {
|
||||
node_ids: ["question:11"],
|
||||
},
|
||||
],
|
||||
paths: [
|
||||
{
|
||||
id: "path-1",
|
||||
result: "Allowed",
|
||||
summary: "Allowed / MACHINE / 1 task / 2 signals",
|
||||
allowed: true,
|
||||
services: ["MACHINE"],
|
||||
tasks: [
|
||||
{
|
||||
id: 41,
|
||||
node_id: "task:41",
|
||||
label: "Fold mirrors",
|
||||
services: ["MACHINE"],
|
||||
buttons: ["reset", 1, "start"],
|
||||
order_priority: 1,
|
||||
},
|
||||
],
|
||||
signals: [
|
||||
{
|
||||
sequence: 1,
|
||||
runtime_stage: "eligibility_sync",
|
||||
signal_type: "session_event",
|
||||
relay_role: "SESSION",
|
||||
source: "none",
|
||||
virtual: false,
|
||||
predicted_status: "sent",
|
||||
payload: { allowed: true, allowed_services: ["MACHINE"] },
|
||||
},
|
||||
{
|
||||
sequence: 2,
|
||||
runtime_stage: "machine_start_signal",
|
||||
signal_type: "shelly_event",
|
||||
relay_role: "MACHINE",
|
||||
relay_id: "M-7",
|
||||
target_gateway_label: "Virtual Studio Gateway",
|
||||
target_binding: "binding:virtual-main:M-7:0",
|
||||
source: "virtual",
|
||||
virtual: true,
|
||||
predicted_status: "virtual_only",
|
||||
payload: { event: "input.toggle_on", bill_machine_wash: true },
|
||||
},
|
||||
],
|
||||
task_count: 1,
|
||||
signal_count: 2,
|
||||
answers: [
|
||||
{
|
||||
question_id: 11,
|
||||
question: "Are mirrors folded?",
|
||||
node_id: "question:11",
|
||||
answer: true,
|
||||
answer_label: "Yes",
|
||||
},
|
||||
{
|
||||
question_id: 12,
|
||||
question: "Is the lift lowered?",
|
||||
node_id: "question:12",
|
||||
answer: true,
|
||||
answer_label: "Yes",
|
||||
},
|
||||
],
|
||||
scope: { lane: "Lane 7", vehicle_type: vehicleType },
|
||||
node_ids: ["question:11", "question:12", "task:41", "binding:virtual-main:M-7:0"],
|
||||
},
|
||||
{
|
||||
id: "path-2",
|
||||
result: "Blocked",
|
||||
summary: "Blocked / No services / 0 tasks / 1 signal",
|
||||
allowed: false,
|
||||
services: [],
|
||||
tasks: [],
|
||||
signals: [
|
||||
{
|
||||
sequence: 1,
|
||||
runtime_stage: "eligibility_sync",
|
||||
signal_type: "session_event",
|
||||
relay_role: "SESSION",
|
||||
source: "none",
|
||||
virtual: false,
|
||||
predicted_status: "skipped",
|
||||
payload: { allowed: false, allowed_services: [] },
|
||||
},
|
||||
],
|
||||
task_count: 0,
|
||||
signal_count: 1,
|
||||
answers: [
|
||||
{
|
||||
question_id: 11,
|
||||
question: "Are mirrors folded?",
|
||||
node_id: "question:11",
|
||||
answer: false,
|
||||
answer_label: "No",
|
||||
},
|
||||
],
|
||||
scope: { lane: "Lane 7", vehicle_type: vehicleType },
|
||||
node_ids: ["question:11"],
|
||||
},
|
||||
],
|
||||
warnings: [],
|
||||
truncated: false,
|
||||
};
|
||||
@@ -2140,6 +2238,10 @@ test.describe("All-in-one self-serve studio", () => {
|
||||
await expect.poll(() => captured.pathOutcomes[0]?.vehicle_type_id).toBe(8);
|
||||
await expect(page.getByTestId("studio-path-outcomes-summary")).toContainText("2");
|
||||
await expect(page.getByTestId("studio-path-outcome-detail")).toContainText("MACHINE");
|
||||
await expect(page.getByTestId("studio-path-results")).toContainText("Paths and Results");
|
||||
await expect(page.getByTestId("studio-path-results")).toContainText("Allowed / MACHINE / 1 task / 2 signals");
|
||||
await expect(page.getByTestId("studio-path-results")).toContainText("Are mirrors folded?");
|
||||
await expect(page.getByTestId("studio-path-results")).toContainText("Yes");
|
||||
await expect(page.getByTestId("studio-path-signal-timeline")).toContainText("machine_start_signal / MACHINE");
|
||||
await expect(page.getByTestId("studio-path-signal-timeline")).toContainText('"bill_machine_wash":true');
|
||||
await page.getByTestId("studio-path-outcome-focus").click();
|
||||
|
||||
@@ -44,6 +44,12 @@ async function selectVehicleType(page, vehicleTypeId = 2) {
|
||||
await expect(page.getByTestId("self-serve-vehicle-selector-description")).toContainText(/Truck|Van|Car/);
|
||||
}
|
||||
|
||||
async function advanceGuidedWashToLastStep(page) {
|
||||
for (let index = 0; index < 5; index += 1) {
|
||||
await page.getByTestId("self-serve-guided-next").click();
|
||||
}
|
||||
}
|
||||
|
||||
function waitForLaneCommandRequest(page, command) {
|
||||
return page.waitForRequest((request) => {
|
||||
if (!request.url().includes("/modules/self-serve/lane/command")) {
|
||||
@@ -196,6 +202,9 @@ test.describe("Self-serve wash", () => {
|
||||
await page.reload({ waitUntil: "domcontentloaded" });
|
||||
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
await advanceGuidedWashToLastStep(page);
|
||||
await expect(page.getByTestId("self-serve-guided-next")).toBeHidden();
|
||||
await expect(page.getByTestId("self-serve-nav-complete")).toBeVisible();
|
||||
await page.getByTestId("self-serve-nav-complete").click();
|
||||
await expect(page.getByTestId("self-serve-completed-step")).toBeVisible();
|
||||
await page.getByTestId("self-serve-nav-close").click();
|
||||
@@ -260,8 +269,49 @@ test.describe("Self-serve wash", () => {
|
||||
await page.goto("/user/wash/start");
|
||||
await expect(page.getByTestId("self-serve-live-elapsed")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId("self-serve-department-name")).toContainText("Odense");
|
||||
await expect(page.getByTestId("self-serve-nav-help")).toContainText("Har du brug for hjælp?");
|
||||
expect(requests.commands).toHaveLength(0);
|
||||
|
||||
const bottomActions = page.getByTestId("self-serve-bottom-actions");
|
||||
const guidedActions = bottomActions.getByTestId("self-serve-guided-bottom-actions");
|
||||
const sessionActions = bottomActions.getByTestId("self-serve-session-bottom-actions");
|
||||
const gateActions = bottomActions.getByTestId("self-serve-property-gate-actions");
|
||||
await expect(bottomActions).toBeVisible();
|
||||
await expect
|
||||
.poll(async () =>
|
||||
bottomActions.evaluate((element) =>
|
||||
Array.from(element.children).map((child) => child.getAttribute("data-testid"))
|
||||
)
|
||||
)
|
||||
.toEqual([
|
||||
"self-serve-session-bottom-actions",
|
||||
"self-serve-property-gate-actions",
|
||||
"self-serve-guided-bottom-actions",
|
||||
]);
|
||||
await expect(guidedActions.getByTestId("self-serve-guided-prev")).toBeVisible();
|
||||
await expect(guidedActions.getByTestId("self-serve-guided-next")).toBeVisible();
|
||||
await expect(sessionActions.getByTestId("self-serve-nav-help")).toContainText("Har du brug for hjælp?");
|
||||
await expect(sessionActions.getByTestId("self-serve-nav-complete")).toBeHidden();
|
||||
await expect(guidedActions.getByTestId("self-serve-nav-complete")).toBeHidden();
|
||||
await expect(gateActions).toBeVisible();
|
||||
await expect(gateActions.getByTestId("self-serve-nav-open-property-access-gate")).toBeVisible();
|
||||
await expect(gateActions.getByTestId("self-serve-nav-open-property-exit-gate")).toBeVisible();
|
||||
|
||||
await advanceGuidedWashToLastStep(page);
|
||||
await expect(page.getByText("6 / 6")).toBeVisible();
|
||||
await expect(guidedActions.getByTestId("self-serve-guided-next")).toBeHidden();
|
||||
await expect(guidedActions.getByTestId("self-serve-nav-complete")).toBeVisible();
|
||||
|
||||
if ((page.viewportSize()?.width || 0) <= 768) {
|
||||
await expect(bottomActions).toHaveCSS("position", "fixed");
|
||||
const footerBox = await page.locator(".fixed-bottom-footer").boundingBox();
|
||||
const actionsBox = await bottomActions.boundingBox();
|
||||
|
||||
expect(footerBox).not.toBeNull();
|
||||
expect(actionsBox).not.toBeNull();
|
||||
expect(actionsBox.y + actionsBox.height).toBeLessThanOrEqual(footerBox.y + 2);
|
||||
}
|
||||
|
||||
const accessCommandRequestPromise = waitForLaneCommandRequest(page, "OPEN_PROPERTY_ACCESS_GATE");
|
||||
await page.getByTestId("self-serve-nav-open-property-access-gate").click();
|
||||
const accessCommandRequest = await accessCommandRequestPromise;
|
||||
|
||||
@@ -688,11 +688,52 @@ describe("MyWashStart", () => {
|
||||
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.get('[data-testid="self-serve-nav-open-property-access-gate"]').trigger("click");
|
||||
await wrapper.get('[data-testid="self-serve-nav-open-property-exit-gate"]').trigger("click");
|
||||
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");
|
||||
|
||||
expect(mocks.onStopWash).toHaveBeenCalledWith(7);
|
||||
});
|
||||
|
||||
it("restores an active wash from the server when local progress is missing", async () => {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { guidedWashFlowSteps } from "@/constants/guidedWashFlowSteps";
|
||||
import da from "@/i18n/locales/da.json";
|
||||
|
||||
describe("Danish self-serve labels", () => {
|
||||
it("uses Danish gate and assistance text", () => {
|
||||
expect(da.self_wash.open_property_access_gate).toBe("Åbn adgangsport");
|
||||
expect(da.self_wash.open_property_exit_gate).toBe("Åbn udgangsport");
|
||||
expect(da.self_wash.assistance).toBe("Har du brug for hjælp?");
|
||||
});
|
||||
|
||||
it("uses Danish guided wash step text", () => {
|
||||
expect(guidedWashFlowSteps.map((step) => step.title)).toEqual([
|
||||
"Højtryksforvask",
|
||||
"Sæbepåføring",
|
||||
"Børstevask",
|
||||
"Højtryksskylning",
|
||||
"Vokspåføring",
|
||||
"Spot Free-skylning",
|
||||
]);
|
||||
expect(guidedWashFlowSteps[0]).toMatchObject({
|
||||
content: "Spul med højtryk for at løsne skidt.",
|
||||
brushes: [
|
||||
{
|
||||
label: "Sort slange",
|
||||
description: "til højtryksforvask.",
|
||||
warning: "Min. afstand til bil: 20 cm.",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(guidedWashFlowSteps[5]).toMatchObject({
|
||||
content: "Skyl hurtigt førerhuset med Spot Free for at undgå pletter.",
|
||||
brushes: [
|
||||
{
|
||||
label: "Hvid slange",
|
||||
description: "til Spot Free-skylning.",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -43,6 +43,24 @@ describe("SelfServe guided/completed components", () => {
|
||||
expect(wrapper.emitted("update:currentStep")).toEqual([[1]]);
|
||||
});
|
||||
|
||||
it("can hide guided step actions when the parent owns bottom controls", () => {
|
||||
const wrapper = mountWithApp(SelfServeGuidedInstructions, {
|
||||
props: {
|
||||
currentStep: 0,
|
||||
showActions: false,
|
||||
steps: [
|
||||
{ title: "Foam", content: "Apply foam", brushes: [] },
|
||||
{ title: "Rinse", content: "Rinse off", brushes: [] },
|
||||
],
|
||||
},
|
||||
messages,
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="self-serve-guided-prev"]').exists()).toBe(false);
|
||||
expect(wrapper.find('[data-testid="self-serve-guided-next"]').exists()).toBe(false);
|
||||
expect(wrapper.get('[data-testid="self-serve-guided-step-0"]').text()).toContain("Apply foam");
|
||||
});
|
||||
|
||||
it("formats the completed duration through the completion message", () => {
|
||||
const wrapper = mountWithApp(SelfServeCompletedStep, {
|
||||
props: {
|
||||
|
||||
Reference in New Issue
Block a user