//! What the renderer promises, asserted.
//!
//! Two kinds of test here, and the second is the interesting one. The first
//! checks that a description comes out as the markup it should. The second
//! checks the architectural claims the design rests on — that no `hx-target`
//! is ever emitted, that htmx appears in exactly one function, that a
//! description's text cannot become markup — because those are the properties
//! that would decay silently, one convenient exception at a time.
use makeover_layout as layout;
use quasi_http::Serves;
use quasi_router::screen::{
Accepted, Act, Candidate, Canvas, Cell, CellKey, Choice, Column, Consult, Document, Field,
Figure, Jump, Meter, Prose, Repeat, Repeating, Rest, Row, Tag, ThemeChoice,
};
use quasi_router::{Action, Frame, Node, RegionKind, Reveal, Richness, Run, Screen, Slot, Trust};
use crate::{Emit, Shell, Webview};
/// A head with the screen's discovery tags removed.
///
/// They are the one part of the head that comes off the `Screen` rather than
/// off the `Shell`, so a comparison against the shell's own parts has to drop
/// them or it is comparing two different questions.
fn strip_discovery(head: &str) -> String {
let mut out = String::with_capacity(head.len());
let mut rest = head;
while let Some(start) = rest.find("').expect("a tag closes") + start + 1;
rest = &rest[end..];
}
out.push_str(rest);
out
}
/// A band with every member in it, for the corpus the class checks read.
///
/// Every class this crate emits has to be reachable from that corpus or the
/// second of the two checks reads the name as dead and fails; a band with a
/// brand, a search box and a disclosure is what reaches all six of the band's.
fn banded() -> quasi_router::Band {
quasi_router::Band::new()
.branded(quasi_router::Brand::new("Makenot.work", Action::get("/")).marking("."))
.searching(Field::new(layout::FieldKind::Text, "q", "Search"))
.disclosing(quasi_router::Disclose::Narrow)
}
fn render(screen: &Screen) -> String {
Webview::new().screen(screen)
}
fn fragment(node: &Node) -> String {
Webview::new().fragment(node)
}
/// One of every `Node` member, in declaration order.
///
/// Kept as a function so more than one test can walk it, and it has to stay
/// complete: `Node` is `#[non_exhaustive]`, so a member added upstream lands on
/// a catch-all arm and compiles. This list plus the count below is what says
/// `node_html` has learned the member rather than merely accepting it.
fn one_of_everything() -> Vec {
vec![
Node::page("Tasks"),
Node::text("plain"),
Node::rich("**bold** and `code`"),
Node::Act(Act::new("Save", Action::post("/save"))),
Node::Link {
text: "Docs".to_owned(),
action: Action::get("/docs"),
},
Node::Figure(Figure::new("17", "Streak")),
Node::since(std::time::SystemTime::UNIX_EPOCH),
Node::until(std::time::SystemTime::UNIX_EPOCH),
Node::age(std::time::SystemTime::UNIX_EPOCH),
Node::Image(quasi_router::Image::new("/cover.png", "The library view")),
Node::Token(Tag::badge("beta")),
Node::banner(layout::Tone::Info, "Saved"),
Node::empty("Nothing here yet"),
Node::Field(Box::new(Field::new(
layout::FieldKind::Text,
"title",
"Title",
))),
Node::Form {
marks: ::quasi_router::stage::Marks::none(),
action: Action::post("/save"),
submit: "Save".to_owned(),
fields: vec![Field::new(layout::FieldKind::Text, "title", "Title")],
},
Node::list([Row::new("One")]),
Node::Table {
marks: ::quasi_router::stage::Marks::none(),
columns: vec![Column::new("Name")],
rows: vec![Row::cells([Cell::new("One")])],
more: None,
},
Node::Timeline {
marks: ::quasi_router::stage::Marks::none(),
track: layout::Track::DAY,
entries: vec![quasi_router::Placed::new(540, 45, Row::new("Standup"))],
focus: Some(540),
},
Node::Meter(Meter::new(3, 6)),
Node::stats([Figure::new("17", "Streak")]),
Node::Region(Slot::new("nested", RegionKind::Pane)),
]
}
#[test]
fn every_described_node_emits_markup() {
// One of everything, through `node_html`. A member the walk does not know
// reaches a catch-all rather than a compile error, so an empty fragment is
// what silence looks like here and this is what objects to it.
for node in one_of_everything() {
let html = fragment(&node);
assert!(!html.is_empty(), "nothing came out for {node:?}");
}
}
#[test]
fn the_exhaustiveness_list_holds_one_of_every_member() {
// A count rather than a comment. `Node` cannot be iterated, so nothing but
// this stops the list above going stale while the walk keeps compiling.
assert_eq!(
one_of_everything().len(),
21,
"one of every `Node` member, in declaration order"
);
}
#[test]
fn a_screen_is_a_whole_document() {
let html = render(&Screen::list_detail("Tasks", false));
assert!(html.starts_with(""));
assert!(html.contains("Tasks"));
assert!(html.ends_with("