lmjtfy.git / apps / lmjtfy / src / view / toast.rs
1//! The toasts every open page is shown when something happens elsewhere,
2//! pushed by the archive over `/live`. Pure: what happened in, markup out.
3
4use archive::{Answer, Fetch};
5use maud::{Markup, html};
6
7use super::url_encode;
8
9/// Someone asked a question. `shown` is the question and Jev's answers if
10/// the feed may show it; otherwise the toast says only that someone asked.
11/// `data-q` lets the page that asked it skip its own toast.
12pub fn asked(shown: Option<(&str, &[Answer])>) -> Markup {
13    html! {
14        @match shown {
15            Some((input, answers)) => {
16                div .toast data-q=(input) {
17                    "Someone asked "
18                    a href={ "/?q=" (url_encode(input)) } { "“" (input) "”" }
19                    @if !answers.is_empty() {
20                        " · Jev: "
21                        b { (answers.iter().map(|answer| answer.headline.as_str()).collect::<Vec<_>>().join(" ")) }
22                    }
23                }
24            }
25            None => div .toast { "Someone asked Jev something." },
26        }
27    }
28}
29
30/// What a deploy changed, told to a page from before it.
31pub fn note(note: &str) -> Markup {
32    html! { div .toast .note { b { "New: " } (note) } }
33}
34
35/// Someone cloned or pulled a repository (`lmjtfy.git`): this site's is
36/// "the code", any other is named.
37pub fn fetched(fetch: Fetch, served: &str) -> Markup {
38    let what = match fetch {
39        Fetch::Clone => "cloned",
40        Fetch::Pull => "pulled",
41    };
42    html! {
43        div .toast {
44            "Someone " (what) " "
45            a href={ "/" (served) } { @if served == "lmjtfy.git" { "the code" } @else { (served) } }
46            "."
47        }
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use archive::Detail;
55
56    #[test]
57    fn an_unlisted_question_is_not_shown() {
58        let html = asked(None).into_string();
59        assert_eq!(html, r#"<div class="toast">Someone asked Jev something.</div>"#);
60    }
61
62    #[test]
63    fn a_listed_question_is_shown_escaped_with_its_answer() {
64        let answers = [Answer { headline: "No.".into(), detail: Detail::Noul { p_yes: 0.03 } }];
65        let html = asked(Some(("is <b>this</b> ok?", &answers))).into_string();
66        assert!(html.contains("&lt;b&gt;this&lt;/b&gt;"), "{html}");
67        assert!(html.contains("<b>No.</b>"), "{html}");
68        assert!(html.contains(r#"href="/?q=is+%3Cb%3Ethis%3C%2Fb%3E+ok%3F""#), "{html}");
69    }
70
71    #[test]
72    fn a_release_note_is_text() {
73        assert_eq!(note("a <b> c").into_string(), r#"<div class="toast note"><b>New: </b>a &lt;b&gt; c</div>"#);
74    }
75
76    #[test]
77    fn a_clone_links_to_the_code() {
78        assert!(fetched(Fetch::Pull, "lmjtfy.git").into_string().contains(r#"Someone pulled <a href="/lmjtfy.git">the code</a>."#));
79        assert!(fetched(Fetch::Clone, "jevsnes.git").into_string().contains(r#"Someone cloned <a href="/jevsnes.git">jevsnes.git</a>."#));
80    }
81}