from __future__ import annotations from backend.rules import typography_rules from backend.utils.config_loader import load_scoring_config from tests.fixtures import bad_page, clean_page THRESHOLDS = load_scoring_config()["thresholds"] def rule_ids(issues) -> list[str]: return [i.rule_id for i in issues] class TestCleanPage: def test_no_typography_issues(self): issues = typography_rules.analyze(clean_page(), THRESHOLDS) assert issues == [] class TestBodyFontSize: def test_small_body_font_flagged(self): page = clean_page() page["body_font_size_px"] = 11.0 issues = typography_rules.analyze(page, THRESHOLDS) assert any(i.rule_id == "T1_body_font_size" for i in issues) def test_exactly_minimum_not_flagged(self): page = clean_page() page["body_font_size_px"] = 14.0 issues = typography_rules.analyze(page, THRESHOLDS) assert not any(i.rule_id == "T1_body_font_size" for i in issues) def test_none_font_size_skipped(self): page = clean_page() page["body_font_size_px"] = None issues = typography_rules.analyze(page, THRESHOLDS) assert not any(i.rule_id == "T1_body_font_size" for i in issues) def test_bad_page_triggers_t1(self): issues = typography_rules.analyze(bad_page(), THRESHOLDS) assert any(i.rule_id == "T1_body_font_size" for i in issues) class TestFontFamilyCount: def test_too_many_fonts_flagged(self): page = clean_page() page["fonts"] = ["Arial", "Georgia", "Verdana"] issues = typography_rules.analyze(page, THRESHOLDS) assert any(i.rule_id == "T2_font_family_count" for i in issues) def test_two_fonts_allowed(self): page = clean_page() page["fonts"] = ["Inter", "JetBrains Mono"] issues = typography_rules.analyze(page, THRESHOLDS) assert not any(i.rule_id == "T2_font_family_count" for i in issues) def test_bad_page_triggers_t2(self): issues = typography_rules.analyze(bad_page(), THRESHOLDS) assert any(i.rule_id == "T2_font_family_count" for i in issues) class TestLineHeight: def test_tight_line_height_flagged(self): page = clean_page() page["line_height"] = 1.1 issues = typography_rules.analyze(page, THRESHOLDS) assert any(i.rule_id == "T3_line_height" for i in issues) def test_good_line_height_passes(self): page = clean_page() page["line_height"] = 1.5 issues = typography_rules.analyze(page, THRESHOLDS) assert not any(i.rule_id == "T3_line_height" for i in issues) def test_none_line_height_skipped(self): page = clean_page() page["line_height"] = None issues = typography_rules.analyze(page, THRESHOLDS) assert not any(i.rule_id == "T3_line_height" for i in issues) class TestHeadingHierarchy: def test_weak_hierarchy_flagged(self): page = clean_page() page["headings"] = [ {"level": 1, "font_size_px": 20.0, "text": "Title"}, {"level": 2, "font_size_px": 18.0, "text": "Subtitle"}, ] issues = typography_rules.analyze(page, THRESHOLDS) assert any(i.rule_id == "T4_heading_hierarchy" for i in issues) def test_strong_hierarchy_passes(self): issues = typography_rules.analyze(clean_page(), THRESHOLDS) assert not any(i.rule_id == "T4_heading_hierarchy" for i in issues) def test_no_h2_skips_hierarchy_check(self): page = clean_page() page["headings"] = [{"level": 1, "font_size_px": 32.0, "text": "Only H1"}] issues = typography_rules.analyze(page, THRESHOLDS) assert not any(i.rule_id == "T4_heading_hierarchy" for i in issues) class TestHeadingSize: def test_small_h1_flagged(self): page = clean_page() page["headings"] = [{"level": 1, "font_size_px": 14.0, "text": "Title"}] issues = typography_rules.analyze(page, THRESHOLDS) assert any(i.rule_id == "T5_heading_size" for i in issues) def test_adequate_h1_passes(self): issues = typography_rules.analyze(clean_page(), THRESHOLDS) assert not any(i.rule_id == "T5_heading_size" for i in issues)