pagetop/extensions/pagetop-htmx/tests/request.rs

169 lines
6.1 KiB
Rust

use pagetop::prelude::*;
use pagetop_htmx::prelude::*;
struct TestApp;
#[async_trait]
impl Extension for TestApp {
fn configure_router(&self, router: Router) -> Router {
router.route("/echo", web::get(echo_request))
}
}
// Reports every `HtmxRequestExt` value as JSON, so a single route can back every test in this file
// without needing a dedicated handler per header.
async fn echo_request(request: HttpRequest) -> String {
serde_json::json!({
"is_htmx": request.is_htmx(),
"is_boosted": request.is_boosted(),
"is_history_restore": request.is_history_restore(),
"current_url": request.hx_current_url(),
"target": request.hx_target(),
"trigger_id": request.hx_trigger_id(),
"trigger_name": request.hx_trigger_name(),
"prompt": request.hx_prompt(),
})
.to_string()
}
async fn echo(app: &Router, headers: &[(&str, &str)]) -> serde_json::Value {
let mut req = web::test::TestRequest::get().uri("/echo");
for (name, value) in headers {
req = req.header(*name, *value);
}
let resp = web::test::send_request(app, req.to_request()).await;
let body = web::test::read_body_text(resp).await;
serde_json::from_str(&body).unwrap()
}
// All tests in this file share the same root extension (`TestApp`), since `EXTENSIONS` is a global
// `OnceLock` initialized only once per test binary (see `core/extension/all.rs`).
// **< is_htmx() >**********************************************************************************
#[pagetop::test]
async fn is_htmx_is_true_only_when_hx_request_is_exactly_true() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let with_header = echo(&app, &[("hx-request", "true")]).await;
assert_eq!(with_header["is_htmx"], true);
let without_header = echo(&app, &[]).await;
assert_eq!(without_header["is_htmx"], false);
// A stray/incorrect value must not be treated as a truthy HTMX request.
let wrong_value = echo(&app, &[("hx-request", "false")]).await;
assert_eq!(wrong_value["is_htmx"], false);
}
// **< is_boosted() >*******************************************************************************
#[pagetop::test]
async fn is_boosted_reflects_the_hx_boosted_header() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let boosted = echo(&app, &[("hx-boosted", "true")]).await;
assert_eq!(boosted["is_boosted"], true);
let not_boosted = echo(&app, &[]).await;
assert_eq!(not_boosted["is_boosted"], false);
}
// **< is_history_restore() >***********************************************************************
#[pagetop::test]
async fn is_history_restore_reflects_the_hx_history_restore_request_header() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let restoring = echo(&app, &[("hx-history-restore-request", "true")]).await;
assert_eq!(restoring["is_history_restore"], true);
let not_restoring = echo(&app, &[]).await;
assert_eq!(not_restoring["is_history_restore"], false);
}
// **< hx_current_url() / hx_target() / hx_trigger_id() / hx_trigger_name() / hx_prompt() >*********
#[pagetop::test]
async fn hx_current_url_reads_the_hx_current_url_header_when_present() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let with_url = echo(&app, &[("hx-current-url", "/admin/users?page=2")]).await;
assert_eq!(with_url["current_url"], "/admin/users?page=2");
let without_url = echo(&app, &[]).await;
assert!(without_url["current_url"].is_null());
}
#[pagetop::test]
async fn hx_target_reads_the_hx_target_header_when_present() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let with_target = echo(&app, &[("hx-target", "user-table")]).await;
assert_eq!(with_target["target"], "user-table");
let without_target = echo(&app, &[]).await;
assert!(without_target["target"].is_null());
}
#[pagetop::test]
async fn hx_trigger_id_reads_the_hx_trigger_header_when_present() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let with_trigger = echo(&app, &[("hx-trigger", "save-button")]).await;
assert_eq!(with_trigger["trigger_id"], "save-button");
let without_trigger = echo(&app, &[]).await;
assert!(without_trigger["trigger_id"].is_null());
}
#[pagetop::test]
async fn hx_trigger_name_reads_the_hx_trigger_name_header_when_present() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let with_name = echo(&app, &[("hx-trigger-name", "email")]).await;
assert_eq!(with_name["trigger_name"], "email");
let without_name = echo(&app, &[]).await;
assert!(without_name["trigger_name"].is_null());
}
#[pagetop::test]
async fn hx_prompt_reads_the_hx_prompt_header_when_present() {
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let with_prompt = echo(&app, &[("hx-prompt", "Are you sure?")]).await;
assert_eq!(with_prompt["prompt"], "Are you sure?");
let without_prompt = echo(&app, &[]).await;
assert!(without_prompt["prompt"].is_null());
}
// **< A realistic combined request >***************************************************************
#[pagetop::test]
async fn a_realistic_htmx_request_reports_all_fields_consistently() {
// Simulates a table sort click: a boosted-free HTMX request triggered by a link with an `id`,
// targeting the table wrapper.
let app = web::test::init_router(Application::prepare(&TestApp).await.test());
let result = echo(
&app,
&[
("hx-request", "true"),
("hx-target", "user-table"),
("hx-trigger", "sort-username"),
("hx-current-url", "/admin/users"),
],
)
.await;
assert_eq!(result["is_htmx"], true);
assert_eq!(result["is_boosted"], false);
assert_eq!(result["is_history_restore"], false);
assert_eq!(result["target"], "user-table");
assert_eq!(result["trigger_id"], "sort-username");
assert_eq!(result["current_url"], "/admin/users");
assert!(result["trigger_name"].is_null());
assert!(result["prompt"].is_null());
}