diff --git a/app/lib/__tests__/email.server.test.ts b/app/lib/__tests__/email.server.test.ts new file mode 100644 index 0000000..b926951 --- /dev/null +++ b/app/lib/__tests__/email.server.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it, beforeEach, afterEach } from "vitest"; +import { + escapeHtml, + emailParagraph, + emailButton, + wrapInEmailTemplate, +} from "~/lib/email.server"; + +describe("escapeHtml", () => { + it("escapes all five special characters", () => { + expect(escapeHtml('&<>"\'')).toBe("&<>"'"); + }); + + it("leaves safe strings unchanged", () => { + expect(escapeHtml("Hello World")).toBe("Hello World"); + }); +}); + +describe("emailParagraph", () => { + it("wraps content in a

tag", () => { + const result = emailParagraph("Hello"); + expect(result).toContain("

"); + }); + + it("passes HTML through unescaped (caller is responsible for escaping)", () => { + const result = emailParagraph('link'); + expect(result).toContain(''); + }); +}); + +describe("emailButton", () => { + it("contains the href and button text", () => { + const result = emailButton("https://example.com/verify", "Verify My Email"); + expect(result).toContain("https://example.com/verify"); + expect(result).toContain("Verify My Email"); + }); + + it("renders a table-wrapped anchor for Outlook compatibility", () => { + const result = emailButton("https://example.com", "Click me"); + expect(result).toContain(" { + const originalAppUrl = process.env.APP_URL; + + beforeEach(() => { + process.env.APP_URL = "https://test.brackt.com"; + }); + + afterEach(() => { + if (originalAppUrl === undefined) { + delete process.env.APP_URL; + } else { + process.env.APP_URL = originalAppUrl; + } + }); + + it("returns a complete HTML document", () => { + const result = wrapInEmailTemplate("

Body

"); + expect(result).toContain(""); + expect(result).toContain(""); + }); + + it("injects the content into the template", () => { + const result = wrapInEmailTemplate("

My content

"); + expect(result).toContain("

My content

"); + }); + + it("includes the preheader text when provided", () => { + const result = wrapInEmailTemplate("

Body

", "Preview text here"); + expect(result).toContain("Preview text here"); + }); + + it("omits the preheader span when not provided", () => { + const result = wrapInEmailTemplate("

Body

"); + expect(result).not.toContain("mso-hide:all"); + }); + + it("uses APP_URL for the logo src", () => { + const result = wrapInEmailTemplate("

Body

"); + expect(result).toContain("https://test.brackt.com/email-logo.png"); + }); + + it("falls back to brackt.com when APP_URL is not set", () => { + delete process.env.APP_URL; + const result = wrapInEmailTemplate("

Body

"); + expect(result).toContain("https://brackt.com/email-logo.png"); + }); + + it("escapes preheader text to prevent injection", () => { + const result = wrapInEmailTemplate("

Body

", ""); + expect(result).not.toContain("