7298405e27
req: local/001\nreq: local/002\nreq: local/003\nreq: local/004\nreq: sync/009
204 lines
5.1 KiB
Rust
204 lines
5.1 KiB
Rust
#[hemx::surface]
|
|
pub mod ui {}
|
|
|
|
#[cfg(feature = "client")]
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
struct CardId(String);
|
|
|
|
#[cfg(feature = "client")]
|
|
struct ReorderCommand {
|
|
card: CardId,
|
|
input_kind: String,
|
|
}
|
|
|
|
#[cfg(feature = "client")]
|
|
struct CardReordered {
|
|
card: CardId,
|
|
input_kind: String,
|
|
}
|
|
|
|
#[cfg(feature = "client")]
|
|
struct BoardProjection {
|
|
first: CardId,
|
|
}
|
|
|
|
#[cfg(feature = "client")]
|
|
struct ProjectedReorder {
|
|
card: CardId,
|
|
before: Option<CardId>,
|
|
input_kind: String,
|
|
}
|
|
|
|
#[cfg(feature = "client")]
|
|
impl ReorderCommand {
|
|
fn from_client(event: hemx::wasm::ClientEvent) -> Self {
|
|
Self {
|
|
card: CardId(
|
|
event
|
|
.value
|
|
.filter(|card| !card.is_empty())
|
|
.unwrap_or_else(|| "1".into()),
|
|
),
|
|
input_kind: event.kind,
|
|
}
|
|
}
|
|
|
|
fn decide(self) -> CardReordered {
|
|
CardReordered {
|
|
card: self.card,
|
|
input_kind: self.input_kind,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "client")]
|
|
impl BoardProjection {
|
|
fn restore(state: hemx::wasm::ClientState) -> Self {
|
|
Self {
|
|
first: CardId(state.encoded.split('|').next().unwrap_or("1").to_owned()),
|
|
}
|
|
}
|
|
|
|
fn apply(self, event: CardReordered) -> ProjectedReorder {
|
|
let before = (event.card != self.first).then_some(self.first);
|
|
ProjectedReorder {
|
|
card: event.card,
|
|
before,
|
|
input_kind: event.input_kind,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "client")]
|
|
#[hemx::handler(client)]
|
|
pub fn reorder_card(
|
|
event: hemx::wasm::ClientEvent,
|
|
state: hemx::wasm::ClientState,
|
|
) -> impl hemx::IntoEffect {
|
|
let projected =
|
|
BoardProjection::restore(state).apply(ReorderCommand::from_client(event).decide());
|
|
let card = projected.card.0;
|
|
let move_effect = match projected.before {
|
|
Some(before) => ui::client_board::client_cards.move_before(card.clone(), before.0),
|
|
None => ui::client_board::client_cards.move_to_end(card.clone()),
|
|
};
|
|
vec![
|
|
move_effect,
|
|
ui::client_board::client_notice.text(format!("Moved {card} with {}", projected.input_kind)),
|
|
]
|
|
}
|
|
|
|
#[cfg(all(feature = "fixture", not(target_arch = "wasm32")))]
|
|
mod fixture {
|
|
use super::ui;
|
|
use hemplate::Hemplate;
|
|
use hemx::Html;
|
|
|
|
#[derive(Hemplate)]
|
|
struct ClientBoard {
|
|
cards: Vec<ClientCard>,
|
|
}
|
|
|
|
#[derive(Hemplate)]
|
|
struct ClientCard {
|
|
id: u64,
|
|
title: &'static str,
|
|
}
|
|
|
|
pub fn render() -> Html {
|
|
ui::client_board::page(&ClientBoard {
|
|
cards: vec![
|
|
ClientCard {
|
|
id: 1,
|
|
title: "First",
|
|
},
|
|
ClientCard {
|
|
id: 2,
|
|
title: "Second",
|
|
},
|
|
],
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "fixture", not(target_arch = "wasm32")))]
|
|
pub fn render_client_fixture() -> hemx::Html {
|
|
fixture::render()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::ui::{board, board_card};
|
|
use hemplate::Hemplate;
|
|
use hemx::IntoEffect;
|
|
use hemx_test::inspect;
|
|
use scraper::{Html, Selector};
|
|
|
|
#[derive(Hemplate)]
|
|
#[hemplate = "partials"]
|
|
struct BoardColumns {
|
|
columns: Vec<String>,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Debug)]
|
|
#[hemx::form("create_card")]
|
|
struct CreateCard {
|
|
title: String,
|
|
column: String,
|
|
}
|
|
|
|
// req: examples/001 req: codegen/002 req: list/003
|
|
#[test]
|
|
fn kanban_board_updates_generated_slot() {
|
|
fn render_board() -> impl IntoEffect {
|
|
board::board.put(&empty_board())
|
|
}
|
|
|
|
let effect = inspect(render_board());
|
|
assert!(effect.updates_html(board::board));
|
|
}
|
|
|
|
// req: html_safety/002 req: view/001 req: test/005
|
|
#[test]
|
|
fn kanban_board_test_payload_is_rendered_by_a_hemplate_view() {
|
|
let html = super::ui::page(&empty_board());
|
|
let document = Html::parse_fragment(html.as_str());
|
|
assert_eq!(document.select(&selector(".columns")).count(), 1);
|
|
}
|
|
|
|
fn empty_board() -> BoardColumns {
|
|
// req: html_safety/002 req: view/001
|
|
BoardColumns {
|
|
columns: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn selector(value: &str) -> Selector {
|
|
Selector::parse(value).expect("test selector parses")
|
|
}
|
|
|
|
// req: examples/001 req: form/001 req: form/004 req: form/006 req: derive_handler/003
|
|
#[test]
|
|
fn kanban_form_handler_is_checked_against_hemplate_form() {
|
|
#[hemx::handler]
|
|
fn create_card(_form: hemx::Form<CreateCard>) -> impl IntoEffect {
|
|
board::notice.text("queued")
|
|
}
|
|
|
|
let effect = inspect(create_card(CreateCard::FORM));
|
|
|
|
assert!(effect.updates_text(board::notice));
|
|
}
|
|
|
|
// req: examples/001 req: form/002 req: codegen/003
|
|
#[test]
|
|
fn kanban_template_exports_form_and_card_handles() {
|
|
assert_ne!(board::create_card.id(), board_card::move_right.id());
|
|
assert_eq!(
|
|
board::create_card_form.field("title").resource,
|
|
board::create_card_form.id()
|
|
);
|
|
}
|
|
}
|