signal-bot/tests/test_stats_render_period.py
Krzysztof kuhy Rudnicki c195e6b4c1
Some checks are pending
CI / check (push) Waiting to run
CI / web (push) Waiting to run
Funny numbers: 69 (nice), 2137 🙏 and five more on every count the stats print
Seven numbers the group finds funny get a marker wherever they show up as
a count: the nightly table, every !stats_* view, !ja, the records and the
highlights. Percentages and shares stay bare (a 69% share is not a 69), and
so does the (N 📷) aside, so one line never carries two jokes. One table,
FUNNY in stats/text.py, and one helper, marked(), so adding a number is one
line. Documented in DOCS-statistics.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 21:55:33 +02:00

154 lines
5.7 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
"""Rendering a period, and the on-demand answers."""
from __future__ import annotations
from signal_bot.stats import periods
from signal_bot.stats.events import Person
from signal_bot.stats.queries_day import DayRow
from signal_bot.stats.queries_period import Trend
from signal_bot.stats.records import DayRecord, Silence
from signal_bot.stats.render_extra import (
ME_TITLE,
render_crown,
render_me,
render_records,
render_weekdays,
)
from signal_bot.stats.render_period import MAX_ROWS, bar, render_period, trend_line
WEEK = periods.week_of("2026-09-01")
def rows(spec: list[tuple[str, int]]) -> list[DayRow]:
"""Build period rows from (name, messages) pairs."""
return [
DayRow(f"u{i}", name, messages, 0, 0, messages * 10)
for i, (name, messages) in enumerate(spec)
]
def test_a_period_with_nobody_in_it() -> None:
rendered = render_period(WEEK, [], Trend(0, 0))
assert "Nikt nic nie napisał." in rendered
def test_the_bar_fills_with_the_share() -> None:
assert bar(0) == "░" * 10
assert bar(100) == "█" * 10
assert bar(50) == "█████░░░░░"
def test_trend_says_first_rather_than_no_change() -> None:
"""Zero percent and "nothing to compare against" are different claims."""
assert "pierwszy taki okres" in trend_line(Trend(now=10, before=0), "poprzednio")
assert "dokładnie tyle samo" in trend_line(Trend(now=10, before=10), "poprzednio")
assert "↑ 50% więcej" in trend_line(Trend(now=15, before=10), "poprzednio")
assert "↓ 50% mniej" in trend_line(Trend(now=5, before=10), "poprzednio")
def test_a_long_period_is_truncated_with_its_total() -> None:
many = rows([(f"Osoba {i:02d}", 20 - i) for i in range(MAX_ROWS + 4)])
rendered = render_period(WEEK, many, Trend(100, 90))
assert "…i 4 osoby więcej" in rendered
assert len([line for line in rendered.split("\n") if line.startswith(("1.", "2."))]) >= 1
def test_medals_only_appear_on_a_podium() -> None:
three = rows([("A", 3), ("B", 2), ("C", 1)])
assert "🥇" in render_period(WEEK, three, Trend(6, 5), podium=True)
assert "🥇" not in render_period(WEEK, three, Trend(6, 5))
def test_an_empty_command_still_says_what_it_is_for() -> None:
"""A bare "za mało danych" tells nobody what they asked for.
Reported by the group within minutes of 0.1.0: somebody typed !korona,
got only that line, and learned neither what the command does nor when
it would work.
"""
for rendered in (
render_records({}, None, None, [], "2026-08-30"),
render_crown({}, [], "2026-08-30"),
render_weekdays({}, 0, "2026-08-30"),
):
assert "Jeszcze za mało danych." in rendered
assert "Pokażę:" in rendered
assert "Liczę od 30 sierpnia." in rendered
def test_an_empty_command_without_a_start_date_omits_it() -> None:
assert "Liczę od" not in render_crown({}, [])
def test_records_reports_each_kind_it_has() -> None:
rendered = render_records(
{"2026-09-01": 10, "2026-09-03": 2},
DayRecord("2026-09-01", 4),
Silence("2026-09-04", "2026-09-06", 3),
[("Ala", DayRecord("2026-09-01", 9))],
)
assert "Najwięcej w jeden dzień: 10" in rendered
assert "Najciszej (ale ktoś pisał): 2" in rendered
assert "Najwięcej zdjęć" in rendered
assert "Najdłuższa cisza: 3 dni" in rendered
assert "Ala — 9" in rendered
def test_the_crown_reports_now_longest_and_totals() -> None:
winners = {
"2026-09-01": [("u1", "Ala")],
"2026-09-02": [("u1", "Ala")],
"2026-09-03": [("u2", "kuhy")],
}
people = [Person("u1", "Ala", ""), Person("u2", "kuhy", "")]
rendered = render_crown(winners, people)
assert "Teraz: kuhy — 1 dzień z rzędu" in rendered
assert "Najdłuższa seria: Ala — 2 dni" in rendered
assert "Ala — 2 dni" in rendered
def test_weekdays_skips_days_never_recorded() -> None:
rendered = render_weekdays({0: [10, 20], 4: [5]}, 4)
assert "poniedziałki: średnio 15" in rendered
assert "piątki: średnio 5" in rendered
assert "← dziś" in rendered
assert "wtorki" not in rendered
def test_me_omits_what_it_does_not_know() -> None:
"""No record, no favourite hour, no streak: three clauses that vanish."""
rendered = render_me("kuhy", 3, None, 10, None, 0)
assert rendered == f"{ME_TITLE}\n\nkuhy — 3 wiadomości w historii\nUdział w rozmowie: 10%"
def test_me_reports_everything_it_does_know() -> None:
rendered = render_me("kuhy", 30, DayRecord("2026-09-01", 12), 40, 23, 2)
assert "Rekord życiowy: 12 (1 września)" in rendered
assert "Najczęściej piszesz między 23:00 a 00:00" in rendered
assert "Korona: 2 dni z rzędu" in rendered
def test_funny_counts_are_marked_wherever_a_count_is_printed() -> None:
"""The number carries its marker in a row, a record and the me block alike.
Shares are left alone: the 69% here is the rounding of 2137 / 3106, not
something anybody did.
"""
period = render_period(WEEK, rows([("Ala", 2137), ("kuhy", 969)]), Trend(0, 0))
assert "Ala — 2137 🙏 " in period
assert "3106 wiadomości" in period
assert "69%" in period
assert "69 (nice)%" not in period
records = render_records(
{"2026-09-01": 420}, None, None, [("Ala", DayRecord("2026-09-01", 1410))]
)
assert "Najwięcej w jeden dzień: 420 🌿 wiadomości" in records
assert "Ala — 1410 ⚔️ (1 września)" in records
me = render_me("kuhy", 1916, DayRecord("2026-09-01", 314), 40, None, 67)
assert "kuhy — 1916 🇵🇱 wiadomości w historii" in me
assert "Rekord życiowy: 314 🥧 (1 września)" in me
assert "Korona: 67 🤷 dni z rzędu" in me