Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example: server-fns without cargo-leptos/ssr #2290

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/trunk_and_server_fns/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.leptos.kdl
35 changes: 35 additions & 0 deletions examples/trunk_and_server_fns/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
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"] }
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"
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-web",
"dep:sqlx",
"leptos_actix",
"dep:tokio",
]

[package.metadata.cargo-all-features]
denylist = ["actix-web", "leptos_actix", "sqlx"]
21 changes: 21 additions & 0 deletions examples/trunk_and_server_fns/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions examples/trunk_and_server_fns/Makefile.toml
Original file line number Diff line number Diff line change
@@ -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", "${@}"]
15 changes: 15 additions & 0 deletions examples/trunk_and_server_fns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Leptos Todo App Without SSR

This is a minimal example of how to use Leptos' server functions without using Server Side Rendering or cargo-leptos.

To run the app, first start the server.

```
cargo run --features=server
```

Then use [Trunk](https://trunkrs.dev/) to build and serve the web assets:

```
trunk serve
```
Binary file added examples/trunk_and_server_fns/Todos.db
Binary file not shown.
15 changes: 15 additions & 0 deletions examples/trunk_and_server_fns/Trunk.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[build]
# The index HTML file to drive the bundling process.
target = "index.html"

[serve]
address = "127.0.0.1"
port = 9000

[watch]
watch = ["./"]

[[proxy]]
# This proxy specifies only the backend, which is the only required field. In this example,
# request URIs are not modified when proxied.
backend = "http://localhost:3000/api/"
18 changes: 18 additions & 0 deletions examples/trunk_and_server_fns/e2e/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions examples/trunk_and_server_fns/e2e/Makefile.toml
Original file line number Diff line number Diff line change
@@ -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",
"${@}",
]
33 changes: 33 additions & 0 deletions examples/trunk_and_server_fns/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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 |
| [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
```
17 changes: 17 additions & 0 deletions examples/trunk_and_server_fns/e2e/features/add_todo.feature
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions examples/trunk_and_server_fns/e2e/features/delete_todo.feature
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions examples/trunk_and_server_fns/e2e/features/open_app.feature
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions examples/trunk_and_server_fns/e2e/tests/app_suite.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
60 changes: 60 additions & 0 deletions examples/trunk_and_server_fns/e2e/tests/fixtures/action.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
57 changes: 57 additions & 0 deletions examples/trunk_and_server_fns/e2e/tests/fixtures/check.rs
Original file line number Diff line number Diff line change
@@ -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(())
}