#[slhx::surface] pub mod ui {} #[cfg(test)] mod tests { use super::ui::{auth, counter, notifications, page_swap, todos, wizard}; use hemplate::Hemplate; use slhx::{push, Effect, IntoEffect, NavigateMode, Payload, RenderKeyedSlotExt}; use slhx_test::inspect; #[derive(Clone, Debug)] struct Todo { id: u64, title: String, } #[derive(Clone, Debug)] #[slhx::form("new_todo")] struct TodoInput { title: String, } #[derive(Clone, Debug)] #[slhx::form("wizard_input")] struct WizardInput { step: u8, } #[derive(Clone, Debug)] #[slhx::form("credentials")] struct Credentials { email: String, password: String, } #[derive(Hemplate)] #[hemplate = "partials"] struct TodoRow { title: String, } #[derive(Hemplate)] #[hemplate = "partials"] struct DocsContent { message: &'static str, } // req: examples/001 req: ceremony/001 req: build/001 req: build/005 req: codegen/002 #[test] fn counter_updates_a_generated_slot() { fn increment(count: u64) -> impl IntoEffect { counter::slots::counter_value.text(count + 1) } let effect = inspect(increment(1)); assert!(effect.has_slot(counter::slots::counter_value)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } // req: examples/001 req: form/001 req: form/004 req: form/006 req: derive_handler/003 #[test] fn form_handler_is_checked_against_hemplate_form() { #[slhx::handler] fn add_todo(_form: slhx::Form) -> impl IntoEffect { todos::slots::todo_list.text("queued") } let effect = inspect(add_todo(TodoInput::FORM)); assert!(effect.has_slot(todos::slots::todo_list)); assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }])); } // req: examples/001 req: progressive_disclosure/001 req: build/001 req: build/005 req: codegen/002 #[test] fn todos_append_keyed_rows_from_form_input() { fn add_todo(input: TodoInput) -> impl IntoEffect { let todo = Todo { id: 7, title: input.title }; todos::slots::todo_row.append( todo.id.to_string(), &TodoRow { title: todo.title }, ) } let effect = inspect(add_todo(TodoInput { title: "Ship v0".into() })); assert!(effect.has_resource(todos::slots::todo_row.id())); assert!(matches!(effect.ops(), [Effect::Insert { key, payload, .. }] if key == "7" && matches!(payload, Payload::Html(_)))); } // req: examples/001 req: form/001 req: form/004 req: form/006 req: derive_handler/003 #[test] fn wizard_form_handler_is_checked_against_hemplate_form() { #[slhx::handler] fn next_step(_form: slhx::Form) -> impl IntoEffect { wizard::slots::wizard_step.text("queued") } let effect = inspect(next_step(WizardInput::FORM)); assert!(effect.has_slot(wizard::slots::wizard_step)); assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }])); } // req: examples/001 req: progressive_disclosure/001 req: build/001 req: build/005 req: codegen/004 #[test] fn wizard_form_swaps_the_current_step() { fn next_step(input: WizardInput) -> impl IntoEffect { wizard::slots::wizard_step.text(format!("Step {}", input.step + 1)) } let effect = inspect(next_step(WizardInput { step: 1 })); assert!(effect.has_slot(wizard::slots::wizard_step)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } // req: examples/001 req: page_swap/002 req: page_swap/003 req: build/001 req: build/005 req: view/001 #[test] fn page_swap_updates_content_and_history() { fn load_docs() -> impl IntoEffect { ( page_swap::slots::content.html(page_swap::render(&DocsContent { message: "This page was swapped.", })), page_swap::slots::title.text("Docs"), push("/docs"), ) } let effect = inspect(load_docs()); assert!(effect.has_slot(page_swap::slots::content)); assert!(effect.has_slot(page_swap::slots::title)); assert!(matches!(effect.ops().first(), Some(Effect::Put { payload: Payload::Html(_), .. }))); assert!(matches!(effect.ops().last(), Some(Effect::Navigate { url, mode: NavigateMode::Push, .. }) if url == "/docs")); } // req: examples/001 req: form/001 req: form/004 req: form/006 req: derive_handler/003 #[test] fn auth_form_handler_is_checked_against_hemplate_form() { #[slhx::handler] fn login(_form: slhx::Form) -> impl IntoEffect { auth::slots::login_status.text("queued") } let effect = inspect(login(Credentials::FORM)); assert!(effect.has_slot(auth::slots::login_status)); assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }])); } // req: examples/001 req: progressive_disclosure/001 req: build/001 req: build/005 req: codegen/004 #[test] fn auth_flow_reads_typed_input_and_updates_status() { fn login(input: Credentials) -> impl IntoEffect { let status = if input.email == "demo@example.com" && !input.password.is_empty() { "Signed in" } else { "Try again" }; auth::slots::login_status.text(status) } let effect = inspect(login(Credentials { email: "demo@example.com".into(), password: "secret".into() })); assert!(effect.has_slot(auth::slots::login_status)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } // req: examples/001 req: push/001 req: push/003 req: build/001 req: build/005 #[test] fn sse_notifications_update_a_generated_slot() { fn notification(message: &str) -> impl IntoEffect { notifications::slots::notifications.text(message) } let effect = inspect(notification("Build finished")); assert!(effect.has_slot(notifications::slots::notifications)); assert!(matches!(effect.ops(), [Effect::Put { .. }])); } }