114 lines
8.8 KiB
TypeScript
114 lines
8.8 KiB
TypeScript
import {
|
|
attachmentPreviewLinkableFiles,
|
|
attachmentPreviewMatchedFiles,
|
|
type AttachmentPreviewFileLike,
|
|
type AttachmentPreviewLike
|
|
} from "../src/features/campaigns/utils/attachmentPreview";
|
|
|
|
declare function require(name: string): {
|
|
readFileSync(path: string, encoding: string): string;
|
|
createHash(algorithm: string): {
|
|
update(value: string): {digest(encoding: "hex"): string};
|
|
};
|
|
};
|
|
|
|
const { readFileSync } = require("node:fs");
|
|
const { createHash } = require("node:crypto");
|
|
|
|
function assert(condition: unknown, message = "assertion failed"): void {
|
|
if (!condition) throw new Error(message);
|
|
}
|
|
|
|
function previewFile(index: number): AttachmentPreviewFileLike {
|
|
return {
|
|
id: `file-${index}`,
|
|
display_path: `/campaign/attachments/file-${String(index).padStart(2, "0")}.pdf`,
|
|
filename: `file-${String(index).padStart(2, "0")}.pdf`,
|
|
linked_to_campaign: index % 2 === 0
|
|
};
|
|
}
|
|
|
|
function previewRule(matches: AttachmentPreviewFileLike[]): {matches: AttachmentPreviewFileLike[]} {
|
|
return {
|
|
matches
|
|
};
|
|
}
|
|
|
|
const files = Array.from({ length: 25 }, (_, index) => previewFile(index));
|
|
const duplicatePromotedToLinked = { ...files[1], linked_to_campaign: true };
|
|
const preview: AttachmentPreviewLike<AttachmentPreviewFileLike> = {
|
|
rules: [
|
|
previewRule(files.slice(0, 14)),
|
|
previewRule([...files.slice(14), duplicatePromotedToLinked])
|
|
]
|
|
};
|
|
|
|
const matched = attachmentPreviewMatchedFiles(preview);
|
|
assert(matched.length === 25, "all unique attachment matches remain available; the review model must not truncate at twelve");
|
|
assert(matched.find((file) => file.id === "file-1")?.linked_to_campaign === true, "duplicate matches preserve the strongest linked state");
|
|
assert(matched.every((file, index) => index === 0 || Number(matched[index - 1].linked_to_campaign === false) <= Number(file.linked_to_campaign === false)), "linked matches sort before unlinked candidates");
|
|
|
|
const fallbackLinkable = attachmentPreviewLinkableFiles(preview);
|
|
assert(fallbackLinkable.length === 11, "linkable fallback contains every unique unlinked candidate");
|
|
|
|
const explicitLinkablePreview = {
|
|
...preview,
|
|
linkable_files: [files[3], files[3], files[5]]
|
|
};
|
|
assert(attachmentPreviewLinkableFiles(explicitLinkablePreview).length === 2, "explicit linkable candidates are deduplicated without a display cap");
|
|
|
|
const reviewSource = readFileSync("src/features/campaigns/ReviewSendPage.tsx", "utf8");
|
|
assert(!reviewSource.includes("attachmentPreviewMatchedFiles(preview).slice("), "review attachment links must not use an arbitrary display slice");
|
|
assert(reviewSource.includes("<AttachmentLinkingFileList files={matchedFiles} compact />"), "compact review exposes the complete bounded attachment list");
|
|
assert(reviewSource.includes('aria-haspopup="dialog"'), "the matched count advertises its attachment detail dialog");
|
|
assert(reviewSource.includes("tabIndex={0}"), "bounded attachment lists are keyboard-focusable scroll regions");
|
|
assert(reviewSource.includes('i18nMessage("i18n:govoplan-campaign.attachment_file_links_value.ce230e30"'), "dynamic attachment dialog title uses the i18n message contract");
|
|
assert(reviewSource.includes('i18nMessage("i18n:govoplan-campaign.view_all_value_matched_attachment_file_links.81e53951"'), "dynamic attachment count aria label uses the i18n message contract");
|
|
assert(!reviewSource.includes(">Attachment file links<"), "attachment review heading is not raw English");
|
|
assert(!reviewSource.includes('label="Matched"'), "attachment review facts are not raw English");
|
|
assert(!reviewSource.includes("`Link ${unlinkedCount}"), "attachment link actions are not assembled from raw English");
|
|
assert(!reviewSource.includes("Linked: already part of the campaign file snapshot."), "attachment link-state detail is not raw English");
|
|
assert(!reviewSource.includes("Unlinked: candidate match, potentially missing until linked."), "unlinked attachment detail is not raw English");
|
|
|
|
const newTranslations = [
|
|
["i18n:govoplan-campaign.all_unique_managed_files_matched_by_the_current_.0214c3e5", "All unique managed files matched by the current attachment rules are available in this list.", "Diese Liste enthält alle eindeutigen verwalteten Dateien, die den aktuellen Anhangsregeln entsprechen."],
|
|
["i18n:govoplan-campaign.attachment_file_links.0be74fd1", "Attachment file links", "Verknüpfungen von Anhangsdateien"],
|
|
["i18n:govoplan-campaign.attachment_file_links_value.ce230e30", "Attachment file links ({value0})", "Verknüpfungen von Anhangsdateien ({value0})"],
|
|
["i18n:govoplan-campaign.link_value_file.4d4ce740", "Link {value0} file", "{value0} Datei verknüpfen"],
|
|
["i18n:govoplan-campaign.link_value_files.88b7e6a7", "Link {value0} files", "{value0} Dateien verknüpfen"],
|
|
["i18n:govoplan-campaign.linked_already_part_of_the_campaign_file_snapsho.d037a6bc", "Linked: already part of the campaign file snapshot.", "Verknüpft: bereits Teil des Kampagnen-Datei-Snapshots."],
|
|
["i18n:govoplan-campaign.linking.a5f54e0f", "Linking", "Wird verknüpft"],
|
|
["i18n:govoplan-campaign.matched.1bf3ec5b", "Matched", "Treffer"],
|
|
["i18n:govoplan-campaign.need_link.fa4ab530", "Need link", "Zu verknüpfen"],
|
|
["i18n:govoplan-campaign.no_managed_files_matched_the_current_attachment_.dba99f5c", "No managed files matched the current attachment patterns.", "Keine verwalteten Dateien entsprechen den aktuellen Anhangsmustern."],
|
|
["i18n:govoplan-campaign.preview_includes_already_linked_files_and_access.dfef92af", "Preview includes already linked files and accessible unlinked candidates. Locking can link candidates before validation.", "Die Vorschau enthält bereits verknüpfte Dateien und zugängliche, noch nicht verknüpfte Kandidaten. Beim Sperren können Kandidaten vor der Validierung verknüpft werden."],
|
|
["i18n:govoplan-campaign.refreshing.505dddc9", "Refreshing", "Wird aktualisiert"],
|
|
["i18n:govoplan-campaign.shared_source.7d4a1bf2", "Shared source", "Gemeinsame Quelle"],
|
|
["i18n:govoplan-campaign.unlinked_candidate_files_are_not_yet_part_of_the.b8fd5998", "Unlinked candidate files are not yet part of the campaign snapshot. They will be linked when you confirm locking.", "Nicht verknüpfte Kandidatendateien sind noch nicht Teil des Kampagnen-Snapshots. Sie werden verknüpft, wenn Sie das Sperren bestätigen."],
|
|
["i18n:govoplan-campaign.unlinked_candidate_match_potentially_missing_unt.2bae9433", "Unlinked: candidate match, potentially missing until linked.", "Nicht verknüpft: möglicher Treffer, der ohne Verknüpfung fehlen kann."],
|
|
["i18n:govoplan-campaign.value_matched_attachment_file_link.30a84824", "{value0} matched attachment file link", "{value0} gefundene Verknüpfung einer Anhangsdatei"],
|
|
["i18n:govoplan-campaign.value_matched_attachment_file_links.ce509a3a", "{value0} matched attachment file links", "{value0} gefundene Verknüpfungen von Anhangsdateien"],
|
|
["i18n:govoplan-campaign.view_all_value_matched_attachment_file_links.81e53951", "View all {value0} matched attachment file links", "Alle {value0} gefundenen Verknüpfungen von Anhangsdateien anzeigen"]
|
|
] as const;
|
|
|
|
const translationCatalog = readFileSync("src/i18n/generatedTranslations.ts", "utf8");
|
|
for (const [key, english, german] of newTranslations) {
|
|
const digest = createHash("sha1").update(english).digest("hex").slice(0, 8);
|
|
assert(key.endsWith(`.${digest}`), `${key} uses the stable SHA-1 suffix for its English source`);
|
|
assert(translationCatalog.split(`"${key}"`).length - 1 === 2, `${key} is present exactly once in both language catalogs`);
|
|
assert(translationCatalog.includes(`"${key}": ${JSON.stringify(english)}`), `${key} has its English catalog value`);
|
|
assert(translationCatalog.includes(`"${key}": ${JSON.stringify(german)}`), `${key} has its German catalog value`);
|
|
}
|
|
|
|
const overlaySource = readFileSync("src/features/campaigns/components/MessagePreviewOverlay.tsx", "utf8");
|
|
assert(overlaySource.includes("message-preview-backdrop"), "message previews expose a layout-specific responsive backdrop hook");
|
|
assert(overlaySource.includes("{actions && <div"), "built-message footer actions remain in the stable shared preview footer");
|
|
assert(overlaySource.includes("<Dialog") && overlaySource.includes("closeLabel={closeLabel}"), "the preview uses the central Dialog and its accessible close label");
|
|
|
|
const styles = readFileSync("src/styles/campaign-workspace.css", "utf8");
|
|
assert(styles.includes("height: min(780px, calc(100dvh - 48px));"), "desktop previews use a stable responsive height");
|
|
assert(styles.includes("height: calc(100dvh - 16px);"), "small viewports use the available dynamic viewport height");
|
|
assert(styles.includes(".message-preview-modal .modal-footer"), "preview footer has a stable layout rule");
|
|
assert(styles.includes(".attachment-linking-file-list.is-compact"), "compact attachment links use a bounded scroll surface");
|
|
assert(styles.includes(".attachment-linking-file-list.is-detail"), "attachment detail links use an independent scroll surface");
|