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.
78 lines
2.6 KiB
Go
78 lines
2.6 KiB
Go
// 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)
|
|
}
|
|
}
|