Release v0.1.2
This commit is contained in:
198
webui/tests/mail-components.test.tsx
Normal file
198
webui/tests/mail-components.test.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
function assert(condition: unknown, message = "assertion failed"): void {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
function assertEqual<T>(actual: T, expected: T, message = "values should be equal"): void {
|
||||
if (actual !== expected) throw new Error(`${message}: expected ${String(expected)}, got ${String(actual)}`);
|
||||
}
|
||||
|
||||
function assertDeepEqual(actual: unknown, expected: unknown, message = "values should be deeply equal"): void {
|
||||
const actualJson = JSON.stringify(actual);
|
||||
const expectedJson = JSON.stringify(expected);
|
||||
if (actualJson !== expectedJson) throw new Error(`${message}: expected ${expectedJson}, got ${actualJson}`);
|
||||
}
|
||||
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import PasswordField from "../src/components/PasswordField";
|
||||
import MessageDisplayPanel from "../src/components/MessageDisplayPanel";
|
||||
import MailServerSettingsPanel from "../src/components/mail/MailServerSettingsPanel";
|
||||
|
||||
function noop() {}
|
||||
|
||||
const savedPassword = renderToStaticMarkup(
|
||||
<PasswordField value="" saved savedPlaceholder="Saved password configured" onValueChange={noop} />
|
||||
);
|
||||
assert(savedPassword.includes('placeholder="Saved password configured"'), "saved password placeholder is rendered");
|
||||
assert(!savedPassword.includes("password-field-toggle"), "saved empty password does not show reveal button");
|
||||
|
||||
const typedPassword = renderToStaticMarkup(
|
||||
<PasswordField value="secret" saved savedPlaceholder="Saved password configured" onValueChange={noop} />
|
||||
);
|
||||
assert(typedPassword.includes('aria-label="Show password"'), "typed password shows reveal button");
|
||||
assert(typedPassword.includes("password-field-toggle"), "typed password gets toggle class");
|
||||
|
||||
const settingsPanel = renderToStaticMarkup(
|
||||
<MailServerSettingsPanel
|
||||
smtp={{ host: "smtp.example.org", port: 587, username: "sender", password: "", security: "starttls", timeout_seconds: 30 }}
|
||||
imap={{ host: "imap.example.org", port: 993, username: "sender", password: "", security: "tls", sent_folder: "auto", timeout_seconds: 30 }}
|
||||
onSmtpChange={noop}
|
||||
onImapChange={noop}
|
||||
smtpPasswordSaved
|
||||
smtpSavedPasswordPlaceholder="Saved SMTP password"
|
||||
imapPasswordSaved
|
||||
imapSavedPasswordPlaceholder="Saved IMAP password"
|
||||
onTestSmtp={noop}
|
||||
onTestImap={noop}
|
||||
onLookupFolders={noop}
|
||||
folderLookupResult={{
|
||||
ok: true,
|
||||
protocol: "imap",
|
||||
message: "Folders loaded",
|
||||
detected_sent_folder: "Sent",
|
||||
folders: [{ name: "INBOX", flags: [] }, { name: "Sent", flags: ["\\Sent"] }]
|
||||
}}
|
||||
onUseDetectedFolder={noop}
|
||||
/>
|
||||
);
|
||||
assert(settingsPanel.includes("Test SMTP"), "SMTP test action is rendered");
|
||||
assert(settingsPanel.includes("Server"), "server heading is rendered");
|
||||
assert(settingsPanel.includes("Credentials"), "credentials heading is rendered");
|
||||
assert(!settingsPanel.includes("Enable IMAP"), "legacy IMAP enable toggle is not rendered");
|
||||
assert(settingsPanel.includes('placeholder="Saved SMTP password"'), "SMTP saved credential placeholder is rendered");
|
||||
|
||||
const imapSettingsPanel = renderToStaticMarkup(
|
||||
<MailServerSettingsPanel
|
||||
initialSection="imap"
|
||||
smtp={{ host: "smtp.example.org", port: 587, username: "sender", password: "", security: "starttls", timeout_seconds: 30 }}
|
||||
imap={{ host: "imap.example.org", port: 993, username: "sender", password: "", security: "tls", sent_folder: "auto", timeout_seconds: 30 }}
|
||||
onSmtpChange={noop}
|
||||
onImapChange={noop}
|
||||
imapPasswordSaved
|
||||
imapSavedPasswordPlaceholder="Saved IMAP password"
|
||||
onTestImap={noop}
|
||||
onLookupFolders={noop}
|
||||
folderLookupResult={{
|
||||
ok: true,
|
||||
protocol: "imap",
|
||||
message: "Folders loaded",
|
||||
detected_sent_folder: "Sent",
|
||||
folders: [{ name: "INBOX", flags: [] }, { name: "Sent", flags: ["\\Sent"] }]
|
||||
}}
|
||||
onUseDetectedFolder={noop}
|
||||
/>
|
||||
);
|
||||
assert(imapSettingsPanel.includes("Test IMAP"), "IMAP test action is rendered");
|
||||
assert(imapSettingsPanel.includes("Server"), "IMAP server heading is rendered");
|
||||
assert(imapSettingsPanel.includes("Credentials"), "IMAP credentials heading is rendered");
|
||||
assert(!imapSettingsPanel.includes("Enable IMAP"), "legacy IMAP enable toggle is not rendered in IMAP tab");
|
||||
assert(!imapSettingsPanel.includes("Folders..."), "folder lookup action is not rendered in the IMAP tab");
|
||||
assert(imapSettingsPanel.includes('placeholder="Saved IMAP password"'), "IMAP saved credential placeholder is rendered");
|
||||
assert(!imapSettingsPanel.includes("Folders loaded"), "folder lookup result is not rendered in the IMAP tab");
|
||||
assert(!imapSettingsPanel.includes("Default sent folder"), "default sent folder field is not rendered");
|
||||
|
||||
const advancedSettingsPanel = renderToStaticMarkup(
|
||||
<MailServerSettingsPanel
|
||||
initialSection="advanced"
|
||||
smtp={{ host: "smtp.example.org", security: "starttls" }}
|
||||
imap={{ host: "imap.example.org", security: "tls", sent_folder: "Sent" }}
|
||||
onSmtpChange={noop}
|
||||
onImapChange={noop}
|
||||
onLookupFolders={noop}
|
||||
folderLookupResult={{
|
||||
ok: true,
|
||||
protocol: "imap",
|
||||
message: "Folders loaded",
|
||||
detected_sent_folder: "Sent",
|
||||
folders: [{ name: "INBOX", flags: [] }, { name: "Sent", flags: ["\\Sent"] }]
|
||||
}}
|
||||
onUseDetectedFolder={noop}
|
||||
/>
|
||||
);
|
||||
assert(advancedSettingsPanel.includes("Append target folder"), "append target folder is the advanced IMAP folder field");
|
||||
assert(advancedSettingsPanel.includes("Folders..."), "folder lookup action is rendered near append target folder");
|
||||
assert(advancedSettingsPanel.includes("Folders loaded"), "folder lookup result is rendered in Advanced");
|
||||
assert(advancedSettingsPanel.includes("Use detected folder"), "detected folder action is rendered near append target folder");
|
||||
assert(advancedSettingsPanel.includes('value="Sent"'), "append target folder uses IMAP sent folder value without append override");
|
||||
|
||||
const disabledAppendFolderPanel = renderToStaticMarkup(
|
||||
<MailServerSettingsPanel
|
||||
initialSection="advanced"
|
||||
smtp={{ host: "smtp.example.org", security: "starttls" }}
|
||||
imap={{ host: "imap.example.org", security: "tls", sent_folder: "Sent" }}
|
||||
onSmtpChange={noop}
|
||||
onImapChange={noop}
|
||||
onLookupFolders={noop}
|
||||
folderLookupResult={{
|
||||
ok: true,
|
||||
protocol: "imap",
|
||||
message: "Folders loaded",
|
||||
detected_sent_folder: "Sent",
|
||||
folders: [{ name: "Sent", flags: ["\\Sent"] }]
|
||||
}}
|
||||
onUseDetectedFolder={noop}
|
||||
append={{
|
||||
enabled: true,
|
||||
folder: "Sent",
|
||||
folderDisabled: true,
|
||||
onEnabledChange: noop,
|
||||
onFolderChange: noop
|
||||
}}
|
||||
/>
|
||||
);
|
||||
assert(!disabledAppendFolderPanel.includes("Folders..."), "folder lookup action is hidden when append target is disabled");
|
||||
assert(!disabledAppendFolderPanel.includes("Use detected folder"), "detected folder action is hidden when append target is disabled");
|
||||
|
||||
const defaultPortSettingsPanel = renderToStaticMarkup(
|
||||
<MailServerSettingsPanel
|
||||
smtp={{ host: "smtp.example.org", security: "starttls" }}
|
||||
imap={{ host: "imap.example.org", security: "tls" }}
|
||||
onSmtpChange={noop}
|
||||
onImapChange={noop}
|
||||
/>
|
||||
);
|
||||
const defaultImapPortSettingsPanel = renderToStaticMarkup(
|
||||
<MailServerSettingsPanel
|
||||
initialSection="imap"
|
||||
smtp={{ host: "smtp.example.org", security: "starttls" }}
|
||||
imap={{ host: "imap.example.org", security: "tls" }}
|
||||
onSmtpChange={noop}
|
||||
onImapChange={noop}
|
||||
/>
|
||||
);
|
||||
assert(defaultPortSettingsPanel.includes('value="587"'), "SMTP starttls default port is displayed");
|
||||
assert(defaultImapPortSettingsPanel.includes('value="993"'), "IMAP TLS default port is displayed");
|
||||
|
||||
const messageDisplay = renderToStaticMarkup(
|
||||
<MessageDisplayPanel
|
||||
title="Quarterly update"
|
||||
fields={[
|
||||
{ label: "From", value: "Sender <sender@example.org>" },
|
||||
{ label: "Cc", value: null },
|
||||
{ label: "Bcc", value: "Audit <audit@example.org>" }
|
||||
]}
|
||||
bodyText="Plain body"
|
||||
bodyHtml="<p><strong>HTML body</strong></p>"
|
||||
preferredBodyMode="html"
|
||||
attachments={[
|
||||
{ filename: "agenda.pdf", contentType: "application/pdf", sizeBytes: 2048 },
|
||||
{
|
||||
filename: "secret.pdf",
|
||||
contentType: "application/pdf",
|
||||
archiveGroup: "archive-1",
|
||||
archiveLabel: "recipient-files.zip",
|
||||
protected: true,
|
||||
protectionNote: 'Password-protected ZIP using local field "zip_password". Encryption: AES.'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
assert(messageDisplay.includes("Quarterly update"), "message subject is rendered");
|
||||
assert(messageDisplay.includes("Sender <sender@example.org>"), "sender field is rendered");
|
||||
assert(!messageDisplay.includes("<dt>Cc</dt>"), "empty optional CC field is hidden");
|
||||
assert(messageDisplay.includes("<dt>Bcc</dt>"), "present BCC field is rendered");
|
||||
assert(messageDisplay.includes("HTML"), "HTML body switch is rendered");
|
||||
assert(messageDisplay.includes("Text"), "text body switch is rendered");
|
||||
assert(messageDisplay.includes("agenda.pdf"), "direct attachment is rendered");
|
||||
assert(messageDisplay.includes("recipient-files.zip"), "ZIP archive attachment group is rendered");
|
||||
assert(messageDisplay.includes("Password protected"), "ZIP protection badge is rendered");
|
||||
assert(messageDisplay.includes("zip_password"), "ZIP protection note is rendered");
|
||||
65
webui/tests/module-capabilities.test.ts
Normal file
65
webui/tests/module-capabilities.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { PlatformWebModule } from "../src/types";
|
||||
import {
|
||||
hasUiCapability,
|
||||
moduleInstalled,
|
||||
moduleIntegrationEnabled,
|
||||
routeContributionsForModules,
|
||||
uiCapability
|
||||
} from "../src/platform/moduleLogic";
|
||||
|
||||
function assert(condition: unknown, message: string): void {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
const access: PlatformWebModule = { id: "access", label: "Access", version: "test" };
|
||||
const filesCapability = { kind: "fileExplorer" };
|
||||
const mailCapability = { kind: "mailProfiles" };
|
||||
const files: PlatformWebModule = {
|
||||
id: "files",
|
||||
label: "Files",
|
||||
version: "test",
|
||||
routes: [{ path: "/files", order: 20, render: () => null }],
|
||||
uiCapabilities: { "files.fileExplorer": filesCapability }
|
||||
};
|
||||
const mail: PlatformWebModule = {
|
||||
id: "mail",
|
||||
label: "Mail",
|
||||
version: "test",
|
||||
routes: [{ path: "/mail", order: 30, render: () => null }],
|
||||
uiCapabilities: { "mail.profiles": mailCapability }
|
||||
};
|
||||
const campaigns: PlatformWebModule = {
|
||||
id: "campaigns",
|
||||
label: "Campaigns",
|
||||
version: "test",
|
||||
dependencies: ["access"],
|
||||
optionalDependencies: ["files", "mail"],
|
||||
routes: [{ path: "/campaigns", order: 10, render: () => null }]
|
||||
};
|
||||
|
||||
const cases: Array<{ name: string; modules: PlatformWebModule[]; files: boolean; mail: boolean }> = [
|
||||
{ name: "core-only", modules: [access], files: false, mail: false },
|
||||
{ name: "files-only", modules: [access, files], files: true, mail: false },
|
||||
{ name: "mail-only", modules: [access, mail], files: false, mail: true },
|
||||
{ name: "campaign-only", modules: [access, campaigns], files: false, mail: false },
|
||||
{ name: "campaign-files", modules: [access, campaigns, files], files: true, mail: false },
|
||||
{ name: "campaign-mail", modules: [access, campaigns, mail], files: false, mail: true },
|
||||
{ name: "full-product", modules: [access, campaigns, files, mail], files: true, mail: true }
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
const campaignInstalled = moduleInstalled("campaigns", testCase.modules);
|
||||
assert(moduleInstalled("access", testCase.modules), `${testCase.name}: access should be installed`);
|
||||
assert(moduleInstalled("files", testCase.modules) === testCase.files, `${testCase.name}: files installed state`);
|
||||
assert(moduleInstalled("mail", testCase.modules) === testCase.mail, `${testCase.name}: mail installed state`);
|
||||
assert(hasUiCapability("files.fileExplorer", testCase.modules) === testCase.files, `${testCase.name}: files UI capability`);
|
||||
assert(hasUiCapability("mail.profiles", testCase.modules) === testCase.mail, `${testCase.name}: mail UI capability`);
|
||||
assert(moduleIntegrationEnabled("campaigns", "files", testCase.modules) === (campaignInstalled && testCase.files), `${testCase.name}: campaign files integration`);
|
||||
assert(moduleIntegrationEnabled("campaigns", "mail", testCase.modules) === (campaignInstalled && testCase.mail), `${testCase.name}: campaign mail integration`);
|
||||
}
|
||||
|
||||
assert(uiCapability("files.fileExplorer", [access, files]) === filesCapability, "files capability should return the module-provided object");
|
||||
assert(uiCapability("mail.profiles", [access, mail]) === mailCapability, "mail capability should return the module-provided object");
|
||||
|
||||
const routes = routeContributionsForModules([access, files, mail, campaigns]).map((route) => route.path);
|
||||
assert(routes.join(",") === "/campaigns,/files,/mail", "routes should aggregate and sort by contribution order");
|
||||
Reference in New Issue
Block a user