1use super::*; 2 3fn noul(headline: &str, p_yes: f64) -> Answer { 4 Answer { headline: headline.into(), detail: Detail::Noul { p_yes } } 5} 6 7fn question<'a>(input: &'a str, said: &'a [Answer]) -> Card<'a> { 8 Card::Question { input, said } 9} 10 11fn decoded(bytes: &[u8]) -> (png::OutputInfo, Vec<u8>) { 12 let mut reader = png::Decoder::new(std::io::Cursor::new(bytes)).read_info().unwrap(); 13 let mut pixels = vec![0; reader.output_buffer_size().unwrap()]; 14 let info = reader.next_frame(&mut pixels).unwrap(); 15 (info, pixels) 16} 17 18#[test] 19fn the_card_is_a_png_of_the_size_the_page_declares() { 20 let yes = [noul("Yes?", 0.6)]; 21 for card in [Card::Home, question("is water wet?", &yes)] { 22 let bytes = png(card); 23 assert_eq!(&bytes[1..4], b"PNG"); 24 let (info, pixels) = decoded(&bytes); 25 assert_eq!((info.width as usize, info.height as usize), (WIDTH, HEIGHT)); 26 assert_eq!(pixels.len(), WIDTH * HEIGHT / 4); 27 // Small enough to make and send in a blink. 28 assert!(bytes.len() < 40_000, "{}", bytes.len()); 29 } 30} 31 32#[test] 33fn the_answer_and_its_numbers_change_the_picture() { 34 let input = "is water wet?"; 35 let yes = png(question(input, &[noul("Yes?", 0.6)])); 36 assert_ne!(yes, png(question(input, &[noul("No.", 0.1)]))); 37 assert_ne!(yes, png(question(input, &[]))); 38 // The same words with another probability is another picture. 39 assert_ne!(yes, png(question(input, &[noul("Yes?", 0.69)]))); 40 assert_eq!(yes, png(question(input, &[noul("Yes?", 0.6)]))); 41} 42 43#[test] 44fn every_kind_of_answer_draws_without_running_off_the_card() { 45 let long = "x".repeat(300); 46 let answers = [ 47 Answer { headline: "28%.".into(), detail: Detail::Chance { p: 0.28 } }, 48 Answer { headline: "No?".into(), detail: Detail::Unknown }, 49 Answer { 50 headline: format!("{long}."), 51 detail: Detail::Choice { confidence: 0.9, options: (0..8).map(|n| (format!("{long}{n}"), 0.125)).collect() }, 52 }, 53 Answer { 54 headline: "Medium.".into(), 55 detail: Detail::Score { score: 9.7, confidence: 0.5, levels: (0..10).map(|n| (format!("level {n} {long}"), 0.1)).collect() }, 56 }, 57 Answer { headline: "None.".into(), detail: Detail::Score { score: 0.0, confidence: 0.5, levels: Vec::new() } }, 58 ]; 59 for answer in &answers { 60 let bytes = png(question(&long, std::slice::from_ref(answer))); 61 assert_eq!(decoded(&bytes).1.len(), WIDTH * HEIGHT / 4); 62 } 63 // Several answers at once: the headlines, and each one's note. 64 assert_eq!(decoded(&png(question("two things?", &answers))).1.len(), WIDTH * HEIGHT / 4); 65} 66 67#[test] 68fn a_long_question_is_wrapped_and_cut() { 69 let long = "how spicy is a jalapeno compared to every other pepper there is, and is it a fruit, and should it go on a pizza with pineapple at midnight"; 70 let lines = wrap(long, 30, 3); 71 assert_eq!(lines.len(), 3); 72 assert!(lines.iter().all(|line| line.chars().count() <= 30), "{lines:?}"); 73 assert!(lines[2].ends_with("..."), "{lines:?}"); 74 assert_eq!(wrap("is water wet?", 30, 3), ["is water wet?"]); 75 assert_eq!(wrap("", 30, 3), Vec::<String>::new()); 76 // A word longer than a line is cut, not left to run off the card. 77 assert_eq!(wrap(&"x".repeat(50), 10, 3), ["xxxxxxx..."]); 78} 79 80#[test] 81fn a_short_answer_is_large_and_a_long_one_shrinks_to_fit() { 82 assert_eq!(fit("No?", 528, 12), 12); 83 assert_eq!(fit("Pepperoni.", 528, 12), 6); 84 assert_eq!(fit(&"x".repeat(200), 528, 12), 1); 85} 86 87#[test] 88fn letters_outside_ascii_are_drawn_or_fall_back_to_a_question_mark() { 89 assert_ne!(glyph('ñ'), glyph('?')); 90 assert_eq!(glyph('好'), glyph('?')); 91 assert_ne!(glyph('?'), [0; 8]); 92} 93 94#[test] 95fn nothing_is_drawn_outside_the_canvas() { 96 let mut canvas = Canvas::new(); 97 canvas.fill(WIDTH - 2, HEIGHT - 2, 50, 50, INK); 98 canvas.text(WIDTH - 10, HEIGHT - 4, "overflowing", 4, INK); 99 assert_eq!(canvas.pixels.len(), WIDTH * HEIGHT); 100} 101 102#[test] 103fn the_code_card_draws_with_and_without_the_latest_commit() { 104 let latest = Commit { sha: "4d933531e2".into(), subject: "clone: the token".into(), date: "2026-10-02".into(), count: Some(140) }; 105 let url = "https://lmjtfy.fun/lmjtfy.git"; 106 let code = |latest| Card::Code { name: "lmjtfy.git", url, recurse: true, about: "This site.", branch: "main", latest }; 107 let with = png(code(Some(&latest))); 108 let without = png(code(None)); 109 assert_ne!(decoded(&with).1, decoded(&without).1); 110 assert_eq!(latest.line(), "4d93353 · 2026-10-02 · 140 commits"); 111 assert_eq!(glyph('·') == glyph('?'), false, "the separator is in the face"); 112}