Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/hyper/render.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"Rendering pipeline.

Handles rendering hiccup to HTML and formatting Datastar SSE events."
(:require [dev.onionpancakes.chassis.core :as c]
(:require [clojure.string :as string]
[dev.onionpancakes.chassis.core :as c]
[hyper.context :as context]
[hyper.routes :as routes]
[hyper.state :as state]
Expand All @@ -27,10 +28,16 @@
event: datastar-patch-elements
data: elements <html content>

(blank line to end event)"
For multi-line HTML content, emits multiple 'data: elements' lines
that Datastar will concatenate. This prevents \n in HTML from
prematurely terminating the SSE event."
[html]
(str "event: datastar-patch-elements\n"
"data: elements " html "\n\n"))
(let [lines (string/split-lines html)]
(str "event: datastar-patch-elements\n"
(->> lines
(map (fn [line] (str "data: elements " line "\n")))
(apply str))
"\n")))

(defn mark-head-elements
"Add `{:data-hyper-head true}` to each top-level hiccup element in a
Expand Down
16 changes: 15 additions & 1 deletion test/hyper/e2e_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -578,8 +578,22 @@

(is (= "Live Reloaded!" (w/text-content "h1")))
(is (= "This content was hot-swapped"
(w/text-content "#reloaded-marker")))))
(w/text-content "#reloaded-marker"))))

;; Test that content with newlines renders correctly
(testing "Content with newlines preserved in route handler"
(alter-var-root #'*test-routes*
(constantly
[["/" {:name :home
:title "Newlines Test"
:get (fn [_]
[:div
[:textarea#newline-content "line1\nline2"]
[:pre#pre-content "code\nwith\n\nnew\n\nlines\n\n"]])}]]))
(w/navigate (str base-url "/"))
(wait-for-sse)
(is (= "line1\nline2" (w/text-content "#newline-content")))
(is (= "code\nwith\n\nnew\n\nlines\n\n" (w/text-content "#pre-content")))))
(finally
(close-browser! browser-info)))))

Expand Down
19 changes: 18 additions & 1 deletion test/hyper/render_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,24 @@
(let [html "<span>test</span>"
fragment (render/format-datastar-fragment html)]
(is (.contains fragment html))
(is (.startsWith fragment "event: datastar-patch-elements\n")))))
(is (.startsWith fragment "event: datastar-patch-elements\n"))))

(testing "HTML with 2 newlines emits multiple data lines"
(let [html "<pre>code\nwith\nnewline</pre>"
fragment (render/format-datastar-fragment html)]
(is (= 3 (count (re-seq #"data: elements" fragment))))
(is (.contains fragment "<pre>code"))
(is (.contains fragment "with"))
(is (.contains fragment "newline</pre>"))
(is (.endsWith fragment "\n\n"))))

(testing "HTML with double newlines emits multiple data lines"
(let [html "<textarea>line1\n\nline2</textarea>"
fragment (render/format-datastar-fragment html)]
(is (= 3 (count (re-seq #"data: elements" fragment))))
(is (.contains fragment "<textarea>line1"))
(is (.contains fragment "line2</textarea>"))
(is (.endsWith fragment "\n\n")))))

(deftest test-render-tab
(testing "render-tab returns nil when no render-fn is registered"
Expand Down
Loading