package chrome
import (
"html/template"
"strings"
"testing"
"time"
)
// The door's copy is the suite's one copy set (shared-chrome spec §9.2,
// with Paul's 2026-10-01 ruling that the submit reads "Sign in"). Pinned
// word for word: a change here is a copy decision, made on purpose, in one
// place.
func TestDoorCopyIsPinned(t *testing.T) {
if LinkLifetime != 15*time.Minute {
t.Fatalf("LinkLifetime = %v; the copy below says 15 minutes", LinkLifetime)
}
for _, c := range []struct{ name, got, want string }{
{"Heading", English.Heading, "Sign in"},
{"AddressLabel", English.AddressLabel, "Email address"},
{"Help", English.Help, "We’ll email you a link that signs you in. It works once and expires in 15 minutes."},
{"Submit", English.Submit, "Sign in"},
{"SentHeading", English.SentHeading, "Check your email"},
{"SentBody", English.SentBody, "If that address can sign in here, we’ve sent it a link. It works once and expires in 15 minutes."},
{"Again", English.Again, "Use a different address"},
{"SignedOutPage", English.SignedOutPage, "Signed out"},
{"SignedOutHeading", English.SignedOutHeading, "You’re signed out"},
{"SignedOutBody", English.signedOutBody("Calls"), "You’ve signed out of Calls on this device."},
{"SignIn", English.SignIn, "Sign in"},
} {
if c.got != c.want {
t.Errorf("%s = %q, want %q", c.name, c.got, c.want)
}
}
for _, retired := range []string{"Email me a link", "Continue", "expires shortly", "on its way", "Back to sign in", "Send another link", "Use a different email address", "You are signed out", "Home"} {
for _, s := range []string{English.Heading, English.Help, English.Submit, English.SentHeading, English.SentBody, English.Again, English.SignedOutHeading, English.signedOutBody("Docs"), English.SignIn} {
if strings.Contains(s, retired) {
t.Errorf("%q carries retired copy %q", s, retired)
}
}
}
}
// Every door page's tab title is " · " (shared-chrome spec §10).
func TestDoorTitles(t *testing.T) {
for _, c := range []struct{ got, want string }{
{Title("Sign in", "Docs"), "Sign in · Docs"},
{Title("Sign in", ""), "Sign in"},
{Title("", "Docs"), "Docs"},
{SignIn{App: "Memoria"}.Title(), "Sign in · Memoria"},
{SignIn{App: "Memoria"}.Page(), "Sign in"},
{SignInNotice{App: "Calendar"}.Sent("/signin").Title(), "Check your email · Calendar"},
{SignInNotice{App: "Calendar"}.Sent("/signin").Page(), "Check your email"},
{SignInNotice{App: "Calls"}.SignedOut("/signin").Title(), "Signed out · Calls"},
{SignInNotice{App: "Calls"}.SignedOut("/signin").Page(), "Signed out"},
// A notice that is not one of the door's own is titled by its heading.
{SignInNotice{App: "Sheets", Heading: "Continue signing in"}.Title(), "Continue signing in · Sheets"},
} {
if c.got != c.want {
t.Errorf("title %q, want %q", c.got, c.want)
}
}
}
// The templates draw the door's copy when the app supplies none: the app
// passes its name, mark, sky and destinations, and nothing else.
func TestTheDoorDrawsItsOwnCopy(t *testing.T) {
html := render(t, "estilo-signin", SignIn{Icon: "/i.svg", App: "Calls", Action: "/signin"})
for _, want := range []string{
"
Sign in
",
">Email address",
"We’ll email you a link that signs you in. It works once and expires in 15 minutes.",
``,
} {
if !strings.Contains(html, want) {
t.Errorf("sign-in lacks %q:\n%s", want, html)
}
}
if strings.Contains(html, "Email me a link") {
t.Error("the door's submit must never read “Email me a link”")
}
html = render(t, "estilo-signin-sent", SignInNotice{Icon: "/i.svg", App: "Calls"}.Sent("/signin?return_to=%2Fm%2Fabc"))
for _, want := range []string{
"
Check your email
",
`
If that address can sign in here, we’ve sent it a link. It works once and expires in 15 minutes.
`,
`Use a different address`,
} {
if !strings.Contains(html, want) {
t.Errorf("sent lacks %q:\n%s", want, html)
}
}
html = render(t, "estilo-signed-out", SignInNotice{Icon: "/i.svg", App: "Calls"}.SignedOut("/signin"))
for _, want := range []string{
"
You’re signed out
",
"
You’ve signed out of Calls on this device.
",
`Sign in`,
} {
if !strings.Contains(html, want) {
t.Errorf("signed-out lacks %q:\n%s", want, html)
}
}
}
// The sign-in email names the app and says the true lifetime, the same
// words as the door (shared-chrome spec §9.2).
func TestSignInEmail(t *testing.T) {
subject, body := SignInEmail("Docs", "https://docs.example/auth/verify?token=x")
if subject != "Sign in to Docs" {
t.Errorf("subject %q", subject)
}
want := "Here’s your link to sign in to Docs:\n\nhttps://docs.example/auth/verify?token=x\n\nIt works once and expires in 15 minutes. If you didn’t ask for it, you can ignore this email."
if body != want {
t.Errorf("body %q\nwant %q", body, want)
}
if got := EmailBody("Docs")("L"); !strings.Contains(got, "\n\nL\n\n") {
t.Errorf("EmailBody does not place the link: %q", got)
}
}
// A locale other than English replaces every word through Copy.
func TestDoorCopyIsReplaceablePerLocale(t *testing.T) {
for locale, label := range locales {
t.Run(locale, func(t *testing.T) {
esc := template.HTMLEscapeString
c := &DoorCopy{Heading: label + "-h", AddressLabel: label + "-addr", Help: label + "-help", Submit: label + "-submit", SentHeading: label + "-sent", SentBody: label + "-sentbody", Again: label + "-again", SignedOutPage: label + "-outpage", SignedOutHeading: label + "-out", SignedOutBody: label + " {app}-outbody", SignIn: label + "-in"}
html := render(t, "estilo-signin", SignIn{Icon: "/i.svg", App: "Docs", Copy: c, Action: "/signin"})
for _, want := range []string{"
" + esc(label) + "-h
", esc(label) + "-addr", esc(label) + "-help", esc(label) + "-submit"} {
if !strings.Contains(html, want) {
t.Fatalf("missing %q in %s", want, html)
}
}
n := SignInNotice{Icon: "/i.svg", App: "Docs", Copy: c}
if h := render(t, "estilo-signin-sent", n.Sent("/signin")); !strings.Contains(h, esc(label)+"-sentbody") || !strings.Contains(h, esc(label)+"-again") {
t.Fatal("sent", h)
}
if h := render(t, "estilo-signed-out", n.SignedOut("/signin")); !strings.Contains(h, esc(label)+" Docs-outbody") || !strings.Contains(h, esc(label)+"-in") {
t.Fatal("signed out", h)
}
if got := n.SignedOut("/signin").Title(); got != label+"-outpage · Docs" {
t.Fatalf("title %q", got)
}
})
}
}
// The copy's slots are tokens, {app} and {link}, not Printf verbs: a
// translation that drops the slot, moves it, or carries a literal "%"
// renders as written rather than as "%!(EXTRA …)" (Fable on estilo #19,
// finding 4).
func TestDoorCopyFillsTokensNotFormats(t *testing.T) {
c := English
c.SignedOutBody = "100% done: {app} is signed out here."
if got := (SignInNotice{App: "Docs", Copy: &c}).SignedOut("/signin").Body; got != "100% done: Docs is signed out here." {
t.Errorf("signed-out body %q", got)
}
c.SignedOutBody = "Signed out."
if got := (SignInNotice{App: "Docs", Copy: &c}).SignedOut("/signin").Body; got != "Signed out." {
t.Errorf("a body without {app} rendered %q", got)
}
// With no app, the suite is named, as the hub's own page does.
if got := (SignInNotice{}).SignedOut("/signin").Body; got != "You’ve signed out of Oficina on this device." {
t.Errorf("no-app signed-out body %q", got)
}
// The link is substituted once: a link that happens to carry "{app}"
// is not rewritten.
if _, body := English.SignInEmail("Docs", "https://x.example/?q={app}"); !strings.Contains(body, "\n\nhttps://x.example/?q={app}\n\n") {
t.Errorf("the link was rewritten: %q", body)
}
}
// The email is part of the copy set: a locale's subject and body are what
// a locale's email says (Fable on estilo #19, finding 3), and the package
// helpers are English's.
func TestSignInEmailReadsTheCopy(t *testing.T) {
c := English
c.EmailSubject = "Entra en {app}"
c.EmailBody = "{app}: {link} (100%)"
subject, body := c.SignInEmail("Docs", "L")
if subject != "Entra en Docs" || body != "Docs: L (100%)" {
t.Errorf("locale email = %q, %q", subject, body)
}
if got := c.MailSubject("Docs"); got != "Entra en Docs" {
t.Errorf("MailSubject %q", got)
}
if got := c.MailBody("Docs")("L"); got != "Docs: L (100%)" {
t.Errorf("MailBody %q", got)
}
s, b := SignInEmail("Docs", "L")
if es, eb := English.SignInEmail("Docs", "L"); s != es || b != eb || EmailSubject("Docs") != es || EmailBody("Docs")("L") != eb {
t.Error("the package helpers are not English's email")
}
}