Print an act's answer as text for a golden file
Add actAnswer to the server's tests: the status, each HX-* header in name order with each HX-Trigger event on a line of its own beside its message, and the text of every element the caller's selectors pick, in document order. A selector that picks nothing prints nothing, so a golden shows which regions an answer filled. Later tests of the plan-wide acts and of the Stripe sync will read their answers through it. No output changed: the commit adds one test file and touches no production file. The names and results of ./internal/server, run after a database reset at the parent and at this commit, differ by the one new passing test. 7 breaks, each caught by the new test; the collapse break edits the HTML helper's Text, where the collapse now lives. Unprobed: an HX-Trigger header that is not JSON, because every HX-Trigger the console sets is JSON (the toasts through one JSON-producing function), and the test stops on one that is not.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
||||
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.coopcloud.tech/wiki-cafe/member-console/internal/testkit/htmltest"
|
||||
)
|
||||
|
||||
// actAnswer is what an act answered, as text for a golden file: the status;
|
||||
// each HX-* header the handler set, in name order, HX-Trigger as one line
|
||||
// per event it fires with its message; then, for each selector in regions,
|
||||
// the text of every element it selects, in document order. A region that
|
||||
// selects nothing prints nothing, so the file shows which regions an answer
|
||||
// filled. Pass the text through the fixture's normalizer before comparing.
|
||||
func actAnswer(t *testing.T, rec *httptest.ResponseRecorder, regions ...string) string {
|
||||
t.Helper()
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "status %d\n", rec.Code)
|
||||
for _, name := range slices.Sorted(maps.Keys(rec.Header())) {
|
||||
if !strings.HasPrefix(name, "Hx-") {
|
||||
continue
|
||||
}
|
||||
value := rec.Header().Get(name)
|
||||
if name != "Hx-Trigger" {
|
||||
fmt.Fprintf(&b, "%s %s\n", name, value)
|
||||
continue
|
||||
}
|
||||
var fired map[string]any
|
||||
if err := json.Unmarshal([]byte(value), &fired); err != nil {
|
||||
t.Fatalf("HX-Trigger %q: %v", value, err)
|
||||
}
|
||||
for _, event := range slices.Sorted(maps.Keys(fired)) {
|
||||
fmt.Fprintf(&b, "%s %s: %v\n", name, event, fired[event])
|
||||
}
|
||||
}
|
||||
page := htmltest.Parse(t, rec.Body.String())
|
||||
for _, sel := range regions {
|
||||
for _, text := range page.Texts(sel) {
|
||||
fmt.Fprintf(&b, "%s: %s\n", sel, text)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// TestActAnswerPrintsStatusHeadersAndRegions: the status, the HX-* headers in
|
||||
// name order with each fired event on its own line, and the selected regions'
|
||||
// text with its spaces collapsed; other headers and unselected markup stay
|
||||
// out.
|
||||
func TestActAnswerPrintsStatusHeadersAndRegions(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
fireErrorToast(rec, "Removal failed and nothing was changed.")
|
||||
rec.Header().Set("HX-Retarget", "#main")
|
||||
rec.Header().Set("Content-Type", "text/html")
|
||||
rec.WriteHeader(http.StatusUnprocessableEntity)
|
||||
_, _ = rec.WriteString(`<main><div class="alert alert-danger" role="alert">
|
||||
Choose whether to keep
|
||||
or migrate.</div><p>Other</p><div class="alert alert-danger">Second.</div></main>`)
|
||||
got := actAnswer(t, rec, ".alert-danger", ".alert-success")
|
||||
want := "status 422\n" +
|
||||
"Hx-Retarget #main\n" +
|
||||
"Hx-Trigger showErrorToast: Removal failed and nothing was changed.\n" +
|
||||
".alert-danger: Choose whether to keep or migrate.\n" +
|
||||
".alert-danger: Second.\n"
|
||||
if got != want {
|
||||
t.Errorf("actAnswer =\n%s\nwant\n%s", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user