fix(email): derive preview sample data from each template's variables
The preview modal's hardcoded sampleData had drifted from the templates'
declared variables arrays: it carried `password` and `expiration_date` and no
`host_name` at all, so {{host_name}}, {{gallery_password}} and {{expiry_date}}
rendered as literal placeholders in the gallery_created preview while
event_name/event_date/gallery_link substituted fine.
Derive the key set from the template's own `variables` instead, so nothing can
be missing again. editedTemplate already carries the array at the call site,
so no plumbing was needed. A small module-level lookup keeps sensible shapes
for the ~11 variables where shape matters (dates look like dates, links like
URLs), with a readable [name] fallback for anything uncurated -- curating all
~60 distinct variable names across the ~32 template seeds would just recreate
the drift trap.
Preview-only; real sent mail was never affected.
Refs testplan REPORT.md #17 (Part 3, J.04).
This commit is contained in:
@@ -68,6 +68,39 @@ const CORE_SUBCATEGORY_ORDER: readonly string[] = [
|
||||
'system',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Realistic stand-ins for the variables whose *shape* matters in a
|
||||
* preview — a date has to read like a date, a link like a link. This is
|
||||
* deliberately not a full list of every variable every template declares;
|
||||
* `buildPreviewSampleData` below covers the rest.
|
||||
*/
|
||||
const PREVIEW_SAMPLE_VALUES: Record<string, string> = {
|
||||
event_name: 'John & Jane Wedding',
|
||||
event_date: 'December 25, 2024',
|
||||
expiry_date: 'January 25, 2025',
|
||||
gallery_link: 'https://photos.example.com/gallery/john-jane-wedding',
|
||||
gallery_password: '••••••••',
|
||||
password: '••••••••',
|
||||
host_name: 'Jane Doe',
|
||||
host_email: 'host@example.com',
|
||||
admin_email: 'admin@example.com',
|
||||
days_remaining: '30',
|
||||
welcome_message: 'Thank you for celebrating our special day with us!',
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the preview payload from the template's OWN declared `variables`,
|
||||
* so the two can no longer drift apart. The previous hand-maintained key
|
||||
* list had gone stale and left {{host_name}}, {{gallery_password}} and
|
||||
* {{expiry_date}} rendering as raw tokens in the gallery_created preview.
|
||||
* Variables without a curated value get a readable stand-in rather than an
|
||||
* unsubstituted {{token}}.
|
||||
*/
|
||||
export const buildPreviewSampleData = (variables: string[] = []): Record<string, string> =>
|
||||
Object.fromEntries(
|
||||
variables.map((name) => [name, PREVIEW_SAMPLE_VALUES[name] ?? `[${name}]`])
|
||||
);
|
||||
|
||||
const defaultTemplateKeys = [
|
||||
{
|
||||
key: 'gallery_created',
|
||||
@@ -395,18 +428,8 @@ export const EmailConfigPage: React.FC = () => {
|
||||
const handlePreviewTemplate = async () => {
|
||||
if (!selectedTemplateKey || !editedTemplate) return;
|
||||
|
||||
// Generate sample data based on the template
|
||||
const sampleData: Record<string, string> = {
|
||||
event_name: 'John & Jane Wedding',
|
||||
event_date: 'December 25, 2024',
|
||||
password: '••••••••',
|
||||
gallery_link: 'https://photos.example.com/gallery/john-jane-wedding',
|
||||
expiration_date: 'January 25, 2025',
|
||||
welcome_message: 'Thank you for celebrating our special day with us!',
|
||||
days_remaining: '30',
|
||||
admin_email: 'admin@example.com',
|
||||
host_email: 'host@example.com'
|
||||
};
|
||||
// Sample data is derived from the template's declared variables
|
||||
const sampleData = buildPreviewSampleData(editedTemplate.variables);
|
||||
|
||||
try {
|
||||
const preview = await emailService.previewTemplate(selectedTemplateKey, sampleData, editingLang);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* The template Preview modal substituted a hand-maintained sample-data object
|
||||
* whose keys had drifted from the templates' declared `variables` (QA J.04):
|
||||
* {{host_name}}, {{gallery_password}} and {{expiry_date}} rendered as literal
|
||||
* tokens because the object still carried `password` / `expiration_date` and
|
||||
* no `host_name` at all. The payload is now derived from `variables`, so the
|
||||
* property that matters is coverage, not the contents of any one value.
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { buildPreviewSampleData } from '../EmailConfigPage';
|
||||
|
||||
describe('buildPreviewSampleData', () => {
|
||||
const galleryCreatedVariables = [
|
||||
'host_name',
|
||||
'event_name',
|
||||
'event_date',
|
||||
'gallery_link',
|
||||
'gallery_password',
|
||||
'expiry_date',
|
||||
];
|
||||
|
||||
it('supplies a value for every variable the template declares', () => {
|
||||
const sample = buildPreviewSampleData(galleryCreatedVariables);
|
||||
|
||||
expect(Object.keys(sample).sort()).toEqual([...galleryCreatedVariables].sort());
|
||||
for (const name of galleryCreatedVariables) {
|
||||
expect(sample[name]).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('never leaves a variable to render as a raw {{token}}', () => {
|
||||
const sample = buildPreviewSampleData(['customer_name', 'invoice_number', 'total_amount']);
|
||||
|
||||
for (const value of Object.values(sample)) {
|
||||
expect(value).not.toMatch(/\{\{|\}\}/);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps date- and link-shaped variables looking like dates and links', () => {
|
||||
const sample = buildPreviewSampleData(galleryCreatedVariables);
|
||||
|
||||
expect(sample.event_date).toMatch(/\d{4}/);
|
||||
expect(sample.expiry_date).toMatch(/\d{4}/);
|
||||
expect(sample.gallery_link).toMatch(/^https?:\/\//);
|
||||
});
|
||||
|
||||
it('falls back to a readable stand-in for variables it does not know', () => {
|
||||
expect(buildPreviewSampleData(['storno_number'])).toEqual({ storno_number: '[storno_number]' });
|
||||
});
|
||||
|
||||
it('handles a template with no declared variables', () => {
|
||||
expect(buildPreviewSampleData()).toEqual({});
|
||||
expect(buildPreviewSampleData([])).toEqual({});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user