55 lines
4.3 KiB
JavaScript
55 lines
4.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
const api = readFileSync("src/api/campaigns.ts", "utf8");
|
|
const page = readFileSync("src/features/reports/AggregateReportsPage.tsx", "utf8");
|
|
const reportPage = readFileSync("src/features/campaigns/CampaignReportPage.tsx", "utf8");
|
|
const reviewPage = readFileSync("src/features/campaigns/ReviewSendPage.tsx", "utf8");
|
|
const operatorPage = readFileSync("src/features/operator/OperatorQueuePage.tsx", "utf8");
|
|
const deliveryMode = readFileSync("src/features/campaigns/utils/deliveryMode.ts", "utf8");
|
|
const translationCatalog = readFileSync("src/i18n/generatedTranslations.ts", "utf8");
|
|
|
|
assert(api.includes('"/api/v1/campaigns/aggregate-reports"'), "the report list uses the safe aggregate collection");
|
|
assert(api.includes("/api/v1/campaigns/aggregate-reports/${encodeURIComponent(campaignId)}"), "the selected report uses the safe aggregate projection");
|
|
|
|
assert(page.includes("listAggregateReportCampaigns"), "the page loads only the safe Campaign selector projection");
|
|
assert(page.includes("getAggregateCampaignReport"), "the page loads only the safe aggregate report projection");
|
|
assert(!page.includes("getCampaignReport"), "the aggregate page cannot load the recipient-aware full report");
|
|
assert(!page.includes("getCampaignJobs"), "the aggregate page cannot cache recipient jobs");
|
|
assert(!page.includes("getCampaignJobDetail"), "the aggregate page has no detail route");
|
|
assert(!page.includes("downloadCampaignJobsCsv"), "the aggregate page has no export path");
|
|
assert(!page.includes("localStorage"), "the aggregate page does not persist report data in local storage");
|
|
assert(!page.includes("sessionStorage"), "the aggregate page does not persist report data in session storage");
|
|
assert(page.includes("TableActionGroup"), "campaign selection uses the central icon-only table action group");
|
|
assert(page.includes("disabled: campaign.id === selectedFromUrl"), "the selected row action stays visible and disabled");
|
|
assert(!page.includes("{report.population.denominator_definition}"), "the known denominator contract uses a localized UI explanation");
|
|
assert(!page.includes("{report.privacy.rule}"), "the known privacy contract uses a localized UI explanation");
|
|
assert(page.includes("all_persisted_recipient_delivery_jobs_for_the_se.208e90cc"), "the denominator explanation is bilingual");
|
|
assert(page.includes("positive_counts_below_the_threshold_are_hidden_a.1dc97093"), "the suppression rule is bilingual");
|
|
|
|
const touchedSources = [page, reportPage, reviewPage, operatorPage, deliveryMode].join("\n");
|
|
const touchedKeys = new Set(touchedSources.match(/i18n:govoplan-campaign\.[a-z0-9_]+\.[a-f0-9]{8}/g) ?? []);
|
|
for (const key of touchedKeys) {
|
|
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
const catalogEntries = [...translationCatalog.matchAll(new RegExp(`"${escapedKey}":\\s*("(?:[^"\\\\]|\\\\.)*")`, "g"))];
|
|
assert.equal(catalogEntries.length, 2, `${key} is present exactly once in both language catalogs`);
|
|
const english = JSON.parse(catalogEntries[0][1]);
|
|
const german = JSON.parse(catalogEntries[1][1]);
|
|
assert.notEqual(english.trim(), "", `${key} has a non-empty English translation`);
|
|
assert.notEqual(german.trim(), "", `${key} has a non-empty German translation`);
|
|
}
|
|
|
|
const sliceSections = [...translationCatalog.matchAll(/ "i18n:govoplan-campaign\.campaign_reports_available_to_you\.f14fa403":[\s\S]*? "i18n:govoplan-campaign\.worker_queue_committed_value0_message_s_and_publ\.24400d3c":.*\n/g)];
|
|
assert.equal(sliceSections.length, 2, "the current slice has one contiguous English and German catalog section");
|
|
const newEnglishEntries = [...sliceSections[0][0].matchAll(/"(i18n:govoplan-campaign\.[a-z0-9_]+\.[a-f0-9]{8})":\s*("(?:[^"\\]|\\.)*")/g)];
|
|
for (const [, key, encodedEnglish] of newEnglishEntries) {
|
|
const english = JSON.parse(encodedEnglish);
|
|
const digest = createHash("sha1").update(english).digest("hex").slice(0, 8);
|
|
const slug = english.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+/, "").slice(0, 48);
|
|
assert.equal(key, `i18n:govoplan-campaign.${slug}.${digest}`, `${key} uses the normalized source slug and exact SHA-1 suffix`);
|
|
assert(touchedKeys.has(key), `${key} is referenced by the current Campaign UI slices`);
|
|
}
|
|
|
|
console.log("aggregate report UI structure checks passed");
|