max / quasi
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
3 files changed,
+171 insertions,
-4 deletions
| @@ -6197,10 +6197,6 @@ | |||
| 6197 | 6197 | "winnow 1.0.4", | |
| 6198 | 6198 | ] | |
| 6199 | 6199 | ||
| 6200 | - | [[patch.unused]] | |
| 6201 | - | name = "quasi-type" | |
| 6202 | - | version = "0.1.0" | |
| 6203 | - | ||
| 6204 | 6200 | [[patch.unused]] | |
| 6205 | 6201 | name = "kberg" | |
| 6206 | 6202 | version = "0.1.0" | |
| @@ -6217,6 +6213,10 @@ | |||
| 6217 | 6213 | name = "tagtree" | |
| 6218 | 6214 | version = "0.4.1" | |
| 6219 | 6215 | ||
| 6216 | + | [[patch.unused]] | |
| 6217 | + | name = "quasi-type" | |
| 6218 | + | version = "0.1.0" | |
| 6219 | + | ||
| 6220 | 6220 | [[patch.unused]] | |
| 6221 | 6221 | name = "synckit-client" | |
| 6222 | 6222 | version = "0.8.1" |
| @@ -122,6 +122,94 @@ | |||
| 122 | 122 | ("Settings", layout::Priority::Essential), | |
| 123 | 123 | ]; | |
| 124 | 124 | ||
| 125 | + | /// One of every `Node` member, in declaration order. | |
| 126 | + | /// | |
| 127 | + | /// Kept as a function so more than one test can walk it, and it has to stay | |
| 128 | + | /// complete: `Node` is `#[non_exhaustive]`, so a member added upstream lands on | |
| 129 | + | /// a catch-all arm and compiles. This list plus the count below is what says a | |
| 130 | + | /// walk has learned the member rather than merely accepting it. | |
| 131 | + | fn one_of_everything() -> Vec<Node> { | |
| 132 | + | vec![ | |
| 133 | + | Node::page("Tasks"), | |
| 134 | + | Node::text("plain"), | |
| 135 | + | Node::rich("**bold** and `code`"), | |
| 136 | + | Node::Act(Act::new("Save", Action::post("/save"))), | |
| 137 | + | Node::Link { | |
| 138 | + | text: "Docs".to_owned(), | |
| 139 | + | action: Action::get("/docs"), | |
| 140 | + | }, | |
| 141 | + | Node::Figure(Figure::new("17", "Streak")), | |
| 142 | + | Node::since(std::time::SystemTime::UNIX_EPOCH), | |
| 143 | + | Node::until(std::time::SystemTime::UNIX_EPOCH), | |
| 144 | + | Node::age(std::time::SystemTime::UNIX_EPOCH), | |
| 145 | + | Node::Image(quasi_router::Picture::new("/cover.png", "The library view")), | |
| 146 | + | Node::Token(Tag::badge("beta")), | |
| 147 | + | Node::banner(layout::Tone::Info, "Saved"), | |
| 148 | + | Node::empty("Nothing here yet"), | |
| 149 | + | Node::Field(Box::new(Field::new( | |
| 150 | + | layout::FieldKind::Text, | |
| 151 | + | "title", | |
| 152 | + | "Title", | |
| 153 | + | ))), | |
| 154 | + | Node::Form { | |
| 155 | + | action: Action::post("/save"), | |
| 156 | + | submit: "Save".to_owned(), | |
| 157 | + | fields: vec![Field::new(layout::FieldKind::Text, "title", "Title")], | |
| 158 | + | }, | |
| 159 | + | Node::list([Row::new("One")]), | |
| 160 | + | Node::Table { | |
| 161 | + | columns: vec![Column::new("Name")], | |
| 162 | + | rows: vec![Cells::new([Cell::new("One")])], | |
| 163 | + | more: None, | |
| 164 | + | }, | |
| 165 | + | Node::Timeline { | |
| 166 | + | track: layout::Track::DAY, | |
| 167 | + | entries: vec![quasi_router::Placed::new(540, 45, Row::new("Standup"))], | |
| 168 | + | focus: Some(540), | |
| 169 | + | }, | |
| 170 | + | Node::Select { | |
| 171 | + | kind: layout::Selector::Tabs, | |
| 172 | + | options: vec![(Choice::new("all", "All"), None)], | |
| 173 | + | chosen: Some("all".to_owned()), | |
| 174 | + | action: Some(Action::get("/filter")), | |
| 175 | + | }, | |
| 176 | + | Node::Meter(Meter::new(3, 6)), | |
| 177 | + | Node::stats([Figure::new("17", "Streak")]), | |
| 178 | + | Node::Region(Slot::new("nested", RegionKind::Pane)), | |
| 179 | + | ] | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | #[test] | |
| 183 | + | fn every_described_node_draws_without_panicking() { | |
| 184 | + | // This crate walks `Node` twice, so the exhaustiveness it claims is two | |
| 185 | + | // claims. Drawing is the first of them: one of everything, painted. | |
| 186 | + | for node in one_of_everything() { | |
| 187 | + | let _ = drawn(&node, 60, 12); | |
| 188 | + | } | |
| 189 | + | } | |
| 190 | + | ||
| 191 | + | #[test] | |
| 192 | + | fn every_described_node_is_measured_without_panicking() { | |
| 193 | + | // The second walk, and the one that has no visible symptom when it drifts: | |
| 194 | + | // a member `height` does not know is measured as something else and the | |
| 195 | + | // rows below it land in the wrong place. | |
| 196 | + | let tui = tui(); | |
| 197 | + | for node in one_of_everything() { | |
| 198 | + | let _ = tui.height(&node, 60); | |
| 199 | + | } | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | #[test] | |
| 203 | + | fn the_exhaustiveness_list_holds_one_of_every_member() { | |
| 204 | + | // A count rather than a comment. `Node` cannot be iterated, so nothing but | |
| 205 | + | // this stops the list above going stale while both walks keep compiling. | |
| 206 | + | assert_eq!( | |
| 207 | + | one_of_everything().len(), | |
| 208 | + | 22, | |
| 209 | + | "one of every `Node` member, in declaration order" | |
| 210 | + | ); | |
| 211 | + | } | |
| 212 | + | ||
| 125 | 213 | #[test] | |
| 126 | 214 | fn a_member_of_a_run_is_drawn_at_all() { | |
| 127 | 215 | // The defect: `draw` and `height` both walked `body` and nothing walked |
| @@ -46,6 +46,85 @@ | |||
| 46 | 46 | Webview::new().fragment(node) | |
| 47 | 47 | } | |
| 48 | 48 | ||
| 49 | + | /// One of every `Node` member, in declaration order. | |
| 50 | + | /// | |
| 51 | + | /// Kept as a function so more than one test can walk it, and it has to stay | |
| 52 | + | /// complete: `Node` is `#[non_exhaustive]`, so a member added upstream lands on | |
| 53 | + | /// a catch-all arm and compiles. This list plus the count below is what says | |
| 54 | + | /// `node_html` has learned the member rather than merely accepting it. | |
| 55 | + | fn one_of_everything() -> Vec<Node> { | |
| 56 | + | vec![ | |
| 57 | + | Node::page("Tasks"), | |
| 58 | + | Node::text("plain"), | |
| 59 | + | Node::rich("**bold** and `code`"), | |
| 60 | + | Node::Act(Act::new("Save", Action::post("/save"))), | |
| 61 | + | Node::Link { | |
| 62 | + | text: "Docs".to_owned(), | |
| 63 | + | action: Action::get("/docs"), | |
| 64 | + | }, | |
| 65 | + | Node::Figure(Figure::new("17", "Streak")), | |
| 66 | + | Node::since(std::time::SystemTime::UNIX_EPOCH), | |
| 67 | + | Node::until(std::time::SystemTime::UNIX_EPOCH), | |
| 68 | + | Node::age(std::time::SystemTime::UNIX_EPOCH), | |
| 69 | + | Node::Image(quasi_router::Picture::new("/cover.png", "The library view")), | |
| 70 | + | Node::Token(Tag::badge("beta")), | |
| 71 | + | Node::banner(layout::Tone::Info, "Saved"), | |
| 72 | + | Node::empty("Nothing here yet"), | |
| 73 | + | Node::Field(Box::new(Field::new( | |
| 74 | + | layout::FieldKind::Text, | |
| 75 | + | "title", | |
| 76 | + | "Title", | |
| 77 | + | ))), | |
| 78 | + | Node::Form { | |
| 79 | + | action: Action::post("/save"), | |
| 80 | + | submit: "Save".to_owned(), | |
| 81 | + | fields: vec![Field::new(layout::FieldKind::Text, "title", "Title")], | |
| 82 | + | }, | |
| 83 | + | Node::list([Row::new("One")]), | |
| 84 | + | Node::Table { | |
| 85 | + | columns: vec![Column::new("Name")], | |
| 86 | + | rows: vec![Cells::new([Cell::new("One")])], | |
| 87 | + | more: None, | |
| 88 | + | }, | |
| 89 | + | Node::Timeline { | |
| 90 | + | track: layout::Track::DAY, | |
| 91 | + | entries: vec![quasi_router::Placed::new(540, 45, Row::new("Standup"))], | |
| 92 | + | focus: Some(540), | |
| 93 | + | }, | |
| 94 | + | Node::Select { | |
| 95 | + | kind: layout::Selector::Tabs, | |
| 96 | + | options: vec![(Choice::new("all", "All"), None)], | |
| 97 | + | chosen: Some("all".to_owned()), | |
| 98 | + | action: Some(Action::get("/filter")), | |
| 99 | + | }, | |
| 100 | + | Node::Meter(Meter::new(3, 6)), | |
| 101 | + | Node::stats([Figure::new("17", "Streak")]), | |
| 102 | + | Node::Region(Slot::new("nested", RegionKind::Pane)), | |
| 103 | + | ] | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | #[test] | |
| 107 | + | fn every_described_node_emits_markup() { | |
| 108 | + | // One of everything, through `node_html`. A member the walk does not know | |
| 109 | + | // reaches a catch-all rather than a compile error, so an empty fragment is | |
| 110 | + | // what silence looks like here and this is what objects to it. | |
| 111 | + | for node in one_of_everything() { | |
| 112 | + | let html = fragment(&node); | |
| 113 | + | assert!(!html.is_empty(), "nothing came out for {node:?}"); | |
| 114 | + | } | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | #[test] | |
| 118 | + | fn the_exhaustiveness_list_holds_one_of_every_member() { | |
| 119 | + | // A count rather than a comment. `Node` cannot be iterated, so nothing but | |
| 120 | + | // this stops the list above going stale while the walk keeps compiling. | |
| 121 | + | assert_eq!( | |
| 122 | + | one_of_everything().len(), | |
| 123 | + | 22, | |
| 124 | + | "one of every `Node` member, in declaration order" | |
| 125 | + | ); | |
| 126 | + | } | |
| 127 | + | ||
| 49 | 128 | #[test] | |
| 50 | 129 | fn a_screen_is_a_whole_document() { | |
| 51 | 130 | let html = render(&Screen::list_detail("Tasks", false)); |