lmjtfy.git / packages / card / src / tests.rs
tests.rsannotatedtests.rssource112 lines · 4.6 KB · raw
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}