115 lines
7.8 KiB
JavaScript
115 lines
7.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
function read(relativePath) {
|
|
return readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), "utf8");
|
|
}
|
|
|
|
const profiles = `${read("../src/features/mail/MailProfileManagement.tsx")}\n${read("../src/features/mail/MailProfilePolicyEditor.tsx")}`;
|
|
const mailbox = read("../src/features/mail/MailboxPage.tsx");
|
|
const mailApi = read("../src/api/mail.ts");
|
|
const bounces = read("../src/features/mail/MailBouncePage.tsx");
|
|
const legacyImport = read("../src/features/mail/MailLegacyImportPage.tsx");
|
|
const moduleSource = read("../src/module.ts");
|
|
const styles = read("../src/styles/mail-profiles.css");
|
|
const migration = read("../../docs/INTERFACE_PATTERN_MIGRATION.md");
|
|
|
|
assert.match(profiles, /ActionBlockerHint,[\s\S]*DocumentationHelpLink/);
|
|
for (const sharedComponent of ["ConnectionTree", "ConfirmDialog", "Dialog", "LoadingFrame"]) {
|
|
assert.match(profiles, new RegExp(`\\b${sharedComponent}\\b`));
|
|
}
|
|
assert.match(profiles, /topicId: "mail\.profiles-and-policy"/);
|
|
assert.match(profiles, /disabledReason: credentialMutationBlocker/);
|
|
assert.match(profiles, /disabledReason=\{editorSaveBlocker\}/);
|
|
assert.match(profiles, /disabledReason=\{policySaveBlocker\}/);
|
|
assert.match(profiles, /smtpActionDisabledReason=\{smtpTestBlocker\}/);
|
|
assert.match(profiles, /folder_mappings: draft\.imapFolderMappings/);
|
|
assert.match(profiles, /listMailProfileImapFolders[\s\S]*listImapFolders/);
|
|
assert.match(profiles, /onLookupImapFolders=\{\(\) => void runImapFolderLookup\(\)\}/);
|
|
assert.match(profiles, /imapFolderLookupResult=\{imapFolderResult\}/);
|
|
|
|
assert.match(mailbox, /ActionBlockerHint/);
|
|
assert.match(mailbox, /DocumentationHelpLink/);
|
|
assert.match(mailbox, /topicId: "mail\.workflow\.read-mailbox"/);
|
|
assert.match(mailbox, /disabledReason=\{folderReloadBlocker\}/);
|
|
assert.match(mailbox, /folder_mappings\?\.inbox/);
|
|
assert.match(mailbox, /detected_folder_mappings\?\.inbox/);
|
|
const openFolderNode = mailbox.slice(mailbox.indexOf(" function openFolderNode("), mailbox.indexOf(" function toggleFolderNode("));
|
|
assert.doesNotMatch(openFolderNode, /toggleFolderNode\(/, "Folder labels select; only the folder button expands or collapses.");
|
|
assert.match(openFolderNode, /setSelectedFolderGroup\(\{ id: node\.id, label: node\.label \}\)/);
|
|
assert.match(openFolderNode, /messageDetailRequestRef\.current \+= 1/, "Selecting a grouping invalidates pending message detail.");
|
|
assert.match(mailbox, /selectedFolderGroup\?\.id \?\? findFolderNodeId/);
|
|
assert.match(mailbox, /<WorkspaceFrame[^>]*height="viewport"/);
|
|
assert.match(mailbox, /<WorkspaceActionBar\s+variant="workspace"\s+scope="workspace"[\s\S]*refreshable[\s\S]*onReload: \(\) => void reloadMailbox\(\)/);
|
|
assert.ok(mailbox.indexOf("<WorkspaceActionBar") < mailbox.indexOf('<aside className="file-tree-panel"'), "Mailbox actions remain outside all selectable panes.");
|
|
assert.equal((mailbox.match(/<WorkspaceActionBar/g) ?? []).length, 1, "One persistent page-wide action bar.");
|
|
assert.doesNotMatch(mailbox, /mailbox-toolbar|<ToolbarGroup/);
|
|
assert.match(mailbox, /const MAILBOX_READ_OPTIONS = \{ cache: "no-store" \}/);
|
|
assert.match(mailbox, /listMailServerProfiles\(settings, false, undefined, MAILBOX_READ_OPTIONS\)/);
|
|
for (const helper of ["listMailServerProfiles", "listMailboxFolders", "bootstrapMailbox", "listMailboxMessages", "getMailboxMessage"]) {
|
|
const start = mailApi.indexOf(`export async function ${helper}(`);
|
|
const end = mailApi.indexOf("\nexport async function ", start + 1);
|
|
const source = mailApi.slice(start, end < 0 ? undefined : end);
|
|
assert.match(source, /options\?: MailboxReadOptions/, `${helper} supports a scoped fresh read.`);
|
|
assert.match(source, /\}\), options\)/, `${helper} passes the read options to Core without global invalidation.`);
|
|
}
|
|
assert.match(mailbox, /<Dialog open=\{mailToolsOpen\}[\s\S]*mailbox_refresh_tools[\s\S]*refresh_mailbox_profiles[\s\S]*refresh_mailbox_folder_catalogue[\s\S]*refresh_mailbox_message_index[\s\S]*mailbox_related_tools/);
|
|
const refreshMailbox = mailbox.slice(mailbox.indexOf(" async function reloadMailbox("), mailbox.indexOf(" function selectProfile("));
|
|
assert.match(refreshMailbox, /await loadProfiles\(\)[\s\S]*if \(selectedFolderGroup\) await refreshFolderCatalogue\(result.selected\)/);
|
|
assert.match(refreshMailbox, /requestAuthority !== authorityRef.current/);
|
|
assert.match(refreshMailbox, /profileId === selectedProfileIdRef.current/);
|
|
assert.match(refreshMailbox, /if \(!preservePreview\) setSelectedMessage\(null\)/, "Failed preview refresh keeps the previously loaded message.");
|
|
assert.doesNotMatch(refreshMailbox, /sendMail|appendMail|moveMail|deleteMail|setFlags|createMail/, "Mailbox reload must remain provider-read-only.");
|
|
assert.match(mailbox, /onKeyDown=\{\(event\) => \{[\s\S]*event\.key === "Enter" \|\| event\.key === " "/);
|
|
|
|
assert.match(bounces, /DocumentationHelpLink/);
|
|
assert.match(bounces, /topicId: "mail\.bounce-processing"/);
|
|
assert.match(bounces, /<ConfirmDialog[\s\S]*confirmLabel="Remove watcher"[\s\S]*tone="danger"/);
|
|
assert.match(bounces, /disabledReason=\{saveWatcherBlocker\}/);
|
|
|
|
for (const sharedComponent of ["PageLayout", "PageActionBar", "SelectionList", "DataGrid", "Dialog", "ConfirmDialog", "ToggleSwitch", "StatusBadge"]) {
|
|
assert.match(legacyImport, new RegExp(`\\b${sharedComponent}\\b`));
|
|
}
|
|
assert.match(legacyImport, /archetype="collection"/);
|
|
assert.match(legacyImport, /variant="collection"[\s\S]*refreshable[\s\S]*reloadAction=/);
|
|
assert.match(legacyImport, /topicId: "mail\.workflow\.legacy-pop3-import"/);
|
|
assert.match(legacyImport, /helpContextId="mail\.pop3"[\s\S]*helpTopicId="mail\.workflow\.legacy-pop3-import"/);
|
|
for (const helpContextId of [
|
|
"mail.pop3.source-editor",
|
|
"mail.pop3.action.reload",
|
|
"mail.pop3.action.save-source",
|
|
"mail.pop3.action.import",
|
|
"mail.pop3.field.transport-security",
|
|
"mail.pop3.field.max-message-size",
|
|
"mail.pop3.field.max-batch-size",
|
|
"mail.pop3.field.password",
|
|
"mail.pop3.field.delete-after-import",
|
|
"mail.pop3.confirm-delete-source",
|
|
]) {
|
|
assert.match(
|
|
legacyImport,
|
|
new RegExp(`helpContextId(?:=|:\\s*)"${helpContextId.replaceAll(".", "\\.")}"`)
|
|
);
|
|
}
|
|
assert.match(legacyImport, /data-help-context-id="mail\.pop3\.field\.message-selection"/);
|
|
assert.match(legacyImport, /<ConfirmDialog[\s\S]*tone="danger"[\s\S]*onConfirm=\{\(\) => void runImport\(\)\}/);
|
|
assert.match(legacyImport, /legacy_import_enabled: false[\s\S]*is_active: false[\s\S]*createMailServerCredential[\s\S]*updateMailServerEndpoint/);
|
|
assert.match(legacyImport, /expected_transport_revision: preview\.transport_revision/);
|
|
|
|
assert.doesNotMatch(`${profiles}\n${mailbox}\n${bounces}\n${legacyImport}`, /window\.(?:alert|confirm)\(/);
|
|
assert.doesNotMatch(`${profiles}\n${mailbox}\n${bounces}\n${legacyImport}\n${moduleSource}`, /@govoplan\/(?:campaign|files|docs|calendar)-webui|govoplan_(?:campaign|files|docs|calendar)/);
|
|
assert.match(moduleSource, /"mail\.profiles"/);
|
|
assert.match(styles, /@media \(max-width: 900px\)[\s\S]*\.mail-profile-transport-summary[\s\S]*grid-template-columns: 1fr/);
|
|
assert.match(styles, /@media \(max-width: 1280px\)[\s\S]*\.mailbox-shell\.file-manager-shell[\s\S]*grid-template-columns:/);
|
|
assert.doesNotMatch(styles, /\.mailbox-toolbar/, "Core owns workspace toolbar wrapping and action ordering.");
|
|
assert.match(styles, /@media \(max-width: 760px\)[\s\S]*\.mailbox-message-row[\s\S]*grid-template-columns: 1fr/);
|
|
|
|
for (const archetype of ["Directory/explorer", "Administration/configuration", "Effective-policy editor", "Evidence/reporting"]) {
|
|
assert.match(migration, new RegExp(archetype.replace("/", "\\/")));
|
|
}
|
|
assert.match(migration, /Shared `Dialog` owns focus entry, Escape handling, focus containment, and focus\s+return/);
|
|
assert.match(migration, /Passwords are write-only[\s\S]*saved\s+state marker/);
|
|
|
|
console.log("Mail surfaces satisfy the recorded interface pattern-language contract.");
|