From 1c045add9e1b0ca8c5d2c74aee971cfa027cd3d5 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Mon, 5 Feb 2024 16:41:36 +0800 Subject: [PATCH 1/5] refactor(response): move `Headers` function down --- response.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/response.go b/response.go index 28de987..88b44ef 100644 --- a/response.go +++ b/response.go @@ -76,6 +76,20 @@ func (r Response) Write(w http.ResponseWriter) error { return nil } +// RenderTempl renders a Templ component along with the defined HTMX headers. +func (r Response) RenderTempl(ctx context.Context, w http.ResponseWriter, c templComponent) error { + err := r.Write(w) + if err != nil { + return err + } + + err = c.Render(ctx, w) + if err != nil { + return err + } + + return nil +} // Headers returns a copied map of the headers. Any modifications to the // returned headers will not affect the headers in this struct. func (r Response) Headers() (map[string]string, error) { @@ -112,17 +126,3 @@ func (r Response) Headers() (map[string]string, error) { return m, nil } -// RenderTempl renders a Templ component along with the defined HTMX headers. -func (r Response) RenderTempl(ctx context.Context, w http.ResponseWriter, c templComponent) error { - err := r.Write(w) - if err != nil { - return err - } - - err = c.Render(ctx, w) - if err != nil { - return err - } - - return nil -} From c4af2745db7446bceea0d04c9e9a652a8d7f6e6f Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Mon, 5 Feb 2024 16:47:59 +0800 Subject: [PATCH 2/5] feat(response): add `RenderHTML` method --- response.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/response.go b/response.go index 88b44ef..6c4f17a 100644 --- a/response.go +++ b/response.go @@ -76,6 +76,16 @@ func (r Response) Write(w http.ResponseWriter) error { return nil } +// RenderHTML renders an HTML document fragment along with the defined HTMX headers. +func (r Response) RenderHTML(w http.ResponseWriter, html template.HTML) (int, error) { + err := r.Write(w) + if err != nil { + return 0, err + } + + return w.Write([]byte(html)) +} + // RenderTempl renders a Templ component along with the defined HTMX headers. func (r Response) RenderTempl(ctx context.Context, w http.ResponseWriter, c templComponent) error { err := r.Write(w) From e95716209aba2c62d2435831e97651f72eb9823b Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Mon, 5 Feb 2024 17:19:43 +0800 Subject: [PATCH 3/5] feat: add must methods to `htmx.Response` --- response.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/response.go b/response.go index 6c4f17a..bbddaaa 100644 --- a/response.go +++ b/response.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "html/template" "net/http" ) @@ -100,6 +101,37 @@ func (r Response) RenderTempl(ctx context.Context, w http.ResponseWriter, c temp return nil } + +// MustWrite applies the defined HTMX headers to a given response writer, otherwise it panics. +// +// Under the hood this uses [Response.Write]. +func (r Response) MustWrite(w http.ResponseWriter) { + err := r.Write(w) + if err != nil { + panic(err) + } +} + +// MustRenderHTML renders an HTML document fragment along with the defined HTMX headers, otherwise it panics. +// +// Under the hood this uses [Response.RenderHTML]. +func (r Response) MustRenderHTML(w http.ResponseWriter, html template.HTML) { + _, err := r.RenderHTML(w, html) + if err != nil { + panic(err) + } +} + +// MustRenderTempl renders a Templ component along with the defined HTMX headers, otherwise it panics. +// +// Under the hood this uses [Response.RenderTempl]. +func (r Response) MustRenderTempl(ctx context.Context, w http.ResponseWriter, c templComponent) { + err := r.RenderTempl(ctx, w, c) + if err != nil { + panic(err) + } +} + // Headers returns a copied map of the headers. Any modifications to the // returned headers will not affect the headers in this struct. func (r Response) Headers() (map[string]string, error) { @@ -135,4 +167,3 @@ func (r Response) Headers() (map[string]string, error) { return m, nil } - From 61688711e23a5a00ee424a97797499df5c7f99ab Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Mon, 5 Feb 2024 17:25:12 +0800 Subject: [PATCH 4/5] test: add tests for `response.go` --- response_test.go | 102 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 response_test.go diff --git a/response_test.go b/response_test.go new file mode 100644 index 0000000..ff75929 --- /dev/null +++ b/response_test.go @@ -0,0 +1,102 @@ +package htmx + +import ( + "html/template" + "net/http" + "testing" +) + +func TestWrite(t *testing.T) { + w := newMockResponseWriter() + + err := NewResponse(). + StatusCode(StatusStopPolling). + Location("/profiles"). + Redirect("/pull"). + PushURL("/push"). + Refresh(true). + ReplaceURL("/water"). + Retarget("#world"). + Reselect("#hello"). + AddTrigger(Trigger("myEvent")). + Reswap(SwapInnerHTML.ShowOn("#swappy", Top)). + Write(w) + if err != nil { + t.Errorf("an error occurred writing a response: %v", err) + } + + if w.statusCode != StatusStopPolling { + t.Errorf("wrong error code. want=%v, got=%v", StatusStopPolling, w.statusCode) + } + + expectedHeaders := map[string]string{ + HeaderTrigger: "myEvent", + HeaderLocation: "/profiles", + HeaderRedirect: "/pull", + HeaderPushURL: "/push", + HeaderRefresh: "true", + HeaderReplaceUrl: "/water", + HeaderRetarget: "#world", + HeaderReselect: "#hello", + HeaderReswap: "innerHTML show:#swappy:top", + } + + for k, v := range expectedHeaders { + got := w.header.Get(k) + if got != v { + t.Errorf("wrong value for header %q. got=%q, want=%q", k, got, v) + } + } +} + +func TestRenderHTML(t *testing.T) { + text := `hello world!` + + w := newMockResponseWriter() + + _, err := NewResponse().Location("/conversation/message").RenderHTML(w, template.HTML(text)) + if err != nil { + t.Errorf("an error occurred writing HTML: %v", err) + } + + if got, want := w.Header().Get(HeaderLocation), "/conversation/message"; got != want { + t.Errorf("wrong value for header %q. got=%q, want=%q", HeaderLocation, got, want) + } + + if string(w.body) != text { + t.Errorf("wrong response body. got=%q, want=%q", string(w.body), text) + } +} + +func TestMustRenderHTML(t *testing.T) { + text := `hello world!` + + w := newMockResponseWriter() + + NewResponse().MustRenderHTML(w, template.HTML(text)) +} + +type mockResponseWriter struct { + body []byte + statusCode int + header http.Header +} + +func newMockResponseWriter() *mockResponseWriter { + return &mockResponseWriter{ + header: http.Header{}, + } +} + +func (mrw *mockResponseWriter) Header() http.Header { + return mrw.header +} + +func (mrw *mockResponseWriter) Write(b []byte) (int, error) { + mrw.body = append(mrw.body, b...) + return 0, nil +} + +func (mrw *mockResponseWriter) WriteHeader(statusCode int) { + mrw.statusCode = statusCode +} From 57c50fd87c6a1f7f5c1477edcfa8b1fe34e19f5d Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Mon, 5 Feb 2024 17:28:34 +0800 Subject: [PATCH 5/5] ci: add golangci-lint step --- .github/workflows/go.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f5c0e4a..fb54383 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -26,3 +26,8 @@ jobs: - name: Test run: go test -v ./... + + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest