Build v0 example and SSE slices
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "slhx-v0-examples"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
slhx = { path = "../../slhx" }
|
||||
|
||||
[dev-dependencies]
|
||||
slhx-test = { path = "../../slhx-test" }
|
||||
|
||||
[build-dependencies]
|
||||
slhx-build = { path = "../../slhx-build" }
|
||||
@@ -0,0 +1 @@
|
||||
fn main() { slhx_build::app().run().unwrap(); }
|
||||
@@ -0,0 +1,108 @@
|
||||
#[slhx::surface]
|
||||
pub mod ui {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ui;
|
||||
use slhx::{push, Effect, IntoEffect, NavigateMode};
|
||||
use slhx_test::inspect;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Todo {
|
||||
id: u64,
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TodoInput {
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct WizardInput {
|
||||
step: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Credentials {
|
||||
email: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
ui::slots::counter_value.text(count + 1)
|
||||
}
|
||||
|
||||
let effect = inspect(increment(1));
|
||||
|
||||
assert!(effect.has_slot(ui::slots::counter_value));
|
||||
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
||||
}
|
||||
|
||||
// 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 };
|
||||
ui::slots::todo_row.append(todo.id.to_string(), format!("<li>{}</li>", todo.title))
|
||||
}
|
||||
|
||||
let effect = inspect(add_todo(TodoInput { title: "Ship v0".into() }));
|
||||
|
||||
assert!(effect.has_resource(ui::slots::todo_row.id()));
|
||||
assert!(matches!(effect.ops(), [Effect::Insert { key, .. }] if key == "7"));
|
||||
}
|
||||
|
||||
// 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 {
|
||||
ui::slots::wizard_step.text(format!("Step {}", input.step + 1))
|
||||
}
|
||||
|
||||
let effect = inspect(next_step(WizardInput { step: 1 }));
|
||||
|
||||
assert!(effect.has_slot(ui::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
|
||||
#[test]
|
||||
fn page_swap_updates_content_and_history() {
|
||||
fn load_docs() -> impl IntoEffect {
|
||||
(
|
||||
ui::slots::content.text("<h1>Docs</h1>"),
|
||||
ui::slots::title.text("Docs"),
|
||||
push("/docs"),
|
||||
)
|
||||
}
|
||||
|
||||
let effect = inspect(load_docs());
|
||||
|
||||
assert!(effect.has_slot(ui::slots::content));
|
||||
assert!(effect.has_slot(ui::slots::title));
|
||||
assert!(matches!(effect.ops().last(), Some(Effect::Navigate { url, mode: NavigateMode::Push, .. }) if url == "/docs"));
|
||||
}
|
||||
|
||||
// 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"
|
||||
};
|
||||
|
||||
ui::slots::login_status.text(status)
|
||||
}
|
||||
|
||||
let effect = inspect(login(Credentials { email: "demo@example.com".into(), password: "secret".into() }));
|
||||
|
||||
assert!(effect.has_slot(ui::slots::login_status));
|
||||
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<section data-slhx-root="auth">
|
||||
<form data-slhx-handle="login" data-slhx-form="credentials">
|
||||
<input name="email" type="email" autocomplete="username">
|
||||
<input name="password" type="password" autocomplete="current-password">
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
<p data-slhx-slot="login_status">Signed out</p>
|
||||
</section>
|
||||
@@ -0,0 +1,4 @@
|
||||
<section data-slhx-root="counter">
|
||||
<output data-slhx-slot="counter_value">0</output>
|
||||
<button type="button" data-slhx-handle="increment">+</button>
|
||||
</section>
|
||||
@@ -0,0 +1,7 @@
|
||||
<main data-slhx-root="docs">
|
||||
<nav data-slhx-slot="nav">
|
||||
<a href="/docs" data-slhx-nav="" data-slhx-handle="load_docs">Docs</a>
|
||||
</nav>
|
||||
<article data-slhx-slot="content">Welcome</article>
|
||||
<title data-slhx-slot="title">Welcome</title>
|
||||
</main>
|
||||
@@ -0,0 +1,11 @@
|
||||
<section data-slhx-root="todos">
|
||||
<form data-slhx-handle="add_todo" data-slhx-form="new_todo">
|
||||
<input name="title" required="required">
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
<ul data-slhx-slot="todo_list">
|
||||
<template h-for="todo in &self.todos" h-key="todo.id">
|
||||
<li data-slhx-slot="todo_row">{+ todo.title +}</li>
|
||||
</template>
|
||||
</ul>
|
||||
</section>
|
||||
@@ -0,0 +1,6 @@
|
||||
<section data-slhx-root="wizard">
|
||||
<form data-slhx-handle="next_step" data-slhx-form="wizard_input">
|
||||
<div data-slhx-slot="wizard_step">Step 1</div>
|
||||
<button type="submit">Next</button>
|
||||
</form>
|
||||
</section>
|
||||
Reference in New Issue
Block a user