diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 000000000..b511e3dd0 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,40 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + if: github.event.pull_request.draft == false + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@v3 + - uses: actions-rs/cargo@v1 + with: + command: install + args: --git https://github.com/DioxusLabs/cli + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index e093afe8a..bca0fe28f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target +/playwrite-tests/web/dist +/playwrite-tests/fullstack/dist /dist Cargo.lock .DS_Store @@ -11,4 +13,8 @@ Cargo.lock tarpaulin-report.html # Jetbrain -.idea/ \ No newline at end of file +.idea/ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/Cargo.toml b/Cargo.toml index 03b5c1296..4b9cd35bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,10 @@ members = [ # Full project examples "examples/tailwind", "examples/PWA-example", + # Playwrite tests + "playwrite-tests/liveview", + "playwrite-tests/web", + "playwrite-tests/fullstack", ] # This is a "virtual package" diff --git a/docs/guide/src/en/contributing/index.md b/docs/guide/src/en/contributing/index.md index 0e37db2ad..9f25553b3 100644 --- a/docs/guide/src/en/contributing/index.md +++ b/docs/guide/src/en/contributing/index.md @@ -41,6 +41,12 @@ cargo check --workspace --examples --tests cargo clippy --workspace --examples --tests -- -D warnings ``` +- Browser tests are automated with [Playwrite](https://playwright.dev/docs/intro#installing-playwright) + +```sh +npx playwright test +``` + - Crates that use unsafe are checked for undefined behavior with [MIRI](https://github.com/rust-lang/miri). MIRI can be helpful to debug what unsafe code is causing issues. Only code that does not interact with system calls can be checked with MIRI. Currently, this is used for the two MIRI tests in `dioxus-core` and `dioxus-native-core`. ```sh diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..348b505d4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "dioxus", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dioxus", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.34.3" + } + }, + "node_modules/@playwright/test": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.34.3.tgz", + "integrity": "sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.34.3" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright-core": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.34.3.tgz", + "integrity": "sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..ceb38db89 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "dioxus", + "version": "1.0.0", + "description": "

", + "main": "index.js", + "directories": { + "doc": "docs", + "example": "examples" + }, + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.34.3" + } +} diff --git a/packages/fullstack/examples/axum-hello-world/src/main.rs b/packages/fullstack/examples/axum-hello-world/src/main.rs index 0301a190c..1e314fd8a 100644 --- a/packages/fullstack/examples/axum-hello-world/src/main.rs +++ b/packages/fullstack/examples/axum-hello-world/src/main.rs @@ -78,7 +78,7 @@ fn app(cx: Scope) -> Element { } } }, - "Run a server function! testing1234" + "Run a server function!" } "Server said: {text}" }) diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 000000000..19859bcc8 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,99 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); +const path = require('path'); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = defineConfig({ + testDir: './playwrite-tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: [ + { + command: 'cargo run --package dioxus-playwrite-liveview-test --bin dioxus-playwrite-liveview-test', + port: 3030, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + }, + { + cwd: path.join(process.cwd(), 'playwrite-tests', 'web'), + command: 'dioxus serve', + port: 8080, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + }, + { + cwd: path.join(process.cwd(), 'playwrite-tests', 'fullstack'), + command: 'dioxus build --features web\ncargo run --release --features ssr --no-default-features', + port: 3333, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + } + ], +}); diff --git a/playwrite-tests/fullstack.spec.js b/playwrite-tests/fullstack.spec.js new file mode 100644 index 000000000..dd16c9b93 --- /dev/null +++ b/playwrite-tests/fullstack.spec.js @@ -0,0 +1,32 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test('button click', async ({ page }) => { + await page.goto('http://localhost:3333'); + + // Expect the page to contain the counter text. + const main = page.locator('#main'); + await expect(main).toContainText('hello axum! 12345'); + + // Click the increment button. + let button = await page.locator('button.increment-button'); + await button.click(); + + // Expect the page to contain the updated counter text. + await expect(main).toContainText('hello axum! 12346'); +}); + +test('fullstack communication', async ({ page }) => { + await page.goto('http://localhost:3333'); + + // Expect the page to contain the counter text. + const main = page.locator('#main'); + await expect(main).toContainText('Server said: ...'); + + // Click the increment button. + let button = await page.locator('button.server-button'); + await button.click(); + + // Expect the page to contain the updated counter text. + await expect(main).toContainText('Server said: Hello from the server!'); +}); diff --git a/playwrite-tests/fullstack/.gitignore b/playwrite-tests/fullstack/.gitignore new file mode 100644 index 000000000..6047329c6 --- /dev/null +++ b/playwrite-tests/fullstack/.gitignore @@ -0,0 +1,2 @@ +dist +target \ No newline at end of file diff --git a/playwrite-tests/fullstack/Cargo.toml b/playwrite-tests/fullstack/Cargo.toml new file mode 100644 index 000000000..1e39f06b2 --- /dev/null +++ b/playwrite-tests/fullstack/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "dioxus-playwrite-fullstack-test" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus-web = { path = "../../packages/web", features=["hydrate"], optional = true } +dioxus = { path = "../../packages/dioxus" } +dioxus-fullstack = { path = "../../packages/fullstack" } +axum = { version = "0.6.12", optional = true } +tokio = { version = "1.27.0", features = ["full"], optional = true } +serde = "1.0.159" +execute = "0.2.12" + +[features] +default = ["web"] +ssr = ["axum", "tokio", "dioxus-fullstack/axum"] +web = ["dioxus-web"] diff --git a/playwrite-tests/fullstack/src/main.rs b/playwrite-tests/fullstack/src/main.rs new file mode 100644 index 000000000..9497bbbfc --- /dev/null +++ b/playwrite-tests/fullstack/src/main.rs @@ -0,0 +1,104 @@ +// This test is used by playwrite configured in the root of the repo +// Tests: +// - Server functions +// - SSR +// - Hydration + +#![allow(non_snake_case)] +use dioxus::prelude::*; +use dioxus_fullstack::prelude::*; +use serde::{Deserialize, Serialize}; + +fn main() { + #[cfg(feature = "web")] + dioxus_web::launch_with_props( + app, + get_root_props_from_document().unwrap_or_default(), + dioxus_web::Config::new().hydrate(true), + ); + #[cfg(feature = "ssr")] + { + // Start hot reloading + hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| { + execute::shell("dioxus build --features web") + .spawn() + .unwrap() + .wait() + .unwrap(); + execute::shell("cargo run --features ssr --no-default-features") + .spawn() + .unwrap(); + true + })); + + PostServerData::register().unwrap(); + GetServerData::register().unwrap(); + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3333)); + axum::Server::bind(&addr) + .serve( + axum::Router::new() + .serve_dioxus_application( + "", + ServeConfigBuilder::new(app, AppProps { count: 12345 }).build(), + ) + .into_make_service(), + ) + .await + .unwrap(); + }); + } +} + +#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)] +struct AppProps { + count: i32, +} + +fn app(cx: Scope) -> Element { + let mut count = use_state(cx, || cx.props.count); + let text = use_state(cx, || "...".to_string()); + + cx.render(rsx! { + h1 { "hello axum! {count}" } + button { + class: "increment-button", + onclick: move |_| count += 1, + "Increment" + } + button { + class: "server-button", + onclick: move |_| { + to_owned![text]; + let sc = cx.sc(); + async move { + if let Ok(data) = get_server_data().await { + println!("Client received: {}", data); + text.set(data.clone()); + post_server_data(sc, data).await.unwrap(); + } + } + }, + "Run a server function!" + } + "Server said: {text}" + }) +} + +#[server(PostServerData)] +async fn post_server_data(cx: DioxusServerContext, data: String) -> Result<(), ServerFnError> { + // The server context contains information about the current request and allows you to modify the response. + cx.response_headers_mut() + .insert("Set-Cookie", "foo=bar".parse().unwrap()); + println!("Server received: {}", data); + println!("Request parts are {:?}", cx.request_parts()); + + Ok(()) +} + +#[server(GetServerData)] +async fn get_server_data() -> Result { + Ok("Hello from the server!".to_string()) +} diff --git a/playwrite-tests/liveview.spec.js b/playwrite-tests/liveview.spec.js new file mode 100644 index 000000000..d8303911c --- /dev/null +++ b/playwrite-tests/liveview.spec.js @@ -0,0 +1,72 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test('button click', async ({ page }) => { + await page.goto('http://127.0.0.1:3030'); + + // Expect the page to contain the counter text. + const main = page.locator('#main'); + await expect(main).toContainText('hello axum! 0'); + + // Click the increment button. + await page.getByRole('button', { name: 'Increment' }).click(); + + // Expect the page to contain the updated counter text. + await expect(main).toContainText('hello axum! 1'); +}); + +test('svg', async ({ page }) => { + await page.goto('http://127.0.0.1:3030'); + + // Expect the page to contain the svg. + const svg = page.locator('svg'); + + // Expect the svg to contain the circle. + const circle = svg.locator('circle'); + await expect(circle).toHaveAttribute('cx', '50'); + await expect(circle).toHaveAttribute('cy', '50'); + await expect(circle).toHaveAttribute('r', '40'); + await expect(circle).toHaveAttribute('stroke', 'green'); + await expect(circle).toHaveAttribute('fill', 'yellow'); +}); + +test('raw attribute', async ({ page }) => { + await page.goto('http://127.0.0.1:3030'); + + // Expect the page to contain the div with the raw attribute. + const div = page.locator('div.raw-attribute-div'); + await expect(div).toHaveAttribute('raw-attribute', 'raw-attribute-value'); +}); + +test('hidden attribute', async ({ page }) => { + await page.goto('http://127.0.0.1:3030'); + + // Expect the page to contain the div with the hidden attribute. + const div = page.locator('div.hidden-attribute-div'); + await expect(div).toHaveAttribute('hidden', 'true'); +}); + +test('dangerous inner html', async ({ page }) => { + await page.goto('http://127.0.0.1:3030'); + + // Expect the page to contain the div with the dangerous inner html. + const div = page.locator('div.dangerous-inner-html-div'); + await expect(div).toContainText('hello dangerous inner html'); +}); + +test('input value', async ({ page }) => { + await page.goto('http://127.0.0.1:3030'); + + // Expect the page to contain the input with the value. + const input = page.locator('input'); + await expect(input).toHaveValue('hello input'); +}); + +test('style', async ({ page }) => { + await page.goto('http://127.0.0.1:3030'); + + // Expect the page to contain the div with the style. + const div = page.locator('div.style-div'); + await expect(div).toHaveText('colored text'); + await expect(div).toHaveCSS('color', 'rgb(255, 0, 0)'); +}); diff --git a/playwrite-tests/liveview/Cargo.toml b/playwrite-tests/liveview/Cargo.toml new file mode 100644 index 000000000..8c4cad1ef --- /dev/null +++ b/playwrite-tests/liveview/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dioxus-playwrite-liveview-test" +version = "0.0.1" +edition = "2021" +description = "Playwrite test for Dioxus Liveview" +license = "MIT/Apache-2.0" +publish = false + +[dependencies] +dioxus = { path = "../../packages/dioxus" } +dioxus-liveview = { path = "../../packages/liveview", features = ["axum"] } +tokio = { version = "1.19.2", features = ["full"] } +axum = { version = "0.6.1", features = ["ws"] } diff --git a/playwrite-tests/liveview/src/main.rs b/playwrite-tests/liveview/src/main.rs new file mode 100644 index 000000000..42bd9a3e3 --- /dev/null +++ b/playwrite-tests/liveview/src/main.rs @@ -0,0 +1,78 @@ +// This test is used by playwrite configured in the root of the repo + +use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router}; +use dioxus::prelude::*; + +fn app(cx: Scope) -> Element { + let mut num = use_state(cx, || 0); + + cx.render(rsx! { + div { + "hello axum! {num}" + button { onclick: move |_| num += 1, "Increment" } + } + svg { + circle { cx: 50, cy: 50, r: 40, stroke: "green", fill: "yellow" } + } + div { + class: "raw-attribute-div", + "raw-attribute": "raw-attribute-value", + } + div { + class: "hidden-attribute-div", + hidden: true, + } + div { + class: "dangerous-inner-html-div", + dangerous_inner_html: "

hello dangerous inner html

", + } + input { + value: "hello input", + } + div { + class: "style-div", + color: "red", + "colored text" + } + }) +} + +#[tokio::main] +async fn main() { + let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into(); + + let view = dioxus_liveview::LiveViewPool::new(); + + let app = Router::new() + .route( + "/", + get(move || async move { + Html(format!( + r#" + + + Dioxus LiveView with axum +
+ {glue} + + "#, + glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws")) + )) + }), + ) + .route( + "/ws", + get(move |ws: WebSocketUpgrade| async move { + ws.on_upgrade(move |socket| async move { + _ = view.launch(dioxus_liveview::axum_socket(socket), app).await; + }) + }), + ); + + println!("Listening on http://{addr}"); + + axum::Server::bind(&addr.to_string().parse().unwrap()) + .serve(app.into_make_service()) + .await + .unwrap(); +} diff --git a/playwrite-tests/web.spec.js b/playwrite-tests/web.spec.js new file mode 100644 index 000000000..529a7e0e0 --- /dev/null +++ b/playwrite-tests/web.spec.js @@ -0,0 +1,94 @@ +// @ts-check +const { test, expect, defineConfig } = require('@playwright/test'); + +test('button click', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the counter text. + const main = page.locator('#main'); + await expect(main).toContainText('hello axum! 0'); + + // Click the increment button. + let button = await page.locator('button.increment-button'); + await button.click(); + + // Expect the page to contain the updated counter text. + await expect(main).toContainText('hello axum! 1'); +}); + +test('svg', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the svg. + const svg = page.locator('svg'); + + // Expect the svg to contain the circle. + const circle = svg.locator('circle'); + await expect(circle).toHaveAttribute('cx', '50'); + await expect(circle).toHaveAttribute('cy', '50'); + await expect(circle).toHaveAttribute('r', '40'); + await expect(circle).toHaveAttribute('stroke', 'green'); + await expect(circle).toHaveAttribute('fill', 'yellow'); +}); + +test('raw attribute', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the div with the raw attribute. + const div = page.locator('div.raw-attribute-div'); + await expect(div).toHaveAttribute('raw-attribute', 'raw-attribute-value'); +}); + +test('hidden attribute', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the div with the hidden attribute. + const div = page.locator('div.hidden-attribute-div'); + await expect(div).toHaveAttribute('hidden', 'true'); +}); + +test('dangerous inner html', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the div with the dangerous inner html. + const div = page.locator('div.dangerous-inner-html-div'); + await expect(div).toContainText('hello dangerous inner html'); +}); + +test('input value', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the input with the value. + const input = page.locator('input'); + await expect(input).toHaveValue('hello input'); +}); + +test('style', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the div with the style. + const div = page.locator('div.style-div'); + await expect(div).toHaveText('colored text'); + await expect(div).toHaveCSS('color', 'rgb(255, 0, 0)'); +}); + +test('eval', async ({ page }) => { + await page.goto('http://localhost:8080'); + + // Expect the page to contain the div with the eval and have no text. + const div = page.locator('div.eval-result'); + await expect(div).toHaveText(''); + + // Click the button to run the eval. + let button = await page.locator('button.eval-button'); + await button.click(); + + // Check that the title changed. + await expect(page).toHaveTitle('Hello from Dioxus Eval!'); + + // Check that the div has the eval value. + await expect(div).toHaveText('returned eval value'); + +}); + +// Shutdown the li \ No newline at end of file diff --git a/playwrite-tests/web/Cargo.toml b/playwrite-tests/web/Cargo.toml new file mode 100644 index 000000000..b1becd86e --- /dev/null +++ b/playwrite-tests/web/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dioxus-playwrite-web-test" +version = "0.0.1" +edition = "2021" +description = "Playwrite test for Dioxus Web" +license = "MIT/Apache-2.0" +publish = false + +[dependencies] +dioxus = { path = "../../packages/dioxus" } +dioxus-web = { path = "../../packages/web" } +serde_json = "1.0.96" diff --git a/playwrite-tests/web/src/main.rs b/playwrite-tests/web/src/main.rs new file mode 100644 index 000000000..d1881539c --- /dev/null +++ b/playwrite-tests/web/src/main.rs @@ -0,0 +1,63 @@ +// This test is used by playwrite configured in the root of the repo + +use dioxus::prelude::*; +use dioxus_web::use_eval; + +fn app(cx: Scope) -> Element { + let mut num = use_state(cx, || 0); + let eval = use_eval(cx); + let eval_result = use_state(cx, String::new); + + cx.render(rsx! { + div { + "hello axum! {num}" + button { + class: "increment-button", + onclick: move |_| num += 1, "Increment" + } + } + svg { + circle { cx: 50, cy: 50, r: 40, stroke: "green", fill: "yellow" } + } + div { + class: "raw-attribute-div", + "raw-attribute": "raw-attribute-value", + } + div { + class: "hidden-attribute-div", + hidden: true, + } + div { + class: "dangerous-inner-html-div", + dangerous_inner_html: "

hello dangerous inner html

", + } + input { + value: "hello input", + } + div { + class: "style-div", + color: "red", + "colored text" + } + button { + class: "eval-button", + onclick: move |_| { + // Set the window title + let result = eval(r#"window.document.title = 'Hello from Dioxus Eval!'; + return "returned eval value";"#.to_string()); + if let Ok(serde_json::Value::String(string)) = result.get() { + eval_result.set(string); + } + }, + "Eval" + } + div { + class: "eval-result", + "{eval_result}" + } + }) +} + +fn main() { + dioxus_web::launch(app); +}