package markup
import (
"regexp"
"strings"
"testing"
"craftdesign.group/quarry/internal/idn"
)
// The identicon is idn's face, cell for cell, drawn as a table: five
// rows of five, a cell coloured exactly where the grid is on and in the
// shade the grid says.
func TestIdenticonMarkMatchesTheGrid(t *testing.T) {
const name = "dantest-sandbox"
got := string(IdenticonMark(name))
for _, banned := range []string{"<svg", "<img", "display:grid", "display:flex", "url("} {
if strings.Contains(got, banned) {
t.Errorf("mark carries %s", banned)
}
}
inner := got[strings.Index(got, `align="center"`):]
rows := strings.Split(inner, "<tr>")[1:]
if len(rows) != 5 {
t.Fatalf("%d rows", len(rows))
}
lit, deep := idn.Hex(name, markLit), idn.Hex(name, markDeep)
for r, row := range rows {
cells := regexp.MustCompile(`<td [^>]*>`).FindAllString(row, -1)
if len(cells) != 5 {
t.Fatalf("row %d: %d cells", r, len(cells))
}
for c, cell := range cells {
want := ""
switch idn.Grid(name)[r][c] {
case 1:
want = `bgcolor="` + lit + `"`
case 2:
want = `bgcolor="` + deep + `"`
}
if has := strings.Contains(cell, "bgcolor="); has != (want != "") || (want != "" && !strings.Contains(cell, want)) {
t.Errorf("cell %d,%d = %s, want %q", r, c, cell, want)
}
}
}
}
func TestAvatarMarkIsOneSizedImage(t *testing.T) {
got := string(AvatarMark("https://forge.example/r/alpha/avatar", "alpha"))
for _, want := range []string{`src="https://forge.example/r/alpha/avatar"`, `width="28"`, `height="28"`, `border-radius:6px`, `alt="alpha"`} {
if !strings.Contains(got, want) {
t.Errorf("missing %s", want)
}
}
}
// With nothing else to name, a card is the application's: its Q logo and
// the word QUARRY as styled text (never an image, so a client that
// recolours dark mail recolours it too), again in the footer, small.
func TestAccountCardIsTheApplicationsNotARepositorys(t *testing.T) {
const logo = "https://forge.example/apple-touch-icon.png"
got, ok := EmailDocument(EmailDoc{Title: "t", Surface: "account", Body: "<p>x</p>", Why: "w", LogoURL: logo,
HomeURL: "https://forge.example", HomeName: "forge.example"})
if !ok {
t.Fatal("fell back")
}
if n := strings.Count(got, `<img src="`+logo+`"`); n != 2 {
t.Errorf("%d logo images, want header and footer", n)
}
if n := strings.Count(got, ">QUARRY<"); n != 2 {
t.Errorf("%d QUARRY words, want header and footer", n)
}
for _, want := range []string{
"font-family:'Iosevka Fixed','Iosevka','Arial Narrow','Roboto Condensed','Helvetica Neue Condensed',sans-serif",
"font-weight:600;text-transform:uppercase;letter-spacing:", "color:#282828;",
`.q-word { color:#f0ece4 !important; }`,
`href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka/600.css"`,
} {
if !strings.Contains(got, want) {
t.Errorf("missing %s", want)
}
}
for _, banned := range []string{"lockup", "wordmark", "◆", "Sent by", ">quarry<", "b9541a"} {
if strings.Contains(got, banned) {
t.Errorf("carries %q", banned)
}
}
if strings.Contains(got, "zeitforge.app/") {
t.Error("a font or image is fetched from the forge: a self-hosted fetch is a read signal")
}
}
// A repository's header keeps its own mark and name; the footer carries
// the small logo and the word.
func TestRepoCardKeepsItsMarkAndFooterHasTheWord(t *testing.T) {
const logo = "https://forge.example/apple-touch-icon.png"
got, _ := EmailDocument(EmailDoc{Title: "t", Name: "alpha", Mark: IdenticonMark("alpha"), Body: "<p>x</p>", LogoURL: logo})
if strings.Count(got, "<img") != 1 || strings.Count(got, ">QUARRY<") != 1 || strings.Contains(got, "lockup") {
t.Errorf("repo card: %d images, %d words", strings.Count(got, "<img"), strings.Count(got, ">QUARRY<"))
}
}
// The wordmark is the word as text for everyone, and, in Gmail only, the
// badge image (Gmail recolours text it does not report and never images).
// The hidden copy is display:none, so one reading of QUARRY reaches a
// screen reader.
func TestWordmarkIsTextEverywhereAndTheBadgeInGmail(t *testing.T) {
const logo, big, small = "https://forge.example/apple-touch-icon.png?v=aaaa1111", "https://forge.example/badge.png?v=bbbb2222", "https://forge.example/badge-sm.png?v=cccc3333"
got, ok := EmailDocument(EmailDoc{Title: "t", Surface: "account", Body: "<p>x</p>", Why: "w", LogoURL: logo, BadgeURL: big, BadgeSmallURL: small})
if !ok {
t.Fatal("fell back")
}
for _, want := range []string{
// Text, in the application's face, white where a client is told dark.
`<span class="q-word q-wm-t" style="font-family:'Iosevka Fixed','Iosevka','Arial Narrow','Roboto Condensed','Helvetica Neue Condensed',sans-serif;font-size:36px;line-height:36px;font-weight:600;text-transform:uppercase;letter-spacing:0;color:#282828;">QUARRY</span>`,
`font-size:18px;line-height:18px;`,
`.q-word { color:#f0ece4 !important; }`, `[data-ogsc] .q-word { color:#f0ece4 !important; }`,
// The badge: hidden by default, shown only in Gmail, which also drops the logo cell and the text.
`<img class="q-wm-b" src="` + big + `" alt="QUARRY" width="92" height="28" style="display:none;max-height:0;overflow:hidden;mso-hide:all;`,
`<img class="q-wm-b" src="` + small + `" alt="QUARRY" width="59" height="18" style="display:none;`,
`u + .body .q-lgtd, u + .body .q-wm-t { display:none !important; }`,
`u + .body .q-wm-b { display:block !important;`,
`class="q-lgtd"`, `<body class="body"`, `<!doctype html>`,
`href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka/600.css"`,
} {
if !strings.Contains(got, want) {
t.Errorf("missing %s", want)
}
}
for _, banned := range []string{"wordmark", "lockup", "ink.png", "b9541a", "q-wm-d", "q-wm-l"} {
if strings.Contains(got, banned) {
t.Errorf("carries %q", banned)
}
}
if n := strings.Count(got, ">QUARRY<"); n != 2 {
t.Errorf("%d text copies, want header and footer", n)
}
if n := strings.Count(got, `alt="QUARRY"`); n != 2 {
t.Errorf("%d badge images, want header and footer", n)
}
// Without a badge the text is all there is, and it is visible.
got, _ = EmailDocument(EmailDoc{Title: "t", Surface: "account", Body: "<p>x</p>", Why: "w"})
if strings.Contains(got, "<img") || !strings.Contains(got, ">QUARRY<") {
t.Error("no-badge fallback")
}
}