The ranked bars said "8825 ms" beside a table that said "8.8 s". One formatter, humanLatency, now serves both, with a test. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
156 lines
5.6 KiB
Go
156 lines
5.6 KiB
Go
package web
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRankedBarsLabelEveryRowDirectly(t *testing.T) {
|
|
// The label is the point of this chart: the question is which person or
|
|
// which model, not what shape the distribution is. Rotated or truncated
|
|
// axis labels would say the chart matters more than reading it.
|
|
out := string(HBarChart([]Row{
|
|
{Label: "ilker@example.com", Value: 1200},
|
|
{Label: "deniz@example.com", Value: 300},
|
|
}, "--viz-1", "tokens"))
|
|
|
|
for _, want := range []string{"ilker@example.com", "deniz@example.com", "1.2k", "300"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("the chart does not show %q:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestASmallValueStillDrawsAMark(t *testing.T) {
|
|
// A bar that rounds to zero width reads as no data rather than as a small
|
|
// number, which is a different and wrong claim.
|
|
out := string(HBarChart([]Row{
|
|
{Label: "huge", Value: 1_000_000},
|
|
{Label: "tiny", Value: 1},
|
|
}, "--viz-1", ""))
|
|
// Two bars, one per row, and the tiny one has a width.
|
|
if n := strings.Count(out, `class="rk-bar"`); n != 2 {
|
|
t.Errorf("drew %d marks for two rows", n)
|
|
}
|
|
if strings.Contains(out, "width:0.0%") {
|
|
t.Error("the tiny value was drawn with no width")
|
|
}
|
|
}
|
|
|
|
func TestAnEmptyChartSaysSoRatherThanRenderingNothing(t *testing.T) {
|
|
// A missing chart reads as a broken page.
|
|
out := string(HBarChart(nil, "--viz-1", ""))
|
|
if !strings.Contains(out, "Nothing recorded") {
|
|
t.Errorf("an empty series rendered as %q", out)
|
|
}
|
|
}
|
|
|
|
func TestChartTextUsesTextColoursNotTheSeriesColour(t *testing.T) {
|
|
// Values and labels wear text tokens; the mark beside them carries the
|
|
// identity. Colouring the text too makes the palette do two jobs and reads
|
|
// badly for anyone the hue separation was chosen to protect.
|
|
out := string(HBarChart([]Row{{Label: "a", Value: 5}}, "--viz-2", "req"))
|
|
if strings.Count(out, "var(--viz-2)") != 1 || !strings.Contains(out, `class="rk-bar"`) {
|
|
t.Errorf("the series colour should be on the bar and nowhere else:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestEveryBarCarriesItsOwnHoverTitle(t *testing.T) {
|
|
// The chart is in a browser, so it should answer a pointer. A <title> does
|
|
// that with no JavaScript, which is the right trade for a page this project
|
|
// serves from a single binary.
|
|
out := string(HBarChart([]Row{
|
|
{Label: "a", Value: 1}, {Label: "b", Value: 2},
|
|
}, "--viz-1", "tokens"))
|
|
if n := strings.Count(out, `title="`); n != 2 {
|
|
t.Errorf("%d hover titles for two bars", n)
|
|
}
|
|
}
|
|
|
|
func TestALongLabelIsTrimmedRatherThanOverflowing(t *testing.T) {
|
|
long := strings.Repeat("a", 80) + "@example.com"
|
|
out := string(HBarChart([]Row{{Label: long, Value: 1}}, "--viz-1", ""))
|
|
// Cut where it is drawn — the label cell ellipsises in CSS — and in full
|
|
// where it is hovered, so the reader can still find out whose row it is.
|
|
if !strings.Contains(out, `title="`+long+`: `) {
|
|
t.Error("the full label is not available on hover")
|
|
}
|
|
css, err := staticFiles.ReadFile("static/base.css")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(css), ".ranked .rk-label { font-size: .82rem; overflow: hidden; text-overflow: ellipsis;") {
|
|
t.Error("the label cell no longer cuts a long label with an ellipsis")
|
|
}
|
|
}
|
|
|
|
func TestASparklineOfNothingIsNotDrawn(t *testing.T) {
|
|
// One point is not a trend, and a single dot invites reading one.
|
|
if got := Sparkline([]float64{5}, ""); got != "" {
|
|
t.Errorf("a one-point sparkline rendered: %q", got)
|
|
}
|
|
if got := Sparkline(nil, ""); got != "" {
|
|
t.Errorf("an empty sparkline rendered: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestAFlatSparklineIsDrawnAlongTheBottom(t *testing.T) {
|
|
// No traffic is a real answer. An empty box would read as a broken chart.
|
|
out := string(Sparkline([]float64{0, 0, 0}, ""))
|
|
if !strings.Contains(out, "<polyline") {
|
|
t.Errorf("a flat series did not draw: %q", out)
|
|
}
|
|
}
|
|
|
|
func TestTheChartScalesToItsContainer(t *testing.T) {
|
|
// The dashboard is read on laptops and phones. A fixed pixel width is how a
|
|
// table ends up scrolling sideways.
|
|
out := string(HBarChart([]Row{{Label: "a", Value: 1}}, "", ""))
|
|
// Bars are a share of whatever width the grid gives them, never pixels.
|
|
if !strings.Contains(out, "width:100.0%") {
|
|
t.Errorf("the bar is not sized as a share of its track:\n%s", out)
|
|
}
|
|
if strings.Contains(out, "px") {
|
|
t.Error("a fixed pixel size was set on the chart")
|
|
}
|
|
}
|
|
|
|
func TestTheRankedWrapperIsNotGivenAFixedHeight(t *testing.T) {
|
|
// This chart grows a row at a time, so whatever class it wears must not be
|
|
// one the stylesheet caps. It wore "chart", which is the fixed-height box
|
|
// the time-series charts live in, and the bars spilled over the table
|
|
// underneath them.
|
|
out := string(HBarChart([]Row{{Label: "a", Value: 1}}, "", ""))
|
|
class := "" // the wrapper's class, read back out of the markup
|
|
if i := strings.Index(out, `<div class="`); i >= 0 {
|
|
rest := out[i+len(`<div class="`):]
|
|
class = rest[:strings.Index(rest, `"`)]
|
|
}
|
|
if class == "" {
|
|
t.Fatalf("no wrapper to check:\n%s", out)
|
|
}
|
|
|
|
css, err := staticFiles.ReadFile("static/base.css")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, line := range strings.Split(string(css), "\n") {
|
|
decl, ok := strings.CutPrefix(strings.TrimSpace(line), "."+class+" ")
|
|
if !ok || !strings.HasPrefix(decl, "{") {
|
|
continue
|
|
}
|
|
if strings.Contains(decl, "height:") && !strings.Contains(decl, "height: auto") {
|
|
t.Errorf("%q is capped by the stylesheet, so taller charts overflow it: %s",
|
|
class, strings.TrimSpace(line))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLatencyReadsAsAPersonWouldSayIt(t *testing.T) {
|
|
for ms, want := range map[float64]string{5: "5 ms", 8825: "8.8 s", 110695: "1m 50s", 225634: "3m 45s"} {
|
|
if got := humanLatency(ms); got != want {
|
|
t.Errorf("%v ms: got %q, want %q", ms, got, want)
|
|
}
|
|
}
|
|
}
|