Widen the administration interface
This commit is contained in:
@@ -13,6 +13,8 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -1791,3 +1793,95 @@ func TestRotationEndsLiveSessions(t *testing.T) {
|
||||
t.Errorf("id = %q: the rotated token lost its vanity permission", res.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting has to compare the underlying values, not their rendered form: two
|
||||
// uploads in the same minute render identically but are not equal.
|
||||
func TestAdminSortOrdersByValue(t *testing.T) {
|
||||
h := newHarness(t, nil)
|
||||
|
||||
// Three files, distinct in every sortable dimension.
|
||||
type spec struct {
|
||||
name string
|
||||
size int
|
||||
expiry string
|
||||
}
|
||||
for i, s := range []spec{
|
||||
{"big", 300, "3h"},
|
||||
{"small", 10, "1h"},
|
||||
{"medium", 100, "2h"},
|
||||
} {
|
||||
h.now = clock.Add(time.Duration(i) * time.Second) // same minute, distinct instants
|
||||
resp := h.upload(t, bytes.Repeat([]byte("x"), s.size), map[string]string{
|
||||
"Authorization": "Bearer " + h.token,
|
||||
"Vanity": s.name,
|
||||
"Expiry": s.expiry,
|
||||
"Content-Disposition": `attachment; filename="` + s.name + `.bin"`,
|
||||
})
|
||||
resp.Body.Close()
|
||||
}
|
||||
h.now = clock
|
||||
|
||||
order := func(sortBy string) []string {
|
||||
resp := h.get(t, "/admin?sort="+sortBy, h.admin)
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
var ids []string
|
||||
for _, m := range regexp.MustCompile(`class="id mono">([a-z]+)`).FindAllStringSubmatch(string(raw), -1) {
|
||||
ids = append(ids, m[1])
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
for _, c := range []struct {
|
||||
sortBy string
|
||||
want []string
|
||||
}{
|
||||
{"size", []string{"big", "medium", "small"}}, // largest first
|
||||
{"expires", []string{"small", "medium", "big"}}, // soonest first
|
||||
{"created", []string{"medium", "small", "big"}}, // newest first
|
||||
{"name", []string{"big", "medium", "small"}}, // by filename
|
||||
} {
|
||||
got := order(c.sortBy)
|
||||
if !slices.Equal(got, c.want) {
|
||||
t.Errorf("sort=%s gave %v, want %v", c.sortBy, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The listing is a table, and a table needs more room than a form. It also has
|
||||
// to stop being a table on a narrow screen rather than grow a scrollbar.
|
||||
func TestAdminPageIsLaidOutForATable(t *testing.T) {
|
||||
h := newHarness(t, nil)
|
||||
h.upload(t, []byte("x"), nil).Body.Close()
|
||||
|
||||
resp := h.get(t, "/admin", h.admin)
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
page := string(raw)
|
||||
|
||||
if !strings.Contains(page, `class="wide"`) {
|
||||
t.Error("the listing renders at the narrow reading measure meant for forms")
|
||||
}
|
||||
// Every cell needs its label for the stacked layout, where the header row
|
||||
// is hidden.
|
||||
for _, label := range []string{"Size", "Owner", "Uploaded", "Expires"} {
|
||||
if !strings.Contains(page, `data-label="`+label+`"`) {
|
||||
t.Errorf("cells carry no %q label, so the stacked layout loses its headings", label)
|
||||
}
|
||||
}
|
||||
|
||||
css := h.get(t, "/static/style.css", "")
|
||||
cssRaw, _ := io.ReadAll(css.Body)
|
||||
css.Body.Close()
|
||||
style := string(cssRaw)
|
||||
|
||||
if !strings.Contains(style, "body.wide") {
|
||||
t.Error("no wide layout is defined")
|
||||
}
|
||||
if !strings.Contains(style, "@media (max-width: 46rem)") {
|
||||
t.Error("no narrow-screen rule, so the table will scroll sideways on a phone")
|
||||
}
|
||||
if strings.Contains(style, "overflow-x: auto") && strings.Contains(page, "tablewrap") {
|
||||
t.Error("the listing still relies on a horizontal scroll container")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user