From 75cbd29d44e8af92dbe1d16a0d601114af654aef Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 14 May 2026 18:47:31 +0200 Subject: [PATCH] test(01-02): add ui package smoke tests - TestButton_DefaultSolidMD: asserts root class and label - TestButton_PassesThroughAttrs: asserts hx-* attribute spread - TestButton_ExplicitTypeSubmit: type override - TestCard_RendersChildren: templ.WithChildren child injection - TestBadge_{Info,Success}Variant + zero-value normalization - TestButtonClass_String / TestBadgeClass_String: class contract - TestNormalizers_ZeroValueDefaults: every Normalized* returns safe default --- backend/internal/web/ui/ui_test.go | 132 +++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 backend/internal/web/ui/ui_test.go diff --git a/backend/internal/web/ui/ui_test.go b/backend/internal/web/ui/ui_test.go new file mode 100644 index 0000000..85d8465 --- /dev/null +++ b/backend/internal/web/ui/ui_test.go @@ -0,0 +1,132 @@ +package ui + +import ( + "bytes" + "context" + "strings" + "testing" + + "github.com/a-h/templ" +) + +// render is a tiny helper that renders a templ.Component to a string for +// substring assertions. Uses context.Background to match production calls. +func render(t *testing.T, ctx context.Context, c templ.Component) string { + t.Helper() + var buf bytes.Buffer + if err := c.Render(ctx, &buf); err != nil { + t.Fatalf("render: unexpected error: %v", err) + } + return buf.String() +} + +func TestButton_DefaultSolidMD(t *testing.T) { + out := render(t, context.Background(), Button(ButtonProps{Label: "Fetch server time"})) + + wantClass := `class="ui-button ui-button-solid-default-md"` + if !strings.Contains(out, wantClass) { + t.Errorf("output missing class %q\ngot: %s", wantClass, out) + } + if !strings.Contains(out, "Fetch server time") { + t.Errorf("output missing label literal; got: %s", out) + } + if !strings.Contains(out, `type="button"`) { + t.Errorf("output missing default type=\"button\"; got: %s", out) + } +} + +func TestButton_PassesThroughAttrs(t *testing.T) { + props := ButtonProps{ + Label: "x", + Attrs: templ.Attributes{ + "hx-get": "/demo/time", + "hx-target": "#demo-out", + }, + } + out := render(t, context.Background(), Button(props)) + + if !strings.Contains(out, `hx-get="/demo/time"`) { + t.Errorf("output missing hx-get; got: %s", out) + } + if !strings.Contains(out, `hx-target="#demo-out"`) { + t.Errorf("output missing hx-target; got: %s", out) + } +} + +func TestButton_ExplicitTypeSubmit(t *testing.T) { + out := render(t, context.Background(), Button(ButtonProps{Label: "Go", Type: "submit"})) + if !strings.Contains(out, `type="submit"`) { + t.Errorf("expected type=\"submit\"; got: %s", out) + } +} + +// Card injects children via templ.WithChildren. The test feeds a raw child +// component through context and asserts the rendered wrapper + child content. +func TestCard_RendersChildren(t *testing.T) { + child := templ.Raw("

x

") + ctx := templ.WithChildren(context.Background(), child) + out := render(t, ctx, Card(nil)) + + if !strings.Contains(out, `
; got: %s", out) + } + if !strings.Contains(out, "

x

") { + t.Errorf("output missing child markup; got: %s", out) + } +} + +func TestBadge_InfoVariant(t *testing.T) { + out := render(t, context.Background(), Badge(BadgeProps{Label: "OK", Variant: BadgeVariantInfo})) + wantClass := `class="ui-badge ui-badge-info"` + if !strings.Contains(out, wantClass) { + t.Errorf("output missing %q; got: %s", wantClass, out) + } + if !strings.Contains(out, "OK") { + t.Errorf("output missing label; got: %s", out) + } +} + +func TestBadge_SuccessVariant(t *testing.T) { + out := render(t, context.Background(), Badge(BadgeProps{Label: "OK", Variant: BadgeVariantSuccess})) + if !strings.Contains(out, "ui-badge-success") { + t.Errorf("output missing ui-badge-success; got: %s", out) + } +} + +func TestBadge_ZeroValueDefaultsToInfo(t *testing.T) { + out := render(t, context.Background(), Badge(BadgeProps{Label: "OK"})) + if !strings.Contains(out, "ui-badge-info") { + t.Errorf("zero-value Variant should normalize to info; got: %s", out) + } +} + +func TestButtonClass_String(t *testing.T) { + got := ButtonClass(ButtonVariantDefault, ButtonToneSolid, SizeMD) + want := "ui-button ui-button-solid-default-md" + if got != want { + t.Errorf("ButtonClass = %q; want %q", got, want) + } +} + +func TestBadgeClass_String(t *testing.T) { + got := BadgeClass(BadgeVariantInfo) + want := "ui-badge ui-badge-info" + if got != want { + t.Errorf("BadgeClass = %q; want %q", got, want) + } +} + +func TestNormalizers_ZeroValueDefaults(t *testing.T) { + if got := NormalizedSize(""); got != SizeMD { + t.Errorf("NormalizedSize(\"\") = %q; want %q", got, SizeMD) + } + if got := NormalizedButtonVariant(""); got != ButtonVariantDefault { + t.Errorf("NormalizedButtonVariant(\"\") = %q; want %q", got, ButtonVariantDefault) + } + if got := NormalizedButtonTone(""); got != ButtonToneSolid { + t.Errorf("NormalizedButtonTone(\"\") = %q; want %q", got, ButtonToneSolid) + } + if got := NormalizedBadgeVariant(""); got != BadgeVariantInfo { + t.Errorf("NormalizedBadgeVariant(\"\") = %q; want %q", got, BadgeVariantInfo) + } +}