From 77d3b99eb185cc1439507c809430b8a7266139e9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 22 Jan 2025 08:06:19 -0600 Subject: [PATCH] Fix additional attributes for js renderers --- packages/document/src/elements/link.rs | 1 + packages/document/src/elements/meta.rs | 1 + packages/document/src/elements/mod.rs | 23 ++++++++++++++ packages/document/src/elements/script.rs | 1 + packages/document/src/elements/style.rs | 1 + packages/fullstack/src/document/server.rs | 3 +- packages/playwright-tests/fullstack.spec.js | 31 +++++++++++++++++++ .../playwright-tests/fullstack/src/main.rs | 17 ++++++++++ packages/playwright-tests/web.spec.js | 31 +++++++++++++++++++ packages/playwright-tests/web/src/main.rs | 17 ++++++++++ 10 files changed, 125 insertions(+), 1 deletion(-) diff --git a/packages/document/src/elements/link.rs b/packages/document/src/elements/link.rs index 15bf8ac320..215bd8a8a4 100644 --- a/packages/document/src/elements/link.rs +++ b/packages/document/src/elements/link.rs @@ -28,6 +28,7 @@ impl LinkProps { /// Get all the attributes for the link tag pub fn attributes(&self) -> Vec<(&'static str, String)> { let mut attributes = Vec::new(); + extend_attributes(&mut attributes, &self.additional_attributes); if let Some(rel) = &self.rel { attributes.push(("rel", rel.clone())); } diff --git a/packages/document/src/elements/meta.rs b/packages/document/src/elements/meta.rs index 695718eb09..f799653534 100644 --- a/packages/document/src/elements/meta.rs +++ b/packages/document/src/elements/meta.rs @@ -19,6 +19,7 @@ impl MetaProps { /// Get all the attributes for the meta tag pub fn attributes(&self) -> Vec<(&'static str, String)> { let mut attributes = Vec::new(); + extend_attributes(&mut attributes, &self.additional_attributes); if let Some(property) = &self.property { attributes.push(("property", property.clone())); } diff --git a/packages/document/src/elements/mod.rs b/packages/document/src/elements/mod.rs index e9fbbc4a46..05753b230c 100644 --- a/packages/document/src/elements/mod.rs +++ b/packages/document/src/elements/mod.rs @@ -124,3 +124,26 @@ impl DeduplicationContext { } } } + +/// Extend a list of string attributes with a list of dioxus attribute +pub(crate) fn extend_attributes( + attributes: &mut Vec<(&'static str, String)>, + additional_attributes: &[Attribute], +) { + for additional_attribute in additional_attributes { + let attribute_value_as_string = match &additional_attribute.value { + dioxus_core::AttributeValue::Text(v) => v.to_string(), + dioxus_core::AttributeValue::Float(v) => v.to_string(), + dioxus_core::AttributeValue::Int(v) => v.to_string(), + dioxus_core::AttributeValue::Bool(v) => v.to_string(), + dioxus_core::AttributeValue::Listener(_) | dioxus_core::AttributeValue::Any(_) => { + tracing::error!("document::* elements do not support event listeners or any value attributes. Expected displayable attribute, found {:?}", additional_attribute.value); + continue; + } + dioxus_core::AttributeValue::None => { + continue; + } + }; + attributes.push((additional_attribute.name, attribute_value_as_string)); + } +} diff --git a/packages/document/src/elements/script.rs b/packages/document/src/elements/script.rs index c7b8d1044a..5737b1dc11 100644 --- a/packages/document/src/elements/script.rs +++ b/packages/document/src/elements/script.rs @@ -25,6 +25,7 @@ impl ScriptProps { /// Get all the attributes for the script tag pub fn attributes(&self) -> Vec<(&'static str, String)> { let mut attributes = Vec::new(); + extend_attributes(&mut attributes, &self.additional_attributes); if let Some(defer) = &self.defer { attributes.push(("defer", defer.to_string())); } diff --git a/packages/document/src/elements/style.rs b/packages/document/src/elements/style.rs index b924291211..bd85e799b8 100644 --- a/packages/document/src/elements/style.rs +++ b/packages/document/src/elements/style.rs @@ -20,6 +20,7 @@ impl StyleProps { /// Get all the attributes for the style tag pub fn attributes(&self) -> Vec<(&'static str, String)> { let mut attributes = Vec::new(); + extend_attributes(&mut attributes, &self.additional_attributes); if let Some(href) = &self.href { attributes.push(("href", href.clone())); } diff --git a/packages/fullstack/src/document/server.rs b/packages/fullstack/src/document/server.rs index dacc752176..067dc1889a 100644 --- a/packages/fullstack/src/document/server.rs +++ b/packages/fullstack/src/document/server.rs @@ -88,7 +88,7 @@ impl Document for ServerDocument { http_equiv: props.http_equiv, content: props.content, property: props.property, - ..props.additional_attributes + ..props.additional_attributes, } }); } @@ -142,6 +142,7 @@ impl Document for ServerDocument { integrity: props.integrity, r#type: props.r#type, blocking: props.blocking, + ..props.additional_attributes, } }) } diff --git a/packages/playwright-tests/fullstack.spec.js b/packages/playwright-tests/fullstack.spec.js index 2e981d7c9e..7732b98a00 100644 --- a/packages/playwright-tests/fullstack.spec.js +++ b/packages/playwright-tests/fullstack.spec.js @@ -37,3 +37,34 @@ test("hydration", async ({ page }) => { const mountedDiv = page.locator("div.onmounted-div"); await expect(mountedDiv).toHaveText("onmounted was called 1 times"); }); + +test("document elements", async ({ page }) => { + await page.goto("http://localhost:9999"); + // wait until the meta element is mounted + const meta = page.locator("meta#meta-head[name='testing']"); + await meta.waitFor({ state: "attached" }); + await expect(meta).toHaveAttribute("data", "dioxus-meta-element"); + + const link = page.locator("link#link-head[rel='stylesheet']"); + await link.waitFor({ state: "attached" }); + await expect(link).toHaveAttribute( + "href", + "https://fonts.googleapis.com/css?family=Roboto+Mono" + ); + + const stylesheet = page.locator("link#stylesheet-head[rel='stylesheet']"); + await stylesheet.waitFor({ state: "attached" }); + await expect(stylesheet).toHaveAttribute( + "href", + "https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic" + ); + + const script = page.locator("script#script-head"); + await script.waitFor({ state: "attached" }); + await expect(script).toHaveAttribute("async", "true"); + + const style = page.locator("style#style-head"); + await style.waitFor({ state: "attached" }); + const main = page.locator("#main"); + await expect(main).toHaveCSS("font-family", "Roboto"); +}); diff --git a/packages/playwright-tests/fullstack/src/main.rs b/packages/playwright-tests/fullstack/src/main.rs index dba72c9390..1b2feba66b 100644 --- a/packages/playwright-tests/fullstack/src/main.rs +++ b/packages/playwright-tests/fullstack/src/main.rs @@ -41,6 +41,7 @@ fn app() -> Element { Errors {} } OnMounted {} + DocumentElements {} } } @@ -117,3 +118,19 @@ pub fn ThrowsError() -> Element { "success" } } + +/// This component tests the document::* elements pre-rendered on the server +#[component] +fn DocumentElements() -> Element { + rsx! { + document::Meta { id: "meta-head", name: "testing", data: "dioxus-meta-element" } + document::Link { + id: "link-head", + rel: "stylesheet", + href: "https://fonts.googleapis.com/css?family=Roboto+Mono" + } + document::Stylesheet { id: "stylesheet-head", href: "https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic" } + document::Script { id: "script-head", async: true, "console.log('hello world');" } + document::Style { id: "style-head", "body {{ font-family: 'Roboto'; }}" } + } +} diff --git a/packages/playwright-tests/web.spec.js b/packages/playwright-tests/web.spec.js index d5cac90cb7..dca040fcb4 100644 --- a/packages/playwright-tests/web.spec.js +++ b/packages/playwright-tests/web.spec.js @@ -124,3 +124,34 @@ test("web-sys closure", async ({ page }) => { await page.keyboard.press("Enter"); await expect(scrollDiv).toHaveText("the keydown event was triggered"); }); + +test("document elements", async ({ page }) => { + await page.goto("http://localhost:9999"); + // wait until the meta element is mounted + const meta = page.locator("meta#meta-head[name='testing']"); + await meta.waitFor({ state: "attached" }); + await expect(meta).toHaveAttribute("data", "dioxus-meta-element"); + + const link = page.locator("link#link-head[rel='stylesheet']"); + await link.waitFor({ state: "attached" }); + await expect(link).toHaveAttribute( + "href", + "https://fonts.googleapis.com/css?family=Roboto+Mono" + ); + + const stylesheet = page.locator("link#stylesheet-head[rel='stylesheet']"); + await stylesheet.waitFor({ state: "attached" }); + await expect(stylesheet).toHaveAttribute( + "href", + "https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic" + ); + + const script = page.locator("script#script-head"); + await script.waitFor({ state: "attached" }); + await expect(script).toHaveAttribute("async", "true"); + + const style = page.locator("style#style-head"); + await style.waitFor({ state: "attached" }); + const main = page.locator("#main"); + await expect(main).toHaveCSS("font-family", "Roboto"); +}); diff --git a/packages/playwright-tests/web/src/main.rs b/packages/playwright-tests/web/src/main.rs index 58163ebd78..e90c858b5d 100644 --- a/packages/playwright-tests/web/src/main.rs +++ b/packages/playwright-tests/web/src/main.rs @@ -55,6 +55,7 @@ fn app() -> Element { PreventDefault {} OnMounted {} WebSysClosure {} + DocumentElements {} } } @@ -131,6 +132,22 @@ fn WebSysClosure() -> Element { } } +/// This component tests the document::* elements +#[component] +fn DocumentElements() -> Element { + rsx! { + document::Meta { id: "meta-head", name: "testing", data: "dioxus-meta-element" } + document::Link { + id: "link-head", + rel: "stylesheet", + href: "https://fonts.googleapis.com/css?family=Roboto+Mono" + } + document::Stylesheet { id: "stylesheet-head", href: "https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic" } + document::Script { id: "script-head", async: true, "console.log('hello world');" } + document::Style { id: "style-head", "body {{ font-family: 'Roboto'; }}" } + } +} + fn main() { tracing_wasm::set_as_global_default_with_config( tracing_wasm::WASMLayerConfigBuilder::default()