Skip to content

Commit

Permalink
docs(examples): added js interop example
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jul 12, 2022
1 parent 191d8c9 commit 766dd44
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 0 deletions.
3 changes: 3 additions & 0 deletions examples/core/js_interop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
target_engine/
target_wasm/
35 changes: 35 additions & 0 deletions examples/core/js_interop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "perseus-example-js-interop"
version = "0.4.0-beta.3"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] }
sycamore = "=0.8.0-beta.7"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
fantoccini = "0.17"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] }
# This is an internal convenience crate that exposes all integrations through features for testing
perseus-integration = { path = "../../../packages/perseus-integration", default-features = false }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"

[lib]
name = "lib"
path = "src/lib.rs"
crate-type = [ "cdylib", "rlib" ]

[[bin]]
name = "perseus-example-js-interop"
path = "src/lib.rs"

[package.metadata.wasm-pack.profile.release]
wasm-opt = [ "-Oz" ]
28 changes: 28 additions & 0 deletions examples/core/js_interop/Cargo.toml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
perseus = { version = "=0.4.0-beta.3", features = [ "hydrate" ] }
sycamore = "=0.8.0-beta.7"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] }
perseus-warp = { version = "=0.4.0-beta.3", features = [ "dflt-server" ] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"

[lib]
name = "lib"
path = "src/lib.rs"
crate-type = [ "cdylib", "rlib" ]

[[bin]]
name = "my-app"
path = "src/lib.rs"
5 changes: 5 additions & 0 deletions examples/core/js_interop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Basic Example

This is a basic example of Perseus that shows the fundamentals of a slightly more advanced Perseus app. This is considered a core example because it not only contains end-to-end tests for Perseus itself, it's also the site of the development of the default Perseus engine. For that reason, the `.perseus/` directory is checked into Git here, and this is used as the single source of truth for the default engine. The reason for developing it here is to provide the context of an actual usage of Perseus, which makes a number of things easier. Then, some scripts bridge the gap to make the engine integrate into the CLI (you shouldn't have to worry about this when working in the Perseus repo as long as you're using the `bonnie dev example ...` script).

Note that this example used to be more complex, illustrating features such as setting custom headers and hosting static content, though demonstrations of these have since been moved to independent examples.
6 changes: 6 additions & 0 deletions examples/core/js_interop/src/changeMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Changes some text in a specific HTML element
// If possible, try to never use JS, `web-sys` will hopefully cover everything you want to do
// But, sometimes, it's unavoidable, so Perseus supports interop easily
export function changeMessage() {
document.getElementById("message").innerHTML = "Message from JS!";
}
17 changes: 17 additions & 0 deletions examples/core/js_interop/src/error_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use perseus::{ErrorPages, Html};
use sycamore::view;

pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
let mut error_pages = ErrorPages::new(|cx, url, status, err, _| {
view! { cx,
p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) }
}
});
error_pages.add_page(404, |cx, _, _, _, _| {
view! { cx,
p { "Page not found." }
}
});

error_pages
}
11 changes: 11 additions & 0 deletions examples/core/js_interop/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod error_pages;
mod templates;

use perseus::{Html, PerseusApp};

#[perseus::main(perseus_integration::dflt_server)]
pub fn main<G: Html>() -> PerseusApp<G> {
PerseusApp::new()
.template(crate::templates::index::get_template)
.error_pages(crate::error_pages::get_error_pages)
}
30 changes: 30 additions & 0 deletions examples/core/js_interop/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use perseus::{Html, Template};
use sycamore::prelude::{view, Scope, View};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;

#[perseus::template_rx]
pub fn index_page<'a, G: Html>(cx: Scope<'a>) -> View<G> {
view! { cx,
// We'll use JS to change this message manually
p(id = "message") { "Hello World!" }
button(id = "change-message", on:click = |_| {
#[cfg(target_arch = "wasm32")]
change_message()
}) { "Change message with JS" }
}
}

pub fn get_template<G: Html>() -> Template<G> {
Template::new("index").template(index_page)
}

// Of course, JS will only run in the browser, so this should be browser-only
#[cfg(target_arch = "wasm32")]
// This path should be relative to the root of your project
// That file will then be hosted behind `/.perseus/` and automatically fetched as needed
#[wasm_bindgen(module = "/src/changeMessage.js")]
extern "C" {
#[wasm_bindgen(js_name = "changeMessage")]
fn change_message();
}
1 change: 1 addition & 0 deletions examples/core/js_interop/src/templates/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod index;
37 changes: 37 additions & 0 deletions examples/core/js_interop/tests/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use fantoccini::{Client, Locator};
use perseus::wait_for_checkpoint;

#[perseus::test]
async fn main(c: &mut Client) -> Result<(), fantoccini::error::CmdError> {
c.goto("http://localhost:8080").await?;
wait_for_checkpoint!("begin", 0, c);
let url = c.current_url().await?;
assert!(url.as_ref().starts_with("http://localhost:8080"));

// The greeting was passed through using build state
wait_for_checkpoint!("initial_state_present", 0, c);
wait_for_checkpoint!("page_visible", 0, c);
let greeting = c.find(Locator::Css("p")).await?.text().await?;
assert_eq!(greeting, "Hello World!");
// For some reason, retrieving the inner HTML or text of a `<title>` doens't
// work
let title = c.find(Locator::Css("title")).await?.html(false).await?;
assert!(title.contains("Index Page"));

// Go to `/about`
c.find(Locator::Id("about-link")).await?.click().await?;
let url = c.current_url().await?;
assert!(url.as_ref().starts_with("http://localhost:8080/about"));
wait_for_checkpoint!("initial_state_not_present", 0, c);
wait_for_checkpoint!("page_visible", 1, c);
// Make sure the hardcoded text there exists
let text = c.find(Locator::Css("p")).await?.text().await?;
assert_eq!(text, "About.");
let title = c.find(Locator::Css("title")).await?.html(false).await?;
assert!(title.contains("About Page"));
// Make sure we get initial state if we refresh
c.refresh().await?;
wait_for_checkpoint!("initial_state_present", 0, c);

Ok(())
}

0 comments on commit 766dd44

Please sign in to comment.