| 203 |
203 |
|
}
|
| 204 |
204 |
|
}
|
| 205 |
205 |
|
|
|
206 |
+ |
/// The `<body>` class a screen that owns its document carries: its measure
|
|
207 |
+ |
/// first, then whatever else its template said beside it.
|
|
208 |
+ |
///
|
|
209 |
+ |
/// [`quasi_router::Document::classed`] replaces rather than appends, so a
|
|
210 |
+ |
/// screen with a grouping or identity token of its own hands over one string.
|
|
211 |
+ |
/// Composing it here is what stops a screen that meant to add `feed-page` from
|
|
212 |
+ |
/// dropping `padded-page` on the way.
|
|
213 |
+ |
///
|
|
214 |
+ |
/// The global half is not here. `Shell` carries what is true of every document
|
|
215 |
+ |
/// and the renderer writes the two beside each other, the same composition
|
|
216 |
+ |
/// `base.html` does with [`body_attrs`].
|
|
217 |
+ |
#[must_use]
|
|
218 |
+ |
pub fn body_class(page: Measure, own: &[&str]) -> String {
|
|
219 |
+ |
let mut class = String::from(measure(page));
|
|
220 |
+ |
for token in own {
|
|
221 |
+ |
class.push(' ');
|
|
222 |
+ |
class.push_str(token);
|
|
223 |
+ |
}
|
|
224 |
+ |
class
|
|
225 |
+ |
}
|
|
226 |
+ |
|
| 206 |
227 |
|
#[cfg(test)]
|
| 207 |
228 |
|
mod tests {
|
| 208 |
229 |
|
use super::*;
|
| 268 |
289 |
|
assert_eq!(measure(Measure::Reading), "article-page");
|
| 269 |
290 |
|
}
|
| 270 |
291 |
|
|
|
292 |
+ |
#[test]
|
|
293 |
+ |
fn a_screen_with_nothing_of_its_own_carries_only_its_measure() {
|
|
294 |
+ |
assert_eq!(body_class(Measure::Contained, &[]), "centered-page");
|
|
295 |
+ |
}
|
|
296 |
+ |
|
|
297 |
+ |
#[test]
|
|
298 |
+ |
fn a_screens_own_token_lands_beside_its_measure_rather_than_instead_of_it() {
|
|
299 |
+ |
// The exact string `templates/pages/feed.html` renders today, which is
|
|
300 |
+ |
// the parity the next conversion has to hold.
|
|
301 |
+ |
assert_eq!(
|
|
302 |
+ |
body_class(Measure::Wide, &["feed-page"]),
|
|
303 |
+ |
"padded-page feed-page"
|
|
304 |
+ |
);
|
|
305 |
+ |
assert_eq!(
|
|
306 |
+ |
body_class(Measure::Wide, &["dashboard-page", "dashboard-user-page"]),
|
|
307 |
+ |
"padded-page dashboard-page dashboard-user-page"
|
|
308 |
+ |
);
|
|
309 |
+ |
}
|
|
310 |
+ |
|
| 271 |
311 |
|
#[test]
|
| 272 |
312 |
|
fn no_template_still_writes_a_layout_class_by_hand() {
|
| 273 |
313 |
|
// The done-condition, checked rather than remembered: the layout axis
|
| 277 |
317 |
|
// The four standalone tokens are screen identity rather than measure
|
| 278 |
318 |
|
// and are deliberately left alone, so they are not looked for.
|
| 279 |
319 |
|
let mut offenders = Vec::new();
|
| 280 |
|
- |
for entry in walk("templates") {
|
|
320 |
+ |
for entry in walk("templates", "html") {
|
| 281 |
321 |
|
let source = std::fs::read_to_string(&entry).expect("a template reads");
|
| 282 |
322 |
|
for (at, line) in source.lines().enumerate() {
|
| 283 |
323 |
|
if !line.contains("block body_attrs") {
|
| 297 |
337 |
|
assert!(offenders.is_empty(), "{offenders:?}");
|
| 298 |
338 |
|
}
|
| 299 |
339 |
|
|
| 300 |
|
- |
/// Every `.html` under a directory.
|
| 301 |
|
- |
fn walk(root: &str) -> Vec<std::path::PathBuf> {
|
|
340 |
+ |
#[test]
|
|
341 |
+ |
fn no_described_screen_writes_a_layout_class_by_hand() {
|
|
342 |
+ |
// The screen-side twin of the template check above: a converted screen
|
|
343 |
+ |
// states its measure and reads the class off it, so a literal reaching
|
|
344 |
+ |
// `Document::classed` is a screen that will not follow `style.css` when
|
|
345 |
+ |
// the mapping moves. The four standalone identity tokens and the embed
|
|
346 |
+ |
// classes are screen identity rather than measure and are not looked
|
|
347 |
+ |
// for.
|
|
348 |
+ |
let mut offenders = Vec::new();
|
|
349 |
+ |
for entry in walk("src", "rs") {
|
|
350 |
+ |
// This file states the three strings once, which is the point of it.
|
|
351 |
+ |
if entry.ends_with("shell.rs") {
|
|
352 |
+ |
continue;
|
|
353 |
+ |
}
|
|
354 |
+ |
let source = std::fs::read_to_string(&entry).expect("a source file reads");
|
|
355 |
+ |
for (at, line) in source.lines().enumerate() {
|
|
356 |
+ |
let literal = ["padded-page", "centered-page", "article-page"]
|
|
357 |
+ |
.iter()
|
|
358 |
+ |
.any(|name| line.contains(name));
|
|
359 |
+ |
if line.contains("classed(") && literal {
|
|
360 |
+ |
offenders.push(format!("{}:{}", entry.display(), at + 1));
|
|
361 |
+ |
}
|
|
362 |
+ |
}
|
|
363 |
+ |
}
|
|
364 |
+ |
assert!(offenders.is_empty(), "{offenders:?}");
|
|
365 |
+ |
}
|
|
366 |
+ |
|
|
367 |
+ |
/// Every file with the given extension under a directory.
|
|
368 |
+ |
fn walk(root: &str, wanted: &str) -> Vec<std::path::PathBuf> {
|
| 302 |
369 |
|
let mut found = Vec::new();
|
| 303 |
370 |
|
let mut stack = vec![std::path::PathBuf::from(root)];
|
| 304 |
371 |
|
while let Some(at) = stack.pop() {
|
| 309 |
376 |
|
let path = entry.path();
|
| 310 |
377 |
|
if path.is_dir() {
|
| 311 |
378 |
|
stack.push(path);
|
| 312 |
|
- |
} else if path.extension().is_some_and(|ext| ext == "html") {
|
|
379 |
+ |
} else if path.extension().is_some_and(|ext| ext == wanted) {
|
| 313 |
380 |
|
found.push(path);
|
| 314 |
381 |
|
}
|
| 315 |
382 |
|
}
|
| 345 |
412 |
|
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
| 346 |
413 |
|
let mut linked = 0;
|
| 347 |
414 |
|
let mut sources = vec![head().to_string()];
|
| 348 |
|
- |
for entry in walk("templates") {
|
|
415 |
+ |
for entry in walk("templates", "html") {
|
| 349 |
416 |
|
sources.push(std::fs::read_to_string(&entry).expect("a template reads"));
|
| 350 |
417 |
|
}
|
| 351 |
418 |
|
for source in &sources {
|