feat: render wml decks with paginated card sub-documents

This commit is contained in:
randogoth 2026-10-04 21:04:49 +03:00
parent 36b868e094
commit 1de4674b09
17 changed files with 1519 additions and 5 deletions

View file

@ -28,6 +28,12 @@ impl Server {
write(dir.path(), "one/img.png", "not really a png");
write(dir.path(), "one/.itsybitsy.toml", "[defaults]\nmax_age = 120\n");
write(dir.path(), "two/index.md", "# Two\n\nSecond site.\n");
write(
dir.path(),
"one/trail.md",
"Intro.\n\n<!-- card Weather -->\n\nCold and clear. Permit fee: $5.\n\n\
<!-- card Status -->\n\n- North: open\n",
);
let text = format!(
r#"
@ -401,3 +407,107 @@ fn nothing_outside_the_root_is_reachable_over_any_protocol() {
assert_eq!(server.send("spartan", b"one.test /.itsybitsy.toml 0\r\n"), "4 Not found\r\n");
assert_eq!(server.send("nex", b"/.itsybitsy.toml\r\n"), "Not found\n");
}
// -- Sub-documents -------------------------------------------------------
/// A page that paginates addresses sub-documents under its own URL. Only a
/// format that paginates can serve them, so every other format redirects to the
/// parent rather than substituting something else.
#[cfg(feature = "wml")]
mod parts {
use super::*;
/// Like `Server::start`, but with a listener that can serve WML.
fn wml_server() -> Server {
let dir = tempfile::tempdir().unwrap();
let root = dir.path().join("one");
fs::create_dir_all(&root).unwrap();
fs::write(root.join("index.md"), "# One\n").unwrap();
fs::write(
root.join("trail.md"),
"Intro.\n\n<!-- card Weather -->\n\nCold and clear. Permit fee: $5.\n\n\
<!-- card Status -->\n\n- North: open\n",
)
.unwrap();
fs::write(root.join("about.md"), "# About\n\nNo dividers here.\n").unwrap();
fs::write(root.join(".itsybitsy.toml"), "[defaults]\ndeck_per_card = true\n").unwrap();
let text = format!(
r#"
version = 1
[site.one]
root = "{root}"
hosts = ["one.test"]
[listener.web]
protocol = "http"
bind = "127.0.0.1:0"
formats = ["wml", "html"]
default_site = "one"
"#,
root = root.to_str().unwrap(),
);
let config: ServerConfig = toml::from_str(&text).unwrap();
let registry = Arc::new(itsybitsy::registry::build());
let formats: Vec<String> = ["wml", "html"].iter().map(|s| s.to_string()).collect();
let sites = Arc::new(SiteSet::build(&config, registry.clone(), formats).unwrap());
let bound = serve::bind(&config, registry, sites).unwrap();
let addrs = bound
.iter()
.map(|entry| (entry.listener.name.clone(), entry.local_addr().unwrap()))
.collect();
serve::spawn_all(bound);
Server { addrs, _dir: dir }
}
#[test]
fn a_wml_client_gets_the_card_itself() {
let server = wml_server();
let response =
server.request("web", "one.test", "/trail/weather", "Accept: text/vnd.wap.wml\r\n");
let (status, body) = split(&response);
assert_eq!(status, "HTTP/1.1 200 OK");
assert!(response.contains("Content-Type: text/vnd.wap.wml"), "{response}");
assert!(body.contains("<card id=\"card1\" title=\"Weather\">"), "{body}");
// WML reads a single dollar as a variable reference.
assert!(body.contains("Permit fee: $$5."), "{body}");
}
#[test]
fn a_browser_is_redirected_to_the_parent_page() {
// That URL space belongs to WML; HTML does not address it.
let server = wml_server();
let response = server.get("one.test", "/trail/weather");
assert_eq!(split(&response).0, "HTTP/1.1 301 Moved Permanently");
assert!(response.contains("Location: /trail"), "{response}");
assert!(response.contains("Vary: Accept"), "the redirect depends on Accept");
}
#[test]
fn an_unknown_card_redirects_even_for_a_wml_client() {
let server = wml_server();
let response =
server.request("web", "one.test", "/trail/nonexistent", "Accept: text/vnd.wap.wml\r\n");
assert_eq!(split(&response).0, "HTTP/1.1 301 Moved Permanently");
assert!(response.contains("Location: /trail"), "{response}");
}
#[test]
fn a_page_that_does_not_paginate_has_no_such_urls() {
let server = wml_server();
let response =
server.request("web", "one.test", "/about/whatever", "Accept: text/vnd.wap.wml\r\n");
assert_eq!(split(&response).0, "HTTP/1.1 404 Not Found");
}
#[test]
fn the_index_deck_links_out_to_each_card() {
let server = wml_server();
let response = server.request("web", "one.test", "/trail", "Accept: text/vnd.wap.wml\r\n");
let (_, body) = split(&response);
assert!(body.contains("<a href=\"/trail/weather#card1\">Weather</a>"), "{body}");
assert!(body.contains("<a href=\"/trail/status#card2\">Status</a>"), "{body}");
}
}