Skip to content

Commit bfa208a

Browse files
committed
Email rendering tests
1 parent 4ba7bd9 commit bfa208a

3 files changed

Lines changed: 651 additions & 0 deletions

File tree

apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,188 @@ describe("create, patch, and get email theme", () => {
389389
});
390390
});
391391

392+
describe("invalid JSX inputs", () => {
393+
it("should reject theme that throws an error when rendered", async ({ expect }) => {
394+
await Project.createAndSwitch({
395+
display_name: "Test Email Theme Project",
396+
});
397+
398+
const response = await niceBackendFetch(
399+
`/api/latest/internal/email-themes/${validThemeId}`,
400+
{
401+
method: "PATCH",
402+
accessType: "admin",
403+
body: {
404+
tsx_source: `
405+
import { Html } from '@react-email/components';
406+
export function EmailTheme({ children }: { children: React.ReactNode }) {
407+
throw new Error('Intentional error from theme');
408+
}
409+
`,
410+
},
411+
}
412+
);
413+
expect(response.status).toBe(400);
414+
expect(response.body).toMatchInlineSnapshot("???");
415+
});
416+
417+
it("should reject theme that does not export EmailTheme function", async ({ expect }) => {
418+
await Project.createAndSwitch({
419+
display_name: "Test Email Theme Project",
420+
});
421+
422+
const response = await niceBackendFetch(
423+
`/api/latest/internal/email-themes/${validThemeId}`,
424+
{
425+
method: "PATCH",
426+
accessType: "admin",
427+
body: {
428+
tsx_source: `
429+
import { Html } from '@react-email/components';
430+
export function WrongFunctionName({ children }: { children: React.ReactNode }) {
431+
return <Html>{children}</Html>;
432+
}
433+
`,
434+
},
435+
}
436+
);
437+
expect(response.status).toBe(400);
438+
expect(response.body).toMatchInlineSnapshot(`
439+
{
440+
"code": "EMAIL_RENDERING_ERROR",
441+
"details": {
442+
"error": deindent\`
443+
Build failed with 1 error:
444+
virtual:/render.tsx:9:9: ERROR: No matching export in "virtual:/theme.tsx" for import "EmailTheme"
445+
\`,
446+
},
447+
"error": deindent\`
448+
Failed to render email with theme: Build failed with 1 error:
449+
virtual:/render.tsx:9:9: ERROR: No matching export in "virtual:/theme.tsx" for import "EmailTheme"
450+
\`,
451+
}
452+
`);
453+
});
454+
455+
it("should reject theme with invalid JSX syntax", async ({ expect }) => {
456+
await Project.createAndSwitch({
457+
display_name: "Test Email Theme Project",
458+
});
459+
460+
const response = await niceBackendFetch(
461+
`/api/latest/internal/email-themes/${validThemeId}`,
462+
{
463+
method: "PATCH",
464+
accessType: "admin",
465+
body: {
466+
tsx_source: `
467+
export function EmailTheme({ children }) {
468+
return <div><span>unclosed tag
469+
}
470+
`,
471+
},
472+
}
473+
);
474+
expect(response.status).toBe(400);
475+
expect(response.body).toMatchInlineSnapshot(`
476+
{
477+
"code": "EMAIL_RENDERING_ERROR",
478+
"details": {
479+
"error": deindent\`
480+
Build failed with 2 errors:
481+
virtual:/theme.tsx:4:12: ERROR: The character "}" is not valid inside a JSX element
482+
virtual:/theme.tsx:5:10: ERROR: Unexpected end of file before a closing "span" tag
483+
\`,
484+
},
485+
"error": deindent\`
486+
Failed to render email with theme: Build failed with 2 errors:
487+
virtual:/theme.tsx:4:12: ERROR: The character "}" is not valid inside a JSX element
488+
virtual:/theme.tsx:5:10: ERROR: Unexpected end of file before a closing "span" tag
489+
\`,
490+
}
491+
`);
492+
});
493+
494+
it.todo("should reject theme that causes infinite loop during rendering", async ({ expect }) => {
495+
await Project.createAndSwitch({
496+
display_name: "Test Email Theme Project",
497+
});
498+
499+
const response = await niceBackendFetch(
500+
`/api/latest/internal/email-themes/${validThemeId}`,
501+
{
502+
method: "PATCH",
503+
accessType: "admin",
504+
body: {
505+
tsx_source: `
506+
import { Html } from '@react-email/components';
507+
export function EmailTheme({ children }: { children: React.ReactNode }) {
508+
while (true) {}
509+
return <Html>{children}</Html>;
510+
}
511+
`,
512+
},
513+
}
514+
);
515+
// Should timeout or return an error, not hang indefinitely
516+
expect(response.status).toBe(400);
517+
expect(response.body).toMatchInlineSnapshot("todo");
518+
});
519+
520+
it.todo("should reject theme that allocates too much memory", async ({ expect }) => {
521+
await Project.createAndSwitch({
522+
display_name: "Test Email Theme Project",
523+
});
524+
525+
const response = await niceBackendFetch(
526+
`/api/latest/internal/email-themes/${validThemeId}`,
527+
{
528+
method: "PATCH",
529+
accessType: "admin",
530+
body: {
531+
tsx_source: `
532+
import { Html } from '@react-email/components';
533+
export function EmailTheme({ children }: { children: React.ReactNode }) {
534+
const arr = [];
535+
for (let i = 0; i < 1e9; i++) {
536+
arr.push(new Array(1e6).fill('x'));
537+
}
538+
return <Html>{children}</Html>;
539+
}
540+
`,
541+
},
542+
}
543+
);
544+
// Should fail due to memory limits, not hang or crash the server
545+
expect(response.status).toBe(400);
546+
expect(response.body).toMatchInlineSnapshot("todo");
547+
});
548+
549+
it("should reject theme that exports a non-function", async ({ expect }) => {
550+
await Project.createAndSwitch({
551+
display_name: "Test Email Theme Project",
552+
});
553+
554+
const response = await niceBackendFetch(
555+
`/api/latest/internal/email-themes/${validThemeId}`,
556+
{
557+
method: "PATCH",
558+
accessType: "admin",
559+
body: {
560+
tsx_source: `
561+
export const EmailTheme = "not a function";
562+
`,
563+
},
564+
}
565+
);
566+
expect(response.status).toBe(400);
567+
expect(response.body).toMatchInlineSnapshot(`
568+
{
569+
"code": "EMAIL_RENDERING_ERROR",
570+
"details": { "error": "{\\"message\\":\\"element.type is not a function. (In 'element.type(element.props || {})', 'element.type' is \\\\\\"not a function\\\\\\")\\",\\"stack\\":\\"TypeError: element.type is not a function. (In 'element.type(element.props || {})', 'element.type' is \\\\\\"not a function\\\\\\")\\\\n at findComponentValue (/app/tmp/job-<stripped UUID>/script.ts:70:20)\\\\n at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:145:18)\\\\n at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}" },
571+
"error": "Failed to render email with theme: {\\"message\\":\\"element.type is not a function. (In 'element.type(element.props || {})', 'element.type' is \\\\\\"not a function\\\\\\")\\",\\"stack\\":\\"TypeError: element.type is not a function. (In 'element.type(element.props || {})', 'element.type' is \\\\\\"not a function\\\\\\")\\\\n at findComponentValue (/app/tmp/job-<stripped UUID>/script.ts:70:20)\\\\n at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:145:18)\\\\n at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}",
572+
}
573+
`);
574+
});
575+
});
576+

0 commit comments

Comments
 (0)