Skip to main content

max / quasi

Prove one description renders identically through both host adapters
Author: Max Johnson <me@maxj.phd> · 2026-08-08 23:26 UTC
Signed with PGP, not checked
Commit: 29b0c50f814820669c0640dcac7b64c4389b47ca
Parent: a97a2b0
5 files changed, +127 insertions, -12 deletions
M Cargo.lock +14 -12
@@ -2350,6 +2350,7 @@
2350 2350 "http-body-util",
2351 2351 "quasi-http",
2352 2352 "quasi-router",
2353 + "quasi-webview",
2353 2354 "tokio",
2354 2355 "tower",
2355 2356 ]
@@ -2377,6 +2378,7 @@
2377 2378 "http",
2378 2379 "quasi-http",
2379 2380 "quasi-router",
2381 + "quasi-webview",
2380 2382 "tauri",
2381 2383 ]
2382 2384
@@ -4528,18 +4530,6 @@
4528 4530 source = "registry+https://github.com/rust-lang/crates.io-index"
4529 4531 checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
4530 4532
4531 - [[patch.unused]]
4532 - name = "kberg"
4533 - version = "0.1.0"
4534 -
4535 - [[patch.unused]]
4536 - name = "painhours"
4537 - version = "0.1.0"
4538 -
4539 - [[patch.unused]]
4540 - name = "tagtree"
4541 - version = "0.4.0"
4542 -
4543 4533 [[patch.unused]]
4544 4534 name = "synckit-client"
4545 4535 version = "0.8.0"
@@ -4551,3 +4541,15 @@
4551 4541 [[patch.unused]]
4552 4542 name = "docengine"
4553 4543 version = "0.4.0"
4544 +
4545 + [[patch.unused]]
4546 + name = "kberg"
4547 + version = "0.1.0"
4548 +
4549 + [[patch.unused]]
4550 + name = "painhours"
4551 + version = "0.1.0"
4552 +
4553 + [[patch.unused]]
4554 + name = "tagtree"
4555 + version = "0.4.0"
@@ -20,6 +20,7 @@
20 20 tokio = { version = "1.50.0", features = ["rt"] }
21 21
22 22 [dev-dependencies]
23 + quasi-webview = { path = "../quasi-webview", version = "0.1.0" }
23 24 tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread"] }
24 25 tower = { version = "0.5.3", features = ["util"] }
25 26 http-body-util = "0.1.3"
@@ -22,6 +22,7 @@
22 22 tauri = { version = "2.11.5", default-features = false }
23 23
24 24 [dev-dependencies]
25 + quasi-webview = { path = "../quasi-webview", version = "0.1.0" }
25 26 # The doctest names a concrete `Runtime`, and `Wry` is behind a default feature
26 27 # this crate does not otherwise ask for. Only the example needs it: nothing in
27 28 # the test module builds a window, which is what keeps them runnable with no
@@ -255,3 +255,58 @@
255 255 "text/html; charset=utf-8"
256 256 );
257 257 }
258 +
259 + /// The one screen the end-to-end test renders for real.
260 + ///
261 + /// Deliberately not the `Spy`. Everything above proves the adapter's own
262 + /// boundary against a renderer that says what it was handed; this proves the
263 + /// boundary holds when the renderer is the one an app actually ships, which is
264 + /// the only place the two could disagree.
265 + fn real_screen() -> Screen {
266 + Screen::list_detail("Tasks", false)
267 + .with(
268 + Slot::new("list", RegionKind::Pane)
269 + .with(Node::list([quasi_router::screen::Row::new("Write it down")
270 + .activate(quasi_router::Action::get("/task/1"))])),
271 + )
272 + .with(Slot::new("detail", RegionKind::Pane).with(Node::text("Nothing selected")))
273 + }
274 +
275 + fn real_home(_app: &App, _params: Params) -> Result<Response, RouteError> {
276 + Ok(real_screen().into())
277 + }
278 +
279 + #[tokio::test]
280 + async fn a_description_served_here_is_the_renderer_s_own_output() {
281 + let router = Router::<App>::new().get("/", real_home);
282 + let service = super::Adapter::new(
283 + router,
284 + Arc::new(App),
285 + Arc::new(quasi_webview::Webview::under("/static")),
286 + )
287 + .into_router();
288 +
289 + let response = service.oneshot(get("/")).await.unwrap();
290 + assert_eq!(response.status(), StatusCode::OK);
291 + assert_eq!(
292 + response
293 + .headers()
294 + .get(header::CONTENT_TYPE)
295 + .and_then(|v| v.to_str().ok()),
296 + Some("text/html; charset=utf-8")
297 + );
298 +
299 + let body = response.into_body().collect().await.unwrap().to_bytes();
300 + let served = String::from_utf8(body.to_vec()).unwrap();
301 +
302 + // The adapter adds nothing to the document and removes nothing from it.
303 + // Anything it wanted to add would be a second party emitting markup, which
304 + // is the thing `quasi_http::render`'s existence rules out.
305 + use quasi_http::Render as _;
306 + assert_eq!(
307 + served,
308 + quasi_webview::Webview::under("/static").screen(&real_screen())
309 + );
310 + assert!(served.starts_with("<!doctype html>"));
311 + assert!(served.contains("hx-get=\"/task/1\""));
312 + }
@@ -253,3 +253,59 @@
253 253 "text/html; charset=utf-8"
254 254 );
255 255 }
256 +
257 + /// The one screen the end-to-end test renders for real.
258 + ///
259 + /// The same description the axum adapter's own end-to-end test serves. Two
260 + /// hosts, one description, and the only difference between the documents is
261 + /// where the assets live — which is the stack's whole claim, and the reason
262 + /// `Shell` takes an asset prefix rather than hard-coding one.
263 + fn real_screen() -> Screen {
264 + Screen::list_detail("Tasks", false)
265 + .with(
266 + Slot::new("list", RegionKind::Pane)
267 + .with(Node::list([quasi_router::screen::Row::new("Write it down")
268 + .activate(quasi_router::Action::get("/task/1"))])),
269 + )
270 + .with(Slot::new("detail", RegionKind::Pane).with(Node::text("Nothing selected")))
271 + }
272 +
273 + fn real_home(_app: &App, _params: Params) -> Result<Response, RouteError> {
274 + Ok(real_screen().into())
275 + }
276 +
277 + #[test]
278 + fn a_description_served_here_is_the_renderer_s_own_output() {
279 + use quasi_http::Render as _;
280 +
281 + let render = quasi_webview::Webview::under("quasi://localhost/static");
282 + let protocol = Protocol::new(
283 + "quasi",
284 + Router::<App>::new().get("/", real_home),
285 + Arc::new(App),
286 + Arc::new(render),
287 + );
288 + let context = Context {
289 + router: protocol.router,
290 + state: protocol.state,
291 + render: protocol.render,
292 + body_limit: protocol.body_limit,
293 + passthrough: protocol.passthrough,
294 + };
295 +
296 + let response = serve(&context, &get("quasi://localhost/"));
297 + assert_eq!(response.status(), 200);
298 +
299 + let served = text(&response);
300 + assert_eq!(
301 + served,
302 + quasi_webview::Webview::under("quasi://localhost/static").screen(&real_screen())
303 + );
304 +
305 + // The asset paths are the custom scheme's, which is the one thing that
306 + // differs from the served case.
307 + assert!(served.contains("src=\"quasi://localhost/static/htmx.min.js\""));
308 + // Everything the description said is identical to what axum emits.
309 + assert!(served.contains("hx-get=\"/task/1\""));
310 + assert!(served.contains("id=\"detail\""));
311 + }