Files
pleno-vue/tests/unit/i18n-xlvask-review-coverage.spec.js
T
7c9eb8b644 chore(release): merge develop into master — XL Vask integration fixes (Aug 15 2026) (#312)
Brings all of the develop branch's commits into master.

## What this contains

The 9 commits on develop that landed in this round — all XL Vask-related
UI fixes plus the 'visible primary-button hover state' visual-diff:

- **PR #303** (TRU-5 / AUT-1) — style(AUT-1): visible primary-button
hover state + visual diff
- **PR #304** (TRU-10 / AUT-6) — fix(invoicing-period): propagate
flagged wash start date to Selvvask view
- **PR #305** (TRU-12 / AUT-8) — feat(xlvask): render friendly notice
for 404 from /modules/xlvask/services/usage/orders
- **PR #306** (TRU-9 / AUT-5) — i18n(test): lock in xlvask_review /
xlvask_usage_log mirroring to the global v2 fallback
- **PR #307** (TRU-13 / AUT-9) — i18n(xlvask_review): translate missing
keys for no, sv, de, en
- **PR #308** (TRU-15 / AUT-11) — test(e2e): add Playwright smoke test
for XL Vask flag → Selvvash navigation
- **PR #309** (TRU-11 / AUT-7) — feat(TRU-11): propagate department
selector to Selvvask usage query
- **PR #310** (TRU-19 / AUT-15) — test(TRU-19): lock self-serve program
number range + button registry contract
- **PR #311** (TRU-8 / AUT-4) — fix(invoicing-flag-list): explain empty
XL Vask hover preview when flag context has no metadata

## Why

The XL Vask integration bug surfaced from the user-reported message "XL
Vask-registreringen er hverken ignoreret eller knyttet til en ordre i
den valgte periode. doesn't show the wash." After dispatching 9
diagnostic + fix tasks and merging all 9 PRs into develop via the
OpenSymphony orchestrator running against MiniMax M3, this PR is the
canonical release to bring develop's accumulated changes into master.

No new code in this PR — just the squash-merged output of the 9 source
PRs combined into a single develop→master merge.

## Verification

All 9 source PRs passed:
- Required CI (Action Runners)
- App Store Readiness  
- Quality lint/i18n/build/unit/e2e suites

The required checks on this PR will run the same gate.

## Notes

- The api repo has its own equivalent PR/merge — see CHANGELOG for that
side.

---------

Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
2026-08-16 08:58:06 +02:00

60 lines
2.3 KiB
JavaScript

// Regression guard for TRU-13 / AUT-9: every `invoicing_period.xlvask_review.*`
// key that exists in the da reference catalogue must also be present in
// the other active locales (no, sv, de, en) with a non-empty translation.
// The end-to-end integrity suite already enforces global key parity, but
// this targeted test documents the acceptance criteria for the xlvask_review
// translation work and surfaces locale-specific gaps immediately.
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const root = process.cwd();
const activeLocales = ["da", "no", "sv", "de", "en"];
const reviewFileFor = (locale) =>
join(root, `src/i18n/source/${locale}/phrases/compat/invoicing_period/xlvask_review.json`);
const flattenPairs = (node, prefix = "") => {
if (typeof node === "string") {
return [{ key: prefix, value: node }];
}
if (!node || typeof node !== "object" || Array.isArray(node)) {
return [];
}
return Object.entries(node).flatMap(([key, value]) => {
const nextPrefix = prefix ? `${prefix}.${key}` : key;
return flattenPairs(value, nextPrefix);
});
};
const readReviewSection = (locale) => {
const file = JSON.parse(readFileSync(reviewFileFor(locale), "utf8"));
return file?.compat?.invoicing_period?.xlvask_review ?? {};
};
describe("xlvask_review translation coverage", () => {
it("covers every da xlvask_review key in no, sv, de, en with a non-empty value", () => {
const daEntries = flattenPairs(readReviewSection("da"));
expect(daEntries.length, "da xlvask_review should expose translatable strings").toBeGreaterThan(0);
for (const locale of activeLocales.filter((entry) => entry !== "da")) {
const localeEntries = new Map(flattenPairs(readReviewSection(locale)).map((entry) => [entry.key, entry.value]));
const missing = [];
const empty = [];
for (const { key } of daEntries) {
if (!localeEntries.has(key)) {
missing.push(key);
continue;
}
const localized = localeEntries.get(key);
if (typeof localized !== "string" || localized.trim().length === 0) {
empty.push(key);
}
}
expect(missing, `${locale} xlvask_review keys missing from da`).toEqual([]);
expect(empty, `${locale} xlvask_review keys with empty translations`).toEqual([]);
}
});
});