package htmldiff import ( "context" "errors" "fmt" "io" "strings" "sync" "github.com/a-h/htmlformat" "github.com/a-h/templ" "github.com/google/go-cmp/cmp" ) func DiffStrings(expected, actual string) (diff string, err error) { // Format both strings. var wg sync.WaitGroup wg.Add(2) var errs []error // Format expected. go func() { defer wg.Done() e := new(strings.Builder) err := htmlformat.Fragment(e, strings.NewReader(expected)) if err != nil { errs = append(errs, fmt.Errorf("expected html formatting error: %w", err)) } expected = e.String() }() // Format actual. go func() { defer wg.Done() a := new(strings.Builder) err := htmlformat.Fragment(a, strings.NewReader(actual)) if err != nil { errs = append(errs, fmt.Errorf("actual html formatting error: %w", err)) } actual = a.String() }() // Wait for processing. wg.Wait() return cmp.Diff(expected, actual), errors.Join(errs...) } func Diff(input templ.Component, expected string) (diff string, err error) { _, diff, err = DiffCtx(context.Background(), input, expected) return diff, err } func DiffCtx(ctx context.Context, input templ.Component, expected string) (formattedInput, diff string, err error) { var wg sync.WaitGroup wg.Add(2) var errs []error // Format the expected value. go func() { defer wg.Done() e := new(strings.Builder) err := htmlformat.Fragment(e, strings.NewReader(expected)) if err != nil { errs = append(errs, fmt.Errorf("expected html formatting error: %w", err)) } expected = e.String() }() // Pipe via the HTML formatter. actual := new(strings.Builder) r, w := io.Pipe() go func() { defer wg.Done() err := htmlformat.Fragment(actual, r) if err != nil { errs = append(errs, fmt.Errorf("actual html formatting error: %w", err)) } }() // Render the component. err = input.Render(ctx, w) if err != nil { errs = append(errs, fmt.Errorf("failed to render component: %w", err)) } w.Close() // Wait for processing. wg.Wait() return actual.String(), cmp.Diff(expected, actual.String()), errors.Join(errs...) }