use rustbot::karma_web::{parse_board, parse_events, render}; fn board_with_events(net: &str, store: &str, log: &str) -> rustbot::karma_web::Board { let mut b = parse_board(net, store); b.events = parse_events(log); b } #[test] fn parses_flat_number_format() { let b = parse_board("libera", r#"{"romaka":2}"#); assert_eq!(b.rows.len(), 1); assert_eq!(b.rows[0].thing, "romaka"); assert_eq!(b.rows[0].count, 2); } #[test] fn parses_object_format_with_reason_and_by() { let b = parse_board("t", r#"{"rust":{"n":5,"why":"clean PR","by":"alice"}}"#); assert_eq!(b.rows[0].count, 5); assert_eq!(b.rows[0].reason.as_deref(), Some("clean PR")); assert_eq!(b.rows[0].by.as_deref(), Some("alice")); } #[test] fn sorts_descending_by_count() { let b = parse_board("t", r#"{"a":1,"b":9,"c":-3}"#); let order: Vec<&str> = b.rows.iter().map(|r| r.thing.as_str()).collect(); assert_eq!(order, vec!["b", "a", "c"]); } #[test] fn parses_event_log_lines() { let log = "{\"t\":100,\"o\":\"rust\",\"d\":1,\"by\":\"alice\",\"why\":\"clean PR\"}\n\ {\"t\":200,\"o\":\"rust\",\"d\":-1,\"by\":\"bob\"}\n\ \n\ garbage line\n"; let ev = parse_events(log); assert_eq!(ev.len(), 2); assert_eq!(ev[0].thing, "rust"); assert_eq!(ev[0].delta, 1); assert_eq!(ev[0].by.as_deref(), Some("alice")); assert_eq!(ev[0].why.as_deref(), Some("clean PR")); assert_eq!(ev[1].delta, -1); assert!(ev[1].why.is_none()); } #[test] fn renders_hero_podium_and_scores() { let b = parse_board( "libera", r#"{"rust":{"n":5,"why":"clean PR","by":"alice"}}"#, ); let html = render(&[b], 0); assert!( html.contains(">karma<"), "wordmark missing: has no karma mark" ); assert!(html.contains("podium"), "no podium"); assert!(html.contains(">rust<"), "champion nick missing"); assert!(html.contains(">+5<"), "score missing"); assert!(html.contains("clean PR"), "reason missing"); } #[test] fn renders_activity_and_givers_from_events() { let log = "{\"t\":100,\"o\":\"rust\",\"d\":1,\"by\":\"alice\",\"why\":\"clean PR\"}\n\ {\"t\":200,\"o\":\"docs\",\"d\":1,\"by\":\"alice\"}\n"; let b = board_with_events("libera", r#"{"rust":1,"docs":1}"#, log); let html = render(&[b], 0); assert!(html.contains("recent activity"), "no activity section"); assert!(html.contains("top givers"), "no givers section"); assert!(html.contains("gave"), "no feed verb"); // alice made two gifts -> shown in givers panel assert!( html.contains("2 gifts"), "giver count wrong: {}", html.contains("gift") ); assert!( html.contains("data-t=\"200\""), "feed sorted/newest-first missing" ); } #[test] fn empty_states_are_friendly() { // no karma at all let html = render(&[], 0); assert!(html.contains("No karma yet"), "{html}"); // karma but no event history yet let b = parse_board("libera", r#"{"romaka":2}"#); let html = render(&[b], 0); assert!(html.contains("Nothing yet"), "empty activity state missing"); assert!(html.contains("Be the first"), "empty givers state missing"); } #[test] fn escapes_untrusted_irc_input_everywhere() { let log = "{\"t\":1,\"o\":\"