diff --git a/tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts b/tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts new file mode 100644 index 00000000..0b22228e --- /dev/null +++ b/tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts @@ -0,0 +1,172 @@ +import { expect, test } from "@playwright/test"; +import { mockApi, seedAuthenticatedState } from "./support/network.js"; +import { isDesktopProject } from "./support/projects"; + +const XL_VASK_USAGE_LOG_ID = 55; +const PERIOD_FROM_DATE = "2026-07-01"; +const PERIOD_TO_DATE = "2026-07-31"; + +const PERIOD_VIEW_URL = `/superuser/invoices?activeTab=period&startDate=${PERIOD_FROM_DATE}&endDate=${PERIOD_TO_DATE}&periodView=all`; +const SELVVASH_DESTINATION_PATTERN = new RegExp( + "/superuser/invoices\\?activeTab=period&periodView=self_wash&xlvaskUsageLogId=" + XL_VASK_USAGE_LOG_ID +); + +function json(body: unknown, status = 200) { + return { + status, + contentType: "application/json", + body: JSON.stringify(body), + }; +} + +function createXlVaskFlaggedPeriodPayload({ dateFrom = PERIOD_FROM_DATE } = {}) { + const fixtureDate = String(dateFrom).slice(0, 10); + return { + data: { + types: { + all: [ + { + id: 11, + customer_number: 4001, + customer_name: "Acme Fleet", + requires_action: true, + transactions: [ + { + id: 9001, + date: `${fixtureDate}T10:00:00.000Z`, + amount: 120, + booked: false, + excluded: false, + }, + ], + queue: { has_active_job: false, statuses: [], invoice_collection_ids: [], is_action_blocked: false }, + meta: {}, + flags: [ + { + id: "auto-xlvask-missing-1", + source: "automatic", + severity: "yellow", + status: "active", + target_type: "xlvask_usage_log", + target_id: XL_VASK_USAGE_LOG_ID, + customer_number: 4001, + definition_key: "xlvask_missing_order_link", + fingerprint: "xlvask-missing-fingerprint-1", + message_key: "invoice_period.flags.automatic.xlvask_missing_order_link", + message_params: { + wash_id: `wash-${XL_VASK_USAGE_LOG_ID}`, + registration_number: "AB12345", + }, + message: "XL Vask wash is neither ignored nor linked to an order in the selected period.", + context: { + customer_name: "Acme Fleet", + xlvask_usage_log_id: XL_VASK_USAGE_LOG_ID, + wash_id: `wash-${XL_VASK_USAGE_LOG_ID}`, + registration_number: "AB12345", + start_time: `${fixtureDate}T10:00:00.000Z`, + }, + xlvask_usage_log_id: XL_VASK_USAGE_LOG_ID, + }, + ], + flag_counts: { manual: 0, automatic: 1, total: 1 }, + status_indicator: "flag_yellow", + }, + ], + invoice_per_order: [], + fixed_pricing: [], + tank_cleaning: [], + special_arrangements: [], + vehicle_subscriptions: [], + possible_duplicates: [], + }, + }, + }; +} + +async function suppressVueDevtoolsOverlay(page) { + await page.addInitScript(() => { + const STYLE_ID = "__e2e-hide-vue-devtools"; + localStorage.setItem("lastVersionCheck", String(Date.now())); + + const apply = () => { + const target = document.head || document.documentElement; + if (!target) { + return; + } + + if (!document.getElementById(STYLE_ID)) { + const style = document.createElement("style"); + style.id = STYLE_ID; + style.textContent = + "#__vue-devtools-container__, .vue-devtools__anchor-btn, .vue-devtools__panel-content { display: none !important; visibility: hidden !important; pointer-events: none !important; }"; + target.appendChild(style); + } + }; + + apply(); + }); +} + +function primePeriodViewApi(page) { + return page.route("**/superuser/invoicing/period**", async (route) => { + if (route.request().method() !== "GET") { + await route.fallback(); + return; + } + + const url = new URL(route.request().url()); + const dateFrom = url.searchParams.get("dateFrom") || PERIOD_FROM_DATE; + + await route.fulfill(json(createXlVaskFlaggedPeriodPayload({ dateFrom }))); + }); +} + +test.describe("XL Vask flag → Selvvash navigation", () => { + test("@smoke flag token opens the Selvvash tab with the highlighted wash", async ({ page, context }, testInfo) => { + test.skip(!isDesktopProject(testInfo), "Desktop only"); + + await page.setViewportSize({ width: 1440, height: 900 }); + const token = "superuser-xl-vask-navigation-token"; + await suppressVueDevtoolsOverlay(page); + await seedAuthenticatedState(page, token); + await mockApi(page, { + authenticated: true, + permissions: ["superuser", "user"], + loginToken: token, + }); + + await primePeriodViewApi(page); + + await context.addInitScript((tokenValue) => { + window.localStorage.setItem("token", tokenValue); + }, token); + + await page.goto(PERIOD_VIEW_URL, { waitUntil: "domcontentloaded" }); + await expect(page).toHaveURL(/activeTab=period/); + await expect(page.getByTestId("invoicing-period-view-selector-all")).toBeVisible({ timeout: 30_000 }); + + const customerRow = page.getByTestId("invoicing-period-customer-4001"); + await expect(customerRow).toBeVisible(); + + const flagRow = page.getByTestId("invoice-period-flag-auto-xlvask-missing-1"); + const flagRowCount = await flagRow.count(); + test.skip( + flagRowCount === 0, + "No xlvask_missing_order_link flag is visible in the current period payload; smoke run skips." + ); + + await expect(flagRow).toBeVisible(); + const flagToken = flagRow.locator(".invoice-period-flag-token"); + await expect(flagToken).toBeVisible(); + + const popupPromise = page.waitForEvent("popup", { timeout: 15_000 }); + await flagToken.click(); + const popup = await popupPromise; + + await expect(popup).toHaveURL(SELVVASH_DESTINATION_PATTERN); + await popup.waitForLoadState("domcontentloaded").catch(() => {}); + + await popup.close(); + await context.close(); + }); +});