# -*- coding: utf-8 -*- """The note must render as the published note, not merely in its spirit. The brief was asked to reproduce a specific published document exactly — same colours, same placements. "Looks close" is not the requirement, so these tests compare against the document itself rather than against a description of it. ``tests/fixtures/published_note.html`` holds the two things that can be compared mechanically: the stylesheet, and the three figures. Everything else about the page is data-dependent, so those assertions are structural — the ten sections in their published order, six tiles in a six-column grid, a section that keeps its slot when its data is missing. Two divergences from the published file are deliberate, and each is asserted here so it cannot drift into an accidental third: * every selector carries a ``.rt-note`` prefix, because Streamlit's own base styles would otherwise win on specificity; * every ```` carries ``xmlns``, because Streamlit's sanitiser drops an SVG without it and the figure renders as an empty box. """ from __future__ import annotations import pathlib import re import pytest FIXTURE = pathlib.Path(__file__).parent / "fixtures" / "published_note.html" @pytest.fixture(scope="module") def published() -> str: return FIXTURE.read_text(encoding="utf-8") # ── The stylesheet ─────────────────────────────────────────────────────────── def _rules(css: str) -> list[tuple[str, str]]: """Flatten CSS to ``(selector, sorted declarations)`` pairs, in order.""" css = re.sub(r"/\*.*?\*/", "", css, flags=re.S) out = [] for selector, block in re.findall(r"([^{}]+)\{([^{}]*)\}", css): selector = " ".join(selector.split()) if selector.startswith("@"): continue declarations = ";".join( sorted(d.strip() for d in block.split(";") if d.strip()) ) out.append((selector, declarations)) return out def _unscope(selector: str) -> str: """Strip the ``.rt-note`` prefix so selectors can be compared.""" parts = [] for part in selector.split(","): part = part.strip() if part == ".rt-note": part = "body" # the note is a div here, a document there elif part.startswith(".rt-note "): part = part[len(".rt-note "):] parts.append(part) return ",".join(parts) # Rules we add on top of the published stylesheet, each with its reason. # Anything not listed here is drift, and the test says so. Empty is the goal: # the stylesheet is currently the published one, rule for rule. ADDED_RULES: dict[str, str] = {} def test_stylesheet_declarations_are_the_published_ones(published): """Every published rule, in order, with the same declarations and values.""" from dashboard.note_css import NOTE_CSS, TOKENS original = _rules(published[published.index("")]) # margin:0 belongs to the real there; here the note is a div inside # a page that already sets it. original = [ (sel, ";".join(d for d in decl.split(";") if d != "margin:0") if sel == "body" else decl) for sel, decl in original ] mine = [(_unscope(sel), decl) for sel, decl in _rules(TOKENS + NOTE_CSS)] extra = [(sel, decl) for sel, decl in mine if sel in ADDED_RULES] assert len(extra) == len(ADDED_RULES), ( f"expected the additions {sorted(ADDED_RULES)}, found {[s for s, _ in extra]}" ) kept = [(sel, decl) for sel, decl in mine if sel not in ADDED_RULES] assert len(kept) == len(original), ( f"rule count drifted: published {len(original)}, ours {len(kept)}. " f"A new rule needs an entry in ADDED_RULES with its reason." ) for (sel_a, decl_a), (sel_b, decl_b) in zip(original, kept): assert sel_a.replace(" ", "") == sel_b.replace(" ", ""), ( f"selector drifted: published {sel_a!r}, ours {sel_b!r}" ) assert decl_a == decl_b, f"{sel_a}: published {decl_a!r}, ours {decl_b!r}" def test_every_selector_is_scoped_to_the_note(): """An unscoped selector would restyle the whole Streamlit page.""" from dashboard.note_css import NOTE_CSS for selector, _ in _rules(NOTE_CSS): for part in selector.split(","): part = part.strip() assert part == ".rt-note" or part.startswith(".rt-note "), ( f"{part!r} escapes the note's scope" ) def test_the_light_tokens_are_the_published_values(): """The palette the app is pinned to is the note's own light palette.""" from dashboard.note_css import TOKENS, TOKENS_LIGHT def values(css: str) -> dict[str, str]: block = css[css.index(":root{") + 6: css.index("}", css.index(":root{"))] out = {} for decl in block.split(";"): if "--" in decl: name, _, value = decl.partition(":") out[name.strip()] = value.strip() return out published_light = values(TOKENS) pinned = values(TOKENS_LIGHT) for name, value in published_light.items(): assert pinned.get(name) == value, f"{name}: pinned {pinned.get(name)!r} != {value!r}" # ── The figures ────────────────────────────────────────────────────────────── def _normalise(svg: str) -> str: """Compare on rendered output, not on the two encodings we must differ in. Undone here: the ``xmlns`` the sanitiser requires, and ``$`` for the dollar sign, which st.markdown would otherwise read as LaTeX. Both render exactly as the published note does; everything else must match literally. """ svg = re.sub(r">\s+<", "><", svg.strip()) return svg.replace(' xmlns="http://www.w3.org/2000/svg"', "").replace("$", "$") def _fixture_figure(published: str, name: str) -> str: block = re.search(rf'
(.*?)
', published, re.S) assert block, f"fixture has no figure {name!r}" return block.group(1) def _footnote(svg: str) -> str: from html import unescape match = re.search(r'([^<]*)', svg) return unescape(match.group(1)) if match else "" def test_bar_figure_geometry_is_identical(published): """Same bars, ticks, labels and coordinates as the published figure. The series is recovered from the published SVG's own geometry rather than from its printed labels: the note plots the unrounded value and prints the rounded one, so reading the labels back would move every bar by a tenth of a pixel and make the comparison meaningless. """ from dashboard.note_charts import bars_vs_guidance svg = _fixture_figure(published, "revenue_vs_guidance") scale = (274.0 - 50.4) / 100.0 bars = re.findall( r'([^<]+)', svg) printed = re.findall(r'class="cv">([\d,]+)', svg) deltas = re.findall(r'y="306" text-anchor="middle" class="cb">([^<]+)', svg) tick_y = re.findall(r'y1="([\d.]+)" y2="[\d.]+" stroke="var\(--warn\)"', svg) guides = [None] + [(274.0 - float(y)) / scale for y in tick_y] points = [ { "label": label, "value": (274.0 - float(bar[0])) / scale, "value_text": printed[n], "guide": guides[n], "delta": deltas[n - 1] if n else "", "derived": bar[1] == "--ink-3", } for n, (bar, label) in enumerate(zip(bars, labels)) ] assert len(points) == 14 mine = bars_vs_guidance( points, label="Chiffre d’affaires trimestriel et point médian de la guidance", footnote=_footnote(svg), ) assert _normalise(mine) == _normalise(svg) def test_paired_figure_geometry_is_identical(published): from dashboard.note_charts import paired_bars svg = _fixture_figure(published, "net_income_vs_fcf") scale = (250.0 - 59.7) / 60.0 bars = re.findall( r'([\d,]+)', svg) labels = re.findall(r'y="266" text-anchor="middle" class="ct">([^<]+)', svg) ratios = re.findall(r'y="284" text-anchor="middle" class="(cb(?: warn)?)">([^<]+)', svg) groups = [ { "label": label, "left": (250.0 - float(bars[2 * n])) / scale, "left_text": printed[2 * n], "right": (250.0 - float(bars[2 * n + 1])) / scale, "right_text": printed[2 * n + 1], "ratio": ratios[n][1], "ratio_warn": "warn" in ratios[n][0], } for n, label in enumerate(labels) ] mine = paired_bars( groups, label="Résultat net contre free cash-flow par trimestre", footnote=_footnote(svg), ) assert _normalise(mine) == _normalise(svg) def test_line_figure_geometry_is_identical(published): from dashboard.note_charts import line_series svg = _fixture_figure(published, "days_sales_outstanding") scale = (226.0 - 28.0) / 40.0 dots = re.findall(r'([\d,]+)', svg) labels = re.findall(r'y="242" text-anchor="middle" class="ct">([^<]+)', svg) points = [ { "label": label, "value": 30.0 + (226.0 - float(cy)) / scale, "value_text": text, "emphasis": radius == "6", } for (cy, radius), text, label in zip(dots, printed, labels) ] mine = line_series( points, label="Délai de recouvrement clients en jours", footnote=_footnote(svg), ) assert _normalise(mine) == _normalise(svg) def test_figures_declare_their_namespace(): """Without xmlns, Streamlit's sanitiser drops the SVG and the figure empties.""" from dashboard.note_charts import bars_vs_guidance, line_series, paired_bars bars = bars_vs_guidance([{"label": "Q1", "value": 1.0}], label="x") pair = paired_bars([{"label": "Q1", "left": 1.0, "right": 0.5}], label="x") line = line_series([{"label": "Q1", "value": 1.0}], label="x") for svg in (bars, pair, line): assert svg.startswith(' element that carries it. This is not hypothetical: a comment in this stylesheet that mentioned the svg tag by name cut the element off at that point, dropping every rule after it — the note's grid, its sticky rail, and the app chrome CSS concatenated behind it. The page still rendered, just with no layout. """ from dashboard.note_css import FONT_IMPORT, NOTE_CSS, TOKENS, TOKENS_LIGHT for name, css in (("FONT_IMPORT", FONT_IMPORT), ("TOKENS", TOKENS), ("TOKENS_LIGHT", TOKENS_LIGHT), ("NOTE_CSS", NOTE_CSS)): assert "<" not in css, f"{name} contains '<', which truncates the style element" assert " dict: """A brief with nothing in it — the case that must not lose a section.""" return { "ticker": "ZZZZ", "company_name": "Nothing Ingested Inc", "filing_date": "2026-01-31", "what_matters_most": "", "what_changed": [], "bull_points": [], "bear_points": [], "what_to_watch": [], } def _sections(html: str) -> list[str]: return re.findall(r'
", 'class="foot"'): assert marker in html, f"{marker} missing" def test_currency_follows_the_language(thin_brief): """"96,2 Md$" in French, "$96.2bn" in English — same amount, same tile.""" from dashboard.note import _money, _per_share with_lang = lambda fn, lang: fn(96_200_000_000, lang=lang) if fn is _money else fn(2.46, lang=lang) assert _money(96_200_000_000, lang="fr") == "96,2 Md$" assert _money(96_200_000_000, lang="en") == "$96.2bn" assert _per_share(2.46, lang="fr") == "2,46 $" assert _per_share(2.46, lang="en") == "$2.46" def test_rounding_is_half_up_like_a_printed_table(): """13.25 prints as 13.3, the way the filing does — not 13.2.""" from dashboard.note import _num assert _num(13.25, 1, lang="en") == "13.3" assert _num(13.25, 1, lang="fr") == "13,3"