Files
pleno-vue/tests/unit/invoicing-period-review-workspace.spec.js
T
Jeppe B 0149e06c42 Fix invoice period i18n release gate (#245)
Replace unreviewed dynamic invoice-period translation calls with a bounded literal-key contract and focused fallback coverage.
2026-08-03 07:04:48 +02:00

151 lines
6.8 KiB
JavaScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { buildPeriodCacheKey } from "../../src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportPaging.js";
import { deriveLegacyPeriodCustomerReview } from "../../src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodReview.js";
import { translateInvoicingPeriodReview } from "../../src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/services/invoicingPeriodTranslation.js";
const readProjectFile = (path) => readFileSync(fileURLToPath(new URL(`../../${path}`, import.meta.url)), "utf8");
describe("invoicing period review workspace", () => {
it("translates only the bounded review-workspace key contract", () => {
const calls = [];
const composer = {
t(key, params) {
calls.push([key, params]);
return key === "invoicing_period.review_workspace.queue.count" ? `${params.count} kunder` : key;
},
};
expect(translateInvoicingPeriodReview(composer, "queue.count", "fallback", { count: 3 })).toBe("3 kunder");
expect(translateInvoicingPeriodReview(composer, "queue.unknown", "fallback")).toBe("fallback");
expect(translateInvoicingPeriodReview(composer, "../queue.count", "fallback")).toBe("fallback");
expect(calls).toEqual([["invoicing_period.review_workspace.queue.count", { count: 3 }]]);
});
it("scopes cached pages by every server-side review and sorting input", () => {
const base = {
dateFrom: "2026-08-01",
dateTo: "2026-08-31",
periodView: "all",
page: 1,
limit: 100,
search: "",
flagTab: "all",
includeRequiresAction: true,
includeBooked: true,
reviewState: "blocked",
severity: "red",
invoiceState: "draft",
departmentId: "7",
sort: "priority",
direction: "asc",
};
expect(buildPeriodCacheKey(base)).not.toBe(buildPeriodCacheKey({ ...base, reviewState: "ready" }));
expect(buildPeriodCacheKey(base)).not.toBe(buildPeriodCacheKey({ ...base, sort: "total_amount" }));
expect(buildPeriodCacheKey(base)).not.toBe(buildPeriodCacheKey({ ...base, direction: "desc" }));
});
it("keeps selected-customer restoration route-only while review inputs reload the server page", () => {
const source = readProjectFile(
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/Right.vue"
);
expect(source).toContain("reviewState: periodPaging.reviewState");
expect(source).toContain("departmentId: periodPaging.departmentId");
expect(source).toContain("sort: periodPaging.sort");
expect(source).toContain("() => periodPaging.selectedCustomerNumber");
expect(source).toContain("syncPeriodRouteQuery();");
expect(source).not.toMatch(/periodPaging\.direction,\s*periodPaging\.selectedCustomerNumber/);
});
it("exposes a dedicated keyboard selection control, explicit states, and two-pane review semantics", () => {
const source = readProjectFile(
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue"
);
expect(source).toContain('class="period-review-workspace"');
expect(source).toContain('role="group"');
expect(source).toContain('data-testid="`invoicing-period-customer-select-${customer.customer_number}`"');
expect(source).toContain(':aria-pressed="selectedCustomer === customer"');
expect(source).not.toContain('@keydown.enter.prevent="onClickCustomer(customer)"');
expect(source).toContain('data-testid="invoicing-period-initial-loading"');
expect(source).toContain('data-testid="invoicing-period-filtered-empty"');
expect(source).toContain('data-testid="invoicing-period-retained-error"');
});
it.each([
{
name: "manual flags take blocking precedence",
customer: { invoice_collections: [{ error_message: "failed" }], transactions: [] },
flags: { manualFlags: 2 },
expected: ["blocked", "resolve_manual_flags", ["manual_flags", "collection_errors"]],
},
{
name: "collection errors block",
customer: { invoice_collections: [{ error_message: "failed" }], transactions: [] },
flags: {},
expected: ["blocked", "resolve_collection_errors", ["collection_errors"]],
},
{
name: "drafts block with the backend reason code",
customer: { draft: { is_action_blocked: true }, transactions: [] },
flags: {},
expected: ["blocked", "resolve_draft", ["draft_blocks_action"]],
},
{
name: "requires action only means attention when no transaction is unbooked",
customer: { requires_action: true, transactions: [] },
flags: {},
expected: ["attention", "review_warnings", ["requires_action"]],
},
{
name: "automatic warnings outrank an active export",
customer: { queue: { has_active_job: true, invoice_collection_ids: [1] }, transactions: [] },
flags: { automaticFlags: 1 },
expected: ["attention", "review_warnings", ["automatic_warnings", "export_in_progress"]],
},
{
name: "an active export queues otherwise ready work",
customer: {
queue: { has_active_job: true, invoice_collection_ids: [1, 2] },
transactions: [{ booked: false, excluded: false }],
},
flags: {},
expected: ["queued", "wait_for_export", ["export_in_progress", "unbooked_transactions"]],
},
{
name: "only booked work is completed",
customer: { transactions: [{ booked: true, excluded: false }] },
flags: {},
expected: ["completed", "none", []],
},
{
name: "unbooked work remains ready even when requires action is set",
customer: { requires_action: true, transactions: [{ booked: false, excluded: false }] },
flags: {},
expected: ["ready", "create_invoice", ["unbooked_transactions"]],
},
{
name: "an empty customer is ready rather than completed",
customer: { transactions: [] },
flags: {},
expected: ["ready", "create_invoice", []],
},
])("matches the backend fallback matrix: $name", ({ customer, flags, expected }) => {
const review = deriveLegacyPeriodCustomerReview(customer, flags);
expect([review.state, review.next_action, review.reasons.map((reason) => reason.code)]).toEqual(expected);
});
it("uses durable backend queue jobs and keeps browser enqueueing only as a legacy fallback", () => {
const source = readProjectFile(
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodObjectTree.vue"
);
expect(source).toContain("const wasDurablyQueuedByServer");
expect(source).toContain("applied?.queue_job_ids ?? applied?.result?.queue_job_ids");
expect(source).toMatch(/if \(!wasDurablyQueuedByServer\) \{[\s\S]*addInvoiceCollectionsToQueue/);
});
});