diff --git a/docs/REACT_TEST_PARITY.md b/docs/REACT_TEST_PARITY.md index 95034a79..7176286d 100644 --- a/docs/REACT_TEST_PARITY.md +++ b/docs/REACT_TEST_PARITY.md @@ -40,6 +40,7 @@ re-renders/effects are observable inline. SSR behavior is tested via | ReactDOMServerIntegrationUntrustedURL | javascript: URL sanitization | ✅ | ui/url_sanitization_test.go (+ found v3.4.7) | | ReactDOMServerIntegrationAttributes | attr boolean/nil/numeric/escaping | ✅ | ui/attribute_serialization_test.go | | ReactDOMServerIntegrationElements | void elements, text escaping | ✅ | ui/void_element_test.go, ui/text_escaping_test.go | +| ReactDOMServerIntegrationElements (dangerouslySetInnerHTML) | raw HTML in SSR | ✅ | ui/raw_html_ssr_test.go — html.RawHTML renders raw but sanitizes (script/js-url/on*= stripped); RawHTMLUnsafe verbatim | | ReactDOMServerIntegrationBasic | basic SSR render, nested components, nil | ✅ | ui/ssr_components_test.go | | ReactDOMServerIntegrationFragment | fragment SSR | ✅ | ui/ssr_components_test.go (component->fragment hoist) | | ReactDOMServerIntegrationSpecialTypes | numbers/bools/null children | 🚫 | Go static typing (ui.Node children) | diff --git a/ui/raw_html_ssr_test.go b/ui/raw_html_ssr_test.go new file mode 100644 index 00000000..b6ade48c --- /dev/null +++ b/ui/raw_html_ssr_test.go @@ -0,0 +1,50 @@ +//go:build !js || !wasm + +package ui_test + +import ( + "strings" + "testing" + + "github.com/monstercameron/GoWebComponents/html" + "github.com/monstercameron/GoWebComponents/ui" +) + +// Ported from React's ReactDOMServerIntegrationElements dangerouslySetInnerHTML +// tests, adapted to GWC's html.RawHTML — which renders raw markup but sanitizes +// it by default (a safety improvement over React's raw injection). html.RawHTMLUnsafe +// renders the markup verbatim. +func TestRawHTMLSSR(t *testing.T) { + render := func(parseNodes []ui.Node) string { + parseOut, parseErr := ui.RenderToString(html.Div(html.Props{}, parseNodes...)) + if parseErr != nil { + t.Fatalf("render error: %v", parseErr) + } + return parseOut + } + + // Safe markup is preserved. + if parseGot := render(html.RawHTML("bold")); parseGot != "
bold
" { + t.Errorf("safe RawHTML = %q, want
bold
", parseGot) + } + + // A keep")); strings.Contains(parseGot, ": %q", parseGot) + } + + // A javascript: href is stripped by the sanitizer. + if parseGot := render(html.RawHTML(`x`)); strings.Contains(strings.ToLower(parseGot), "javascript:") { + t.Errorf("RawHTML did not strip javascript: href: %q", parseGot) + } + + // An on*= handler is stripped. + if parseGot := render(html.RawHTML(``)); strings.Contains(strings.ToLower(parseGot), "onerror") { + t.Errorf("RawHTML did not strip on*= handler: %q", parseGot) + } + + // RawHTMLUnsafe renders verbatim (no sanitization). + if parseGot := render(html.RawHTMLUnsafe("raw")); parseGot != "
raw
" { + t.Errorf("RawHTMLUnsafe = %q, want
raw
", parseGot) + } +}