use topcoat::{
Result,
context::Cx,
runtime::{procedure, signal},
view::{View, ViewExt, component, view},
};
#[component]
async fn toggle_button(cx: &Cx) -> Result<impl View> {
let open = signal(cx, || false);
Ok(view! { <button @click=$(|_e| open.toggle())>"x"</button> })
}
#[tokio::test]
async fn toggle_reaches_the_generated_javascript() {
let cx = &Cx::default();
let html = view! { cx => toggle_button() }
.single()
.await
.unwrap()
.render(cx);
assert!(html.contains(".toggle()"), "{html}");
}
#[tokio::test]
async fn bool_then_avoids_javascript_thenable_assimilation() {
let cx = &Cx::default();
let html = view! { cx => $(true.then(|| "yes").unwrap()) }
.single()
.await
.unwrap()
.render(cx);
assert!(html.contains(".then_("), "{html}");
assert!(!html.contains(".then("), "{html}");
}
#[component]
async fn counter_buttons(cx: &Cx) -> Result<impl View> {
let count = signal(cx, || 0.0);
Ok(view! {
<button @click=$(|_e| count.increment())>"+"</button>
<button @click=$(|_e| count.decrement())>"-"</button>
})
}
#[tokio::test]
async fn increment_and_decrement_reach_the_generated_javascript() {
let cx = &Cx::default();
let html = view! { cx => counter_buttons() }
.single()
.await
.unwrap()
.render(cx);
assert!(html.contains(".increment()"), "{html}");
assert!(html.contains(".decrement()"), "{html}");
}
#[component]
async fn push_str_button(cx: &Cx) -> Result<impl View> {
let name = signal(cx, String::new);
Ok(view! { <button @click=$(|_e| name.push_str("!"))>"x"</button> })
}
#[tokio::test]
async fn push_str_reaches_the_generated_javascript_with_its_argument() {
let cx = &Cx::default();
let html = view! { cx => push_str_button() }
.single()
.await
.unwrap()
.render(cx);
assert!(html.contains(".push_str("), "{html}");
}
#[component]
async fn counter_display(cx: &Cx) -> Result<impl View> {
let count = signal(cx, || 0.0);
Ok(view! {
<button @click=$(|_e| count.increment())>"+"</button>
<p>$(count.get())</p>
})
}
#[tokio::test]
async fn a_signal_is_declared_once_ahead_of_every_capture() {
let cx = &Cx::default();
let html = view! { cx => counter_display() }
.single()
.await
.unwrap()
.render(cx);
let declaration = html.find("<!--::topcoat::signal(").expect(&html);
let first_capture = html.find("cx.hydrate(").expect(&html);
assert!(declaration < first_capture, "{html}");
assert_eq!(html.matches("::topcoat::signal(").count(), 1, "{html}");
assert_eq!(html.matches("cx.hydrate(").count(), 2, "{html}");
}
#[procedure]
async fn test_procedure(v: String) -> Result<String, std::convert::Infallible> {
Ok(v)
}
#[tokio::test]
async fn procedure_call_inside_if_is_an_async_func() {
let cx = &Cx::default();
let html = view! {
cx =>
<button
@click=$(async |_e| {
if true {
let _v = test_procedure("hello test".to_owned()).await;
}
})
>
"Test"
</button>
}
.single()
.await
.unwrap()
.render(cx);
assert!(
html.contains("(await (async ()"),
"if block containing an await must be an async anonymous function: {html}"
);
}
#[tokio::test]
async fn procedure_call_inside_block_is_an_async_func() {
let cx = &Cx::default();
let html = view! {
cx =>
<button
@click=$(async |_e| {
{
let _v = test_procedure("hello test".to_owned()).await;
}
})
>
"Test"
</button>
}
.single()
.await
.unwrap()
.render(cx);
assert!(
html.contains("(await (async ()"),
"block containing an await must be an async anonymous function: {html}"
);
}
#[component]
async fn push_str_input(cx: &Cx) -> Result<impl View> {
let message = signal(cx, String::new);
Ok(view! {
<input
@input=$(|e: topcoat::runtime::Event| { message.push_str(e.target.value) })
>
})
}
#[tokio::test]
async fn push_str_accepts_the_owned_string_from_an_event() {
let cx = &Cx::default();
let html = view! { cx => push_str_input() }
.single()
.await
.unwrap()
.render(cx);
assert!(html.contains(".push_str("), "{html}");
}
#[tokio::test]
async fn a_captured_value_cannot_break_out_of_its_marker_comment() {
let cx = &Cx::default();
let spicy = String::from(r#"-->"<&"#);
let html = view! { cx => <p>$(spicy.to_owned())</p> }
.single()
.await
.unwrap()
.render(cx);
assert!(
html.contains(r"cx.hydrate("-->\"<&")"),
"{html}"
);
assert!(html.contains(r#"-->-->"<&<!--"#), "{html}");
assert!(!html.contains(r#"-->"<&"#), "{html}");
}
#[component]
async fn quoted_push_str_button(cx: &Cx) -> Result<impl View> {
let name = signal(cx, String::new);
Ok(view! { <button @click=$(|_e| name.push_str("say \"hi\""))>"x"</button> })
}
#[tokio::test]
async fn a_string_literal_with_quotes_stays_inside_the_handler_attribute() {
let cx = &Cx::default();
let html = view! { cx => quoted_push_str_button() }
.single()
.await
.unwrap()
.render(cx);
assert!(html.contains(r"say \"hi\""), "{html}");
assert!(!html.contains(r#"say \"hi"#), "{html}");
}