Incluye soporte HTMX vía `sort_link()`/`hx_pager` para navegación sin recarga.
162 lines
5.4 KiB
Rust
162 lines
5.4 KiB
Rust
use pagetop::prelude::*;
|
|
use pagetop_htmx::prelude::*;
|
|
|
|
// Forces an effective language different from the default negotiated one (en-US, with no `?lang` in
|
|
// the request), so that `Context::route()` decides to propagate `?lang=...` in local routes.
|
|
fn cx_with_lang(lang: &str) -> Context {
|
|
Context::default().with_langid(&Locale::resolve(lang))
|
|
}
|
|
|
|
async fn render_column(column: table::Column) -> String {
|
|
let mut table = Table::new().with_column(column);
|
|
table.render(&mut Context::default()).await.into_string()
|
|
}
|
|
|
|
// **< sort_link() - htmx attributes >**************************************************************
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_sets_the_four_fixed_htmx_attributes() {
|
|
let column = table::Column::new(L10n::n("User")).with_sort(hx_table::sort_link(
|
|
"/admin/users",
|
|
"#user-table",
|
|
None,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r#"hx-get="/admin/users""#));
|
|
assert!(html.contains(r##"hx-target="#user-table""##));
|
|
assert!(html.contains(r#"hx-swap="outerHTML scroll:top""#));
|
|
assert!(html.contains(r#"hx-push-url="true""#));
|
|
}
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_href_matches_the_hx_get_value() {
|
|
// The link must work with or without HTMX: `href` is the real destination, and `hx-get` must
|
|
// request that very same URL so both navigation paths land on the same state.
|
|
let column = table::Column::new(L10n::n("User")).with_sort(hx_table::sort_link(
|
|
"/admin/users?sort=username",
|
|
"#user-table",
|
|
None,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r#"href="/admin/users?sort=username""#));
|
|
assert!(html.contains(r#"hx-get="/admin/users?sort=username""#));
|
|
}
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_target_is_configurable_per_table() {
|
|
let column = table::Column::new(L10n::n("Email")).with_sort(hx_table::sort_link(
|
|
"/admin/users",
|
|
"#other-wrapper",
|
|
None,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r##"hx-target="#other-wrapper""##));
|
|
}
|
|
|
|
// **< sort_link() - sort direction propagation >***************************************************
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_without_active_direction_marks_aria_sort_none() {
|
|
let column = table::Column::new(L10n::n("User")).with_sort(hx_table::sort_link(
|
|
"/admin/users",
|
|
"#user-table",
|
|
None,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r#"aria-sort="none""#));
|
|
}
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_with_active_direction_marks_aria_sort_and_css_class() {
|
|
let column = table::Column::new(L10n::n("User")).with_sort(hx_table::sort_link(
|
|
"/admin/users",
|
|
"#user-table",
|
|
SortDir::Desc,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r#"aria-sort="descending""#));
|
|
assert!(html.contains("table-sort table-sort-desc"));
|
|
}
|
|
|
|
// **< sort_link() - RoutePath / Context::route() integration >*************************************
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_with_a_bare_literal_href_never_adds_lang() {
|
|
// `sort_link()` does not receive `cx`, so it cannot add `lang` on its own: passing a raw
|
|
// literal must leave both `href` and `hx-get` exactly as given.
|
|
let column = table::Column::new(L10n::n("User")).with_sort(hx_table::sort_link(
|
|
"/admin/users",
|
|
"#user-table",
|
|
None,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r#"href="/admin/users""#));
|
|
assert!(!html.contains("lang="));
|
|
}
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_carries_through_a_lang_aware_href_unchanged() {
|
|
// The caller is expected to resolve `href` with `cx.route(...)` beforehand (see the type's own
|
|
// doc example); `sort_link()` must not re-encode or otherwise alter what it receives.
|
|
let cx = cx_with_lang("es-ES");
|
|
let href = cx.route("/admin/users");
|
|
|
|
let column = table::Column::new(L10n::n("User")).with_sort(hx_table::sort_link(
|
|
href,
|
|
"#user-table",
|
|
None,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r#"href="/admin/users?lang=es-ES""#));
|
|
assert!(html.contains(r#"hx-get="/admin/users?lang=es-ES""#));
|
|
}
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_carries_through_extra_query_params_in_order() {
|
|
let cx = cx_with_lang("es-ES");
|
|
let href = cx
|
|
.route("/admin/users")
|
|
.with_param("sort", "username")
|
|
.with_param("dir", "desc");
|
|
|
|
let column = table::Column::new(L10n::n("User")).with_sort(hx_table::sort_link(
|
|
href,
|
|
"#user-table",
|
|
SortDir::Desc,
|
|
));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
// `&` is escaped to `&` because this ends up inside an HTML attribute value.
|
|
assert!(html.contains(r#"href="/admin/users?lang=es-ES&sort=username&dir=desc""#));
|
|
}
|
|
|
|
#[pagetop::test]
|
|
async fn sort_link_with_an_external_href_is_left_untouched() {
|
|
// `Context::route()` never adds `lang` to a URL that looks external; `sort_link()` must not
|
|
// reintroduce it either, since it only forwards whatever `RoutePath` it receives.
|
|
let cx = cx_with_lang("es-ES");
|
|
let href = cx.route("https://example.com/export");
|
|
|
|
let column =
|
|
table::Column::new(L10n::n("Export")).with_sort(hx_table::sort_link(href, "#table", None));
|
|
|
|
let html = render_column(column).await;
|
|
|
|
assert!(html.contains(r#"href="https://example.com/export""#));
|
|
assert!(!html.contains("lang="));
|
|
}
|