Spaces:
Running
Running
| """Tests for backend/rules/performance_rules.py""" | |
| from __future__ import annotations | |
| import pytest | |
| from backend.rules.performance_rules import analyze | |
| from backend.models.issue import Category, Severity | |
| def _run(page_overrides: dict, threshold_overrides: dict | None = None) -> list: | |
| base = { | |
| "web_fonts": [], | |
| "has_font_display": False, | |
| "images": [], | |
| "media_query_breakpoints": [], | |
| } | |
| base.update(page_overrides) | |
| t = {"performance": {"required_breakpoints": [768]}} | |
| if threshold_overrides: | |
| t["performance"].update(threshold_overrides) | |
| return analyze(base, t) | |
| def rule_ids(issues): | |
| return {i.rule_id for i in issues} | |
| def _img(has_alt=True, has_srcset=True, has_width=True, has_height=True): | |
| return { | |
| "src": "img.png", "has_alt": has_alt, "alt_text": "desc", | |
| "has_srcset": has_srcset, "has_width": has_width, "has_height": has_height, | |
| } | |
| class TestP1MissingFontDisplay: | |
| def test_fires_when_web_font_no_display(self): | |
| issues = _run({"web_fonts": ["Inter"], "has_font_display": False}) | |
| assert "P1_missing_font_display" in rule_ids(issues) | |
| def test_not_fires_when_font_display_set(self): | |
| issues = _run({"web_fonts": ["Inter"], "has_font_display": True}) | |
| assert "P1_missing_font_display" not in rule_ids(issues) | |
| def test_not_fires_when_no_web_fonts(self): | |
| issues = _run({"web_fonts": [], "has_font_display": False}) | |
| assert "P1_missing_font_display" not in rule_ids(issues) | |
| def test_severity_is_medium(self): | |
| issues = _run({"web_fonts": ["Roboto"], "has_font_display": False}) | |
| p1 = next(i for i in issues if i.rule_id == "P1_missing_font_display") | |
| assert p1.severity == Severity.MEDIUM | |
| def test_category_is_performance(self): | |
| issues = _run({"web_fonts": ["Roboto"], "has_font_display": False}) | |
| p1 = next(i for i in issues if i.rule_id == "P1_missing_font_display") | |
| assert p1.category == Category.PERFORMANCE | |
| def test_evidence_contains_font_name(self): | |
| issues = _run({"web_fonts": ["Poppins"], "has_font_display": False}) | |
| p1 = next(i for i in issues if i.rule_id == "P1_missing_font_display") | |
| assert "Poppins" in p1.evidence | |
| class TestP2MissingImageAlt: | |
| def test_fires_when_images_missing_alt(self): | |
| issues = _run({"images": [_img(has_alt=False)]}) | |
| assert "P2_missing_image_alt" in rule_ids(issues) | |
| def test_not_fires_when_all_have_alt(self): | |
| issues = _run({"images": [_img(has_alt=True), _img(has_alt=True)]}) | |
| assert "P2_missing_image_alt" not in rule_ids(issues) | |
| def test_not_fires_when_no_images(self): | |
| issues = _run({"images": []}) | |
| assert "P2_missing_image_alt" not in rule_ids(issues) | |
| def test_fires_accessibility_category(self): | |
| issues = _run({"images": [_img(has_alt=False)]}) | |
| p2 = next(i for i in issues if i.rule_id == "P2_missing_image_alt") | |
| assert p2.category == Category.ACCESSIBILITY | |
| def test_severity_is_high(self): | |
| issues = _run({"images": [_img(has_alt=False)]}) | |
| p2 = next(i for i in issues if i.rule_id == "P2_missing_image_alt") | |
| assert p2.severity == Severity.HIGH | |
| def test_evidence_includes_count(self): | |
| issues = _run({"images": [_img(has_alt=False), _img(has_alt=False)]}) | |
| p2 = next(i for i in issues if i.rule_id == "P2_missing_image_alt") | |
| assert "2" in p2.evidence | |
| class TestP3LayoutShiftImages: | |
| def test_fires_when_images_missing_dimensions(self): | |
| issues = _run({"images": [_img(has_width=False, has_height=False)]}) | |
| assert "P3_layout_shift_images" in rule_ids(issues) | |
| def test_fires_when_only_width_missing(self): | |
| issues = _run({"images": [_img(has_width=False, has_height=True)]}) | |
| assert "P3_layout_shift_images" in rule_ids(issues) | |
| def test_not_fires_when_all_have_dimensions(self): | |
| issues = _run({"images": [_img(has_width=True, has_height=True)]}) | |
| assert "P3_layout_shift_images" not in rule_ids(issues) | |
| def test_not_fires_when_no_images(self): | |
| issues = _run({"images": []}) | |
| assert "P3_layout_shift_images" not in rule_ids(issues) | |
| def test_severity_is_medium(self): | |
| issues = _run({"images": [_img(has_width=False)]}) | |
| p3 = next(i for i in issues if i.rule_id == "P3_layout_shift_images") | |
| assert p3.severity == Severity.MEDIUM | |
| def test_category_is_performance(self): | |
| issues = _run({"images": [_img(has_width=False)]}) | |
| p3 = next(i for i in issues if i.rule_id == "P3_layout_shift_images") | |
| assert p3.category == Category.PERFORMANCE | |
| class TestP4MissingSrcset: | |
| def test_fires_when_multiple_images_no_srcset(self): | |
| issues = _run({"images": [_img(has_srcset=False), _img(has_srcset=False)]}) | |
| assert "P4_missing_srcset" in rule_ids(issues) | |
| def test_not_fires_when_only_one_image(self): | |
| # Threshold: >= 2 images without srcset | |
| issues = _run({"images": [_img(has_srcset=False)]}) | |
| assert "P4_missing_srcset" not in rule_ids(issues) | |
| def test_not_fires_when_images_have_srcset(self): | |
| issues = _run({"images": [_img(has_srcset=True), _img(has_srcset=True)]}) | |
| assert "P4_missing_srcset" not in rule_ids(issues) | |
| def test_severity_is_low(self): | |
| issues = _run({"images": [_img(has_srcset=False), _img(has_srcset=False)]}) | |
| p4 = next(i for i in issues if i.rule_id == "P4_missing_srcset") | |
| assert p4.severity == Severity.LOW | |
| class TestP5MissingBreakpoints: | |
| def test_fires_when_no_media_queries(self): | |
| issues = _run({"media_query_breakpoints": []}) | |
| assert "P5_missing_breakpoints" in rule_ids(issues) | |
| def test_not_fires_when_768_breakpoint_present(self): | |
| issues = _run({"media_query_breakpoints": [768]}) | |
| assert "P5_missing_breakpoints" not in rule_ids(issues) | |
| def test_allows_breakpoints_within_64px(self): | |
| # 800px is within 64px of 768px | |
| issues = _run({"media_query_breakpoints": [800]}) | |
| assert "P5_missing_breakpoints" not in rule_ids(issues) | |
| def test_fires_on_only_tiny_breakpoints(self): | |
| issues = _run({"media_query_breakpoints": [320]}) | |
| assert "P5_missing_breakpoints" in rule_ids(issues) | |
| def test_severity_is_high(self): | |
| issues = _run({"media_query_breakpoints": []}) | |
| p5 = next(i for i in issues if i.rule_id == "P5_missing_breakpoints") | |
| assert p5.severity == Severity.HIGH | |
| def test_custom_required_breakpoint(self): | |
| issues = _run({"media_query_breakpoints": []}, {"required_breakpoints": [1024]}) | |
| p5 = next((i for i in issues if i.rule_id == "P5_missing_breakpoints"), None) | |
| assert p5 is not None | |
| assert "1024" in p5.evidence | |