testutil

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: MPL-2.0 Imports: 7 Imported by: 0

README

Templ Test Utilities

Comprehensive testing utilities for Templ components in the UI modernization project.

Overview

This package provides a set of helper functions and assertions designed specifically for testing Templ template components. It simplifies rendering components and making assertions about the generated HTML.

Installation

import "github.com/open-wander/wander/ui/tests/testutil"

Rendering Functions

RenderToString

Renders a Templ component to a string.

html, err := testutil.RenderToString(component)
if err != nil {
    t.Fatal(err)
}
RenderToBytes

Renders a Templ component to a byte slice.

html, err := testutil.RenderToBytes(component)
if err != nil {
    t.Fatal(err)
}
MustRender

Renders a Templ component to a string and fails the test if an error occurs. This is the recommended approach for most tests.

html := testutil.MustRender(t, component)
// Test assertions on html...

Assertion Functions

ContainsHTML

Asserts that the HTML contains a specific substring.

html := testutil.MustRender(t, button)
testutil.ContainsHTML(t, html, "Click me")
NotContainsHTML

Asserts that the HTML does NOT contain a specific substring.

html := testutil.MustRender(t, button)
testutil.NotContainsHTML(t, html, "btn-danger")
ContainsClass

Asserts that the HTML contains an element with a specific CSS class. This performs exact class matching and won't match partial class names.

html := testutil.MustRender(t, badge)
testutil.ContainsClass(t, html, "bg-success-100")  // Pass
testutil.ContainsClass(t, html, "success")         // Fail - not an exact match

Handles Tailwind's arbitrary value syntax:

testutil.ContainsClass(t, html, "w-[100px]")  // Works correctly with brackets
ContainsElement

Asserts that the HTML contains at least one element with the specified tag name.

html := testutil.MustRender(t, button)
testutil.ContainsElement(t, html, "button")
HasAttribute

Asserts that the HTML contains an element with a specific attribute and value.

// Check attribute with value
testutil.HasAttribute(t, html, "type", "submit")

// Check boolean attribute (disabled, readonly, etc.)
testutil.HasAttribute(t, html, "disabled", "")

// Check HTMX attributes
testutil.HasAttribute(t, html, "hx-post", "/api/submit")
AssertRendersWithoutError

Smoke test to ensure a component renders without error.

testutil.AssertRendersWithoutError(t, component)

Usage Examples

Basic Component Test
func TestButton_Primary(t *testing.T) {
    // Arrange: Create component
    button := components.Button(
        components.ButtonProps{
            Variant: "primary",
            Size:    "md",
        },
        "Click me",
    )

    // Act: Render
    html := testutil.MustRender(t, button)

    // Assert: Verify output
    testutil.ContainsElement(t, html, "button")
    testutil.ContainsClass(t, html, "btn")
    testutil.ContainsClass(t, html, "btn-primary")
    testutil.HasAttribute(t, html, "type", "button")
    testutil.ContainsHTML(t, html, "Click me")
}
Testing Multiple Variants
func TestButton_Variants(t *testing.T) {
    tests := []struct {
        name    string
        variant string
        class   string
    }{
        {"primary", "primary", "btn-primary"},
        {"danger", "danger", "btn-danger"},
        {"secondary", "secondary", "btn-secondary"},
    }

    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            button := components.Button(
                components.ButtonProps{Variant: tc.variant},
                "Test",
            )

            html := testutil.MustRender(t, button)
            testutil.ContainsClass(t, html, tc.class)
        })
    }
}
Testing State
func TestButton_Disabled(t *testing.T) {
    button := components.Button(
        components.ButtonProps{Disabled: true},
        "Disabled",
    )

    html := testutil.MustRender(t, button)
    testutil.HasAttribute(t, html, "disabled", "")
}
Testing HTMX Attributes
func TestButton_HTMX(t *testing.T) {
    button := components.Button(
        components.ButtonProps{
            HxPost:   "/api/submit",
            HxTarget: "#result",
            HxSwap:   "innerHTML",
        },
        "Submit",
    )

    html := testutil.MustRender(t, button)
    testutil.HasAttribute(t, html, "hx-post", "/api/submit")
    testutil.HasAttribute(t, html, "hx-target", "#result")
    testutil.HasAttribute(t, html, "hx-swap", "innerHTML")
}
Negative Assertions
func TestButton_NotContainsUnwantedClass(t *testing.T) {
    button := components.Button(
        components.ButtonProps{Variant: "primary"},
        "Test",
    )

    html := testutil.MustRender(t, button)
    testutil.NotContainsHTML(t, html, "btn-danger")
    testutil.NotContainsHTML(t, html, "undefined")
}

Best Practices

  1. Use MustRender for cleaner tests - It automatically fails the test if rendering errors occur.

  2. Test behavior, not implementation - Focus on what the component renders (classes, attributes, content) rather than internal structure.

  3. Use ContainsClass for CSS classes - It performs exact matching and handles edge cases better than substring search.

  4. Table-driven tests for variants - When testing multiple variants or states, use table-driven tests for clarity.

  5. Test critical paths - Focus on:

    • Required attributes (type, name, etc.)
    • CSS classes that affect styling
    • HTMX/Alpine.js attributes for interactivity
    • Conditional rendering (disabled states, icons, etc.)
  6. Avoid brittle tests - Don't test exact HTML structure or whitespace. Use semantic assertions.

Performance

The utilities are designed to be fast:

BenchmarkRenderToString-16    13569415    79.32 ns/op    240 B/op    4 allocs/op
BenchmarkMustRender-16         7346515   162.0 ns/op     240 B/op    4 allocs/op
BenchmarkContainsHTML-16      10498664   115.2 ns/op       0 B/op    0 allocs/op
BenchmarkContainsClass-16        29635  43074 ns/op    17155 B/op  235 allocs/op

Running Tests

# Run all testutil tests
cd ui-htmx && go test ./tests/testutil/... -v

# Run with benchmarks
cd ui-htmx && go test ./tests/testutil/... -v -bench=. -benchmem

# Run specific test
cd ui-htmx && go test ./tests/testutil -run TestContainsClass -v

Implementation Notes

  • Context: All rendering uses context.Background() which is appropriate for unit tests.
  • Class matching: Uses regex to extract class attributes and performs exact token matching.
  • Attribute matching: Handles both value attributes (type="submit") and boolean attributes (disabled).
  • Special characters: Properly escapes regex metacharacters in class names and attribute values.
  • Tailwind support: Correctly handles Tailwind's arbitrary value syntax like w-[100px].

Future Enhancements

Potential additions for the future:

  • ContainsJSON(t, html, key, value) - Assert JSON attributes (Alpine.js x-data)
  • HasDataAttribute(t, html, attr, value) - Specialized data-* attribute assertion
  • MatchesSelector(t, html, selector) - CSS selector matching
  • CountElements(t, html, tag, expected) - Count specific elements
  • Integration with HTML parser for more sophisticated queries

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertRendersWithoutError

func AssertRendersWithoutError(t *testing.T, component interface{})

AssertRendersWithoutError is a helper to ensure a component renders without error. This is useful for smoke testing components.

Example:

AssertRendersWithoutError(t, components.Modal(components.ModalProps{}, "Content"))

func ContainsClass

func ContainsClass(t *testing.T, html, class string)

ContainsClass asserts that the HTML contains an element with the specified CSS class. This is more robust than a simple substring search as it looks for the class attribute pattern.

Example:

html := MustRender(t, components.Badge(components.BadgeProps{Variant: "success"}, "Active"))
ContainsClass(t, html, "bg-success-100")

func ContainsElement

func ContainsElement(t *testing.T, html, tag string)

ContainsElement asserts that the HTML contains at least one element with the specified tag name.

Example:

html := MustRender(t, components.Button(components.ButtonProps{}, "Click"))
ContainsElement(t, html, "button")

func ContainsHTML

func ContainsHTML(t *testing.T, html, expected string)

ContainsHTML asserts that the HTML string contains the expected substring. Fails the test if the substring is not found.

Example:

html := MustRender(t, components.Button(components.ButtonProps{Variant: "primary"}, "Click"))
ContainsHTML(t, html, "btn-primary")

func ContainsHTMLf

func ContainsHTMLf(t *testing.T, html, expected, msgFormat string, args ...interface{})

ContainsHTMLf is like ContainsHTML but with formatted message support.

func HasAttribute

func HasAttribute(t *testing.T, html, attr, value string)

HasAttribute asserts that the HTML contains an element with the specified attribute and value. The value can be empty string to just check for attribute presence.

Example:

html := MustRender(t, components.Button(components.ButtonProps{Type: "submit"}, "Submit"))
HasAttribute(t, html, "type", "submit")

html := MustRender(t, components.Button(components.ButtonProps{Disabled: true}, "Disabled"))
HasAttribute(t, html, "disabled", "") // checks for presence only

func MustRender

func MustRender(t *testing.T, component templ.Component) string

MustRender renders a templ.Component to a string and fails the test if an error occurs. This is a convenience wrapper around RenderToString for test cases where errors are not expected.

Example:

html := MustRender(t, components.Alert(components.AlertProps{Variant: "info"}, "Information"))
// Test assertions on html...

func NotContainsHTML

func NotContainsHTML(t *testing.T, html, unexpected string)

NotContainsHTML asserts that the HTML string does not contain the unexpected substring. Fails the test if the substring is found.

Example:

html := MustRender(t, components.Button(components.ButtonProps{Variant: "primary"}, "Click"))
NotContainsHTML(t, html, "btn-danger")

func RenderToBytes

func RenderToBytes(component templ.Component) ([]byte, error)

RenderToBytes renders a templ.Component to a byte slice. Returns the rendered HTML as bytes and any error encountered.

Example:

html, err := RenderToBytes(components.Badge(components.BadgeProps{Variant: "success"}, "Active"))
if err != nil {
    t.Fatal(err)
}

func RenderToString

func RenderToString(component templ.Component) (string, error)

RenderToString renders a templ.Component to a string. Returns the rendered HTML string and any error encountered.

Example:

html, err := RenderToString(components.Button(components.ButtonProps{Variant: "primary"}, "Click me"))
if err != nil {
    t.Fatal(err)
}

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL