feat(quick-260914-m97): Feld Fehlermeldungen an im SMTP-Formular — settings-api, Formular, i18n settings.smtp
- SmtpConfig.bugReportRecipient (string | null) und SaveSmtpPayload.bugReportRecipient (null loescht, fehlend bewahrt) - Eingabefeld type=email hinter der Absenderadresse mit Hinweistext; Payload traegt immer trim() || null - Zwei Komponententests: Vorbelegung aus GET, PUT-Payload mit Wert bzw. null - i18n settings.smtp.bugReportRecipient / bugReportRecipientHelp in de und en Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
@@ -0,0 +1,76 @@
|
|||||||
|
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import de from '@/messages/de.json';
|
||||||
|
import { fetchSmtp, saveSmtp } from '@/lib/settings-api';
|
||||||
|
import { SmtpSettingsForm } from './smtp-settings-form';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* smtp-settings-form.test — NEU (quick-260914-m97). Das SMTP-Formular
|
||||||
|
* hatte bisher keine Testdatei. Zwei Tests fuer das neue Feld
|
||||||
|
* „Fehlermeldungen an“: Vorbelegung aus GET /settings/smtp und der
|
||||||
|
* PUT-Vertrag mit `saveSmtpConfig` (Wert bzw. `null` zum Loeschen).
|
||||||
|
*/
|
||||||
|
vi.mock('@/lib/settings-api', () => ({
|
||||||
|
fetchSmtp: vi.fn(),
|
||||||
|
saveSmtp: vi.fn(),
|
||||||
|
testSmtp: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('next-intl', async () => {
|
||||||
|
const messages = (await import('@/messages/de.json')).default as Record<string, unknown>;
|
||||||
|
const lookup = (path: string): string | undefined =>
|
||||||
|
path.split('.').reduce<unknown>((o, k) => (o && typeof o === 'object' ? (o as any)[k] : undefined), messages) as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
return {
|
||||||
|
useTranslations: (ns?: string) => (key: string) => lookup(ns ? `${ns}.${key}` : key) ?? key,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const LABEL = de.settings.smtp.bugReportRecipient;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.mocked(fetchSmtp).mockResolvedValue({
|
||||||
|
host: 'h',
|
||||||
|
port: 587,
|
||||||
|
encryption: 'starttls',
|
||||||
|
fromAddress: 'a@b.invalid',
|
||||||
|
hasPassword: false,
|
||||||
|
bugReportRecipient: 'fehler@b.invalid',
|
||||||
|
});
|
||||||
|
vi.mocked(saveSmtp).mockResolvedValue({} as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SmtpSettingsForm — Feld Fehlermeldungen an (quick-260914-m97)', () => {
|
||||||
|
it('Test 1: das Feld ist aus GET /settings/smtp vorbelegt', async () => {
|
||||||
|
render(<SmtpSettingsForm />);
|
||||||
|
|
||||||
|
const input = (await screen.findByLabelText(LABEL)) as HTMLInputElement;
|
||||||
|
await waitFor(() => expect(input.value).toBe('fehler@b.invalid'));
|
||||||
|
expect(input.type).toBe('email');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 2: PUT-Payload traegt den Wert; leeres Feld -> null', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<SmtpSettingsForm />);
|
||||||
|
const input = (await screen.findByLabelText(LABEL)) as HTMLInputElement;
|
||||||
|
await waitFor(() => expect(input.value).toBe('fehler@b.invalid'));
|
||||||
|
|
||||||
|
await user.clear(input);
|
||||||
|
await user.type(input, 'neu@b.invalid');
|
||||||
|
await user.click(screen.getByRole('button', { name: de.settings.smtp.save }));
|
||||||
|
await waitFor(() => expect(saveSmtp).toHaveBeenCalledTimes(1));
|
||||||
|
expect(vi.mocked(saveSmtp).mock.calls[0][0]).toMatchObject({ bugReportRecipient: 'neu@b.invalid' });
|
||||||
|
|
||||||
|
await user.clear(input);
|
||||||
|
await user.click(screen.getByRole('button', { name: de.settings.smtp.save }));
|
||||||
|
await waitFor(() => expect(saveSmtp).toHaveBeenCalledTimes(2));
|
||||||
|
expect(vi.mocked(saveSmtp).mock.calls[1][0]).toMatchObject({ bugReportRecipient: null });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -23,6 +23,7 @@ interface FormState {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string; // T-07-17: always starts blank; never pre-filled from server
|
password: string; // T-07-17: always starts blank; never pre-filled from server
|
||||||
fromAddress: string;
|
fromAddress: string;
|
||||||
|
bugReportRecipient: string; // quick-260914-m97: Postfach fuer den Fehler-melden-Knopf
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_FORM: FormState = {
|
const DEFAULT_FORM: FormState = {
|
||||||
@@ -32,6 +33,7 @@ const DEFAULT_FORM: FormState = {
|
|||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
fromAddress: '',
|
fromAddress: '',
|
||||||
|
bugReportRecipient: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
type TestFeedback =
|
type TestFeedback =
|
||||||
@@ -70,6 +72,7 @@ export function SmtpSettingsForm() {
|
|||||||
username: config.username ?? '',
|
username: config.username ?? '',
|
||||||
password: '', // T-07-17: never pre-filled
|
password: '', // T-07-17: never pre-filled
|
||||||
fromAddress: config.fromAddress,
|
fromAddress: config.fromAddress,
|
||||||
|
bugReportRecipient: config.bugReportRecipient ?? '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -95,6 +98,8 @@ export function SmtpSettingsForm() {
|
|||||||
if (form.username.trim()) payload.username = form.username.trim();
|
if (form.username.trim()) payload.username = form.username.trim();
|
||||||
// T-07-17: only include password when user has typed a new one
|
// T-07-17: only include password when user has typed a new one
|
||||||
if (form.password) payload.password = form.password;
|
if (form.password) payload.password = form.password;
|
||||||
|
// quick-260914-m97: das Formular ist der einzige Klient — leer bedeutet loeschen (null)
|
||||||
|
payload.bugReportRecipient = form.bugReportRecipient.trim() || null;
|
||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -300,6 +305,26 @@ export function SmtpSettingsForm() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Fehlermeldungen an (quick-260914-m97) */}
|
||||||
|
<div>
|
||||||
|
<label htmlFor="smtp-bug-report-recipient" className={labelClass}>
|
||||||
|
{t('smtp.bugReportRecipient')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="smtp-bug-report-recipient"
|
||||||
|
type="email"
|
||||||
|
placeholder="fehler@example.com"
|
||||||
|
className={inputClass}
|
||||||
|
value={form.bugReportRecipient}
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm((prev) => ({ ...prev, bugReportRecipient: e.target.value }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
|
{t('smtp.bugReportRecipientHelp')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Test-E-Mail Empfänger */}
|
{/* Test-E-Mail Empfänger */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="smtp-test-to" className={labelClass}>
|
<label htmlFor="smtp-test-to" className={labelClass}>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export interface SmtpConfig {
|
|||||||
fromAddress: string;
|
fromAddress: string;
|
||||||
/** True when an encrypted password is stored; the password value is never exposed. */
|
/** True when an encrypted password is stored; the password value is never exposed. */
|
||||||
hasPassword: boolean;
|
hasPassword: boolean;
|
||||||
|
/** Postfach fuer den Fehler-melden-Knopf (quick-260914-m97); null = nicht gesetzt. */
|
||||||
|
bugReportRecipient: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +41,12 @@ export interface SaveSmtpPayload {
|
|||||||
fromAddress: string;
|
fromAddress: string;
|
||||||
/** If set, backend sends a real test email to this address instead of just verify(). */
|
/** If set, backend sends a real test email to this address instead of just verify(). */
|
||||||
testTo?: string;
|
testTo?: string;
|
||||||
|
/**
|
||||||
|
* Postfach fuer den Fehler-melden-Knopf (quick-260914-m97). Vertrag mit
|
||||||
|
* `saveSmtpConfig`: `null` loescht den gespeicherten Wert, ein fehlendes
|
||||||
|
* Feld bewahrt ihn.
|
||||||
|
*/
|
||||||
|
bugReportRecipient?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API functions ---
|
// --- API functions ---
|
||||||
|
|||||||
@@ -168,7 +168,9 @@
|
|||||||
"testToHelp": "Optional — sendet eine echte Test-E-Mail an diese Adresse.",
|
"testToHelp": "Optional — sendet eine echte Test-E-Mail an diese Adresse.",
|
||||||
"testTesting": "Verbindung wird getestet...",
|
"testTesting": "Verbindung wird getestet...",
|
||||||
"testSuccess": "Verbindung erfolgreich",
|
"testSuccess": "Verbindung erfolgreich",
|
||||||
"testFailed": "Verbindung fehlgeschlagen"
|
"testFailed": "Verbindung fehlgeschlagen",
|
||||||
|
"bugReportRecipient": "Fehlermeldungen an",
|
||||||
|
"bugReportRecipientHelp": "Optional – Postfach, an das Anwender über den Knopf „Fehler melden\" ihre Meldungen mit Bildschirmfoto schicken. Leer lassen, wenn der Knopf keine E-Mails senden soll."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
|
|||||||
@@ -168,7 +168,9 @@
|
|||||||
"testToHelp": "Optional — sends a real test email to this address.",
|
"testToHelp": "Optional — sends a real test email to this address.",
|
||||||
"testTesting": "Testing connection...",
|
"testTesting": "Testing connection...",
|
||||||
"testSuccess": "Connection successful",
|
"testSuccess": "Connection successful",
|
||||||
"testFailed": "Connection failed"
|
"testFailed": "Connection failed",
|
||||||
|
"bugReportRecipient": "Bug reports to",
|
||||||
|
"bugReportRecipientHelp": "Optional – mailbox that receives the reports users send via the \"Report a problem\" button, including the screenshot. Leave empty if the button should not send e-mails."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
|
|||||||
Reference in New Issue
Block a user