From 1acf2a93ed92c7f7a809af5961a039178b41870f Mon Sep 17 00:00:00 2001 From: Carson Rajcan Date: Sat, 10 Feb 2024 09:51:43 -0600 Subject: [PATCH 1/6] Copy todo app --- examples/csr_and_server_fns/.gitignore | 1 + examples/csr_and_server_fns/Cargo.toml | 92 ++++++++ examples/csr_and_server_fns/LICENSE | 21 ++ examples/csr_and_server_fns/Makefile.toml | 12 ++ examples/csr_and_server_fns/README.md | 19 ++ examples/csr_and_server_fns/Todos.db | Bin 0 -> 16384 bytes examples/csr_and_server_fns/e2e/Cargo.toml | 18 ++ examples/csr_and_server_fns/e2e/Makefile.toml | 20 ++ examples/csr_and_server_fns/e2e/README.md | 34 +++ .../e2e/features/add_todo.feature | 17 ++ .../e2e/features/delete_todo.feature | 18 ++ .../e2e/features/open_app.feature | 12 ++ .../csr_and_server_fns/e2e/tests/app_suite.rs | 14 ++ .../e2e/tests/fixtures/action.rs | 60 ++++++ .../e2e/tests/fixtures/check.rs | 57 +++++ .../e2e/tests/fixtures/find.rs | 63 ++++++ .../e2e/tests/fixtures/mod.rs | 4 + .../e2e/tests/fixtures/world/action_steps.rs | 57 +++++ .../e2e/tests/fixtures/world/check_steps.rs | 67 ++++++ .../e2e/tests/fixtures/world/mod.rs | 39 ++++ .../20221118172000_create_todo_table.sql | 6 + .../csr_and_server_fns/public/favicon.ico | Bin 0 -> 15406 bytes .../csr_and_server_fns/rust-toolchain.toml | 2 + examples/csr_and_server_fns/src/lib.rs | 11 + examples/csr_and_server_fns/src/main.rs | 61 ++++++ examples/csr_and_server_fns/src/todo.rs | 199 ++++++++++++++++++ examples/csr_and_server_fns/style.css | 3 + 27 files changed, 907 insertions(+) create mode 100644 examples/csr_and_server_fns/.gitignore create mode 100644 examples/csr_and_server_fns/Cargo.toml create mode 100644 examples/csr_and_server_fns/LICENSE create mode 100644 examples/csr_and_server_fns/Makefile.toml create mode 100644 examples/csr_and_server_fns/README.md create mode 100644 examples/csr_and_server_fns/Todos.db create mode 100644 examples/csr_and_server_fns/e2e/Cargo.toml create mode 100644 examples/csr_and_server_fns/e2e/Makefile.toml create mode 100644 examples/csr_and_server_fns/e2e/README.md create mode 100644 examples/csr_and_server_fns/e2e/features/add_todo.feature create mode 100644 examples/csr_and_server_fns/e2e/features/delete_todo.feature create mode 100644 examples/csr_and_server_fns/e2e/features/open_app.feature create mode 100644 examples/csr_and_server_fns/e2e/tests/app_suite.rs create mode 100644 examples/csr_and_server_fns/e2e/tests/fixtures/action.rs create mode 100644 examples/csr_and_server_fns/e2e/tests/fixtures/check.rs create mode 100644 examples/csr_and_server_fns/e2e/tests/fixtures/find.rs create mode 100644 examples/csr_and_server_fns/e2e/tests/fixtures/mod.rs create mode 100644 examples/csr_and_server_fns/e2e/tests/fixtures/world/action_steps.rs create mode 100644 examples/csr_and_server_fns/e2e/tests/fixtures/world/check_steps.rs create mode 100644 examples/csr_and_server_fns/e2e/tests/fixtures/world/mod.rs create mode 100644 examples/csr_and_server_fns/migrations/20221118172000_create_todo_table.sql create mode 100644 examples/csr_and_server_fns/public/favicon.ico create mode 100644 examples/csr_and_server_fns/rust-toolchain.toml create mode 100644 examples/csr_and_server_fns/src/lib.rs create mode 100644 examples/csr_and_server_fns/src/main.rs create mode 100644 examples/csr_and_server_fns/src/todo.rs create mode 100644 examples/csr_and_server_fns/style.css diff --git a/examples/csr_and_server_fns/.gitignore b/examples/csr_and_server_fns/.gitignore new file mode 100644 index 0000000000..5d70b96388 --- /dev/null +++ b/examples/csr_and_server_fns/.gitignore @@ -0,0 +1 @@ +.leptos.kdl \ No newline at end of file diff --git a/examples/csr_and_server_fns/Cargo.toml b/examples/csr_and_server_fns/Cargo.toml new file mode 100644 index 0000000000..5e0727fb69 --- /dev/null +++ b/examples/csr_and_server_fns/Cargo.toml @@ -0,0 +1,92 @@ +[package] +name = "todo_app_sqlite" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +actix-files = { version = "0.6.2", optional = true } +actix-web = { version = "4.2.1", optional = true, features = ["macros"] } +anyhow = "1.0.68" +broadcaster = "1.0.0" +console_log = "1.0.0" +console_error_panic_hook = "0.1.7" +serde = { version = "1.0.152", features = ["derive"] } +futures = "0.3.25" +leptos = { path = "../../leptos", features = ["nightly"] } +leptos_actix = { path = "../../integrations/actix", optional = true } +leptos_meta = { path = "../../meta", features = ["nightly"] } +leptos_router = { path = "../../router", features = ["nightly"] } +log = "0.4.17" +simple_logger = "4.0.0" +gloo = { git = "https://github.com/rustwasm/gloo" } +sqlx = { version = "0.6.2", features = [ + "runtime-tokio-rustls", + "sqlite", +], optional = true } +wasm-bindgen = "0.2" +tokio = { version = "1", features = ["rt", "time"], optional = true } + +[features] +hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] +ssr = [ + "dep:actix-files", + "dep:actix-web", + "dep:sqlx", + "leptos/ssr", + "leptos_actix", + "leptos_meta/ssr", + "leptos_router/ssr", + "dep:tokio", +] + +[package.metadata.cargo-all-features] +denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"] +skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]] + +[package.metadata.leptos] +# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name +output-name = "todo_app_sqlite" +# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. +site-root = "target/site" +# The site-root relative folder where all compiled output (JS, WASM and CSS) is written +# Defaults to pkg +site-pkg-dir = "pkg" +# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css +style-file = "./style.css" +# [Optional] Files in the asset-dir will be copied to the site-root directory +assets-dir = "public" +# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup. +site-addr = "127.0.0.1:3000" +# The port to use for automatic reload monitoring +reload-port = 3001 +# [Optional] Command to use when running end2end tests. It will run in the end2end dir. +end2end-cmd = "cargo make test-ui" +end2end-dir = "e2e" +# The browserlist query used for optimizing the CSS. +browserquery = "defaults" +# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head +watch = false +# The environment Leptos will run in, usually either "DEV" or "PROD" +env = "DEV" +# The features to use when compiling the bin target +# +# Optional. Can be over-ridden with the command line parameter --bin-features +bin-features = ["ssr"] + +# If the --no-default-features flag should be used when compiling the bin target +# +# Optional. Defaults to false. +bin-default-features = false + +# The features to use when compiling the lib target +# +# Optional. Can be over-ridden with the command line parameter --lib-features +lib-features = ["hydrate"] + +# If the --no-default-features flag should be used when compiling the lib target +# +# Optional. Defaults to false. +lib-default-features = false diff --git a/examples/csr_and_server_fns/LICENSE b/examples/csr_and_server_fns/LICENSE new file mode 100644 index 0000000000..77d5625cb3 --- /dev/null +++ b/examples/csr_and_server_fns/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Greg Johnston + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/csr_and_server_fns/Makefile.toml b/examples/csr_and_server_fns/Makefile.toml new file mode 100644 index 0000000000..4caca105f5 --- /dev/null +++ b/examples/csr_and_server_fns/Makefile.toml @@ -0,0 +1,12 @@ +extend = [ + { path = "../cargo-make/main.toml" }, + { path = "../cargo-make/cargo-leptos-webdriver-test.toml" }, +] + +[env] +CLIENT_PROCESS_NAME = "todo_app_sqlite" + +[tasks.test-ui] +cwd = "./e2e" +command = "cargo" +args = ["make", "test-ui", "${@}"] diff --git a/examples/csr_and_server_fns/README.md b/examples/csr_and_server_fns/README.md new file mode 100644 index 0000000000..1a2ceade58 --- /dev/null +++ b/examples/csr_and_server_fns/README.md @@ -0,0 +1,19 @@ +# Leptos Todo App Sqlite + +This example creates a basic todo app with an Actix backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server. + +## Getting Started + +See the [Examples README](../README.md) for setup and run instructions. + +## E2E Testing + +See the [E2E README](./e2e/README.md) for more information about the testing strategy. + +## Rendering + +See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering. + +## Quick Start + +Run `cargo leptos watch` to run this example. diff --git a/examples/csr_and_server_fns/Todos.db b/examples/csr_and_server_fns/Todos.db new file mode 100644 index 0000000000000000000000000000000000000000..e1b64243f84cd3291906c53e7cce974a3963f6ac GIT binary patch literal 16384 zcmeI&Jxmi(902fltEmuCz)+2NQKL|92m~_x|ty`rhxRyWH@x zv}RK>ZkVNlO}bGF;y84O5QMlV2%#Te> z{YhC86p1K;n3hOwl(dHlD`%-`v1B5q22vS?49TfMLC%q*Qf`kIFH%c2wHfyhh$5X( zNG8jUk#xEo)}}4HpzE}lcjKu+X;={khpL^(VX0pjNh>5dBFj=n$yd9S(=A6;sbvu{ zn@vkXrkYods*_ZmvYZkT(^;`5M(3#NxUsl_1R2q=v z>fCF6ZOzu{df(%MoIE1P?$TAGG^10Sx+AUAggctu*|Z>pKjT%lt$5oKzJ@R0Q9R0j zz&*GfM{$V%&VS7UE%UHp5pZ__08?hY<2!H?xfB*=900@8p2!H?xfB*=9z&{r#a)E}f?vU

`uI-{}|1nfh7_lp4HeKU~Qn@Ggs@mRc<#C!V^`}*QN*$4M7-&o$c(DbZrJbJ*$ zEv^uMYrz_9p`+>}Cn5~P$UlH4(fB*=900@8p b2!H?xfB*=900@8p2>e$9A%8=t{954$x-(B} literal 0 HcmV?d00001 diff --git a/examples/csr_and_server_fns/e2e/Cargo.toml b/examples/csr_and_server_fns/e2e/Cargo.toml new file mode 100644 index 0000000000..cd11a0618b --- /dev/null +++ b/examples/csr_and_server_fns/e2e/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "todo_app_sqlite_e2e" +version = "0.1.0" +edition = "2021" + +[dev-dependencies] +anyhow = "1.0.72" +async-trait = "0.1.72" +cucumber = "0.19.1" +fantoccini = "0.19.3" +pretty_assertions = "1.4.0" +serde_json = "1.0.104" +tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] } +url = "2.4.0" + +[[test]] +name = "app_suite" +harness = false # Allow Cucumber to print output instead of libtest diff --git a/examples/csr_and_server_fns/e2e/Makefile.toml b/examples/csr_and_server_fns/e2e/Makefile.toml new file mode 100644 index 0000000000..cd76be24d5 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/Makefile.toml @@ -0,0 +1,20 @@ +extend = { path = "../../cargo-make/main.toml" } + +[tasks.test] +env = { RUN_AUTOMATICALLY = false } +condition = { env_true = ["RUN_AUTOMATICALLY"] } + +[tasks.ci] + +[tasks.test-ui] +command = "cargo" +args = [ + "test", + "--test", + "app_suite", + "--", + "--retry", + "2", + "--fail-fast", + "${@}", +] diff --git a/examples/csr_and_server_fns/e2e/README.md b/examples/csr_and_server_fns/e2e/README.md new file mode 100644 index 0000000000..026f2befd3 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/README.md @@ -0,0 +1,34 @@ +# E2E Testing + +This example demonstrates e2e testing with Rust using executable requirements. + +## Testing Stack + +| | Role | Description | +|---|---|---| +| [Cucumber](https://github.com/cucumber-rs/cucumber/tree/main) | Test Runner | Run [Gherkin](https://cucumber.io/docs/gherkin/reference/) specifications as Rust tests | +| [Fantoccini](https://github.com/jonhoo/fantoccini/tree/main) | Browser Client | Interact with web pages through WebDriver | +| [Cargo Leptos ](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests | +| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome + +## Testing Organization + +Testing is organized around what a user can do and see/not see. Test scenarios are grouped by the **user action** and the **object** of that action. This makes it easier to locate and reason about requirements. + +Here is a brief overview of how things fit together. + +```bash +features +└── {action}_{object}.feature # Specify test scenarios +tests +├── fixtures +│ ├── action.rs # Perform a user action (click, type, etc.) +│ ├── check.rs # Assert what a user can see/not see +│ ├── find.rs # Query page elements +│ ├── mod.rs +│ └── world +│ ├── action_steps.rs # Map Gherkin steps to user actions +│ ├── check_steps.rs # Map Gherkin steps to user expectations +│ └── mod.rs +└── app_suite.rs # Test main +``` diff --git a/examples/csr_and_server_fns/e2e/features/add_todo.feature b/examples/csr_and_server_fns/e2e/features/add_todo.feature new file mode 100644 index 0000000000..1a92258676 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/features/add_todo.feature @@ -0,0 +1,17 @@ +@add_todo +Feature: Add Todo + + Background: + Given I see the app + + @add_todo-see + Scenario: Should see the todo + Given I set the todo as Buy Bread + When I click the Add button + Then I see the todo named Buy Bread + + # @allow.skipped + @add_todo-style + Scenario: Should see the pending todo + When I add a todo as Buy Oranges + Then I see the pending todo diff --git a/examples/csr_and_server_fns/e2e/features/delete_todo.feature b/examples/csr_and_server_fns/e2e/features/delete_todo.feature new file mode 100644 index 0000000000..3c1e743d26 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/features/delete_todo.feature @@ -0,0 +1,18 @@ +@delete_todo +Feature: Delete Todo + + Background: + Given I see the app + + @serial + @delete_todo-remove + Scenario: Should not see the deleted todo + Given I add a todo as Buy Yogurt + When I delete the todo named Buy Yogurt + Then I do not see the todo named Buy Yogurt + + @serial + @delete_todo-message + Scenario: Should see the empty list message + When I empty the todo list + Then I see the empty list message is No tasks were found. \ No newline at end of file diff --git a/examples/csr_and_server_fns/e2e/features/open_app.feature b/examples/csr_and_server_fns/e2e/features/open_app.feature new file mode 100644 index 0000000000..f4b4e39529 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/features/open_app.feature @@ -0,0 +1,12 @@ +@open_app +Feature: Open App + + @open_app-title + Scenario: Should see the home page title + When I open the app + Then I see the page title is My Tasks + + @open_app-label + Scenario: Should see the input label + When I open the app + Then I see the label of the input is Add a Todo \ No newline at end of file diff --git a/examples/csr_and_server_fns/e2e/tests/app_suite.rs b/examples/csr_and_server_fns/e2e/tests/app_suite.rs new file mode 100644 index 0000000000..5c56b6aca8 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/app_suite.rs @@ -0,0 +1,14 @@ +mod fixtures; + +use anyhow::Result; +use cucumber::World; +use fixtures::world::AppWorld; + +#[tokio::main] +async fn main() -> Result<()> { + AppWorld::cucumber() + .fail_on_skipped() + .run_and_exit("./features") + .await; + Ok(()) +} diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/action.rs b/examples/csr_and_server_fns/e2e/tests/fixtures/action.rs new file mode 100644 index 0000000000..79b5c685ee --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/fixtures/action.rs @@ -0,0 +1,60 @@ +use super::{find, world::HOST}; +use anyhow::Result; +use fantoccini::Client; +use std::result::Result::Ok; +use tokio::{self, time}; + +pub async fn goto_path(client: &Client, path: &str) -> Result<()> { + let url = format!("{}{}", HOST, path); + client.goto(&url).await?; + + Ok(()) +} + +pub async fn add_todo(client: &Client, text: &str) -> Result<()> { + fill_todo(client, text).await?; + click_add_button(client).await?; + Ok(()) +} + +pub async fn fill_todo(client: &Client, text: &str) -> Result<()> { + let textbox = find::todo_input(client).await; + textbox.send_keys(text).await?; + + Ok(()) +} + +pub async fn click_add_button(client: &Client) -> Result<()> { + let add_button = find::add_button(client).await; + add_button.click().await?; + + Ok(()) +} + +pub async fn empty_todo_list(client: &Client) -> Result<()> { + let todos = find::todos(client).await; + + for _todo in todos { + let _ = delete_first_todo(client).await?; + } + + Ok(()) +} + +pub async fn delete_first_todo(client: &Client) -> Result<()> { + if let Some(element) = find::first_delete_button(client).await { + element.click().await.expect("Failed to delete todo"); + time::sleep(time::Duration::from_millis(250)).await; + } + + Ok(()) +} + +pub async fn delete_todo(client: &Client, text: &str) -> Result<()> { + if let Some(element) = find::delete_button(client, text).await { + element.click().await?; + time::sleep(time::Duration::from_millis(250)).await; + } + + Ok(()) +} diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/check.rs b/examples/csr_and_server_fns/e2e/tests/fixtures/check.rs new file mode 100644 index 0000000000..f43629b95c --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/fixtures/check.rs @@ -0,0 +1,57 @@ +use super::find; +use anyhow::{Ok, Result}; +use fantoccini::{Client, Locator}; +use pretty_assertions::assert_eq; + +pub async fn text_on_element( + client: &Client, + selector: &str, + expected_text: &str, +) -> Result<()> { + let element = client + .wait() + .for_element(Locator::Css(selector)) + .await + .expect( + format!("Element not found by Css selector `{}`", selector) + .as_str(), + ); + + let actual = element.text().await?; + assert_eq!(&actual, expected_text); + + Ok(()) +} + +pub async fn todo_present( + client: &Client, + text: &str, + expected: bool, +) -> Result<()> { + let todo_present = is_todo_present(client, text).await; + + assert_eq!(todo_present, expected); + + Ok(()) +} + +async fn is_todo_present(client: &Client, text: &str) -> bool { + let todos = find::todos(client).await; + + for todo in todos { + let todo_title = todo.text().await.expect("Todo title not found"); + if todo_title == text { + return true; + } + } + + false +} + +pub async fn todo_is_pending(client: &Client) -> Result<()> { + if let None = find::pending_todo(client).await { + assert!(false, "Pending todo not found"); + } + + Ok(()) +} diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/find.rs b/examples/csr_and_server_fns/e2e/tests/fixtures/find.rs new file mode 100644 index 0000000000..228fce6a28 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/fixtures/find.rs @@ -0,0 +1,63 @@ +use fantoccini::{elements::Element, Client, Locator}; + +pub async fn todo_input(client: &Client) -> Element { + let textbox = client + .wait() + .for_element(Locator::Css("input[name='title")) + .await + .expect("Todo textbox not found"); + + textbox +} + +pub async fn add_button(client: &Client) -> Element { + let button = client + .wait() + .for_element(Locator::Css("input[value='Add']")) + .await + .expect(""); + + button +} + +pub async fn first_delete_button(client: &Client) -> Option { + if let Ok(element) = client + .wait() + .for_element(Locator::Css("li:first-child input[value='X']")) + .await + { + return Some(element); + } + + None +} + +pub async fn delete_button(client: &Client, text: &str) -> Option { + let selector = format!("//*[text()='{text}']//input[@value='X']"); + if let Ok(element) = + client.wait().for_element(Locator::XPath(&selector)).await + { + return Some(element); + } + + None +} + +pub async fn pending_todo(client: &Client) -> Option { + if let Ok(element) = + client.wait().for_element(Locator::Css(".pending")).await + { + return Some(element); + } + + None +} + +pub async fn todos(client: &Client) -> Vec { + let todos = client + .find_all(Locator::Css("li")) + .await + .expect("Todo List not found"); + + todos +} diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/mod.rs b/examples/csr_and_server_fns/e2e/tests/fixtures/mod.rs new file mode 100644 index 0000000000..72b1bd65e4 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/fixtures/mod.rs @@ -0,0 +1,4 @@ +pub mod action; +pub mod check; +pub mod find; +pub mod world; diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/world/action_steps.rs b/examples/csr_and_server_fns/e2e/tests/fixtures/world/action_steps.rs new file mode 100644 index 0000000000..5c4e062dba --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/fixtures/world/action_steps.rs @@ -0,0 +1,57 @@ +use crate::fixtures::{action, world::AppWorld}; +use anyhow::{Ok, Result}; +use cucumber::{given, when}; + +#[given("I see the app")] +#[when("I open the app")] +async fn i_open_the_app(world: &mut AppWorld) -> Result<()> { + let client = &world.client; + action::goto_path(client, "").await?; + + Ok(()) +} + +#[given(regex = "^I add a todo as (.*)$")] +#[when(regex = "^I add a todo as (.*)$")] +async fn i_add_a_todo_titled(world: &mut AppWorld, text: String) -> Result<()> { + let client = &world.client; + action::add_todo(client, text.as_str()).await?; + + Ok(()) +} + +#[given(regex = "^I set the todo as (.*)$")] +async fn i_set_the_todo_as(world: &mut AppWorld, text: String) -> Result<()> { + let client = &world.client; + action::fill_todo(client, &text).await?; + + Ok(()) +} + +#[when(regex = "I click the Add button$")] +async fn i_click_the_button(world: &mut AppWorld) -> Result<()> { + let client = &world.client; + action::click_add_button(client).await?; + + Ok(()) +} + +#[when(regex = "^I delete the todo named (.*)$")] +async fn i_delete_the_todo_named( + world: &mut AppWorld, + text: String, +) -> Result<()> { + let client = &world.client; + action::delete_todo(client, text.as_str()).await?; + + Ok(()) +} + +#[given("the todo list is empty")] +#[when("I empty the todo list")] +async fn i_empty_the_todo_list(world: &mut AppWorld) -> Result<()> { + let client = &world.client; + action::empty_todo_list(client).await?; + + Ok(()) +} diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/world/check_steps.rs b/examples/csr_and_server_fns/e2e/tests/fixtures/world/check_steps.rs new file mode 100644 index 0000000000..3e51215dba --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/fixtures/world/check_steps.rs @@ -0,0 +1,67 @@ +use crate::fixtures::{check, world::AppWorld}; +use anyhow::{Ok, Result}; +use cucumber::then; + +#[then(regex = "^I see the page title is (.*)$")] +async fn i_see_the_page_title_is( + world: &mut AppWorld, + text: String, +) -> Result<()> { + let client = &world.client; + check::text_on_element(client, "h1", &text).await?; + + Ok(()) +} + +#[then(regex = "^I see the label of the input is (.*)$")] +async fn i_see_the_label_of_the_input_is( + world: &mut AppWorld, + text: String, +) -> Result<()> { + let client = &world.client; + check::text_on_element(client, "label", &text).await?; + + Ok(()) +} + +#[then(regex = "^I see the todo named (.*)$")] +async fn i_see_the_todo_is_present( + world: &mut AppWorld, + text: String, +) -> Result<()> { + let client = &world.client; + check::todo_present(client, text.as_str(), true).await?; + + Ok(()) +} + +#[then("I see the pending todo")] +async fn i_see_the_pending_todo(world: &mut AppWorld) -> Result<()> { + let client = &world.client; + + check::todo_is_pending(client).await?; + + Ok(()) +} + +#[then(regex = "^I see the empty list message is (.*)$")] +async fn i_see_the_empty_list_message_is( + world: &mut AppWorld, + text: String, +) -> Result<()> { + let client = &world.client; + check::text_on_element(client, "ul p", &text).await?; + + Ok(()) +} + +#[then(regex = "^I do not see the todo named (.*)$")] +async fn i_do_not_see_the_todo_is_present( + world: &mut AppWorld, + text: String, +) -> Result<()> { + let client = &world.client; + check::todo_present(client, text.as_str(), false).await?; + + Ok(()) +} diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/world/mod.rs b/examples/csr_and_server_fns/e2e/tests/fixtures/world/mod.rs new file mode 100644 index 0000000000..c25a925709 --- /dev/null +++ b/examples/csr_and_server_fns/e2e/tests/fixtures/world/mod.rs @@ -0,0 +1,39 @@ +pub mod action_steps; +pub mod check_steps; + +use anyhow::Result; +use cucumber::World; +use fantoccini::{ + error::NewSessionError, wd::Capabilities, Client, ClientBuilder, +}; + +pub const HOST: &str = "http://127.0.0.1:3000"; + +#[derive(Debug, World)] +#[world(init = Self::new)] +pub struct AppWorld { + pub client: Client, +} + +impl AppWorld { + async fn new() -> Result { + let webdriver_client = build_client().await?; + + Ok(Self { + client: webdriver_client, + }) + } +} + +async fn build_client() -> Result { + let mut cap = Capabilities::new(); + let arg = serde_json::from_str("{\"args\": [\"-headless\"]}").unwrap(); + cap.insert("goog:chromeOptions".to_string(), arg); + + let client = ClientBuilder::native() + .capabilities(cap) + .connect("http://localhost:4444") + .await?; + + Ok(client) +} diff --git a/examples/csr_and_server_fns/migrations/20221118172000_create_todo_table.sql b/examples/csr_and_server_fns/migrations/20221118172000_create_todo_table.sql new file mode 100644 index 0000000000..5bc74dcf27 --- /dev/null +++ b/examples/csr_and_server_fns/migrations/20221118172000_create_todo_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS todos +( + id INTEGER NOT NULL PRIMARY KEY, + title VARCHAR, + completed BOOLEAN +); \ No newline at end of file diff --git a/examples/csr_and_server_fns/public/favicon.ico b/examples/csr_and_server_fns/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2ba8527cb12f5f28f331b8d361eef560492d4c77 GIT binary patch literal 15406 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 k<4FtN!5 impl Responder { + actix_files::NamedFile::open_async("./style.css").await + } +} + +#[cfg(feature = "ssr")] +#[actix_web::main] +async fn main() -> std::io::Result<()> { + use self::{ssr::*, todo::ssr::*}; + + let mut conn = db().await.expect("couldn't connect to DB"); + sqlx::migrate!() + .run(&mut conn) + .await + .expect("could not run SQLx migrations"); + + // Explicit server function registration is no longer required + // on the main branch. On 0.3.0 and earlier, uncomment the lines + // below to register the server functions. + // _ = GetTodos::register(); + // _ = AddTodo::register(); + // _ = DeleteTodo::register(); + + // Setting this to None means we'll be using cargo-leptos and its env vars. + let conf = get_configuration(None).await.unwrap(); + + let addr = conf.leptos_options.site_addr; + + // Generate the list of routes in your Leptos App + let routes = generate_route_list(TodoApp); + + HttpServer::new(move || { + let leptos_options = &conf.leptos_options; + let site_root = &leptos_options.site_root; + let routes = &routes; + + App::new() + .service(css) + .leptos_routes( + leptos_options.to_owned(), + routes.to_owned(), + TodoApp, + ) + .service(Files::new("/", site_root)) + //.wrap(middleware::Compress::default()) + }) + .bind(addr)? + .run() + .await +} diff --git a/examples/csr_and_server_fns/src/todo.rs b/examples/csr_and_server_fns/src/todo.rs new file mode 100644 index 0000000000..cb0095782a --- /dev/null +++ b/examples/csr_and_server_fns/src/todo.rs @@ -0,0 +1,199 @@ +use leptos::*; +use leptos_meta::*; +use leptos_router::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] +pub struct Todo { + id: u16, + title: String, + completed: bool, +} + +#[cfg(feature = "ssr")] +pub mod ssr { + pub use actix_web::HttpRequest; + pub use leptos::ServerFnError; + pub use sqlx::{Connection, SqliteConnection}; + + pub async fn db() -> Result { + Ok(SqliteConnection::connect("sqlite:Todos.db").await?) + } +} + +/// This is an example of a server function using an alternative CBOR encoding. Both the function arguments being sent +/// to the server and the server response will be encoded with CBOR. Good for binary data that doesn't encode well via the default methods +#[server(encoding = "Cbor")] +pub async fn get_todos() -> Result, ServerFnError> { + use self::ssr::*; + + // this is just an example of how to access server context injected in the handlers + let req = use_context::(); + + if let Some(req) = req { + println!("req.path = {:#?}", req.path()); + } + use futures::TryStreamExt; + + let mut conn = db().await?; + + let mut todos = Vec::new(); + let mut rows = + sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn); + while let Some(row) = rows.try_next().await? { + todos.push(row); + } + + Ok(todos) +} + +#[server] +pub async fn add_todo(title: String) -> Result<(), ServerFnError> { + use self::ssr::*; + + let mut conn = db().await?; + + // fake API delay + std::thread::sleep(std::time::Duration::from_millis(1250)); + + match sqlx::query("INSERT INTO todos (title, completed) VALUES ($1, false)") + .bind(title) + .execute(&mut conn) + .await + { + Ok(_row) => Ok(()), + Err(e) => Err(ServerFnError::ServerError(e.to_string())), + } +} + +// The struct name and path prefix arguments are optional. +#[server] +pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { + use self::ssr::*; + + let mut conn = db().await?; + + Ok(sqlx::query("DELETE FROM todos WHERE id = $1") + .bind(id) + .execute(&mut conn) + .await + .map(|_| ())?) +} + +#[component] +pub fn TodoApp() -> impl IntoView { + provide_meta_context(); + + view! { + + + +

+

"My Tasks"

+
+
+ + + +
+ + } +} + +#[component] +pub fn Todos() -> impl IntoView { + let add_todo = create_server_multi_action::(); + let delete_todo = create_server_action::(); + let submissions = add_todo.submissions(); + + // list of todos is loaded from the server in reaction to changes + let todos = create_resource( + move || (add_todo.version().get(), delete_todo.version().get()), + move |_| get_todos(), + ); + + view! { +
+ + + + + "Loading..."

}> + {move || { + let existing_todos = { + move || { + todos.get() + .map(move |todos| match todos { + Err(e) => { + view! {
"Server Error: " {e.to_string()}
}.into_view() + } + Ok(todos) => { + if todos.is_empty() { + view! {

"No tasks were found."

}.into_view() + } else { + todos + .into_iter() + .map(move |todo| { + view! { + +
  • + {todo.title} + + + + +
  • + } + }) + .collect_view() + } + } + }) + .unwrap_or_default() + } + }; + + let pending_todos = move || { + submissions + .get() + .into_iter() + .filter(|submission| submission.pending().get()) + .map(|submission| { + view! { + +
  • {move || submission.input.get().map(|data| data.title) }
  • + } + }) + .collect_view() + }; + + view! { + +
      + {existing_todos} + {pending_todos} +
    + } + } + } +
    +
    + } +} diff --git a/examples/csr_and_server_fns/style.css b/examples/csr_and_server_fns/style.css new file mode 100644 index 0000000000..152dd11327 --- /dev/null +++ b/examples/csr_and_server_fns/style.css @@ -0,0 +1,3 @@ +.pending { + color: purple; +} \ No newline at end of file From e3d52d561a8ab58eb4cf746614419cf268b20479 Mon Sep 17 00:00:00 2001 From: Carson Rajcan Date: Sat, 10 Feb 2024 10:28:20 -0600 Subject: [PATCH 2/6] get Trunk working --- examples/csr_and_server_fns/Cargo.toml | 11 +++-------- examples/csr_and_server_fns/Todos.db | Bin 16384 -> 16384 bytes examples/csr_and_server_fns/Trunk.toml | 15 ++++++++++++++ examples/csr_and_server_fns/index.html | 10 ++++++++++ examples/csr_and_server_fns/src/lib.rs | 16 +++++++-------- examples/csr_and_server_fns/src/main.rs | 25 +++++++++++++----------- examples/csr_and_server_fns/src/todo.rs | 4 ++-- 7 files changed, 52 insertions(+), 29 deletions(-) create mode 100644 examples/csr_and_server_fns/Trunk.toml create mode 100644 examples/csr_and_server_fns/index.html diff --git a/examples/csr_and_server_fns/Cargo.toml b/examples/csr_and_server_fns/Cargo.toml index 5e0727fb69..94864cf4a2 100644 --- a/examples/csr_and_server_fns/Cargo.toml +++ b/examples/csr_and_server_fns/Cargo.toml @@ -30,17 +30,14 @@ wasm-bindgen = "0.2" tokio = { version = "1", features = ["rt", "time"], optional = true } [features] -hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] -ssr = [ +server = [ "dep:actix-files", "dep:actix-web", "dep:sqlx", - "leptos/ssr", "leptos_actix", - "leptos_meta/ssr", - "leptos_router/ssr", "dep:tokio", ] +csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] [package.metadata.cargo-all-features] denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"] @@ -48,7 +45,7 @@ skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]] [package.metadata.leptos] # The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name -output-name = "todo_app_sqlite" +output-name = "server" # The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. site-root = "target/site" # The site-root relative folder where all compiled output (JS, WASM and CSS) is written @@ -73,8 +70,6 @@ watch = false env = "DEV" # The features to use when compiling the bin target # -# Optional. Can be over-ridden with the command line parameter --bin-features -bin-features = ["ssr"] # If the --no-default-features flag should be used when compiling the bin target # diff --git a/examples/csr_and_server_fns/Todos.db b/examples/csr_and_server_fns/Todos.db index e1b64243f84cd3291906c53e7cce974a3963f6ac..d30f8d6394dfa1997e96e99235d4ea3555bdff4b 100644 GIT binary patch delta 77 zcmZo@U~Fh$oFL7(bE1qh + + + + + + + + + \ No newline at end of file diff --git a/examples/csr_and_server_fns/src/lib.rs b/examples/csr_and_server_fns/src/lib.rs index 74f9012fcd..fc0d897a0b 100644 --- a/examples/csr_and_server_fns/src/lib.rs +++ b/examples/csr_and_server_fns/src/lib.rs @@ -1,11 +1,11 @@ pub mod todo; -#[cfg(feature = "hydrate")] -#[wasm_bindgen::prelude::wasm_bindgen] -pub fn hydrate() { - use crate::todo::*; - console_error_panic_hook::set_once(); - _ = console_log::init_with_level(log::Level::Debug); +// #[cfg(feature = "hydrate")] +// #[wasm_bindgen::prelude::wasm_bindgen] +// pub fn hydrate() { +// use crate::todo::*; +// console_error_panic_hook::set_once(); +// _ = console_log::init_with_level(log::Level::Debug); - leptos::mount_to_body(TodoApp); -} +// leptos::mount_to_body(TodoApp); +// } diff --git a/examples/csr_and_server_fns/src/main.rs b/examples/csr_and_server_fns/src/main.rs index f893c85e72..a7e262b8d9 100644 --- a/examples/csr_and_server_fns/src/main.rs +++ b/examples/csr_and_server_fns/src/main.rs @@ -1,6 +1,6 @@ mod todo; -#[cfg(feature = "ssr")] +#[cfg(feature = "server")] mod ssr { pub use crate::todo::*; pub use actix_files::Files; @@ -14,7 +14,7 @@ mod ssr { } } -#[cfg(feature = "ssr")] +#[cfg(feature = "server")] #[actix_web::main] async fn main() -> std::io::Result<()> { use self::{ssr::*, todo::ssr::*}; @@ -25,20 +25,15 @@ async fn main() -> std::io::Result<()> { .await .expect("could not run SQLx migrations"); - // Explicit server function registration is no longer required - // on the main branch. On 0.3.0 and earlier, uncomment the lines - // below to register the server functions. - // _ = GetTodos::register(); - // _ = AddTodo::register(); - // _ = DeleteTodo::register(); - // Setting this to None means we'll be using cargo-leptos and its env vars. - let conf = get_configuration(None).await.unwrap(); + let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); let addr = conf.leptos_options.site_addr; + println!("Server functions available at http://{}", &addr); // Generate the list of routes in your Leptos App let routes = generate_route_list(TodoApp); + println!("routes: {:?}", routes); HttpServer::new(move || { let leptos_options = &conf.leptos_options; @@ -53,9 +48,17 @@ async fn main() -> std::io::Result<()> { TodoApp, ) .service(Files::new("/", site_root)) - //.wrap(middleware::Compress::default()) }) .bind(addr)? .run() .await } + +#[cfg(feature = "csr")] +pub fn main() { + use crate::todo::*; + console_error_panic_hook::set_once(); + _ = console_log::init_with_level(log::Level::Debug); + + leptos::mount_to_body(TodoApp); +} \ No newline at end of file diff --git a/examples/csr_and_server_fns/src/todo.rs b/examples/csr_and_server_fns/src/todo.rs index cb0095782a..cf86707d32 100644 --- a/examples/csr_and_server_fns/src/todo.rs +++ b/examples/csr_and_server_fns/src/todo.rs @@ -4,14 +4,14 @@ use leptos_router::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] +#[cfg_attr(feature = "server", derive(sqlx::FromRow))] pub struct Todo { id: u16, title: String, completed: bool, } -#[cfg(feature = "ssr")] +#[cfg(feature = "server")] pub mod ssr { pub use actix_web::HttpRequest; pub use leptos::ServerFnError; From cf990de05f5e13eec3675d40477a397d39aef3ec Mon Sep 17 00:00:00 2001 From: Carson Rajcan Date: Sat, 10 Feb 2024 10:34:45 -0600 Subject: [PATCH 3/6] Stop serving ssr routes --- examples/csr_and_server_fns/Cargo.toml | 2 +- examples/csr_and_server_fns/Todos.db | Bin 16384 -> 16384 bytes examples/csr_and_server_fns/src/main.rs | 8 ++------ 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/csr_and_server_fns/Cargo.toml b/examples/csr_and_server_fns/Cargo.toml index 94864cf4a2..ae125c73ec 100644 --- a/examples/csr_and_server_fns/Cargo.toml +++ b/examples/csr_and_server_fns/Cargo.toml @@ -79,7 +79,7 @@ bin-default-features = false # The features to use when compiling the lib target # # Optional. Can be over-ridden with the command line parameter --lib-features -lib-features = ["hydrate"] +# lib-features = ["hydrate"] # If the --no-default-features flag should be used when compiling the lib target # diff --git a/examples/csr_and_server_fns/Todos.db b/examples/csr_and_server_fns/Todos.db index d30f8d6394dfa1997e96e99235d4ea3555bdff4b..ccdcad5e24d4605f2633f84b9a872a5f5c96dcf2 100644 GIT binary patch delta 65 zcmZo@U~Fh$oFL7(Z=#GdUy6Z&L0*<4Eu}auF|jxW T$P*OgNJ|6KoST2kvnl`pYl9IM delta 65 zcmZo@U~Fh$oFL7(bE1qh std::io::Result<()> { let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); let addr = conf.leptos_options.site_addr; - println!("Server functions available at http://{}", &addr); + println!("Server functions available at http://{}", &addr); // Generate the list of routes in your Leptos App let routes = generate_route_list(TodoApp); @@ -42,11 +42,7 @@ async fn main() -> std::io::Result<()> { App::new() .service(css) - .leptos_routes( - leptos_options.to_owned(), - routes.to_owned(), - TodoApp, - ) + .route("/api/{tail:.*}", leptos_actix::handle_server_fns()) .service(Files::new("/", site_root)) }) .bind(addr)? From e87d4e31322135e07c2df7682ce0231406275a58 Mon Sep 17 00:00:00 2001 From: Carson Rajcan Date: Sat, 10 Feb 2024 10:40:17 -0600 Subject: [PATCH 4/6] Add back css --- examples/csr_and_server_fns/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/csr_and_server_fns/index.html b/examples/csr_and_server_fns/index.html index ade825e11e..da606bc57a 100644 --- a/examples/csr_and_server_fns/index.html +++ b/examples/csr_and_server_fns/index.html @@ -3,6 +3,7 @@ + From 4fc57b62d98f837a43e650799c7dc0dc88b76ec3 Mon Sep 17 00:00:00 2001 From: Carson Rajcan Date: Sat, 10 Feb 2024 11:53:38 -0600 Subject: [PATCH 5/6] Cleanup --- examples/csr_and_server_fns/Cargo.toml | 60 ++---------------- examples/csr_and_server_fns/README.md | 24 +++---- examples/csr_and_server_fns/Todos.db | Bin 16384 -> 16384 bytes examples/csr_and_server_fns/e2e/README.md | 1 - .../e2e/tests/fixtures/world/mod.rs | 2 +- examples/csr_and_server_fns/index.html | 3 +- examples/csr_and_server_fns/src/lib.rs | 10 --- examples/csr_and_server_fns/src/main.rs | 42 ++---------- examples/csr_and_server_fns/src/todo.rs | 8 +-- 9 files changed, 28 insertions(+), 122 deletions(-) diff --git a/examples/csr_and_server_fns/Cargo.toml b/examples/csr_and_server_fns/Cargo.toml index ae125c73ec..3bf6c449a7 100644 --- a/examples/csr_and_server_fns/Cargo.toml +++ b/examples/csr_and_server_fns/Cargo.toml @@ -1,16 +1,15 @@ [package] -name = "todo_app_sqlite" +name = "todo_app_sqlite_csr" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] +name = "front" [dependencies] actix-files = { version = "0.6.2", optional = true } actix-web = { version = "4.2.1", optional = true, features = ["macros"] } -anyhow = "1.0.68" -broadcaster = "1.0.0" console_log = "1.0.0" console_error_panic_hook = "0.1.7" serde = { version = "1.0.152", features = ["derive"] } @@ -20,68 +19,17 @@ leptos_actix = { path = "../../integrations/actix", optional = true } leptos_meta = { path = "../../meta", features = ["nightly"] } leptos_router = { path = "../../router", features = ["nightly"] } log = "0.4.17" -simple_logger = "4.0.0" -gloo = { git = "https://github.com/rustwasm/gloo" } -sqlx = { version = "0.6.2", features = [ - "runtime-tokio-rustls", - "sqlite", -], optional = true } +sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite"], optional = true } wasm-bindgen = "0.2" tokio = { version = "1", features = ["rt", "time"], optional = true } [features] server = [ - "dep:actix-files", "dep:actix-web", "dep:sqlx", "leptos_actix", "dep:tokio", ] -csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] [package.metadata.cargo-all-features] -denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"] -skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]] - -[package.metadata.leptos] -# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name -output-name = "server" -# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. -site-root = "target/site" -# The site-root relative folder where all compiled output (JS, WASM and CSS) is written -# Defaults to pkg -site-pkg-dir = "pkg" -# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css -style-file = "./style.css" -# [Optional] Files in the asset-dir will be copied to the site-root directory -assets-dir = "public" -# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup. -site-addr = "127.0.0.1:3000" -# The port to use for automatic reload monitoring -reload-port = 3001 -# [Optional] Command to use when running end2end tests. It will run in the end2end dir. -end2end-cmd = "cargo make test-ui" -end2end-dir = "e2e" -# The browserlist query used for optimizing the CSS. -browserquery = "defaults" -# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head -watch = false -# The environment Leptos will run in, usually either "DEV" or "PROD" -env = "DEV" -# The features to use when compiling the bin target -# - -# If the --no-default-features flag should be used when compiling the bin target -# -# Optional. Defaults to false. -bin-default-features = false - -# The features to use when compiling the lib target -# -# Optional. Can be over-ridden with the command line parameter --lib-features -# lib-features = ["hydrate"] - -# If the --no-default-features flag should be used when compiling the lib target -# -# Optional. Defaults to false. -lib-default-features = false +denylist = ["actix-web", "leptos_actix", "sqlx"] \ No newline at end of file diff --git a/examples/csr_and_server_fns/README.md b/examples/csr_and_server_fns/README.md index 1a2ceade58..6a3ca980d9 100644 --- a/examples/csr_and_server_fns/README.md +++ b/examples/csr_and_server_fns/README.md @@ -1,19 +1,15 @@ -# Leptos Todo App Sqlite +# Leptos Todo App Without SSR -This example creates a basic todo app with an Actix backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server. +This is a minimal example of how to use Leptos' server functions without using Server Side Rendering or cargo-leptos. -## Getting Started +To run the app, first start the server. -See the [Examples README](../README.md) for setup and run instructions. +``` +cargo run --features=server +``` -## E2E Testing +Then use [Trunk](https://trunkrs.dev/) to build and serve the web assets: -See the [E2E README](./e2e/README.md) for more information about the testing strategy. - -## Rendering - -See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering. - -## Quick Start - -Run `cargo leptos watch` to run this example. +``` +trunk serve +``` diff --git a/examples/csr_and_server_fns/Todos.db b/examples/csr_and_server_fns/Todos.db index ccdcad5e24d4605f2633f84b9a872a5f5c96dcf2..ec85d2b07f9ac6b3b931f4599e3e7a35107050f5 100644 GIT binary patch delta 145 zcmZo@U~Fh$oFK)-;672tiH*UXL9b}R#+14IvizSInE9VD@c-n0&Hse|6aQWQ?fmOu zaI>JnME*cFNfrh{4li$32^I!^4lf1<26tofPsPkArL<0zr%kG2 - + + diff --git a/examples/csr_and_server_fns/src/lib.rs b/examples/csr_and_server_fns/src/lib.rs index fc0d897a0b..ff6eb8ecd4 100644 --- a/examples/csr_and_server_fns/src/lib.rs +++ b/examples/csr_and_server_fns/src/lib.rs @@ -1,11 +1 @@ pub mod todo; - -// #[cfg(feature = "hydrate")] -// #[wasm_bindgen::prelude::wasm_bindgen] -// pub fn hydrate() { -// use crate::todo::*; -// console_error_panic_hook::set_once(); -// _ = console_log::init_with_level(log::Level::Debug); - -// leptos::mount_to_body(TodoApp); -// } diff --git a/examples/csr_and_server_fns/src/main.rs b/examples/csr_and_server_fns/src/main.rs index 6cbd066614..f744342bed 100644 --- a/examples/csr_and_server_fns/src/main.rs +++ b/examples/csr_and_server_fns/src/main.rs @@ -1,23 +1,10 @@ mod todo; -#[cfg(feature = "server")] -mod ssr { - pub use crate::todo::*; - pub use actix_files::Files; - pub use actix_web::*; - pub use leptos::*; - pub use leptos_actix::{generate_route_list, LeptosRoutes}; - - #[get("/style.css")] - pub async fn css() -> impl Responder { - actix_files::NamedFile::open_async("./style.css").await - } -} - #[cfg(feature = "server")] #[actix_web::main] async fn main() -> std::io::Result<()> { - use self::{ssr::*, todo::ssr::*}; + use self::todo::server::*; + use actix_web::*; let mut conn = db().await.expect("couldn't connect to DB"); sqlx::migrate!() @@ -25,36 +12,21 @@ async fn main() -> std::io::Result<()> { .await .expect("could not run SQLx migrations"); - // Setting this to None means we'll be using cargo-leptos and its env vars. - let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); - - let addr = conf.leptos_options.site_addr; - println!("Server functions available at http://{}", &addr); - - // Generate the list of routes in your Leptos App - let routes = generate_route_list(TodoApp); - println!("routes: {:?}", routes); + let addr = "127.0.0.1:3000"; HttpServer::new(move || { - let leptos_options = &conf.leptos_options; - let site_root = &leptos_options.site_root; - let routes = &routes; - - App::new() - .service(css) - .route("/api/{tail:.*}", leptos_actix::handle_server_fns()) - .service(Files::new("/", site_root)) + App::new().route("/api/{tail:.*}", leptos_actix::handle_server_fns()) }) .bind(addr)? .run() .await } - -#[cfg(feature = "csr")] + +#[cfg(not(feature = "server"))] pub fn main() { use crate::todo::*; console_error_panic_hook::set_once(); _ = console_log::init_with_level(log::Level::Debug); leptos::mount_to_body(TodoApp); -} \ No newline at end of file +} diff --git a/examples/csr_and_server_fns/src/todo.rs b/examples/csr_and_server_fns/src/todo.rs index cf86707d32..745186dbee 100644 --- a/examples/csr_and_server_fns/src/todo.rs +++ b/examples/csr_and_server_fns/src/todo.rs @@ -12,7 +12,7 @@ pub struct Todo { } #[cfg(feature = "server")] -pub mod ssr { +pub mod server { pub use actix_web::HttpRequest; pub use leptos::ServerFnError; pub use sqlx::{Connection, SqliteConnection}; @@ -26,7 +26,7 @@ pub mod ssr { /// to the server and the server response will be encoded with CBOR. Good for binary data that doesn't encode well via the default methods #[server(encoding = "Cbor")] pub async fn get_todos() -> Result, ServerFnError> { - use self::ssr::*; + use self::server::*; // this is just an example of how to access server context injected in the handlers let req = use_context::(); @@ -50,7 +50,7 @@ pub async fn get_todos() -> Result, ServerFnError> { #[server] pub async fn add_todo(title: String) -> Result<(), ServerFnError> { - use self::ssr::*; + use self::server::*; let mut conn = db().await?; @@ -70,7 +70,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> { // The struct name and path prefix arguments are optional. #[server] pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { - use self::ssr::*; + use self::server::*; let mut conn = db().await?; From c908ede739198f5620ae8026c365257a89c641a5 Mon Sep 17 00:00:00 2001 From: Carson Rajcan Date: Sat, 10 Feb 2024 12:30:50 -0600 Subject: [PATCH 6/6] Rename --- .../.gitignore | 0 .../Cargo.toml | 0 .../LICENSE | 0 .../Makefile.toml | 0 .../README.md | 0 .../Todos.db | Bin .../Trunk.toml | 0 .../e2e/Cargo.toml | 0 .../e2e/Makefile.toml | 0 .../e2e/README.md | 0 .../e2e/features/add_todo.feature | 0 .../e2e/features/delete_todo.feature | 0 .../e2e/features/open_app.feature | 0 .../e2e/tests/app_suite.rs | 0 .../e2e/tests/fixtures/action.rs | 0 .../e2e/tests/fixtures/check.rs | 0 .../e2e/tests/fixtures/find.rs | 0 .../e2e/tests/fixtures/mod.rs | 0 .../e2e/tests/fixtures/world/action_steps.rs | 0 .../e2e/tests/fixtures/world/check_steps.rs | 0 .../e2e/tests/fixtures/world/mod.rs | 0 .../index.html | 0 .../migrations/20221118172000_create_todo_table.sql | 0 .../public/favicon.ico | Bin .../rust-toolchain.toml | 0 .../src/lib.rs | 0 .../src/main.rs | 0 .../src/todo.rs | 0 .../style.css | 0 29 files changed, 0 insertions(+), 0 deletions(-) rename examples/{csr_and_server_fns => trunk_and_server_fns}/.gitignore (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/Cargo.toml (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/LICENSE (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/Makefile.toml (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/README.md (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/Todos.db (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/Trunk.toml (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/Cargo.toml (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/Makefile.toml (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/README.md (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/features/add_todo.feature (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/features/delete_todo.feature (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/features/open_app.feature (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/app_suite.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/fixtures/action.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/fixtures/check.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/fixtures/find.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/fixtures/mod.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/fixtures/world/action_steps.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/fixtures/world/check_steps.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/e2e/tests/fixtures/world/mod.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/index.html (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/migrations/20221118172000_create_todo_table.sql (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/public/favicon.ico (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/rust-toolchain.toml (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/src/lib.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/src/main.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/src/todo.rs (100%) rename examples/{csr_and_server_fns => trunk_and_server_fns}/style.css (100%) diff --git a/examples/csr_and_server_fns/.gitignore b/examples/trunk_and_server_fns/.gitignore similarity index 100% rename from examples/csr_and_server_fns/.gitignore rename to examples/trunk_and_server_fns/.gitignore diff --git a/examples/csr_and_server_fns/Cargo.toml b/examples/trunk_and_server_fns/Cargo.toml similarity index 100% rename from examples/csr_and_server_fns/Cargo.toml rename to examples/trunk_and_server_fns/Cargo.toml diff --git a/examples/csr_and_server_fns/LICENSE b/examples/trunk_and_server_fns/LICENSE similarity index 100% rename from examples/csr_and_server_fns/LICENSE rename to examples/trunk_and_server_fns/LICENSE diff --git a/examples/csr_and_server_fns/Makefile.toml b/examples/trunk_and_server_fns/Makefile.toml similarity index 100% rename from examples/csr_and_server_fns/Makefile.toml rename to examples/trunk_and_server_fns/Makefile.toml diff --git a/examples/csr_and_server_fns/README.md b/examples/trunk_and_server_fns/README.md similarity index 100% rename from examples/csr_and_server_fns/README.md rename to examples/trunk_and_server_fns/README.md diff --git a/examples/csr_and_server_fns/Todos.db b/examples/trunk_and_server_fns/Todos.db similarity index 100% rename from examples/csr_and_server_fns/Todos.db rename to examples/trunk_and_server_fns/Todos.db diff --git a/examples/csr_and_server_fns/Trunk.toml b/examples/trunk_and_server_fns/Trunk.toml similarity index 100% rename from examples/csr_and_server_fns/Trunk.toml rename to examples/trunk_and_server_fns/Trunk.toml diff --git a/examples/csr_and_server_fns/e2e/Cargo.toml b/examples/trunk_and_server_fns/e2e/Cargo.toml similarity index 100% rename from examples/csr_and_server_fns/e2e/Cargo.toml rename to examples/trunk_and_server_fns/e2e/Cargo.toml diff --git a/examples/csr_and_server_fns/e2e/Makefile.toml b/examples/trunk_and_server_fns/e2e/Makefile.toml similarity index 100% rename from examples/csr_and_server_fns/e2e/Makefile.toml rename to examples/trunk_and_server_fns/e2e/Makefile.toml diff --git a/examples/csr_and_server_fns/e2e/README.md b/examples/trunk_and_server_fns/e2e/README.md similarity index 100% rename from examples/csr_and_server_fns/e2e/README.md rename to examples/trunk_and_server_fns/e2e/README.md diff --git a/examples/csr_and_server_fns/e2e/features/add_todo.feature b/examples/trunk_and_server_fns/e2e/features/add_todo.feature similarity index 100% rename from examples/csr_and_server_fns/e2e/features/add_todo.feature rename to examples/trunk_and_server_fns/e2e/features/add_todo.feature diff --git a/examples/csr_and_server_fns/e2e/features/delete_todo.feature b/examples/trunk_and_server_fns/e2e/features/delete_todo.feature similarity index 100% rename from examples/csr_and_server_fns/e2e/features/delete_todo.feature rename to examples/trunk_and_server_fns/e2e/features/delete_todo.feature diff --git a/examples/csr_and_server_fns/e2e/features/open_app.feature b/examples/trunk_and_server_fns/e2e/features/open_app.feature similarity index 100% rename from examples/csr_and_server_fns/e2e/features/open_app.feature rename to examples/trunk_and_server_fns/e2e/features/open_app.feature diff --git a/examples/csr_and_server_fns/e2e/tests/app_suite.rs b/examples/trunk_and_server_fns/e2e/tests/app_suite.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/app_suite.rs rename to examples/trunk_and_server_fns/e2e/tests/app_suite.rs diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/action.rs b/examples/trunk_and_server_fns/e2e/tests/fixtures/action.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/fixtures/action.rs rename to examples/trunk_and_server_fns/e2e/tests/fixtures/action.rs diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/check.rs b/examples/trunk_and_server_fns/e2e/tests/fixtures/check.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/fixtures/check.rs rename to examples/trunk_and_server_fns/e2e/tests/fixtures/check.rs diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/find.rs b/examples/trunk_and_server_fns/e2e/tests/fixtures/find.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/fixtures/find.rs rename to examples/trunk_and_server_fns/e2e/tests/fixtures/find.rs diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/mod.rs b/examples/trunk_and_server_fns/e2e/tests/fixtures/mod.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/fixtures/mod.rs rename to examples/trunk_and_server_fns/e2e/tests/fixtures/mod.rs diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/world/action_steps.rs b/examples/trunk_and_server_fns/e2e/tests/fixtures/world/action_steps.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/fixtures/world/action_steps.rs rename to examples/trunk_and_server_fns/e2e/tests/fixtures/world/action_steps.rs diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/world/check_steps.rs b/examples/trunk_and_server_fns/e2e/tests/fixtures/world/check_steps.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/fixtures/world/check_steps.rs rename to examples/trunk_and_server_fns/e2e/tests/fixtures/world/check_steps.rs diff --git a/examples/csr_and_server_fns/e2e/tests/fixtures/world/mod.rs b/examples/trunk_and_server_fns/e2e/tests/fixtures/world/mod.rs similarity index 100% rename from examples/csr_and_server_fns/e2e/tests/fixtures/world/mod.rs rename to examples/trunk_and_server_fns/e2e/tests/fixtures/world/mod.rs diff --git a/examples/csr_and_server_fns/index.html b/examples/trunk_and_server_fns/index.html similarity index 100% rename from examples/csr_and_server_fns/index.html rename to examples/trunk_and_server_fns/index.html diff --git a/examples/csr_and_server_fns/migrations/20221118172000_create_todo_table.sql b/examples/trunk_and_server_fns/migrations/20221118172000_create_todo_table.sql similarity index 100% rename from examples/csr_and_server_fns/migrations/20221118172000_create_todo_table.sql rename to examples/trunk_and_server_fns/migrations/20221118172000_create_todo_table.sql diff --git a/examples/csr_and_server_fns/public/favicon.ico b/examples/trunk_and_server_fns/public/favicon.ico similarity index 100% rename from examples/csr_and_server_fns/public/favicon.ico rename to examples/trunk_and_server_fns/public/favicon.ico diff --git a/examples/csr_and_server_fns/rust-toolchain.toml b/examples/trunk_and_server_fns/rust-toolchain.toml similarity index 100% rename from examples/csr_and_server_fns/rust-toolchain.toml rename to examples/trunk_and_server_fns/rust-toolchain.toml diff --git a/examples/csr_and_server_fns/src/lib.rs b/examples/trunk_and_server_fns/src/lib.rs similarity index 100% rename from examples/csr_and_server_fns/src/lib.rs rename to examples/trunk_and_server_fns/src/lib.rs diff --git a/examples/csr_and_server_fns/src/main.rs b/examples/trunk_and_server_fns/src/main.rs similarity index 100% rename from examples/csr_and_server_fns/src/main.rs rename to examples/trunk_and_server_fns/src/main.rs diff --git a/examples/csr_and_server_fns/src/todo.rs b/examples/trunk_and_server_fns/src/todo.rs similarity index 100% rename from examples/csr_and_server_fns/src/todo.rs rename to examples/trunk_and_server_fns/src/todo.rs diff --git a/examples/csr_and_server_fns/style.css b/examples/trunk_and_server_fns/style.css similarity index 100% rename from examples/csr_and_server_fns/style.css rename to examples/trunk_and_server_fns/style.css