Skip to content
quarry Artifact [bb4c25bcc6]
Login

Artifact [bb4c25bcc6]

Artifact bb4c25bcc69fa91e7c71f779c5d5eed06bfcc094c50208fe8975022bc8b41b81:


package web

import (
	"os"
	"regexp"
	"strings"
	"testing"
)

// consequential lists the data-confirm controls that are NOT
// destructive, keyed by a distinctive phrase of their own confirm
// text. Each one is a deliberate decision, not an oversight:
//
//   - joining a team GRANTS reach; it removes nothing.
//   - rotating a secret shows a new one while the current keeps
//     working, so nothing is lost at the moment of pressing.
//   - resetting a mark returns the generated one; the picture can be
//     uploaded again.
//
// They still confirm, because they surprise; they do not warn, because
// warning everything is warning nothing.
var consequential = []string{
	"joins {{$.Team.Team.Name}}",
	"A new signing secret is shown once",
	"goes back to the mark drawn from its handle",
	"goes back to the mark drawn from its name",
	"You go back to the mark drawn from your name",
}

// Every irreversible control reads as irreversible.
//
// data-confirm is this codebase's own marker for "this will surprise
// somebody", so it is the honest place to look. Before this, NONE of
// the nineteen carried a danger class: fourteen had no class at all
// and five had plain btn, so discarding a ticket, removing a webhook
// and its secret, revoking a token and unregistering a repository all
// looked exactly like Save sitting beside them. btn-danger existed,
// but only inside `.dt td.dt-act`, which is one session table.
//
// The point of scanning rather than listing: a NEW destructive control
// cannot ship unnoticed. It either carries the class or it is argued
// onto the consequential list above, and both are decisions somebody
// made on purpose.
func TestEveryConfirmingControlDeclaresItself(t *testing.T) {
	tag := regexp.MustCompile(`<(?:button|a)\b[^>]*`)
	for _, name := range []string{"web.go", "attach.go", "prefs.go"} {
		src, err := os.ReadFile(name)
		if err != nil {
			t.Fatal(err)
		}
		lines := strings.Split(string(src), "\n")
		for i, line := range lines {
			if !strings.Contains(line, "data-confirm=") {
				continue
			}
			if skip := func() bool {
				for _, c := range consequential {
					if strings.Contains(line, c) {
						return true
					}
				}
				return false
			}(); skip {
				continue
			}
			// The opening tag sits on this line or the one above it.
			found := false
			for _, cand := range []string{line, at(lines, i-1)} {
				if m := tag.FindAllString(cand, -1); len(m) > 0 {
					if strings.Contains(m[len(m)-1], "btn-danger") {
						found = true
					}
				}
				if found {
					break
				}
			}
			if !found {
				t.Errorf("FAIL: %s:%d — a control that asks for confirmation does not read "+
					"as destructive. Give it btn-danger, or argue it onto `consequential` "+
					"in this file.\n  %s", name, i+1, snippet(line))
			}
		}
	}
}

// And the convention is one class, defined once, not per component
// family. The chat menu forked its own danger colour against a
// custom property that is defined NOWHERE, so it fell through to a
// hardcoded hex — not theme-aware, not the forge's danger red, and
// outranking the --del rule below it on specificity.
func TestDangerColourComesFromTheTokenNotAHex(t *testing.T) {
	// The sheet itself, not a source file: these rules live in
	// stylesheet.go now, and a guard that names a filename fails on a
	// move that changes nothing about the rule.
	s := styleSource
	if strings.Contains(s, "var(--bad") {
		t.Error("FAIL: a danger rule reads --bad, which is defined nowhere; it renders " +
			"its hardcoded fallback in both themes. Use --del.")
	}
	if !strings.Contains(s, ".btn-danger { color: var(--del)") {
		t.Error("FAIL: btn-danger does not take its colour from --del")
	}
}

func at(lines []string, i int) string {
	if i < 0 || i >= len(lines) {
		return ""
	}
	return lines[i]
}

func snippet(s string) string {
	s = strings.TrimSpace(s)
	if len(s) > 120 {
		return s[:120] + "…"
	}
	return s
}