diff --git a/src/hyper/render.clj b/src/hyper/render.clj index 20ace83..09d1ea2 100644 --- a/src/hyper/render.clj +++ b/src/hyper/render.clj @@ -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] @@ -27,10 +28,16 @@ event: datastar-patch-elements data: elements - (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 diff --git a/test/hyper/e2e_test.clj b/test/hyper/e2e_test.clj index ba4495c..0f7b4d8 100644 --- a/test/hyper/e2e_test.clj +++ b/test/hyper/e2e_test.clj @@ -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))))) diff --git a/test/hyper/render_test.clj b/test/hyper/render_test.clj index ff33f4c..30f40cd 100644 --- a/test/hyper/render_test.clj +++ b/test/hyper/render_test.clj @@ -38,7 +38,24 @@ (let [html "test" 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 "
code\nwith\nnewline
" + fragment (render/format-datastar-fragment html)] + (is (= 3 (count (re-seq #"data: elements" fragment)))) + (is (.contains fragment "
code"))
+      (is (.contains fragment "with"))
+      (is (.contains fragment "newline
")) + (is (.endsWith fragment "\n\n")))) + + (testing "HTML with double newlines emits multiple data lines" + (let [html "" + fragment (render/format-datastar-fragment html)] + (is (= 3 (count (re-seq #"data: elements" fragment)))) + (is (.contains fragment "")) + (is (.endsWith fragment "\n\n"))))) (deftest test-render-tab (testing "render-tab returns nil when no render-fn is registered"