use axum::http::{header, HeaderMap};
use axum::response::IntoResponse;
use scraper::{Html, Selector};
use slhx_axum::{
interactions, runtime_js, DispatchRejection, EffectResponse, InteractionForm,
InteractionFormRejection, InteractionRequest, PageMode, PageRequest, PageResponse,
SLHX_CONTENT_TYPE, SLHX_FINGERPRINT_HEADER, SLHX_PARTIAL_HEADER, SLHX_RUNTIME_CONTENT_TYPE,
SLHX_TITLE_HEADER,
};
use slhx_core::{push, BuildFingerprint, Handle, SafeHtml, Slot};
fn selector(value: &str) -> Selector {
Selector::parse(value).expect("test selector parses")
}
#[test]
fn page_mode_detects_partial_header() {
let mut headers = HeaderMap::new();
assert_eq!(PageMode::from_headers(&headers), PageMode::Full);
headers.insert(SLHX_PARTIAL_HEADER, "true".parse().unwrap());
assert_eq!(PageMode::from_headers(&headers), PageMode::Partial);
}
#[test]
fn page_request_wraps_full_pages_and_leaves_partials_unwrapped() {
// req: test/005
let full = PageRequest { mode: PageMode::Full }.page(
"Docs",
|content| format!("
{content}"),
);
assert_eq!(full.mode, PageMode::Full);
let full_document = Html::parse_document(&full.html);
assert_eq!(
full_document
.select(&selector("body[data-shell=\"docs\"] main[data-page=\"docs\"]"))
.count(),
1
);
let partial = PageRequest { mode: PageMode::Partial }.page(
"Docs",
|content| format!("{content}"),
);
assert_eq!(partial.mode, PageMode::Partial);
let partial_fragment = Html::parse_fragment(&partial.html);
assert_eq!(partial_fragment.select(&selector("main[data-page=\"docs\"]")).count(), 1);
assert_eq!(partial_fragment.select(&selector("body[data-shell=\"docs\"]")).count(), 0);
}
#[test]
fn page_request_wraps_safe_html_full_pages_and_leaves_partials_unwrapped() {
// req: axum_integration/001 req: html_safety/001 req: html_safety/002 req: test/005
let full = PageRequest {
mode: PageMode::Full,
}
.page_html(
SafeHtml::trusted("Docs"),
|content| {
SafeHtml::trusted(format!(
"{content}"
))
},
);
assert_eq!(full.mode, PageMode::Full);
let full_document = Html::parse_document(&full.html);
assert_eq!(
full_document
.select(&selector("body[data-shell=\"docs\"] main[data-page=\"docs\"]"))
.count(),
1
);
let partial = PageRequest {
mode: PageMode::Partial,
}
.page_html(
SafeHtml::trusted("Docs"),
|content| {
SafeHtml::trusted(format!(
"{content}"
))
},
);
assert_eq!(partial.mode, PageMode::Partial);
let partial_fragment = Html::parse_fragment(&partial.html);
assert_eq!(partial_fragment.select(&selector("main[data-page=\"docs\"]")).count(), 1);
assert_eq!(partial_fragment.select(&selector("body[data-shell=\"docs\"]")).count(), 0);
}
#[test]
fn partial_page_response_sets_partial_and_title_headers() {
let response = PageResponse::partial("Docs")
.title("Docs")
.into_response();
assert_eq!(response.headers()[header::CONTENT_TYPE], "text/html; charset=utf-8");
assert_eq!(response.headers()[SLHX_PARTIAL_HEADER], "true");
assert_eq!(response.headers()[SLHX_TITLE_HEADER], "Docs");
}
#[test]
fn effect_response_is_wire_batch_with_fingerprint_header() {
let response = EffectResponse::new(push("/docs"), BuildFingerprint(99)).into_response();
assert_eq!(response.headers()[header::CONTENT_TYPE], SLHX_CONTENT_TYPE);
assert_eq!(response.headers()[SLHX_FINGERPRINT_HEADER], "99");
}
#[test]
fn interaction_form_parses_handle_and_fields() {
let form = InteractionForm::parse_urlencoded(b"__h=42&title=Hello+World&tag=a&tag=b%2Fc")
.unwrap();
assert_eq!(form.handle_id, 42);
assert_eq!(form.value("title"), Some("Hello World"));
assert_eq!(form.values("tag").collect::>(), ["a", "b/c"]);
}
#[test]
fn interaction_form_parses_typed_values() {
// req: form/004 req: dx/003
let form = InteractionForm::parse_urlencoded(b"__h=42&count=7&bad=nope")
.expect("form should parse");
assert_eq!(form.parse::("count"), Some(7));
assert_eq!(form.parse::("bad"), None);
assert_eq!(form.parse::("missing"), None);
}
#[test]
fn interaction_form_requires_numeric_handle() {
assert_eq!(
InteractionForm::parse_urlencoded(b"title=Hello").unwrap_err(),
InteractionFormRejection::MissingHandle
);
assert_eq!(
InteractionForm::parse_urlencoded(b"__h=nope").unwrap_err(),
InteractionFormRejection::InvalidHandle
);
}
#[test]
fn interaction_form_preserves_hidden_csrf_fields_for_extractors() {
// req: auth/004
let form = InteractionForm::parse_urlencoded(b"__h=42&csrf_token=abc123&title=Hello")
.unwrap();
assert_eq!(form.handle_id, 42);
assert_eq!(form.value("csrf_token"), Some("abc123"));
assert!(form.fields().iter().any(|(name, _)| name == "csrf_token"));
}
#[test]
fn interaction_request_dispatches_with_concise_handlers_helper() {
// req: axum_integration/003 req: ceremony/001 req: dx/003
let request = InteractionRequest::from(InteractionForm::for_handle(
Handle::<()>::new(7),
Vec::new(),
));
let response = request
.dispatch(interactions(BuildFingerprint(4)).on(Handle::<()>::new(7), |_| {
Slot::::new(3).text("ok")
}))
.unwrap();
assert_eq!(response.batch.fingerprint, BuildFingerprint(4));
assert_eq!(response.batch.ops, vec![Slot::::new(3).text("ok")]);
}
#[test]
fn interactions_dispatch_by_checked_handle() {
// req: ceremony/004 req: public_api/001
let title = Slot::::new(7);
let create = Handle::<()>::new(42);
let request = InteractionRequest::from(InteractionForm::for_handle(
create,
[("title".into(), "Hello".into())],
));
let response = request
.dispatch(interactions(BuildFingerprint(123)).on(create, move |form| {
title.text(form.value("title").unwrap_or(""))
}))
.unwrap();
assert_eq!(response.batch.fingerprint, BuildFingerprint(123));
assert_eq!(response.batch.ops, vec![title.text("Hello")]);
}
#[test]
fn interactions_reject_unknown_handle_ids() {
let request = InteractionRequest::from(InteractionForm::new(9, []));
assert_eq!(
request.dispatch(interactions(BuildFingerprint(123))).unwrap_err(),
DispatchRejection::UnknownHandle(9)
);
}
#[test]
fn runtime_js_response_serves_embedded_runtime() {
let response = runtime_js().into_response();
assert_eq!(response.headers()[header::CONTENT_TYPE], SLHX_RUNTIME_CONTENT_TYPE);
assert!(response.headers().contains_key(header::CACHE_CONTROL));
}