Skip to content
quarry Artifact [359497d3b1]
Login

Artifact [359497d3b1]

Artifact 359497d3b1619ac9556a1256d571f293f43f7581e3d30e2835b0c595d41a1f41:


package web

import (
	"html/template"
	"strings"
	"testing"
)

// A device name beside a full IPv6 address is wider than a phone, and
// the wrapper's sideways scroll is one nobody finds inside a card. The
// session table stops being a table below the fold: one block per
// device, each cell under the name it goes by.
func TestSessionTableStacksOnAPhone(t *testing.T) {
	for _, want := range []string{".dt-stack thead { display: none; }", `.dt-stack td[data-label]::before`} {
		if !strings.Contains(sheet, want) {
			t.Errorf("FAIL: the stacked layout is not in the served sheet: missing %q", want)
		}
	}
	// base is CLONED, never executed: executing it would freeze it and
	// every page built from it afterwards would fail to clone.
	tpl := template.Must(template.Must(base.Clone()).Parse(`{{define "content"}}{{end}}`))
	var b strings.Builder
	if err := tpl.ExecuteTemplate(&b, "session-table", map[string]any{
		"Sessions": []sessionRow{{ID: "a", Agent: "Safari on iPhone", IP: "2a09::1", Created: "2026-09-12T10:00:00Z"}},
		"This":     "a", "Action": "/me/sessions/end", "ShowAccount": false,
	}); err != nil {
		t.Fatal(err)
	}
	got := b.String()
	for _, want := range []string{`class="dt dt-stack"`, `data-label="Signed in"`, `data-label="Last seen"`} {
		if !strings.Contains(got, want) {
			t.Errorf("FAIL: the table cannot stack: missing %s", want)
		}
	}
	t.Log("ok: a phone reads the sessions as blocks, not as a table it must drag sideways")
}

// Stacking is decided by two rules that fight, and CSS resolves the
// fight by specificity rather than by which one reads as the intent.
// Both of these were real and both were invisible to every other test,
// because a stylesheet compiles whatever it says:
//
//   - ".dt td.dt-act { width: 1% }" sat AFTER the stacking block at
//     equal specificity, so the action cell stayed 1% wide, the button
//     was squeezed to its minimum, and "Sign out" broke one letter per
//     line down the card.
//   - "main .dt-wrap > table { display: table }" outranks ".dt-stack"
//     (0,1,1 against 0,1,0), so the table stayed a table while its rows
//     became blocks. A display:table box shrink-wraps, so the stacked
//     card was as wide as its widest cell and every row separator
//     stopped mid-card.
//
// This asserts the two overrides that settle it, and that the stacking
// block is last among the .dt rules so the first cannot come back.
func TestStackedSessionsOutrankTheTableRules(t *testing.T) {
	const blockOverride = "main .dt-wrap > table.dt-stack { display: block; }"
	if !strings.Contains(sheet, blockOverride) {
		t.Error("FAIL: the stacked table does not outrank the .dt-wrap table rule, " +
			"so it shrink-wraps and its row separators stop mid-card")
	}
	if !strings.Contains(sheet, ".dt-stack td.dt-act .btn { overflow-wrap: normal; white-space: nowrap; }") {
		t.Error("FAIL: nothing stops a control breaking mid-word inside a cell " +
			"that sets overflow-wrap:anywhere for IPv6")
	}
	// Order matters for the width rule, which is same-specificity: the
	// stacking block has to be the LAST thing that speaks about .dt.
	stack := strings.Index(sheet, ".dt-stack td.dt-act { text-align: left;")
	width := strings.LastIndex(sheet, ".dt td.dt-act, .dt th.dt-act { text-align: right; width: 1%; }")
	if stack < 0 || width < 0 {
		t.Fatalf("FAIL: the rules this pins are gone (stack=%d width=%d)", stack, width)
	}
	if width > stack {
		t.Error("FAIL: a .dt width rule now comes after the stacking block at equal " +
			"specificity, which squeezes the action cell back to 1% on a phone")
	}
	t.Log("ok: the stacked layout wins on both specificity and order")
}