Files
pleno-vue/tests/unit/superuser-invoices-view.spec.js
T

912 lines
52 KiB
JavaScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { readI18nRuntimeMessages } from "./helpers/readI18nRuntimeMessages";
const root = process.cwd();
const routerSource = readFileSync(join(root, "src/router.js"), "utf8");
const invoicesViewSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/CollectedOrderInvoices.vue"),
"utf8"
);
const overviewSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/collectedOrderInvoices/CollectedOrderInvoicesOverview.vue"),
"utf8"
);
const overviewEconomicSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/collectedOrderInvoices/processors/InvoicesOverviewEconomic.vue"),
"utf8"
);
const overviewBoxSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/collectedOrderInvoices/InvoiceProcessorOverviewBox.vue"),
"utf8"
);
const listPaginationSource = readFileSync(
join(root, "src/components/displays/pagination/models/SuperUserDashboard/CollectedOrderInvoicesListPagination.vue"),
"utf8"
);
const invoiceOrdersPaginationSource = readFileSync(
join(root, "src/components/displays/pagination/models/SuperUserDashboard/InvoiceOrdersPagination.vue"),
"utf8"
);
const tableLabeledPaginationSource = readFileSync(
join(root, "src/components/displays/pagination/TableLabeledPagination.vue"),
"utf8"
);
const periodSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/InvoicingBillingPeriod.vue"),
"utf8"
);
const periodLayoutSource = readFileSync(
join(
root,
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/InvoicingBillingPeriodLayoutDefault.vue"
),
"utf8"
);
const periodTopSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/Top.vue"),
"utf8"
);
const periodLeftSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/Left.vue"),
"utf8"
);
const periodRightSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/Right.vue"),
"utf8"
);
const periodDateSelectorSource = readFileSync(
join(
root,
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/InvoicingBillingPeriodDatePeriodSelector.vue"
),
"utf8"
);
const periodStatisticsSource = readFileSync(
join(
root,
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/InvoicingBillingPeriodStatistics.vue"
),
"utf8"
);
const periodViewImportSource = readFileSync(
join(
root,
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportView.vue"
),
"utf8"
);
const periodViewInvoicePerOrderSource = readFileSync(
join(
root,
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewInvoicePerOrder.vue"
),
"utf8"
);
const periodViewSpecialArrangementsSource = readFileSync(
join(
root,
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSpecialArrangements.vue"
),
"utf8"
);
const periodViewAllSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue"),
"utf8"
);
const periodViewHomeSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewHome.vue"),
"utf8"
);
const periodObjectTreeSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodObjectTree.vue"),
"utf8"
);
const periodFlagListSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue"),
"utf8"
);
const periodTreeSnapshotSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodTreeSnapshot.ts"),
"utf8"
);
const periodTreeNodeServiceSource = readFileSync(
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodTreeNodes.js"),
"utf8"
);
const collectedOrderInvoicesObjectSource = readFileSync(
join(root, "src/components/session/token/SessionUser/Objects/CollectedOrderInvoices.vue"),
"utf8"
);
const buefyTreeSource = readFileSync(join(root, "src/components/buefy/tree/BuefyTree.vue"), "utf8");
const buefyTreeNodeSource = readFileSync(join(root, "src/components/buefy/tree/BuefyTreeNode.vue"), "utf8");
const periodViewSelfWashSource = readFileSync(
join(
root,
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSelfWash.vue"
),
"utf8"
);
const xlvaskUsagePaginationSource = readFileSync(
join(root, "src/components/displays/pagination/models/DepartmentPos/XLVaskUsagePagination.vue"),
"utf8"
);
const xlvaskUsagePeriodConstantsSource = readFileSync(
join(root, "src/components/displays/department/pos/sync/xlvaskUsagePeriodConstants.js"),
"utf8"
);
const xlvaskUsageOrdersTableSource = readFileSync(
join(root, "src/components/displays/department/pos/sync/xlvaskUsageOrdersTable.vue"),
"utf8"
);
const desktopHeaderSource = readFileSync(join(root, "src/components/viewport/page/headers/DesktopHeader.vue"), "utf8");
const tabletHeaderSource = readFileSync(join(root, "src/components/viewport/page/headers/TabletHeader.vue"), "utf8");
const mobileHeaderSource = readFileSync(join(root, "src/components/viewport/page/headers/MobileHeader.vue"), "utf8");
const queueMonitorSource = readFileSync(
join(root, "src/components/viewport/page/headers/menu/CollectedInvoiceQueueMonitor.vue"),
"utf8"
);
const localeMessages = ["da", "en", "sv", "de", "no"].map((locale) => ({
locale,
messages: readI18nRuntimeMessages(locale),
}));
describe("superuser invoices route wiring", () => {
it("maps /superuser/invoices to CollectedOrderInvoices view", () => {
expect(routerSource).toMatch(/name:\s*["']collectedorderinvoices["']/);
expect(routerSource).toMatch(/path:\s*["']\/superuser\/invoices["']/);
expect(routerSource).toContain("component: CollectedOrderInvoices");
});
it("maps monthly distribution routes to InvoiceDistributionMonthView", () => {
expect(routerSource).toMatch(/name:\s*["']collectedorderinvoicesdistribution["']/);
expect(routerSource).toMatch(/path:\s*["']\/superuser\/invoices\/distribution\/:year\/:month["']/);
expect(routerSource).toMatch(/name:\s*["']collectedorderinvoicesdistributiontab["']/);
expect(routerSource).toMatch(/path:\s*["']\/superuser\/invoices\/distribution\/:year\/:month\/:tab["']/);
expect(routerSource).toContain("component: InvoiceDistributionMonthView");
});
it("keeps details route for a single collected invoice", () => {
expect(routerSource).toMatch(/name:\s*["']collectedorderinvoice["']/);
expect(routerSource).toMatch(/path:\s*["']\/superuser\/invoices\/:collectedOrderInvoiceId["']/);
expect(routerSource).toContain("component: CollectedOrderInvoice");
});
});
describe("CollectedOrderInvoices tab container contract", () => {
it("defines all expected top-level tabs", () => {
expect(invoicesViewSource).toContain("{ name: 'Overblik', slot: 'overview'");
expect(invoicesViewSource).toContain("{ name: 'Fakturaer', slot: 'invoices'");
expect(invoicesViewSource).toContain("{ name: 'Periode', slot: 'period'");
expect(invoicesViewSource).toContain("slot: 'distribution'");
});
it("keeps active tab synced with route query", () => {
expect(invoicesViewSource).toContain("const getQueryActiveTab = () => (");
expect(invoicesViewSource).toContain("watch(getQueryActiveTab, (newTab) => {");
expect(invoicesViewSource).toContain("activeTab.value = newTab;");
expect(invoicesViewSource).toContain("watch(activeTab, (newTab) => {");
expect(invoicesViewSource).toContain(
"router.replace({ query: { ...router.currentRoute.value.query, activeTab: newTab } });"
);
});
it("defaults to overview when route query is missing or invalid", () => {
expect(invoicesViewSource).toContain("const activeTab = ref('overview');");
expect(invoicesViewSource).toContain("activeTab.value = 'overview';");
});
it("maps each tab slot to the expected component", () => {
expect(invoicesViewSource).toContain("case 'overview':");
expect(invoicesViewSource).toContain("return CollectedOrderInvoicesOverview;");
expect(invoicesViewSource).toContain("case 'invoices':");
expect(invoicesViewSource).toContain("return CollectedOrderInvoicesListPagination;");
expect(invoicesViewSource).toContain("case 'period':");
expect(invoicesViewSource).toContain("return InvoicingBillingPeriod;");
expect(invoicesViewSource).toContain("case 'distribution':");
expect(invoicesViewSource).toContain("return InvoiceDistributionOverview;");
});
});
describe("Overfoersler queue monitor header contract", () => {
it("mounts the monitor in desktop, tablet, and mobile headers", () => {
expect(desktopHeaderSource).toContain(
'import CollectedInvoiceQueueMonitor from "@/components/viewport/page/headers/menu/CollectedInvoiceQueueMonitor.vue";'
);
expect(tabletHeaderSource).toContain(
'import CollectedInvoiceQueueMonitor from "@/components/viewport/page/headers/menu/CollectedInvoiceQueueMonitor.vue";'
);
expect(mobileHeaderSource).toContain(
'import CollectedInvoiceQueueMonitor from "@/components/viewport/page/headers/menu/CollectedInvoiceQueueMonitor.vue";'
);
expect(desktopHeaderSource).toContain("<CollectedInvoiceQueueMonitor />");
expect(tabletHeaderSource).toContain("<CollectedInvoiceQueueMonitor />");
expect(mobileHeaderSource).toContain('<CollectedInvoiceQueueMonitor class="mobile-queue-monitor" />');
});
it("keeps the monitor invoice-route gated and read-only", () => {
expect(queueMonitorSource).toContain("isCollectedInvoiceQueueMonitorRoute(route.path)");
expect(queueMonitorSource).toContain("SessionUser.canAccessSuperUser()");
expect(queueMonitorSource).toContain("queue.monitor(50)");
expect(queueMonitorSource).toContain("queue.dismiss(job.id)");
expect(queueMonitorSource).toContain("queue.dismissTerminal()");
expect(queueMonitorSource).not.toContain("queue.run(");
});
it("renders compact counts, dropdown rows, and clear controls", () => {
expect(queueMonitorSource).toContain('data-testid="invoice-transfer-monitor"');
expect(queueMonitorSource).toContain('data-testid="invoice-transfer-monitor-button"');
expect(queueMonitorSource).toContain('data-testid="invoice-transfer-monitor-menu"');
expect(queueMonitorSource).toContain('data-testid="invoice-transfer-monitor-job"');
expect(queueMonitorSource).toContain('data-testid="invoice-transfer-monitor-clear-one"');
expect(queueMonitorSource).toContain('data-testid="invoice-transfer-monitor-clear-all"');
expect(queueMonitorSource).toContain("counts.queued ?? 0");
expect(queueMonitorSource).toContain("counts.in_progress ?? 0");
expect(queueMonitorSource).toContain("counts.failed ?? 0");
expect(queueMonitorSource).toContain("counts.completed ?? 0");
});
it("outlines the dropdown and marks uncleared failures with a red indicator", () => {
expect(queueMonitorSource).toContain("hasFailedJobs");
expect(queueMonitorSource).toContain("'has-failed-transfers': hasFailedJobs");
expect(queueMonitorSource).toContain(".queue-monitor-menu .dropdown-content");
expect(queueMonitorSource).toContain("border: 2px solid #d1d5db");
expect(queueMonitorSource).toContain(".queue-monitor-progress.has-failed-transfers span");
expect(queueMonitorSource).toContain("background: #ef4444");
});
it("shows customer context and active queue refresh loading in the monitor", () => {
expect(queueMonitorSource).toContain("const activeCount = computed");
expect(queueMonitorSource).toContain("const isRefreshingActiveQueue = computed");
expect(queueMonitorSource).toContain("jobCustomer(job)");
expect(queueMonitorSource).toContain("queue-monitor-customer");
expect(queueMonitorSource).toContain("'is-refreshing-active': isRefreshingActiveQueue");
expect(queueMonitorSource).toContain("queue-monitor-progress-loading");
});
it("turns the monitor progress fully green when all visible transfers completed successfully", () => {
expect(queueMonitorSource).toContain("const isSuccessfulComplete = computed");
expect(queueMonitorSource).toContain("Number(counts.value.completed ?? 0) === total");
expect(queueMonitorSource).toContain("const displayProgressPercent = computed");
expect(queueMonitorSource).toContain("'is-successful-complete': isSuccessfulComplete");
expect(queueMonitorSource).toContain(".queue-monitor-progress.is-successful-complete");
expect(queueMonitorSource).toContain("background: #22c55e");
});
});
describe("Overview tab contract", () => {
it("renders economic overview processor", () => {
expect(overviewSource).toContain("<invoices-overview-economic />");
});
it("loads economic stats via session request", () => {
expect(overviewEconomicSource).toContain("SessionUser.request(");
expect(overviewEconomicSource).toContain("'/collected-invoices/economic/overview'");
expect(overviewEconomicSource).toContain("'GET'");
});
it("updates all key counters from API response", () => {
expect(overviewEconomicSource).toContain("total.value = result.total;");
expect(overviewEconomicSource).toContain("paid.value = result.paid;");
expect(overviewEconomicSource).toContain("unpaid.value = result.unpaid;");
expect(overviewEconomicSource).toContain("overdue.value = result.overdue;");
expect(overviewEconomicSource).toContain("booked.value = result.booked;");
expect(overviewEconomicSource).toContain("draft.value = result.draft;");
expect(overviewEconomicSource).toContain("sent.value = result.sent;");
expect(overviewEconomicSource).toContain("error.value = result.error;");
});
it("keeps both overview actions wired to refresh after execution", () => {
expect(overviewEconomicSource).toContain("runCheckDrafts()");
expect(overviewEconomicSource).toContain("runCheckErrors()");
expect(overviewEconomicSource).toContain("refresh();");
});
it("supports error lifecycle and dismiss interaction", () => {
expect(overviewEconomicSource).toContain("const addErrorMessage =");
expect(overviewEconomicSource).toContain("const removeErrorMessage =");
expect(overviewEconomicSource).toContain("const clearErrorMessages =");
expect(overviewEconomicSource).toContain("const hasErrorMessages =");
expect(overviewEconomicSource).toContain('<button class="delete" @click="removeErrorMessage(index)"></button>');
});
it("keeps refresh propagation through overview card boxes", () => {
expect(overviewEconomicSource).toContain("<InvoiceProcessorOverviewBox");
expect(overviewEconomicSource).toContain('@refresh="refresh"');
});
});
describe("InvoiceProcessorOverviewBox contract", () => {
it("requires options/count/loading props and optional onClick handler", () => {
expect(overviewBoxSource).toContain("options:");
expect(overviewBoxSource).toContain("count:");
expect(overviewBoxSource).toContain("loading:");
expect(overviewBoxSource).toContain("onClick:");
});
it("shows spinner while loading and emits refresh otherwise", () => {
expect(overviewBoxSource).toContain('v-if="loading"');
expect(overviewBoxSource).toContain("fa-spinner fa-spin");
expect(overviewBoxSource).toContain("fa-sync-alt");
expect(overviewBoxSource).toContain("@click=\"$emit('refresh')\"");
});
it("shows View action only when click callback exists", () => {
expect(overviewBoxSource).toContain("const hasClickAction = () => {");
expect(overviewBoxSource).toContain('v-if="hasClickAction()"');
expect(overviewBoxSource).toContain("View");
expect(overviewBoxSource).toContain('v-if="!hasClickAction()"');
});
});
describe("Fakturaer tab contract", () => {
it("configures endpoint, sorting, filters, and optional auto-load", () => {
expect(listPaginationSource).toContain('setEndpoint("/collected-invoices", false);');
expect(listPaginationSource).toContain('setEndpoint("/collected-invoices/ready-to-invoice", false);');
expect(listPaginationSource).toContain('setOrder("closed_at", "asc", false);');
expect(listPaginationSource).toContain("setHideSearchField(props.hideSearch);");
expect(listPaginationSource).toContain('setFilter("customer_number", props.setCustomerFilter, false);');
expect(listPaginationSource).toContain("setFilter(\"closed_at\", 'is_null', false);");
expect(listPaginationSource).toContain("setFilter(\"booked_invoice_id\", 'is_null', false);");
expect(listPaginationSource).toContain("if (props.autoLoad) {");
expect(listPaginationSource).toContain("loadList();");
});
it("supports mass compare flow across all pages with auth and ordering", () => {
expect(listPaginationSource).toContain("const fetchAllPages = async () => {");
expect(listPaginationSource).toContain(
"const safeItemsPerPage = Math.max(1, Number(metaItemsPerPage.value || 0));"
);
expect(listPaginationSource).toContain(
"const totalPages = Math.ceil(Number(metaTotalItems.value || 0) / safeItemsPerPage);"
);
expect(listPaginationSource).toContain("const token = localStorage.getItem('token');");
expect(listPaginationSource).toContain("if (totalPages > 20) {");
expect(listPaginationSource).toContain("axios.get(API_URL + endpoint.value");
expect(listPaginationSource).toContain("Authorization: `Bearer ${token}`");
expect(listPaginationSource).toContain("order: `${orderBy.value}:${orderDirection.value}`");
});
it("keeps compare modal workflow and button safeguards", () => {
expect(listPaginationSource).toContain("initCompareDataStructure();");
expect(listPaginationSource).toContain("clearProgress();");
expect(listPaginationSource).toContain("setTitle('Sammenlign fakturaer med E-conomic');");
expect(listPaginationSource).toContain("getParsedDataFormatFunction()(allInvoices);");
expect(listPaginationSource).toContain("await showMassDataInsertModal();");
expect(listPaginationSource).toContain("Sammenlign alle med E-conomic");
expect(listPaginationSource).toContain(':disabled="isMassComparing || isLoading || !list || list.length === 0"');
});
});
describe("Invoice orders pagination contract", () => {
it("does not expose the invalid order error-status filter", () => {
expect(invoiceOrdersPaginationSource).not.toContain("pagination.error_status");
expect(invoiceOrdersPaginationSource).not.toContain("setFilter('error_message'");
expect(invoiceOrdersPaginationSource).not.toContain('setFilter("error_message"');
});
it("collapses filters behind a text toggle when hide-filter is enabled", () => {
expect(invoiceOrdersPaginationSource).toContain("const areFiltersVisible = ref(!props.hideFilter);");
expect(invoiceOrdersPaginationSource).toContain(':show-filters="areFiltersVisible"');
expect(invoiceOrdersPaginationSource).toContain(':hide-search="props.hideSearch"');
expect(invoiceOrdersPaginationSource).toContain(':hide-pagination="props.hidePagination"');
expect(invoiceOrdersPaginationSource).toContain('<template #afterSearch v-if="props.hideFilter">');
expect(invoiceOrdersPaginationSource).toContain('data-testid="invoice-orders-toggle-filters"');
expect(invoiceOrdersPaginationSource).toContain('t("pagination.show_filters")');
expect(invoiceOrdersPaginationSource).toContain('t("pagination.hide_filters")');
expect(tableLabeledPaginationSource).toContain("showFilters:");
expect(tableLabeledPaginationSource).toContain("hideSearch:");
expect(tableLabeledPaginationSource).toContain("hidePagination:");
expect(tableLabeledPaginationSource).toContain(':hide-search="props.hideSearch"');
expect(tableLabeledPaginationSource).toContain('v-if="!props.hideSearch || $slots.buttons"');
expect(tableLabeledPaginationSource).toContain('v-if="showFilters"');
expect(tableLabeledPaginationSource).toContain('v-if="!props.hidePagination"');
expect(tableLabeledPaginationSource).toContain('data-testid="table-labeled-pagination-filters"');
});
it("updates date filters via a dedicated range handler", () => {
expect(invoiceOrdersPaginationSource).toContain(
"const onDateRangeSelected = (newSelectionStartDate, newSelectionToDate) => {"
);
expect(invoiceOrdersPaginationSource).toContain("formatLocalDateOnly(newSelectionStartDate)");
expect(invoiceOrdersPaginationSource).toContain("formatLocalDateOnly(newSelectionToDate)");
expect(invoiceOrdersPaginationSource).toContain("date_from.value = formattedStartDate;");
expect(invoiceOrdersPaginationSource).toContain("date_to.value = formattedEndDate;");
expect(invoiceOrdersPaginationSource).toContain(
'setDateFilter("created_at-date_from", formattedStartDate, false);'
);
expect(invoiceOrdersPaginationSource).toContain('setDateFilter("created_at-date_to", formattedEndDate, false);');
expect(invoiceOrdersPaginationSource).toContain("loadList();");
expect(invoiceOrdersPaginationSource).toContain('<DatePeriodSelector :on-selection-change="onDateRangeSelected"');
});
});
describe("Periode tab contract", () => {
it("assembles top/left/right/bottom sections in the billing-period layout", () => {
expect(periodSource).toContain("<InvoicingBillingPeriodLayoutDefault>");
expect(periodSource).toContain("<template #content-top>");
expect(periodSource).toContain("<template #content-left>");
expect(periodSource).toContain("<template #content-right>");
expect(periodSource).toContain("<template #content-bottom>");
});
it("places period content beside a three-to-one right navigation rail on desktop", () => {
expect(periodLayoutSource).toContain('data-testid="invoicing-period-layout-navigation"');
expect(periodLayoutSource).toContain('data-testid="invoicing-period-layout-main"');
expect(periodLayoutSource).toContain('"content navigation"');
expect(periodLayoutSource).toContain("grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);");
expect(periodLayoutSource).toContain("@media screen and (min-width: 1024px)");
expect(periodLayoutSource).toContain('"navigation"');
expect(periodLayoutSource).toContain('"content"');
});
it("stacks grouped selectors in the desktop rail and keeps compact navigation responsive", () => {
expect(periodRightSource).toContain("grid-template-columns: minmax(0, 1fr);");
expect(periodRightSource).toContain('class="period-selector-row"');
expect(periodRightSource).toContain("grid-template-columns: minmax(0, 1fr) 72px 1rem;");
expect(periodRightSource).toContain("min-height: 4rem;");
expect(periodRightSource).toContain("@media screen and (min-width: 601px) and (max-width: 1023px)");
expect(periodRightSource).toContain("grid-template-columns: repeat(2, minmax(0, 1fr));");
expect(periodRightSource).toContain("@media screen and (max-width: 600px)");
expect(periodRightSource).toContain("flex: 0 0 min(82vw, 320px);");
});
it("adapts the review workspace to the main region width created by the right rail", () => {
expect(periodViewAllSource).toContain('class="invoicing-period-view-all"');
expect(periodViewAllSource).toContain("container-type: inline-size;");
expect(periodViewAllSource).toContain("grid-template-columns: minmax(330px, 0.8fr) minmax(0, 1.6fr);");
expect(periodViewAllSource).toContain("@container (max-width: 1024px)");
});
it("keeps customer card content and actions on stable grid rows", () => {
expect(periodViewAllSource).toContain('"count amount amount"');
expect(periodViewAllSource).toContain('"attributes attributes attributes"');
expect(periodViewAllSource).toContain("grid-template-columns: 1.25rem auto minmax(0, 1fr);");
expect(periodFlagListSource).toContain("grid-template-columns: 1.2rem minmax(0, 1fr);");
expect(periodFlagListSource).toContain("grid-column: 2;");
expect(periodFlagListSource).toContain("max-height: 2.5rem;");
expect(periodFlagListSource).toContain(".invoice-period-flag-row:focus-within");
});
it("sizes the inline month selector to the available content width with a readable cap", () => {
expect(periodViewHomeSource).toContain('class="invoicing-period-month-entry__selector"');
expect(periodViewHomeSource).toContain("max-width: 32rem;");
expect(periodViewHomeSource).toContain(":deep(.datepicker-months)");
expect(periodViewHomeSource).toContain("min-height: 3rem;");
});
it("keeps top selector and left dynamic view renderer", () => {
expect(periodTopSource).toContain("<InvoicingBillingPeriodDatePeriodSelector");
expect(periodTopSource).toContain("dates.variables.hasSelection.value");
expect(periodLeftSource).toContain('<Component v-bind:is="view.computed.currentViewComponent.value" />');
});
it("shows the top reload control only for the home period view", () => {
expect(periodDateSelectorSource).toContain(
'const shouldShowTopReloadButton = computed(() => view.variables.currentView.value === "home")'
);
expect(periodDateSelectorSource).toContain('<div v-if="shouldShowTopReloadButton" class="level-item">');
});
it("fetches period dataset and refreshes when date range changes", () => {
expect(periodRightSource).toContain("SessionUser.request(");
expect(periodRightSource).toContain("'/superuser/invoicing/period'");
expect(periodRightSource).toContain("dateFrom: dates.computed.formattedStartDate.value");
expect(periodRightSource).toContain("dateTo: dates.computed.formattedEndDate.value");
expect(periodRightSource).toContain("const applyRoutePeriodDates = () =>");
expect(periodRightSource).toContain("dates.functions.setSelection(requestedStartDate, requestedEndDate);");
expect(periodRightSource).toContain("startDate: dates.computed.formattedStartDate.value");
expect(periodRightSource).toContain("endDate: dates.computed.formattedEndDate.value");
expect(periodRightSource).toContain("watch(");
expect(periodRightSource).toContain("[() => dates.variables.start.value, () => dates.variables.end.value]");
expect(periodRightSource).toContain("const loadPeriodForDateChange = () =>");
expect(periodRightSource).toContain("periodPaging.isDateLoading = true;");
expect(periodRightSource).toContain("getPeriod({ forceRefresh: true, showLoading: true })");
expect(periodRightSource).toContain("status.loading = showLoading;");
});
it("uses the list-view reload button as the only visible manual refresh control in searchable period views", () => {
expect(periodViewAllSource).toContain('data-testid="invoicing-period-page-reload-button"');
expect(periodViewAllSource).not.toContain('data-testid="invoicing-period-refreshing"');
});
it("warms current and previous month period pages and preloads adjacent pages", () => {
expect(periodRightSource).toContain("const fetchAndCachePeriodPage = async");
expect(periodRightSource).toContain("preloadAdjacentPeriodPages(requestParameters, paginationMeta)");
expect(periodRightSource).toContain("const warmCurrentAndPreviousPeriodMonths = () =>");
expect(periodRightSource).toContain("const previousMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)");
expect(periodRightSource).toContain('periodViews = Array.from(new Set(["all", getBackendPeriodView()]))');
expect(periodRightSource).toContain("periodWarm: 1");
expect(periodRightSource).toContain("setCachedPeriodPage(cacheKey, normalizedPeriodResult, paginationMeta)");
expect(periodRightSource).toContain("periodCacheRequestInFlight");
});
it("keeps period statistics free of normal-path debug console noise", () => {
expect(periodStatisticsSource).not.toContain("Response from fixed pricing distribution API");
expect(periodStatisticsSource).not.toContain("Fixed pricing distribution fetched successfully");
expect(periodStatisticsSource).not.toContain("Response from vehicle subscription distribution API");
expect(periodStatisticsSource).not.toContain("Vehicle subscription distribution fetched successfully");
expect(periodStatisticsSource).not.toContain("Department 75 total calculation resulted in null");
expect(periodStatisticsSource).not.toContain("onMounted(() =>");
});
it("defines the open invoice collection label in loaded locale messages", () => {
localeMessages.forEach(({ messages }) => {
expect(messages.global.open).toBeTruthy();
});
});
it("exposes all right-panel period views and click switching", () => {
expect(periodRightSource).toContain("view: 'all'");
expect(periodRightSource).toContain("view: 'invoice_per_order'");
expect(periodRightSource).toContain("view: 'fixed_pricing'");
expect(periodRightSource).toContain("view: 'tank_cleaning'");
expect(periodRightSource).toContain("view: 'special_arrangements'");
expect(periodRightSource).toContain("view: 'vehicle_subscriptions'");
expect(periodRightSource).toContain("view: 'possible_duplicates'");
expect(periodRightSource).toContain("view: 'self_wash'");
expect(periodRightSource).toContain("pendingPeriodView.value = type.view;");
expect(periodRightSource).toContain("view.variables.currentView.value = viewToCommit");
});
it("keeps date selector update action and auto change emission", () => {
expect(periodDateSelectorSource).toContain("showReloadAnimation();");
expect(periodDateSelectorSource).toContain("dates.functions.setSelection(startDate, endDate);");
expect(periodDateSelectorSource).toContain('v-bind:auto-emit-change="true"');
expect(periodDateSelectorSource).toContain(
"const isReloadButtonLoading = computed(() => isReloadAnimationActive.value || periodPaging.isDateLoading)"
);
expect(periodDateSelectorSource).toContain(":class=\"{'is-loading': isReloadButtonLoading}\"");
expect(periodDateSelectorSource).toContain(
'@click="onSelectionChange(dates.variables.start.value, dates.variables.end.value); showReloadAnimation();"'
);
});
it("keeps import-view mapping for all period view keys", () => {
expect(periodViewImportSource).toContain("home: InvoicingBillingPeriodViewHome");
expect(periodViewImportSource).toContain("fixed_pricing: InvoicingBillingPeriodViewAll");
expect(periodViewImportSource).toContain("vehicle_subscriptions: InvoicingBillingPeriodViewAll");
expect(periodViewImportSource).toContain("tank_cleaning: InvoicingBillingPeriodViewAll");
expect(periodViewImportSource).toContain("all: InvoicingBillingPeriodViewAll");
expect(periodViewImportSource).toContain("special_arrangements: InvoicingBillingPeriodViewAll");
expect(periodViewImportSource).toContain("invoice_per_order: InvoicingBillingPeriodViewAll");
expect(periodViewImportSource).toContain("possible_duplicates: InvoicingBillingPeriodViewAll");
expect(periodViewImportSource).toContain("self_wash: InvoicingBillingPeriodViewSelfWash");
});
it("uses the selected period while keeping Selvvask results server-paginated", () => {
expect(periodViewSelfWashSource).toContain(':initial-date-from="dates.computed.formattedStartDate.value"');
expect(periodViewSelfWashSource).toContain(':initial-date-to="dates.computed.formattedEndDate.value"');
expect(periodViewSelfWashSource).toContain(':inherit-period-filters="true"');
expect(periodViewSelfWashSource).toContain(':load-all-at-once="false"');
expect(xlvaskUsagePaginationSource).toContain("inheritPeriodFilters");
expect(xlvaskUsagePaginationSource).toContain("loadAllAtOnce");
expect(xlvaskUsagePeriodConstantsSource).toContain("export const SELFWASH_PERIOD_ALL_LIMIT = 10000");
expect(xlvaskUsagePaginationSource).toContain("setMetaItemsPerPage(SELFWASH_PERIOD_ALL_LIMIT, false)");
expect(xlvaskUsagePaginationSource).toContain(
"const shouldShowLocalFilters = computed(() => !props.inheritPeriodFilters)"
);
expect(xlvaskUsagePaginationSource).toContain('v-if="shouldShowLocalFilters"');
expect(xlvaskUsagePaginationSource).toContain('v-if="!hideSearchField && shouldShowLocalFilters"');
expect(xlvaskUsagePaginationSource).toContain('v-if="!props.loadAllAtOnce"');
expect(xlvaskUsagePaginationSource).toContain("const buildImportUsageParams = () => {");
expect(xlvaskUsagePaginationSource).toContain("dateFrom: props.initialDateFrom");
expect(xlvaskUsagePaginationSource).toContain("dateTo: props.initialDateTo");
expect(xlvaskUsagePaginationSource).toContain("buildImportUsageParams()");
});
it("loads Selvvask selector counts and progress from the selected period", () => {
expect(periodRightSource).toContain("normalizeXlvaskAutopilotSummary");
expect(periodRightSource).toContain("const selfWashCounts = ref(emptySelectorCounts())");
expect(periodRightSource).toContain("buildSelfWashCountsCacheKey");
expect(periodRightSource).toContain("getCachedSelfWashCounts");
expect(periodRightSource).toContain("setCachedSelfWashCounts");
expect(periodRightSource).toContain("'/modules/xlvask/services/usage/orders/summary'");
expect(periodRightSource).toContain("dateFrom");
expect(periodRightSource).toContain("dateTo");
expect(periodRightSource).toContain("if (type.name === 'self_wash')");
expect(periodRightSource).toContain("requires_action: Math.max(0, total - completed)");
expect(periodRightSource).not.toContain("showCounts: false");
expect(periodRightSource).not.toContain("showProgress: false");
});
it("keeps Selvvask usage pagination scoped to the period view", () => {
expect(xlvaskUsagePaginationSource).toContain("usePaginatedList");
expect(xlvaskUsagePaginationSource).toContain("provide(PaginatedListKey, paginatedList)");
expect(xlvaskUsageOrdersTableSource).toContain("usePaginatedListInstance");
expect(xlvaskUsageOrdersTableSource).toContain("const { loadList } = usePaginatedListInstance();");
});
it("keeps Selvvask usage entries wrapped inside the period content area", () => {
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-card-primary");
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-card-status");
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-usage-chip-text");
expect(xlvaskUsageOrdersTableSource).toContain("getCachedXlvaskUsageAmount");
expect(xlvaskUsageOrdersTableSource).toContain("setCachedXlvaskUsageAmount");
expect(xlvaskUsageOrdersTableSource).toContain("object?.total_net_amount");
expect(xlvaskUsageOrdersTableSource).toContain("automation.status === 'failed'");
expect(xlvaskUsageOrdersTableSource).toContain("getXlvaskResolutionState(object)");
expect(xlvaskUsageOrdersTableSource).toContain("only fetch one-time fast-link details on demand");
expect(xlvaskUsageOrdersTableSource).not.toContain("scheduleFastLink(object)");
expect(xlvaskUsageOrdersTableSource).toContain("grid-template-columns: minmax(0, 1fr) minmax(9rem, 15rem);");
expect(xlvaskUsageOrdersTableSource).toContain("xlvask-order-comparison-columns");
expect(xlvaskUsageOrdersTableSource).toContain("@media screen and (max-width: 1350px)");
});
it("uses bounded autopilot polling and preview/apply-only review mutations", () => {
expect(xlvaskUsagePaginationSource).toContain("const RUN_POLL_INTERVAL_MS = 2_000");
expect(xlvaskUsagePaginationSource).toContain("const RUN_POLL_MAX_DURATION_MS = 5 * 60 * 1_000");
expect(xlvaskUsagePaginationSource).toContain("document.hidden");
expect(xlvaskUsagePaginationSource).toContain("resumeAutopilotPolling");
expect(xlvaskUsagePaginationSource).toContain("invalidateAutopilotScope");
expect(xlvaskUsagePaginationSource).toContain("if (sequence !== runPollSequence) return;");
expect(xlvaskUsagePaginationSource).toContain("activeRunRecoverySequence += 1");
expect(xlvaskUsagePaginationSource).toContain("await loadAutomationControlState();");
expect(xlvaskUsagePaginationSource).toContain("await recoverActiveAutopilotRun();");
expect(xlvaskUsagePaginationSource).toContain('halted: "advisory"');
expect(xlvaskUsagePaginationSource).toContain("expected_policy_version: preview.expected_policy_version");
expect(xlvaskUsagePaginationSource).toContain("buildImportUsageParams(),");
expect(xlvaskUsagePaginationSource).toContain("const runParams = buildImportUsageParams();");
expect(xlvaskUsagePaginationSource).toContain("const readinessSnapshot = buildExecutionReadinessSnapshot();");
expect(xlvaskUsagePaginationSource).toContain("if (requestScopeSequence !== periodScopeSequence) return;");
expect(xlvaskUsagePaginationSource).toContain("JSON.stringify(buildExecutionReadinessSnapshot())");
expect(xlvaskUsagePaginationSource).toContain("if (!canStartExecute.value) return;");
expect(xlvaskUsageOrdersTableSource).toContain('"/modules/xlvask/services/usage/automation/decisions/preview"');
expect(xlvaskUsageOrdersTableSource).toContain('"/modules/xlvask/services/usage/automation/decisions/apply"');
expect(xlvaskUsageOrdersTableSource).toContain("if (!confirmation.isConfirmed) return false;");
expect(xlvaskUsageOrdersTableSource).toContain("if (applied) selectedUsageLogIds.value = [];");
expect(xlvaskUsageOrdersTableSource).not.toContain("set.wash_id");
expect(xlvaskUsageOrdersTableSource).not.toContain("'/order/items'");
});
it("keeps period subview navigation on valid keys", () => {
expect(periodViewInvoicePerOrderSource).toContain("view.functions.setCurrentView('vehicle_subscriptions')");
expect(periodViewSpecialArrangementsSource).toContain("view.functions.setCurrentView('vehicle_subscriptions')");
expect(periodViewInvoicePerOrderSource).not.toContain("wash_subscriptions");
expect(periodViewSpecialArrangementsSource).not.toContain("wash_subscriptions");
});
it("keeps e-conomic drafts out of completed selector counts and renders a yellow draft segment", () => {
expect(periodRightSource).toContain("const isCustomerDraftBlocked =");
expect(periodRightSource).toContain("const draft = entries.filter(entry => isCustomerDraftBlocked(entry)).length");
expect(periodRightSource).toContain("const completed = Math.max(0, entries.length - requires_action - draft)");
expect(periodRightSource).toContain("return `${type.displayName} (${typeCounts.completed}/${typeCounts.total})`;");
expect(periodRightSource).toContain("period-selector-progress__draft");
expect(periodRightSource).toContain("background: #f6c344");
});
it("renders draft-blocked customers with a warning tag matching the completed tag shape", () => {
expect(periodViewAllSource).toContain('class="tag is-warning is-light is-small"');
expect(periodViewAllSource).toContain('aria-disabled="true"');
expect(periodViewAllSource).toContain("fa-file-alt");
expect(periodViewAllSource).toContain(
':data-testid="`invoicing-period-customer-draft-${customer.customer_number}`"'
);
});
it("scopes customer flag indicators to flags visible in the current customer card", () => {
expect(periodViewAllSource).toContain("const isFlagInCustomerCardScope =");
expect(periodViewAllSource).toContain("const getCustomerScopedFlags =");
expect(periodViewAllSource).toContain("const getCustomerActiveFlagCounts =");
expect(periodViewAllSource).toContain("return getFlagCounts(scopedFlags);");
expect(periodViewAllSource).toContain(
'["order", "order_field", "order_item", "order_item_field"].includes(targetType)'
);
expect(periodViewAllSource).toContain(
"toPositiveIntegerSet(getInvoiceCollectionIdsForTransactionIds(customer, transactionIds)).has(invoiceCollectionId)"
);
expect(periodViewAllSource).toContain("const isOrderLineFlag =");
expect(periodViewAllSource).toContain("const isOrderItemLineFlag =");
expect(periodViewAllSource).toContain("const getCustomerCardFlags = (customer: any) =>");
expect(periodViewAllSource).toContain("!isOrderLineFlag(flag) && !isOrderItemLineFlag(flag)");
expect(periodViewAllSource).toContain(':flags="getCustomerCardFlags(customer)"');
expect(periodViewAllSource).not.toContain("getCustomerExpandedFlags");
expect(periodViewAllSource).toContain(':invoice-period-flags="getCustomerScopedFlags(selectedCustomer)"');
expect(periodViewAllSource).toContain('targetType === "customer"');
expect(periodViewAllSource).toContain('targetType === "collected_order_invoice"');
});
it("groups possible duplicates by vehicle plate and date for comparison", () => {
expect(periodViewAllSource).toContain("buildPossibleDuplicateGroups");
expect(periodViewAllSource).toContain("possibleDuplicateGroupsInCurrentView");
expect(periodViewAllSource).toContain('data-testid="`invoicing-period-duplicate-group-${duplicateGroup.testId}`"');
expect(periodViewAllSource).toContain("duplicate-comparison-row");
expect(periodViewAllSource).toContain(':group-invoice-collection="false"');
expect(periodViewAllSource).not.toContain(':hide-pagination="false"');
expect(periodRightSource).toContain("getCountEntriesForType");
expect(periodRightSource).toContain('typeName !== "possible_duplicates"');
expect(periodRightSource).toContain("buildPossibleDuplicateGroups(entries)");
});
it("uses the invoice period object tree for normal expanded customer rows", () => {
expect(periodViewAllSource).toContain(
'import InvoicingPeriodObjectTree from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodObjectTree.vue";'
);
expect(periodViewAllSource).toContain("<InvoicingPeriodObjectTree");
expect(periodViewAllSource).toContain(':transactions="getTransactionsInView(selectedCustomer)"');
expect(periodViewAllSource).toContain(':excluded-order-ids="getExcludedTransactionIds(selectedCustomer)"');
expect(periodViewAllSource).toContain('@refresh="reloadPeriodPage"');
expect(periodViewAllSource).toContain("<InvoiceOrdersPagination");
expect(periodViewAllSource).toContain("duplicate-comparison-row");
});
it("keeps the object tree lazy, slotted, check-selectable, and error-aware", () => {
expect(buefyTreeSource).toContain("lazyChildrenCache");
expect(buefyTreeSource).toContain("loadErrorKeys");
expect(buefyTreeSource).toContain('selectionMode?: "none" | "single" | "multiple" | "checkbox"');
expect(buefyTreeSource).toContain("node?.selectable !== false");
expect(buefyTreeSource).toContain("node?.checkable === true");
expect(buefyTreeSource).toContain("ensureChildrenLoadedForCheck");
expect(buefyTreeSource).toContain("progressiveBatchSize");
expect(buefyTreeSource).toContain("visibleChildrenOf");
expect(buefyTreeSource).toContain("remainingChildrenCount");
expect(buefyTreeSource).toContain("const expandAll =");
expect(buefyTreeSource).toContain("EXPAND_ALL_CONCURRENCY = 4");
expect(buefyTreeSource).toContain("defineExpose");
expect(buefyTreeNodeSource).toContain('<slot name="icon"');
expect(buefyTreeNodeSource).toContain(':retry="() => tree.retryLoad(node)"');
expect(buefyTreeNodeSource).toContain("b-tree-load-more__button");
expect(periodObjectTreeSource).toContain('selection-mode="checkbox"');
expect(periodObjectTreeSource).toContain(':lazy="true"');
expect(periodObjectTreeSource).toContain(':progressive-batch-size="50"');
expect(periodObjectTreeSource).toContain("@load-error=");
expect(periodObjectTreeSource).toContain("InvoicingPeriodFlagBadge");
expect(periodObjectTreeSource).toContain("getNodeFlags");
expect(periodObjectTreeSource).toContain("getFieldFlags");
expect(periodObjectTreeSource).toContain("getCachedOrderItemRows");
expect(periodObjectTreeSource).toContain("getCachedAttachmentRows");
expect(periodObjectTreeSource).toContain("shouldShowWashCertificateCategory");
expect(periodObjectTreeSource).not.toContain("order?.attachments === undefined");
expect(periodTreeNodeServiceSource).toContain("getTreeAmount");
expect(periodTreeNodeServiceSource).toContain("hasWashCertificateOrderItem");
expect(periodObjectTreeSource).toContain("fetchAttachmentContent");
expect(periodObjectTreeSource).toContain('data-testid="invoice-period-tree-toolbar"');
expect(periodObjectTreeSource).toContain('data-testid="invoice-period-tree-toggle-all"');
expect(periodObjectTreeSource).toContain("`invoice-period-tree-actions-dropdown-${group.type}`");
});
it("keeps registration labels and values from overlapping in compact invoice rows", () => {
expect(periodObjectTreeSource).toContain('key: "registrations"');
expect(periodObjectTreeSource).toContain('class="invoice-period-tree-node-field__line-label"');
expect(periodObjectTreeSource).toContain(".invoice-period-tree-node-field.is-composite");
expect(periodObjectTreeSource).toContain("grid-template-rows: auto auto;");
expect(periodObjectTreeSource).toContain("grid-template-columns: minmax(3rem, max-content) minmax(0, 1fr);");
expect(periodObjectTreeSource).toContain("grid-column: 1 / -1 !important;");
expect(periodObjectTreeSource).toContain(".invoice-period-tree-node-field__line > span:last-child");
expect(periodObjectTreeSource).toContain("overflow-wrap: anywhere;");
});
it("switches narrow object-tree metadata to a consistent four-column grid", () => {
expect(periodObjectTreeSource).toContain("@container (max-width: 56rem)");
expect(periodObjectTreeSource).toContain("grid-template-columns: repeat(4, minmax(0, 1fr));");
expect(periodObjectTreeSource).toContain("`invoice-period-tree-node-field--${field.key}`");
expect(periodObjectTreeSource).toContain(".invoice-period-tree-node-field--total_net_amount");
expect(periodObjectTreeSource).toContain("grid-column: 4 !important;");
});
it("shows the order department in the object-tree metadata", () => {
expect(periodObjectTreeSource).toContain('key: "department"');
expect(periodObjectTreeSource).toContain('label: commonText("department", "Afdeling")');
expect(periodObjectTreeSource).toContain("const orderDepartmentLabel = (order: any) =>");
expect(periodObjectTreeSource).toContain("order?.department_name || order?.department?.name");
expect(periodObjectTreeSource).toContain("getDepartmentName(departmentId)");
expect(periodObjectTreeSource).not.toContain("object.cashier_name ||");
});
it("exposes object-specific lazy child categories and multi-select operations", () => {
[
"COLLECTION_ORDERS",
"COLLECTION_AGREEMENTS",
"COLLECTION_PAYMENTS",
"COLLECTION_ECONOMIC",
"ORDER_ATTACHMENTS_CERTIFICATES",
"ORDER_ATTACHMENTS_IMAGES",
"ORDER_ATTACHMENTS_OTHER",
"ORDER_BOOKINGS",
"ORDER_XLVASK",
"BOOKING_ITEMS",
"XLVASK_ITEMS",
].forEach((category) => {
expect(periodTreeNodeServiceSource).toContain(category);
});
expect(periodObjectTreeSource).toContain("buildOrderItemTableRows");
expect(periodObjectTreeSource).not.toContain("TREE_CATEGORY_TYPES.ORDER_ITEMS");
[
"runCollectionBulkAction",
"moveSelectedOrdersToInvoiceCollection",
"unlinkSelectedOrderBookings",
"unlinkSelectedOrderXlvask",
"deleteSelectedOrderItems",
"resendSelectedWashCertificates",
"resendSelectedBookingCompletionConfirmations",
"decideSelectedXlvaskAutomation",
].forEach((operation) => {
expect(periodObjectTreeSource).toContain(operation);
});
expect(periodObjectTreeSource).toContain("bulk_action_preview");
expect(periodObjectTreeSource).toContain("bulk_action_apply");
expect(periodObjectTreeSource).toContain("createInvoicingPeriodTreeSnapshotLoader");
expect(periodObjectTreeSource).toContain("period_tree_action_preview");
expect(periodObjectTreeSource).toContain("snapshot_revision: activeSnapshot.value?.snapshot_revision");
expect(periodTreeSnapshotSource).toContain('"/superuser/invoicing/period/tree"');
expect(periodTreeSnapshotSource).toContain("complete_total_net_amount");
expect(periodTreeSnapshotSource).toContain("period_total_net_amount");
expect(collectedOrderInvoicesObjectSource).toContain("period_tree_action_preview");
expect(collectedOrderInvoicesObjectSource).toContain("customer_number: parseInt(customer_number)");
expect(collectedOrderInvoicesObjectSource).toContain("snapshot_revision: String(snapshot_revision || '')");
expect(periodObjectTreeSource).toContain("chooseMergeTargetInvoiceCollection");
expect(periodObjectTreeSource).toContain("checkable: true");
expect(periodObjectTreeSource).toContain("uniqueNodesByKey");
expect(periodObjectTreeSource).toContain("const actionGroups = computed<TreeActionGroup[]>");
expect(periodObjectTreeSource).toContain("actionsForType");
expect(periodObjectTreeSource).toContain("runTreeAction");
expect(periodObjectTreeSource).toContain("activeTreeActionKey");
expect(periodObjectTreeSource).toContain("openActionGroupType");
expect(periodObjectTreeSource).toContain("affectedCount: number");
expect(periodObjectTreeSource).toContain("action.affectedCount === 0");
expect(periodObjectTreeSource).toContain("{{ action.affectedCount }}");
expect(periodObjectTreeSource).toContain(
"affectedCount: selectedCollectionCount.value > 1 ? selectedCollectionCount.value : 0"
);
expect(periodObjectTreeSource).toContain("disabled: selectedCollectionCount.value < 2");
expect(periodObjectTreeSource).toContain("affectedCount: selectedOrdersWithBookingCount.value");
expect(periodObjectTreeSource).toContain("affectedCount: selectedOrdersWithWashCount.value");
expect(periodObjectTreeSource).toContain("affectedCount: certificateAttachmentOrderIds.value.length");
expect(periodObjectTreeSource).toContain("affectedCount: actionableXlvaskNodes.value.length");
expect(periodObjectTreeSource).not.toContain("onlySelectedType");
expect(periodObjectTreeSource).not.toContain("selection.mixed_types");
});
it("defines mixed-selection action menu locale labels", () => {
localeMessages.forEach(({ messages }) => {
expect(messages.invoicing_period.object_tree.buttons.actions).toBeTruthy();
expect(messages.invoicing_period.object_tree.buttons.collapse_all).toBeTruthy();
expect(messages.invoicing_period.object_tree.buttons.expand_all).toBeTruthy();
expect(messages.invoicing_period.object_tree.selection.no_actions).toBeTruthy();
});
});
});
describe("Periode runtime view switching", () => {
it("keeps all selectable period views mapped in availableViews", () => {
const selectorViewMatches = [...periodRightSource.matchAll(/view:\s*'([^']+)'/g)].map((m) => m[1]);
const selectableKeys = Array.from(new Set(selectorViewMatches));
const expectedSelectableKeys = [
"all",
"invoice_per_order",
"fixed_pricing",
"tank_cleaning",
"special_arrangements",
"vehicle_subscriptions",
"possible_duplicates",
"self_wash",
];
expect(selectableKeys.sort()).toEqual(expectedSelectableKeys.sort());
selectableKeys.forEach((key) => {
expect(periodViewImportSource).toContain(`${key}:`);
});
expectedSelectableKeys
.filter((key) => key !== "self_wash")
.forEach((key) => {
expect(periodViewImportSource).toContain(`${key}: InvoicingBillingPeriodViewAll`);
});
expect(periodViewImportSource).toContain("self_wash: InvoicingBillingPeriodViewSelfWash");
});
it("keeps home view key available for initial period load", () => {
expect(periodViewImportSource).toContain("home: InvoicingBillingPeriodViewHome");
expect(periodViewImportSource).toContain('const currentView = ref<keyof typeof availableViews>("home")');
});
it("guards setCurrentView with available view existence checks", () => {
expect(periodViewImportSource).toContain("const setCurrentView = (view: keyof typeof availableViews) => {");
expect(periodViewImportSource).toContain("if (availableViews[view]) {");
expect(periodViewImportSource).toContain('console.warn(`View "${view}" does not exist in available views.`);');
});
});