import { describe, expect, it } from "vitest"; import { assembleEmail, parseEmailParts, spellOutEmail } from "./email"; const parts = { user: "hello", domain: "mintel", tld: "me" } as const; describe("parseEmailParts", () => { it("accepts a plain user, domain and top-level domain", () => { expect(parseEmailParts(parts)).toEqual({ ok: true, value: parts }); }); it("accepts dots, dashes, plus signs and digits in the user part", () => { const raw = { user: "marc.m-1+web", domain: "mintel", tld: "me" }; expect(parseEmailParts(raw)).toEqual({ ok: true, value: raw }); }); it("accepts a domain with a subdomain and dashes", () => { const raw = { user: "hello", domain: "mail.my-site", tld: "co" }; expect(parseEmailParts(raw)).toEqual({ ok: true, value: raw }); }); it.each([ ["an empty user", ""], ["an at sign in the user", "he@llo"], ["a space in the user", "he llo"], ["a leading dot", ".hello"], ["a trailing dot", "hello."], ["a missing user", undefined], ["a number instead of text", 42], ])("rejects %s as invalid-user", (_label, user) => { expect(parseEmailParts({ user, domain: "mintel", tld: "me" })).toEqual({ ok: false, error: "invalid-user", }); }); it.each([ ["an empty domain", ""], ["an at sign in the domain", "min@tel"], ["a space in the domain", "min tel"], ["a leading dash", "-mintel"], ["a trailing dash", "mintel-"], ["a leading dot", ".mintel"], ["a trailing dot", "mintel."], ["an underscore", "min_tel"], ["a missing domain", undefined], ])("rejects %s as invalid-domain", (_label, domain) => { expect(parseEmailParts({ user: "hello", domain, tld: "me" })).toEqual({ ok: false, error: "invalid-domain", }); }); it.each([ ["an empty top-level domain", ""], ["a single letter", "m"], ["digits", "m3"], ["a dot inside", "co.uk"], ["a leading dot", ".me"], ["a missing top-level domain", undefined], ])("rejects %s as invalid-tld", (_label, tld) => { expect(parseEmailParts({ user: "hello", domain: "mintel", tld })).toEqual({ ok: false, error: "invalid-tld", }); }); it("accepts a two-letter and a longer top-level domain", () => { expect(parseEmailParts({ ...parts, tld: "me" }).ok).toBe(true); expect(parseEmailParts({ ...parts, tld: "digital" }).ok).toBe(true); }); it("rejects input that is not an object as invalid-user", () => { expect(parseEmailParts(null)).toEqual({ ok: false, error: "invalid-user" }); expect(parseEmailParts("hello@mintel.me")).toEqual({ ok: false, error: "invalid-user", }); }); it("reports the user first when several parts are wrong", () => { expect(parseEmailParts({ user: "", domain: "", tld: "" })).toEqual({ ok: false, error: "invalid-user", }); }); it("reports the domain before the top-level domain", () => { expect(parseEmailParts({ user: "hello", domain: "", tld: "" })).toEqual({ ok: false, error: "invalid-domain", }); }); }); describe("assembleEmail", () => { it("joins the parts into the real address", () => { expect(assembleEmail(parts)).toBe("hello@mintel.me"); }); it("keeps subdomains", () => { expect( assembleEmail({ user: "a.b", domain: "mail.site", tld: "org" }), ).toBe("a.b@mail.site.org"); }); }); describe("spellOutEmail", () => { it("writes the address without an at sign or a dot before the ending", () => { expect(spellOutEmail(parts)).toBe("hello [at] mintel [dot] me"); }); it("never contains the real address", () => { expect(spellOutEmail(parts)).not.toContain("@"); expect(spellOutEmail(parts)).not.toContain(assembleEmail(parts)); }); it("spells out dots inside the domain as well", () => { expect( spellOutEmail({ user: "hello", domain: "mail.site", tld: "org" }), ).toBe("hello [at] mail [dot] site [dot] org"); }); });