refactor(v0): shorten generated resource paths
Import component-scoped generated modules at the example boundary so handlers and tests read as local slots/forms/handles without hiding ownership or adding framework machinery. req: component/003 req: dx/006
This commit is contained in:
+21
-21
@@ -3,7 +3,7 @@ pub mod ui {}
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::ui;
|
use super::ui::{auth, counter, notifications, page_swap, todos, wizard};
|
||||||
use hemplate::Hemplate;
|
use hemplate::Hemplate;
|
||||||
use slhx::{push, Effect, IntoEffect, NavigateMode, Payload, RenderKeyedSlotExt, RenderSlotExt};
|
use slhx::{push, Effect, IntoEffect, NavigateMode, Payload, RenderKeyedSlotExt, RenderSlotExt};
|
||||||
use slhx_test::inspect;
|
use slhx_test::inspect;
|
||||||
@@ -49,12 +49,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn counter_updates_a_generated_slot() {
|
fn counter_updates_a_generated_slot() {
|
||||||
fn increment(count: u64) -> impl IntoEffect {
|
fn increment(count: u64) -> impl IntoEffect {
|
||||||
ui::counter::slots::counter_value.text(count + 1)
|
counter::slots::counter_value.text(count + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(increment(1));
|
let effect = inspect(increment(1));
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::counter::slots::counter_value));
|
assert!(effect.has_slot(counter::slots::counter_value));
|
||||||
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,12 +63,12 @@ mod tests {
|
|||||||
fn form_handler_is_checked_against_hemplate_form() {
|
fn form_handler_is_checked_against_hemplate_form() {
|
||||||
#[slhx::handler]
|
#[slhx::handler]
|
||||||
fn add_todo(_form: slhx::Form<TodoInput>) -> impl IntoEffect {
|
fn add_todo(_form: slhx::Form<TodoInput>) -> impl IntoEffect {
|
||||||
ui::todos::slots::todo_list.text("queued")
|
todos::slots::todo_list.text("queued")
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(add_todo(TodoInput::FORM));
|
let effect = inspect(add_todo(TodoInput::FORM));
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::todos::slots::todo_list));
|
assert!(effect.has_slot(todos::slots::todo_list));
|
||||||
assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }]));
|
assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ mod tests {
|
|||||||
fn todos_append_keyed_rows_from_form_input() {
|
fn todos_append_keyed_rows_from_form_input() {
|
||||||
fn add_todo(input: TodoInput) -> impl IntoEffect {
|
fn add_todo(input: TodoInput) -> impl IntoEffect {
|
||||||
let todo = Todo { id: 7, title: input.title };
|
let todo = Todo { id: 7, title: input.title };
|
||||||
ui::todos::slots::todo_row.append(
|
todos::slots::todo_row.append(
|
||||||
todo.id.to_string(),
|
todo.id.to_string(),
|
||||||
&TodoRow { title: todo.title },
|
&TodoRow { title: todo.title },
|
||||||
)
|
)
|
||||||
@@ -85,7 +85,7 @@ mod tests {
|
|||||||
|
|
||||||
let effect = inspect(add_todo(TodoInput { title: "Ship v0".into() }));
|
let effect = inspect(add_todo(TodoInput { title: "Ship v0".into() }));
|
||||||
|
|
||||||
assert!(effect.has_resource(ui::todos::slots::todo_row.id()));
|
assert!(effect.has_resource(todos::slots::todo_row.id()));
|
||||||
assert!(matches!(effect.ops(), [Effect::Insert { key, payload, .. }] if key == "7" && matches!(payload, Payload::Html(_))));
|
assert!(matches!(effect.ops(), [Effect::Insert { key, payload, .. }] if key == "7" && matches!(payload, Payload::Html(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +94,12 @@ mod tests {
|
|||||||
fn wizard_form_handler_is_checked_against_hemplate_form() {
|
fn wizard_form_handler_is_checked_against_hemplate_form() {
|
||||||
#[slhx::handler]
|
#[slhx::handler]
|
||||||
fn next_step(_form: slhx::Form<WizardInput>) -> impl IntoEffect {
|
fn next_step(_form: slhx::Form<WizardInput>) -> impl IntoEffect {
|
||||||
ui::wizard::slots::wizard_step.text("queued")
|
wizard::slots::wizard_step.text("queued")
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(next_step(WizardInput::FORM));
|
let effect = inspect(next_step(WizardInput::FORM));
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::wizard::slots::wizard_step));
|
assert!(effect.has_slot(wizard::slots::wizard_step));
|
||||||
assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }]));
|
assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,12 +107,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn wizard_form_swaps_the_current_step() {
|
fn wizard_form_swaps_the_current_step() {
|
||||||
fn next_step(input: WizardInput) -> impl IntoEffect {
|
fn next_step(input: WizardInput) -> impl IntoEffect {
|
||||||
ui::wizard::slots::wizard_step.text(format!("Step {}", input.step + 1))
|
wizard::slots::wizard_step.text(format!("Step {}", input.step + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(next_step(WizardInput { step: 1 }));
|
let effect = inspect(next_step(WizardInput { step: 1 }));
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::wizard::slots::wizard_step));
|
assert!(effect.has_slot(wizard::slots::wizard_step));
|
||||||
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,18 +121,18 @@ mod tests {
|
|||||||
fn page_swap_updates_content_and_history() {
|
fn page_swap_updates_content_and_history() {
|
||||||
fn load_docs() -> impl IntoEffect {
|
fn load_docs() -> impl IntoEffect {
|
||||||
(
|
(
|
||||||
ui::page_swap::slots::content.render(&DocsContent {
|
page_swap::slots::content.render(&DocsContent {
|
||||||
message: "This page was swapped.",
|
message: "This page was swapped.",
|
||||||
}),
|
}),
|
||||||
ui::page_swap::slots::title.text("Docs"),
|
page_swap::slots::title.text("Docs"),
|
||||||
push("/docs"),
|
push("/docs"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(load_docs());
|
let effect = inspect(load_docs());
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::page_swap::slots::content));
|
assert!(effect.has_slot(page_swap::slots::content));
|
||||||
assert!(effect.has_slot(ui::page_swap::slots::title));
|
assert!(effect.has_slot(page_swap::slots::title));
|
||||||
assert!(matches!(effect.ops().first(), Some(Effect::Put { payload: Payload::Html(_), .. })));
|
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"));
|
assert!(matches!(effect.ops().last(), Some(Effect::Navigate { url, mode: NavigateMode::Push, .. }) if url == "/docs"));
|
||||||
}
|
}
|
||||||
@@ -142,12 +142,12 @@ mod tests {
|
|||||||
fn auth_form_handler_is_checked_against_hemplate_form() {
|
fn auth_form_handler_is_checked_against_hemplate_form() {
|
||||||
#[slhx::handler]
|
#[slhx::handler]
|
||||||
fn login(_form: slhx::Form<Credentials>) -> impl IntoEffect {
|
fn login(_form: slhx::Form<Credentials>) -> impl IntoEffect {
|
||||||
ui::auth::slots::login_status.text("queued")
|
auth::slots::login_status.text("queued")
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(login(Credentials::FORM));
|
let effect = inspect(login(Credentials::FORM));
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::auth::slots::login_status));
|
assert!(effect.has_slot(auth::slots::login_status));
|
||||||
assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }]));
|
assert!(matches!(effect.ops(), [Effect::Put { payload: Payload::Text(_), .. }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,12 +161,12 @@ mod tests {
|
|||||||
"Try again"
|
"Try again"
|
||||||
};
|
};
|
||||||
|
|
||||||
ui::auth::slots::login_status.text(status)
|
auth::slots::login_status.text(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(login(Credentials { email: "demo@example.com".into(), password: "secret".into() }));
|
let effect = inspect(login(Credentials { email: "demo@example.com".into(), password: "secret".into() }));
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::auth::slots::login_status));
|
assert!(effect.has_slot(auth::slots::login_status));
|
||||||
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,12 +174,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn sse_notifications_update_a_generated_slot() {
|
fn sse_notifications_update_a_generated_slot() {
|
||||||
fn notification(message: &str) -> impl IntoEffect {
|
fn notification(message: &str) -> impl IntoEffect {
|
||||||
ui::notifications::slots::notifications.text(message)
|
notifications::slots::notifications.text(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
let effect = inspect(notification("Build finished"));
|
let effect = inspect(notification("Build finished"));
|
||||||
|
|
||||||
assert!(effect.has_slot(ui::notifications::slots::notifications));
|
assert!(effect.has_slot(notifications::slots::notifications));
|
||||||
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
assert!(matches!(effect.ops(), [Effect::Put { .. }]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-20
@@ -7,6 +7,13 @@ use hemplate::Hemplate;
|
|||||||
use slhx::{push, IntoEffect, RenderSlotExt, SafeHtml};
|
use slhx::{push, IntoEffect, RenderSlotExt, SafeHtml};
|
||||||
use slhx_axum::{runtime_js, sse, EffectResponse, HandlerRegistry, InteractionForm, PageRequest};
|
use slhx_axum::{runtime_js, sse, EffectResponse, HandlerRegistry, InteractionForm, PageRequest};
|
||||||
use slhx_v0_examples::ui;
|
use slhx_v0_examples::ui;
|
||||||
|
use slhx_v0_examples::ui::{auth, counter, notifications, page_swap, todos, wizard};
|
||||||
|
use slhx_v0_examples::ui::auth::{handles as auth_handles, slots as auth_slots};
|
||||||
|
use slhx_v0_examples::ui::counter::{handles as counter_handles, slots as counter_slots};
|
||||||
|
use slhx_v0_examples::ui::notifications::slots as notification_slots;
|
||||||
|
use slhx_v0_examples::ui::page_swap::{handles as page_handles, slots as page_slots};
|
||||||
|
use slhx_v0_examples::ui::todos::{forms as todo_forms, handles as todo_handles, slots as todo_slots};
|
||||||
|
use slhx_v0_examples::ui::wizard::{handles as wizard_handles, slots as wizard_slots};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@@ -101,13 +108,13 @@ async fn runtime() -> impl IntoResponse {
|
|||||||
// req: push/001, req: push/003, req: examples/001
|
// req: push/001, req: push/003, req: examples/001
|
||||||
async fn events(Query(params): Query<BTreeMap<String, String>>) -> impl IntoResponse {
|
async fn events(Query(params): Query<BTreeMap<String, String>>) -> impl IntoResponse {
|
||||||
if params.contains_key("once") {
|
if params.contains_key("once") {
|
||||||
let effect = ui::notifications::slots::notifications.text("Server event #1");
|
let effect = notification_slots::notifications.text("Server event #1");
|
||||||
return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed());
|
return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed());
|
||||||
}
|
}
|
||||||
|
|
||||||
let batches = stream::unfold(1_u64, |count| async move {
|
let batches = stream::unfold(1_u64, |count| async move {
|
||||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||||
let effect = ui::notifications::slots::notifications.text(format!("Server event #{count}"));
|
let effect = notification_slots::notifications.text(format!("Server event #{count}"));
|
||||||
Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), count + 1))
|
Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), count + 1))
|
||||||
})
|
})
|
||||||
.boxed();
|
.boxed();
|
||||||
@@ -116,16 +123,16 @@ async fn events(Query(params): Query<BTreeMap<String, String>>) -> impl IntoResp
|
|||||||
|
|
||||||
fn registry(state: Arc<ExampleState>) -> HandlerRegistry {
|
fn registry(state: Arc<ExampleState>) -> HandlerRegistry {
|
||||||
HandlerRegistry::new(ui::BUILD_FINGERPRINT)
|
HandlerRegistry::new(ui::BUILD_FINGERPRINT)
|
||||||
.register_handle(ui::counter::handles::increment, {
|
.register_handle(counter_handles::increment, {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
// req: examples/001
|
// req: examples/001
|
||||||
let mut counter = state.counter.lock().unwrap();
|
let mut counter = state.counter.lock().unwrap();
|
||||||
*counter += 1;
|
*counter += 1;
|
||||||
ui::counter::slots::counter_value.text(*counter)
|
counter_slots::counter_value.text(*counter)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_handle(ui::todos::handles::add_todo, {
|
.register_handle(todo_handles::add_todo, {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |form| {
|
move |form| {
|
||||||
// req: examples/001
|
// req: examples/001
|
||||||
@@ -136,37 +143,37 @@ fn registry(state: Arc<ExampleState>) -> HandlerRegistry {
|
|||||||
todos.push(Todo { id, title: title.into() });
|
todos.push(Todo { id, title: title.into() });
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
ui::todos::slots::todo_list.render(&todos_view(&todos)),
|
todo_slots::todo_list.render(&todos_view(&todos)),
|
||||||
ui::todos::forms::new_todo.clear("title"),
|
todo_forms::new_todo.clear("title"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_handle(ui::wizard::handles::next_step, {
|
.register_handle(wizard_handles::next_step, {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
// req: examples/001
|
// req: examples/001
|
||||||
let mut step = state.wizard_step.lock().unwrap();
|
let mut step = state.wizard_step.lock().unwrap();
|
||||||
*step += 1;
|
*step += 1;
|
||||||
ui::wizard::slots::wizard_step.text(format!("Step {}", *step + 1))
|
wizard_slots::wizard_step.text(format!("Step {}", *step + 1))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_handle(ui::auth::handles::login, |form| {
|
.register_handle(auth_handles::login, |form| {
|
||||||
// req: examples/001
|
// req: examples/001
|
||||||
let ok = form.value("email") == Some("demo@example.com")
|
let ok = form.value("email") == Some("demo@example.com")
|
||||||
&& form.value("password").is_some_and(|password| !password.is_empty());
|
&& form.value("password").is_some_and(|password| !password.is_empty());
|
||||||
ui::auth::slots::login_status.text(if ok {
|
auth_slots::login_status.text(if ok {
|
||||||
"Signed in as demo@example.com"
|
"Signed in as demo@example.com"
|
||||||
} else {
|
} else {
|
||||||
"Try demo@example.com with any password"
|
"Try demo@example.com with any password"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.register_handle(ui::page_swap::handles::load_docs, |_| {
|
.register_handle(page_handles::load_docs, |_| {
|
||||||
// req: page_swap/002, req: examples/001
|
// req: page_swap/002, req: examples/001
|
||||||
(
|
(
|
||||||
ui::page_swap::slots::content.render(&DocsContent {
|
page_slots::content.render(&DocsContent {
|
||||||
message: "This content came from an EffectBatch.",
|
message: "This content came from an EffectBatch.",
|
||||||
}),
|
}),
|
||||||
ui::page_swap::slots::title.text("Docs"),
|
page_slots::title.text("Docs"),
|
||||||
push("/docs"),
|
push("/docs"),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -174,12 +181,12 @@ fn registry(state: Arc<ExampleState>) -> HandlerRegistry {
|
|||||||
|
|
||||||
fn all_examples() -> String {
|
fn all_examples() -> String {
|
||||||
[
|
[
|
||||||
ui::counter::lower_html(include_str!("../templates/counter.heml")),
|
counter::lower_html(include_str!("../templates/counter.heml")),
|
||||||
ui::todos::lower_html(include_str!("../templates/todos.heml")),
|
todos::lower_html(include_str!("../templates/todos.heml")),
|
||||||
ui::wizard::lower_html(include_str!("../templates/wizard.heml")),
|
wizard::lower_html(include_str!("../templates/wizard.heml")),
|
||||||
ui::auth::lower_html(include_str!("../templates/auth.heml")),
|
auth::lower_html(include_str!("../templates/auth.heml")),
|
||||||
render_page_swap("Welcome", "Welcome"),
|
render_page_swap("Welcome", "Welcome"),
|
||||||
ui::notifications::lower_html(include_str!("../templates/notifications.heml")),
|
notifications::lower_html(include_str!("../templates/notifications.heml")),
|
||||||
]
|
]
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
@@ -208,7 +215,7 @@ fn todos_view(todos: &[Todo]) -> TodoItems {
|
|||||||
|
|
||||||
fn render_page_swap(title: &'static str, message: &'static str) -> String {
|
fn render_page_swap(title: &'static str, message: &'static str) -> String {
|
||||||
// req: html_safety/002 req: view/001 req: component/003
|
// req: html_safety/002 req: view/001 req: component/003
|
||||||
ui::page_swap::render_html(&PageSwap {
|
page_swap::render_html(&PageSwap {
|
||||||
content: render_docs_content(message),
|
content: render_docs_content(message),
|
||||||
title,
|
title,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user