@mcptoolshop/accessibility-suite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +63 -0
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/docs/prov-spec/.github/workflows/ci.yml +68 -0
- package/docs/prov-spec/CHANGELOG.md +69 -0
- package/docs/prov-spec/CODE_OF_CONDUCT.md +129 -0
- package/docs/prov-spec/CONFORMANCE_LEVELS.md +223 -0
- package/docs/prov-spec/CONTRIBUTING.md +145 -0
- package/docs/prov-spec/IMPLEMENTER_CHECKLIST.md +137 -0
- package/docs/prov-spec/LICENSE +21 -0
- package/docs/prov-spec/PRESS_RELEASE.md +74 -0
- package/docs/prov-spec/README.md +182 -0
- package/docs/prov-spec/SETUP.md +135 -0
- package/docs/prov-spec/WHY.md +86 -0
- package/docs/prov-spec/examples/artifact.example.json +14 -0
- package/docs/prov-spec/examples/artifact.ref.example.json +9 -0
- package/docs/prov-spec/examples/evidence.example.json +6 -0
- package/docs/prov-spec/examples/mcp.envelope.example.json +97 -0
- package/docs/prov-spec/examples/mcp.request.example.json +28 -0
- package/docs/prov-spec/examples/prov.record.example.json +35 -0
- package/docs/prov-spec/interop/PROOF_NODE_ENGINE.md +114 -0
- package/docs/prov-spec/spec/MCP_COMPATIBILITY.md +241 -0
- package/docs/prov-spec/spec/PROV_METHODS_CATALOG.md +142 -0
- package/docs/prov-spec/spec/PROV_METHODS_SPEC.md +397 -0
- package/docs/prov-spec/spec/methods.json +213 -0
- package/docs/prov-spec/spec/schemas/artifact.ref.schema.v0.1.json +58 -0
- package/docs/prov-spec/spec/schemas/artifact.schema.v0.1.json +61 -0
- package/docs/prov-spec/spec/schemas/assist.request.schema.v0.1.json +52 -0
- package/docs/prov-spec/spec/schemas/assist.response.schema.v0.1.json +70 -0
- package/docs/prov-spec/spec/schemas/cli.error.schema.v0.1.json +78 -0
- package/docs/prov-spec/spec/schemas/evidence.schema.v0.1.json +37 -0
- package/docs/prov-spec/spec/schemas/mcp.envelope.schema.v0.1.json +141 -0
- package/docs/prov-spec/spec/schemas/mcp.request.schema.v0.1.json +79 -0
- package/docs/prov-spec/spec/schemas/methods.schema.json +93 -0
- package/docs/prov-spec/spec/schemas/prov-capabilities.schema.json +122 -0
- package/docs/prov-spec/spec/schemas/prov.record.schema.v0.1.json +133 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/expected.json +4 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/input.json +1 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/double_wrapped.json +14 -0
- package/docs/prov-spec/spec/vectors/adapter.wrap.envelope_v0_1/negative/wrong_schema_version.json +11 -0
- package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/expected.json +24 -0
- package/docs/prov-spec/spec/vectors/engine.extract.evidence.json_pointer/input.json +8 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/expected.json +7 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/input.json +1 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/non_hex_chars.json +16 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/uppercase_hex.json +16 -0
- package/docs/prov-spec/spec/vectors/integrity.digest.sha256/negative/wrong_length.json +16 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/hyphen_separator.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/reserved_namespace.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/starts_with_digit.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/negative/uppercase.json +8 -0
- package/docs/prov-spec/spec/vectors/method_id_syntax/positive/valid_ids.json +18 -0
- package/docs/prov-spec/tools/python/prov_validator.py +428 -0
- package/examples/a11y-demo-site/.github/workflows/a11y-artifacts.yml +81 -0
- package/examples/a11y-demo-site/.github/workflows/a11y.yml +34 -0
- package/examples/a11y-demo-site/CODE_OF_CONDUCT.md +129 -0
- package/examples/a11y-demo-site/CONTRIBUTING.md +83 -0
- package/examples/a11y-demo-site/LICENSE +21 -0
- package/examples/a11y-demo-site/README.md +155 -0
- package/examples/a11y-demo-site/html/contact.html +15 -0
- package/examples/a11y-demo-site/html/index.html +20 -0
- package/examples/a11y-demo-site/scripts/a11y.sh +20 -0
- package/package.json +26 -0
- package/src/a11y-assist/.github/workflows/publish.yml +52 -0
- package/src/a11y-assist/.github/workflows/test.yml +30 -0
- package/src/a11y-assist/A11Y_ASSIST_TEST_COVERAGE_REQUIREMENTS.md +104 -0
- package/src/a11y-assist/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-assist/CONTRIBUTING.md +98 -0
- package/src/a11y-assist/ENGINE.md +363 -0
- package/src/a11y-assist/LICENSE +21 -0
- package/src/a11y-assist/PRESS_RELEASE.md +71 -0
- package/src/a11y-assist/QUICKSTART.md +101 -0
- package/src/a11y-assist/README.md +192 -0
- package/src/a11y-assist/RELEASE_NOTES.md +319 -0
- package/src/a11y-assist/a11y_assist/__init__.py +3 -0
- package/src/a11y-assist/a11y_assist/cli.py +599 -0
- package/src/a11y-assist/a11y_assist/from_cli_error.py +149 -0
- package/src/a11y-assist/a11y_assist/guard.py +444 -0
- package/src/a11y-assist/a11y_assist/ingest.py +407 -0
- package/src/a11y-assist/a11y_assist/methods.py +137 -0
- package/src/a11y-assist/a11y_assist/parse_raw.py +71 -0
- package/src/a11y-assist/a11y_assist/profiles/__init__.py +29 -0
- package/src/a11y-assist/a11y_assist/profiles/cognitive_load.py +245 -0
- package/src/a11y-assist/a11y_assist/profiles/cognitive_load_render.py +86 -0
- package/src/a11y-assist/a11y_assist/profiles/dyslexia.py +144 -0
- package/src/a11y-assist/a11y_assist/profiles/dyslexia_render.py +77 -0
- package/src/a11y-assist/a11y_assist/profiles/plain_language.py +119 -0
- package/src/a11y-assist/a11y_assist/profiles/plain_language_render.py +66 -0
- package/src/a11y-assist/a11y_assist/profiles/screen_reader.py +348 -0
- package/src/a11y-assist/a11y_assist/profiles/screen_reader_render.py +89 -0
- package/src/a11y-assist/a11y_assist/render.py +95 -0
- package/src/a11y-assist/a11y_assist/schemas/assist.request.schema.v0.1.json +52 -0
- package/src/a11y-assist/a11y_assist/schemas/assist.response.schema.v0.1.json +70 -0
- package/src/a11y-assist/a11y_assist/schemas/cli.error.schema.v0.1.json +78 -0
- package/src/a11y-assist/a11y_assist/storage.py +31 -0
- package/src/a11y-assist/pyproject.toml +60 -0
- package/src/a11y-assist/tests/__init__.py +1 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_high.json +18 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/cli_error_medium.json +16 -0
- package/src/a11y-assist/tests/fixtures/base_inputs/raw_text_low.txt +3 -0
- package/src/a11y-assist/tests/fixtures/cli_error_good.json +9 -0
- package/src/a11y-assist/tests/fixtures/cli_error_missing_id.json +7 -0
- package/src/a11y-assist/tests/fixtures/cli_error_string_format.json +7 -0
- package/src/a11y-assist/tests/fixtures/expected/cognitive_load_high.txt +20 -0
- package/src/a11y-assist/tests/fixtures/expected/dyslexia_high.txt +20 -0
- package/src/a11y-assist/tests/fixtures/expected/lowvision_high.txt +18 -0
- package/src/a11y-assist/tests/fixtures/expected/plain_language_high.txt +14 -0
- package/src/a11y-assist/tests/fixtures/expected/screen_reader_high.txt +19 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_cli_error.txt +16 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_no_id.txt +14 -0
- package/src/a11y-assist/tests/fixtures/golden_screen_reader_raw_with_id.txt +14 -0
- package/src/a11y-assist/tests/fixtures/raw_good.txt +11 -0
- package/src/a11y-assist/tests/fixtures/raw_no_id.txt +2 -0
- package/src/a11y-assist/tests/test_cognitive_load.py +469 -0
- package/src/a11y-assist/tests/test_dyslexia.py +337 -0
- package/src/a11y-assist/tests/test_explain.py +74 -0
- package/src/a11y-assist/tests/test_golden.py +127 -0
- package/src/a11y-assist/tests/test_guard.py +819 -0
- package/src/a11y-assist/tests/test_guard_integration.py +457 -0
- package/src/a11y-assist/tests/test_ingest.py +311 -0
- package/src/a11y-assist/tests/test_methods_metadata.py +236 -0
- package/src/a11y-assist/tests/test_plain_language.py +348 -0
- package/src/a11y-assist/tests/test_render.py +117 -0
- package/src/a11y-assist/tests/test_screen_reader.py +703 -0
- package/src/a11y-assist/tests/test_storage_last.py +61 -0
- package/src/a11y-assist/tests/test_triage.py +86 -0
- package/src/a11y-ci/.github/workflows/ci.yml +43 -0
- package/src/a11y-ci/.github/workflows/test.yml +30 -0
- package/src/a11y-ci/A11Y_CI_TEST_COVERAGE_REQUIREMENTS.md +94 -0
- package/src/a11y-ci/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-ci/CONTRIBUTING.md +142 -0
- package/src/a11y-ci/LICENSE +21 -0
- package/src/a11y-ci/README.md +105 -0
- package/src/a11y-ci/a11y_ci/__init__.py +3 -0
- package/src/a11y-ci/a11y_ci/allowlist.py +83 -0
- package/src/a11y-ci/a11y_ci/cli.py +145 -0
- package/src/a11y-ci/a11y_ci/gate.py +131 -0
- package/src/a11y-ci/a11y_ci/render.py +48 -0
- package/src/a11y-ci/a11y_ci/schemas/allowlist.schema.json +24 -0
- package/src/a11y-ci/a11y_ci/scorecard.py +99 -0
- package/src/a11y-ci/npm/package.json +35 -0
- package/src/a11y-ci/pyproject.toml +64 -0
- package/src/a11y-ci/tests/__init__.py +1 -0
- package/src/a11y-ci/tests/fixtures/allowlist_expired.json +10 -0
- package/src/a11y-ci/tests/fixtures/allowlist_ok.json +10 -0
- package/src/a11y-ci/tests/fixtures/baseline_ok.json +7 -0
- package/src/a11y-ci/tests/fixtures/current_fail.json +6 -0
- package/src/a11y-ci/tests/fixtures/current_ok.json +6 -0
- package/src/a11y-ci/tests/fixtures/current_regresses.json +7 -0
- package/src/a11y-ci/tests/test_gate.py +134 -0
- package/src/a11y-evidence-engine/.github/workflows/ci.yml +53 -0
- package/src/a11y-evidence-engine/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-evidence-engine/CONTRIBUTING.md +128 -0
- package/src/a11y-evidence-engine/LICENSE +21 -0
- package/src/a11y-evidence-engine/README.md +71 -0
- package/src/a11y-evidence-engine/bin/a11y-engine.js +11 -0
- package/src/a11y-evidence-engine/fixtures/bad/button-no-name.html +30 -0
- package/src/a11y-evidence-engine/fixtures/bad/img-missing-alt.html +19 -0
- package/src/a11y-evidence-engine/fixtures/bad/input-missing-label.html +26 -0
- package/src/a11y-evidence-engine/fixtures/bad/missing-lang.html +11 -0
- package/src/a11y-evidence-engine/fixtures/good/index.html +29 -0
- package/src/a11y-evidence-engine/package-lock.json +109 -0
- package/src/a11y-evidence-engine/package.json +45 -0
- package/src/a11y-evidence-engine/src/cli.js +74 -0
- package/src/a11y-evidence-engine/src/evidence/canonicalize.js +52 -0
- package/src/a11y-evidence-engine/src/evidence/json_pointer.js +34 -0
- package/src/a11y-evidence-engine/src/evidence/prov_emit.js +153 -0
- package/src/a11y-evidence-engine/src/fswalk.js +56 -0
- package/src/a11y-evidence-engine/src/html_parse.js +117 -0
- package/src/a11y-evidence-engine/src/ids.js +53 -0
- package/src/a11y-evidence-engine/src/rules/document_missing_lang.js +50 -0
- package/src/a11y-evidence-engine/src/rules/form_control_missing_label.js +105 -0
- package/src/a11y-evidence-engine/src/rules/img_missing_alt.js +77 -0
- package/src/a11y-evidence-engine/src/rules/index.js +37 -0
- package/src/a11y-evidence-engine/src/rules/interactive_missing_name.js +129 -0
- package/src/a11y-evidence-engine/src/scan.js +128 -0
- package/src/a11y-evidence-engine/test/scan.test.js +149 -0
- package/src/a11y-evidence-engine/test/vectors.test.js +200 -0
- package/src/a11y-lint/.github/workflows/ci.yml +46 -0
- package/src/a11y-lint/.github/workflows/test.yml +34 -0
- package/src/a11y-lint/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-lint/CONTRIBUTING.md +70 -0
- package/src/a11y-lint/GOVERNANCE.md +57 -0
- package/src/a11y-lint/LICENSE +21 -0
- package/src/a11y-lint/PRESS_RELEASE.md +50 -0
- package/src/a11y-lint/README.md +276 -0
- package/src/a11y-lint/RELEASE_NOTES.md +57 -0
- package/src/a11y-lint/RELEASING.md +57 -0
- package/src/a11y-lint/a11y_lint/__init__.py +64 -0
- package/src/a11y-lint/a11y_lint/cli.py +319 -0
- package/src/a11y-lint/a11y_lint/errors.py +252 -0
- package/src/a11y-lint/a11y_lint/render.py +293 -0
- package/src/a11y-lint/a11y_lint/report_md.py +289 -0
- package/src/a11y-lint/a11y_lint/scan_cli_text.py +434 -0
- package/src/a11y-lint/a11y_lint/schemas/cli.error.schema.v0.1.json +83 -0
- package/src/a11y-lint/a11y_lint/scorecard.py +244 -0
- package/src/a11y-lint/a11y_lint/validate.py +225 -0
- package/src/a11y-lint/pyproject.toml +75 -0
- package/src/a11y-lint/tests/__init__.py +1 -0
- package/src/a11y-lint/tests/test_cli.py +200 -0
- package/src/a11y-lint/tests/test_errors.py +188 -0
- package/src/a11y-lint/tests/test_render.py +202 -0
- package/src/a11y-lint/tests/test_report_md.py +188 -0
- package/src/a11y-lint/tests/test_scan_cli_text.py +290 -0
- package/src/a11y-lint/tests/test_scorecard.py +195 -0
- package/src/a11y-lint/tests/test_validate.py +257 -0
- package/src/a11y-mcp-tools/.github/workflows/ci.yml +53 -0
- package/src/a11y-mcp-tools/CODE_OF_CONDUCT.md +129 -0
- package/src/a11y-mcp-tools/CONTRIBUTING.md +136 -0
- package/src/a11y-mcp-tools/LICENSE +21 -0
- package/src/a11y-mcp-tools/PROV_METHODS_CATALOG.md +104 -0
- package/src/a11y-mcp-tools/README.md +168 -0
- package/src/a11y-mcp-tools/bin/cli.js +452 -0
- package/src/a11y-mcp-tools/bin/server.js +244 -0
- package/src/a11y-mcp-tools/fixtures/requests/a11y.diagnose.ok.json +27 -0
- package/src/a11y-mcp-tools/fixtures/requests/a11y.evidence.ok.json +25 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.ok.json +139 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.diagnose.provenance_fail.json +13 -0
- package/src/a11y-mcp-tools/fixtures/responses/a11y.evidence.ok.json +88 -0
- package/src/a11y-mcp-tools/package-lock.json +189 -0
- package/src/a11y-mcp-tools/package.json +49 -0
- package/src/a11y-mcp-tools/src/envelope.js +197 -0
- package/src/a11y-mcp-tools/src/index.js +9 -0
- package/src/a11y-mcp-tools/src/schemas/artifact.js +85 -0
- package/src/a11y-mcp-tools/src/schemas/diagnosis.schema.v0.1.json +137 -0
- package/src/a11y-mcp-tools/src/schemas/envelope.schema.v0.1.json +108 -0
- package/src/a11y-mcp-tools/src/schemas/evidence.bundle.schema.v0.1.json +129 -0
- package/src/a11y-mcp-tools/src/schemas/evidence.js +97 -0
- package/src/a11y-mcp-tools/src/schemas/index.js +11 -0
- package/src/a11y-mcp-tools/src/schemas/provenance.js +140 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.request.schema.v0.1.json +77 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.diagnose.response.schema.v0.1.json +50 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.request.schema.v0.1.json +120 -0
- package/src/a11y-mcp-tools/src/schemas/tools/a11y.evidence.response.schema.v0.1.json +50 -0
- package/src/a11y-mcp-tools/src/tools/diagnose.js +597 -0
- package/src/a11y-mcp-tools/src/tools/evidence.js +481 -0
- package/src/a11y-mcp-tools/src/tools/index.js +10 -0
- package/src/a11y-mcp-tools/test/contract.test.mjs +154 -0
- package/src/a11y-mcp-tools/test/diagnose.test.js +485 -0
- package/src/a11y-mcp-tools/test/evidence.test.js +183 -0
- package/src/a11y-mcp-tools/test/schema.test.js +327 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Tests for CLI module."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pytest
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from click.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
from a11y_lint.cli import main
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def runner() -> CliRunner:
|
|
14
|
+
"""Create a CLI test runner."""
|
|
15
|
+
return CliRunner()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def sample_file(tmp_path: Path) -> Path:
|
|
20
|
+
"""Create a sample file with some text."""
|
|
21
|
+
file = tmp_path / "sample.txt"
|
|
22
|
+
file.write_text("This is a test file.\nERROR: Something went wrong.\n")
|
|
23
|
+
return file
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def clean_file(tmp_path: Path) -> Path:
|
|
28
|
+
"""Create a clean file with no issues."""
|
|
29
|
+
file = tmp_path / "clean.txt"
|
|
30
|
+
file.write_text("This is a clean file with no accessibility issues.\n")
|
|
31
|
+
return file
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def json_file(tmp_path: Path) -> Path:
|
|
36
|
+
"""Create a valid JSON messages file."""
|
|
37
|
+
file = tmp_path / "messages.json"
|
|
38
|
+
data = [
|
|
39
|
+
{"level": "OK", "code": "TST001", "what": "Test passed"},
|
|
40
|
+
{"level": "WARN", "code": "TST002", "what": "Test warning", "why": "Reason"},
|
|
41
|
+
]
|
|
42
|
+
file.write_text(json.dumps(data))
|
|
43
|
+
return file
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestMainCommand:
|
|
47
|
+
"""Tests for main CLI group."""
|
|
48
|
+
|
|
49
|
+
def test_help(self, runner: CliRunner) -> None:
|
|
50
|
+
result = runner.invoke(main, ["--help"])
|
|
51
|
+
assert result.exit_code == 0
|
|
52
|
+
assert "Accessibility linter" in result.output
|
|
53
|
+
|
|
54
|
+
def test_version(self, runner: CliRunner) -> None:
|
|
55
|
+
result = runner.invoke(main, ["--version"])
|
|
56
|
+
assert result.exit_code == 0
|
|
57
|
+
assert "0.1.0" in result.output
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestScanCommand:
|
|
61
|
+
"""Tests for scan command."""
|
|
62
|
+
|
|
63
|
+
def test_scan_file(self, runner: CliRunner, sample_file: Path) -> None:
|
|
64
|
+
result = runner.invoke(main, ["scan", str(sample_file)])
|
|
65
|
+
# Should find issues with "ERROR: Something went wrong"
|
|
66
|
+
assert "WARN" in result.output or "ERROR" in result.output
|
|
67
|
+
|
|
68
|
+
def test_scan_clean_file(self, runner: CliRunner, clean_file: Path) -> None:
|
|
69
|
+
result = runner.invoke(main, ["scan", str(clean_file)])
|
|
70
|
+
assert result.exit_code == 0
|
|
71
|
+
|
|
72
|
+
def test_scan_stdin(self, runner: CliRunner) -> None:
|
|
73
|
+
result = runner.invoke(main, ["scan", "--stdin"], input="Clean text.\n")
|
|
74
|
+
assert result.exit_code == 0
|
|
75
|
+
|
|
76
|
+
def test_scan_json_output(self, runner: CliRunner, sample_file: Path) -> None:
|
|
77
|
+
result = runner.invoke(main, ["scan", "--json", str(sample_file)])
|
|
78
|
+
data = json.loads(result.output)
|
|
79
|
+
assert "messages" in data
|
|
80
|
+
assert "summary" in data
|
|
81
|
+
|
|
82
|
+
def test_scan_markdown_output(self, runner: CliRunner, sample_file: Path) -> None:
|
|
83
|
+
result = runner.invoke(main, ["scan", "--format=markdown", str(sample_file)])
|
|
84
|
+
assert "# Accessibility Report" in result.output
|
|
85
|
+
|
|
86
|
+
def test_scan_no_input(self, runner: CliRunner) -> None:
|
|
87
|
+
result = runner.invoke(main, ["scan"])
|
|
88
|
+
assert result.exit_code == 1
|
|
89
|
+
assert "Must specify" in result.output
|
|
90
|
+
|
|
91
|
+
def test_scan_disable_rule(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
92
|
+
file = tmp_path / "test.txt"
|
|
93
|
+
file.write_text("ERROR: It failed")
|
|
94
|
+
result = runner.invoke(
|
|
95
|
+
main,
|
|
96
|
+
["scan", "--disable=no-ambiguous-pronouns", str(file)],
|
|
97
|
+
)
|
|
98
|
+
# Should not find ambiguous pronoun warning
|
|
99
|
+
assert "LNG004" not in result.output
|
|
100
|
+
|
|
101
|
+
def test_scan_strict_mode(self, runner: CliRunner, sample_file: Path) -> None:
|
|
102
|
+
# Strict mode treats warnings as errors
|
|
103
|
+
result = runner.invoke(main, ["scan", "--strict", str(sample_file)])
|
|
104
|
+
# If there are warnings, exit code should be 1
|
|
105
|
+
if "WARN" in result.output:
|
|
106
|
+
assert result.exit_code == 1
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TestValidateCommand:
|
|
110
|
+
"""Tests for validate command."""
|
|
111
|
+
|
|
112
|
+
def test_validate_valid_file(self, runner: CliRunner, json_file: Path) -> None:
|
|
113
|
+
result = runner.invoke(main, ["validate", str(json_file)])
|
|
114
|
+
assert result.exit_code == 0
|
|
115
|
+
assert "[OK]" in result.output
|
|
116
|
+
|
|
117
|
+
def test_validate_invalid_file(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
118
|
+
file = tmp_path / "invalid.json"
|
|
119
|
+
file.write_text(json.dumps({"level": "INVALID", "code": "bad", "what": "x"}))
|
|
120
|
+
result = runner.invoke(main, ["validate", str(file)])
|
|
121
|
+
assert result.exit_code == 1
|
|
122
|
+
assert "[ERROR]" in result.output
|
|
123
|
+
|
|
124
|
+
def test_validate_verbose(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
125
|
+
file = tmp_path / "invalid.json"
|
|
126
|
+
file.write_text(json.dumps({"level": "INVALID", "code": "bad", "what": "x"}))
|
|
127
|
+
result = runner.invoke(main, ["validate", "-v", str(file)])
|
|
128
|
+
assert result.exit_code == 1
|
|
129
|
+
# Verbose should show detailed errors
|
|
130
|
+
assert "level" in result.output.lower() or "code" in result.output.lower()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TestScorecardCommand:
|
|
134
|
+
"""Tests for scorecard command."""
|
|
135
|
+
|
|
136
|
+
def test_scorecard_file(self, runner: CliRunner, sample_file: Path) -> None:
|
|
137
|
+
result = runner.invoke(main, ["scorecard", str(sample_file)])
|
|
138
|
+
assert "Scorecard" in result.output
|
|
139
|
+
assert "Score:" in result.output
|
|
140
|
+
|
|
141
|
+
def test_scorecard_json(self, runner: CliRunner, sample_file: Path) -> None:
|
|
142
|
+
result = runner.invoke(main, ["scorecard", "--json", str(sample_file)])
|
|
143
|
+
data = json.loads(result.output)
|
|
144
|
+
assert "overall_score" in data
|
|
145
|
+
assert "rules" in data
|
|
146
|
+
|
|
147
|
+
def test_scorecard_badge(self, runner: CliRunner, clean_file: Path) -> None:
|
|
148
|
+
result = runner.invoke(main, ["scorecard", "--badge", str(clean_file)])
|
|
149
|
+
assert "shields.io" in result.output
|
|
150
|
+
|
|
151
|
+
def test_scorecard_custom_name(self, runner: CliRunner, clean_file: Path) -> None:
|
|
152
|
+
result = runner.invoke(
|
|
153
|
+
main, ["scorecard", "--name=Custom Name", str(clean_file)]
|
|
154
|
+
)
|
|
155
|
+
assert "Custom Name" in result.output
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestReportCommand:
|
|
159
|
+
"""Tests for report command."""
|
|
160
|
+
|
|
161
|
+
def test_report_stdout(self, runner: CliRunner, sample_file: Path) -> None:
|
|
162
|
+
result = runner.invoke(main, ["report", str(sample_file)])
|
|
163
|
+
assert "# Accessibility Report" in result.output
|
|
164
|
+
|
|
165
|
+
def test_report_to_file(
|
|
166
|
+
self, runner: CliRunner, sample_file: Path, tmp_path: Path
|
|
167
|
+
) -> None:
|
|
168
|
+
output = tmp_path / "report.md"
|
|
169
|
+
result = runner.invoke(main, ["report", str(sample_file), "-o", str(output)])
|
|
170
|
+
assert result.exit_code == 0 or result.exit_code == 1 # Depends on errors
|
|
171
|
+
assert output.exists()
|
|
172
|
+
assert "# Accessibility Report" in output.read_text(encoding="utf-8")
|
|
173
|
+
|
|
174
|
+
def test_report_custom_title(self, runner: CliRunner, clean_file: Path) -> None:
|
|
175
|
+
result = runner.invoke(
|
|
176
|
+
main, ["report", "--title=Custom Title", str(clean_file)]
|
|
177
|
+
)
|
|
178
|
+
assert "# Custom Title" in result.output
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestListRulesCommand:
|
|
182
|
+
"""Tests for list-rules command."""
|
|
183
|
+
|
|
184
|
+
def test_list_rules(self, runner: CliRunner) -> None:
|
|
185
|
+
result = runner.invoke(main, ["list-rules"])
|
|
186
|
+
assert result.exit_code == 0
|
|
187
|
+
assert "line-length" in result.output
|
|
188
|
+
assert "no-all-caps" in result.output
|
|
189
|
+
assert "plain-language" in result.output
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class TestSchemaCommand:
|
|
193
|
+
"""Tests for schema command."""
|
|
194
|
+
|
|
195
|
+
def test_schema(self, runner: CliRunner) -> None:
|
|
196
|
+
result = runner.invoke(main, ["schema"])
|
|
197
|
+
assert result.exit_code == 0
|
|
198
|
+
data = json.loads(result.output)
|
|
199
|
+
assert data["title"] == "CLI Ground Truth Message"
|
|
200
|
+
assert "properties" in data
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Tests for errors module."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from a11y_lint.errors import A11yMessage, Level, Location, ErrorCodes, CODE_PATTERN
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestLevel:
|
|
9
|
+
"""Tests for Level enum."""
|
|
10
|
+
|
|
11
|
+
def test_level_values(self) -> None:
|
|
12
|
+
assert Level.OK.value == "OK"
|
|
13
|
+
assert Level.WARN.value == "WARN"
|
|
14
|
+
assert Level.ERROR.value == "ERROR"
|
|
15
|
+
|
|
16
|
+
def test_level_str(self) -> None:
|
|
17
|
+
assert str(Level.OK) == "OK"
|
|
18
|
+
assert str(Level.WARN) == "WARN"
|
|
19
|
+
assert str(Level.ERROR) == "ERROR"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestLocation:
|
|
23
|
+
"""Tests for Location dataclass."""
|
|
24
|
+
|
|
25
|
+
def test_empty_location(self) -> None:
|
|
26
|
+
loc = Location()
|
|
27
|
+
assert str(loc) == "<unknown>"
|
|
28
|
+
assert loc.to_dict() == {}
|
|
29
|
+
|
|
30
|
+
def test_full_location(self) -> None:
|
|
31
|
+
loc = Location(file="test.py", line=10, column=5, context="some text")
|
|
32
|
+
assert "test.py" in str(loc)
|
|
33
|
+
assert "line 10" in str(loc)
|
|
34
|
+
assert "col 5" in str(loc)
|
|
35
|
+
|
|
36
|
+
d = loc.to_dict()
|
|
37
|
+
assert d["file"] == "test.py"
|
|
38
|
+
assert d["line"] == 10
|
|
39
|
+
assert d["column"] == 5
|
|
40
|
+
assert d["context"] == "some text"
|
|
41
|
+
|
|
42
|
+
def test_location_truncates_context(self) -> None:
|
|
43
|
+
long_context = "x" * 300
|
|
44
|
+
loc = Location(context=long_context)
|
|
45
|
+
d = loc.to_dict()
|
|
46
|
+
assert len(d["context"]) == 200
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestCodePattern:
|
|
50
|
+
"""Tests for error code pattern validation."""
|
|
51
|
+
|
|
52
|
+
def test_valid_codes(self) -> None:
|
|
53
|
+
assert CODE_PATTERN.match("A11Y001")
|
|
54
|
+
assert CODE_PATTERN.match("FMT123")
|
|
55
|
+
assert CODE_PATTERN.match("CLR999")
|
|
56
|
+
assert CODE_PATTERN.match("ABCD000")
|
|
57
|
+
|
|
58
|
+
def test_invalid_codes(self) -> None:
|
|
59
|
+
assert not CODE_PATTERN.match("A1") # Too short
|
|
60
|
+
assert not CODE_PATTERN.match("a11y001") # Lowercase
|
|
61
|
+
assert not CODE_PATTERN.match("A11Y01") # Only 2 digits
|
|
62
|
+
assert not CODE_PATTERN.match("A11Y0001") # 4 digits
|
|
63
|
+
assert not CODE_PATTERN.match("ABCDE001") # 5 letters
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestA11yMessage:
|
|
67
|
+
"""Tests for A11yMessage dataclass."""
|
|
68
|
+
|
|
69
|
+
def test_ok_message(self) -> None:
|
|
70
|
+
msg = A11yMessage.ok("TST001", "Test passed")
|
|
71
|
+
assert msg.level == Level.OK
|
|
72
|
+
assert msg.code == "TST001"
|
|
73
|
+
assert msg.what == "Test passed"
|
|
74
|
+
assert msg.why is None
|
|
75
|
+
assert msg.fix is None
|
|
76
|
+
|
|
77
|
+
def test_warn_message(self) -> None:
|
|
78
|
+
msg = A11yMessage.warn(
|
|
79
|
+
"TST002",
|
|
80
|
+
"Test warning",
|
|
81
|
+
"This is a warning reason",
|
|
82
|
+
fix="Consider fixing this",
|
|
83
|
+
)
|
|
84
|
+
assert msg.level == Level.WARN
|
|
85
|
+
assert msg.code == "TST002"
|
|
86
|
+
assert msg.what == "Test warning"
|
|
87
|
+
assert msg.why == "This is a warning reason"
|
|
88
|
+
assert msg.fix == "Consider fixing this"
|
|
89
|
+
|
|
90
|
+
def test_error_message(self) -> None:
|
|
91
|
+
msg = A11yMessage.error(
|
|
92
|
+
"TST003",
|
|
93
|
+
"Test error",
|
|
94
|
+
"This is an error reason",
|
|
95
|
+
"Fix it like this",
|
|
96
|
+
)
|
|
97
|
+
assert msg.level == Level.ERROR
|
|
98
|
+
assert msg.code == "TST003"
|
|
99
|
+
assert msg.what == "Test error"
|
|
100
|
+
assert msg.why == "This is an error reason"
|
|
101
|
+
assert msg.fix == "Fix it like this"
|
|
102
|
+
|
|
103
|
+
def test_error_requires_why(self) -> None:
|
|
104
|
+
with pytest.raises(ValueError, match="must include 'why'"):
|
|
105
|
+
A11yMessage(
|
|
106
|
+
level=Level.ERROR,
|
|
107
|
+
code="TST001",
|
|
108
|
+
what="Test",
|
|
109
|
+
fix="Some fix",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def test_error_requires_fix(self) -> None:
|
|
113
|
+
with pytest.raises(ValueError, match="must include 'fix'"):
|
|
114
|
+
A11yMessage(
|
|
115
|
+
level=Level.ERROR,
|
|
116
|
+
code="TST001",
|
|
117
|
+
what="Test",
|
|
118
|
+
why="Some reason",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def test_invalid_code_raises(self) -> None:
|
|
122
|
+
with pytest.raises(ValueError, match="Invalid error code"):
|
|
123
|
+
A11yMessage.ok("invalid", "Test")
|
|
124
|
+
|
|
125
|
+
def test_empty_what_raises(self) -> None:
|
|
126
|
+
with pytest.raises(ValueError, match="cannot be empty"):
|
|
127
|
+
A11yMessage.ok("TST001", "")
|
|
128
|
+
|
|
129
|
+
def test_whitespace_what_raises(self) -> None:
|
|
130
|
+
with pytest.raises(ValueError, match="cannot be empty"):
|
|
131
|
+
A11yMessage.ok("TST001", " ")
|
|
132
|
+
|
|
133
|
+
def test_to_dict(self) -> None:
|
|
134
|
+
msg = A11yMessage.error(
|
|
135
|
+
"TST001",
|
|
136
|
+
"Test error",
|
|
137
|
+
"Why",
|
|
138
|
+
"Fix",
|
|
139
|
+
rule="test-rule",
|
|
140
|
+
location=Location(file="test.py", line=1),
|
|
141
|
+
metadata={"key": "value"},
|
|
142
|
+
)
|
|
143
|
+
d = msg.to_dict()
|
|
144
|
+
assert d["level"] == "ERROR"
|
|
145
|
+
assert d["code"] == "TST001"
|
|
146
|
+
assert d["what"] == "Test error"
|
|
147
|
+
assert d["why"] == "Why"
|
|
148
|
+
assert d["fix"] == "Fix"
|
|
149
|
+
assert d["rule"] == "test-rule"
|
|
150
|
+
assert d["location"]["file"] == "test.py"
|
|
151
|
+
assert d["metadata"]["key"] == "value"
|
|
152
|
+
|
|
153
|
+
def test_from_dict(self) -> None:
|
|
154
|
+
data = {
|
|
155
|
+
"level": "WARN",
|
|
156
|
+
"code": "TST002",
|
|
157
|
+
"what": "Test warning",
|
|
158
|
+
"why": "Reason",
|
|
159
|
+
"location": {"file": "test.py", "line": 5},
|
|
160
|
+
}
|
|
161
|
+
msg = A11yMessage.from_dict(data)
|
|
162
|
+
assert msg.level == Level.WARN
|
|
163
|
+
assert msg.code == "TST002"
|
|
164
|
+
assert msg.what == "Test warning"
|
|
165
|
+
assert msg.why == "Reason"
|
|
166
|
+
assert msg.location is not None
|
|
167
|
+
assert msg.location.file == "test.py"
|
|
168
|
+
assert msg.location.line == 5
|
|
169
|
+
|
|
170
|
+
def test_truncates_long_what(self) -> None:
|
|
171
|
+
long_what = "x" * 300
|
|
172
|
+
msg = A11yMessage.ok("TST001", long_what)
|
|
173
|
+
assert len(msg.what) == 200
|
|
174
|
+
|
|
175
|
+
def test_truncates_long_why(self) -> None:
|
|
176
|
+
long_why = "y" * 600
|
|
177
|
+
msg = A11yMessage.warn("TST001", "Test", long_why)
|
|
178
|
+
assert len(msg.why) == 500
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestErrorCodes:
|
|
182
|
+
"""Tests for ErrorCodes constants."""
|
|
183
|
+
|
|
184
|
+
def test_all_codes_valid(self) -> None:
|
|
185
|
+
for attr in dir(ErrorCodes):
|
|
186
|
+
if not attr.startswith("_"):
|
|
187
|
+
code = getattr(ErrorCodes, attr)
|
|
188
|
+
assert CODE_PATTERN.match(code), f"{attr} = {code} is invalid"
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Tests for render module."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from a11y_lint.render import (
|
|
7
|
+
render,
|
|
8
|
+
render_plain,
|
|
9
|
+
render_colored,
|
|
10
|
+
render_batch,
|
|
11
|
+
Renderer,
|
|
12
|
+
format_for_file,
|
|
13
|
+
Colors,
|
|
14
|
+
get_level_color,
|
|
15
|
+
)
|
|
16
|
+
from a11y_lint.errors import A11yMessage, Level, Location
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestRenderPlain:
|
|
20
|
+
"""Tests for render_plain function."""
|
|
21
|
+
|
|
22
|
+
def test_ok_message(self) -> None:
|
|
23
|
+
msg = A11yMessage.ok("TST001", "Test passed")
|
|
24
|
+
output = render_plain(msg)
|
|
25
|
+
assert "[OK] TST001: Test passed" in output
|
|
26
|
+
|
|
27
|
+
def test_warn_message(self) -> None:
|
|
28
|
+
msg = A11yMessage.warn("TST002", "Test warning", "This is why")
|
|
29
|
+
output = render_plain(msg)
|
|
30
|
+
assert "[WARN] TST002: Test warning" in output
|
|
31
|
+
assert "Why: This is why" in output
|
|
32
|
+
|
|
33
|
+
def test_error_message(self) -> None:
|
|
34
|
+
msg = A11yMessage.error("TST003", "Test error", "Reason", "Fix this")
|
|
35
|
+
output = render_plain(msg)
|
|
36
|
+
assert "[ERROR] TST003: Test error" in output
|
|
37
|
+
assert "Why: Reason" in output
|
|
38
|
+
assert "Fix: Fix this" in output
|
|
39
|
+
|
|
40
|
+
def test_message_with_location(self) -> None:
|
|
41
|
+
msg = A11yMessage.ok(
|
|
42
|
+
"TST001",
|
|
43
|
+
"Test",
|
|
44
|
+
location=Location(file="test.py", line=10, column=5),
|
|
45
|
+
)
|
|
46
|
+
output = render_plain(msg)
|
|
47
|
+
assert "at test.py" in output
|
|
48
|
+
assert "line 10" in output
|
|
49
|
+
assert "col 5" in output
|
|
50
|
+
|
|
51
|
+
def test_indentation(self) -> None:
|
|
52
|
+
msg = A11yMessage.ok("TST001", "Test")
|
|
53
|
+
output = render_plain(msg, indent=4)
|
|
54
|
+
assert output.startswith(" [OK]")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestRenderColored:
|
|
58
|
+
"""Tests for render_colored function."""
|
|
59
|
+
|
|
60
|
+
def test_contains_color_codes(self) -> None:
|
|
61
|
+
msg = A11yMessage.ok("TST001", "Test")
|
|
62
|
+
output = render_colored(msg)
|
|
63
|
+
assert Colors.OK in output
|
|
64
|
+
assert Colors.RESET in output
|
|
65
|
+
|
|
66
|
+
def test_error_uses_red(self) -> None:
|
|
67
|
+
msg = A11yMessage.error("TST001", "Test", "Why", "Fix")
|
|
68
|
+
output = render_colored(msg)
|
|
69
|
+
assert Colors.ERROR in output
|
|
70
|
+
|
|
71
|
+
def test_warn_uses_yellow(self) -> None:
|
|
72
|
+
msg = A11yMessage.warn("TST001", "Test", "Why")
|
|
73
|
+
output = render_colored(msg)
|
|
74
|
+
assert Colors.WARN in output
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestRender:
|
|
78
|
+
"""Tests for render function."""
|
|
79
|
+
|
|
80
|
+
def test_default_no_color(self) -> None:
|
|
81
|
+
msg = A11yMessage.ok("TST001", "Test")
|
|
82
|
+
output = render(msg)
|
|
83
|
+
assert Colors.OK not in output
|
|
84
|
+
|
|
85
|
+
def test_color_enabled(self) -> None:
|
|
86
|
+
msg = A11yMessage.ok("TST001", "Test")
|
|
87
|
+
output = render(msg, color=True)
|
|
88
|
+
assert Colors.OK in output
|
|
89
|
+
|
|
90
|
+
def test_color_disabled(self) -> None:
|
|
91
|
+
msg = A11yMessage.ok("TST001", "Test")
|
|
92
|
+
output = render(msg, color=False)
|
|
93
|
+
assert Colors.OK not in output
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestRenderBatch:
|
|
97
|
+
"""Tests for render_batch function."""
|
|
98
|
+
|
|
99
|
+
def test_multiple_messages(self) -> None:
|
|
100
|
+
messages = [
|
|
101
|
+
A11yMessage.ok("TST001", "Test 1"),
|
|
102
|
+
A11yMessage.ok("TST002", "Test 2"),
|
|
103
|
+
]
|
|
104
|
+
output = render_batch(messages)
|
|
105
|
+
assert "TST001" in output
|
|
106
|
+
assert "TST002" in output
|
|
107
|
+
|
|
108
|
+
def test_custom_separator(self) -> None:
|
|
109
|
+
messages = [
|
|
110
|
+
A11yMessage.ok("TST001", "Test 1"),
|
|
111
|
+
A11yMessage.ok("TST002", "Test 2"),
|
|
112
|
+
]
|
|
113
|
+
output = render_batch(messages, separator="\n\n")
|
|
114
|
+
assert "\n\n" in output
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TestGetLevelColor:
|
|
118
|
+
"""Tests for get_level_color function."""
|
|
119
|
+
|
|
120
|
+
def test_ok_green(self) -> None:
|
|
121
|
+
assert get_level_color(Level.OK) == Colors.OK
|
|
122
|
+
|
|
123
|
+
def test_warn_yellow(self) -> None:
|
|
124
|
+
assert get_level_color(Level.WARN) == Colors.WARN
|
|
125
|
+
|
|
126
|
+
def test_error_red(self) -> None:
|
|
127
|
+
assert get_level_color(Level.ERROR) == Colors.ERROR
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestRenderer:
|
|
131
|
+
"""Tests for Renderer class."""
|
|
132
|
+
|
|
133
|
+
def test_render_to_stream(self) -> None:
|
|
134
|
+
stream = io.StringIO()
|
|
135
|
+
renderer = Renderer(color=False, stream=stream)
|
|
136
|
+
msg = A11yMessage.ok("TST001", "Test")
|
|
137
|
+
renderer.write(msg)
|
|
138
|
+
output = stream.getvalue()
|
|
139
|
+
assert "[OK] TST001: Test" in output
|
|
140
|
+
|
|
141
|
+
def test_counts_messages(self) -> None:
|
|
142
|
+
stream = io.StringIO()
|
|
143
|
+
renderer = Renderer(color=False, stream=stream)
|
|
144
|
+
|
|
145
|
+
renderer.write(A11yMessage.ok("TST001", "Test 1"))
|
|
146
|
+
renderer.write(A11yMessage.warn("TST002", "Test 2", "Why"))
|
|
147
|
+
renderer.write(A11yMessage.error("TST003", "Test 3", "Why", "Fix"))
|
|
148
|
+
|
|
149
|
+
assert renderer.ok_count == 1
|
|
150
|
+
assert renderer.warn_count == 1
|
|
151
|
+
assert renderer.error_count == 1
|
|
152
|
+
assert renderer.total_count == 3
|
|
153
|
+
|
|
154
|
+
def test_summary_line(self) -> None:
|
|
155
|
+
renderer = Renderer(color=False, stream=io.StringIO())
|
|
156
|
+
assert renderer.summary_line() == "No issues found"
|
|
157
|
+
|
|
158
|
+
renderer.write(A11yMessage.ok("TST001", "Test"))
|
|
159
|
+
assert "1 passed" in renderer.summary_line()
|
|
160
|
+
|
|
161
|
+
renderer.write(A11yMessage.warn("TST002", "Test", "Why"))
|
|
162
|
+
assert "1 warnings" in renderer.summary_line()
|
|
163
|
+
|
|
164
|
+
renderer.write(A11yMessage.error("TST003", "Test", "Why", "Fix"))
|
|
165
|
+
assert "1 errors" in renderer.summary_line()
|
|
166
|
+
|
|
167
|
+
def test_write_batch(self) -> None:
|
|
168
|
+
stream = io.StringIO()
|
|
169
|
+
renderer = Renderer(color=False, stream=stream)
|
|
170
|
+
messages = [
|
|
171
|
+
A11yMessage.ok("TST001", "Test 1"),
|
|
172
|
+
A11yMessage.ok("TST002", "Test 2"),
|
|
173
|
+
]
|
|
174
|
+
renderer.write_batch(messages)
|
|
175
|
+
output = stream.getvalue()
|
|
176
|
+
assert "TST001" in output
|
|
177
|
+
assert "TST002" in output
|
|
178
|
+
assert renderer.total_count == 2
|
|
179
|
+
|
|
180
|
+
def test_auto_detect_color(self) -> None:
|
|
181
|
+
# StringIO doesn't have isatty, so should default to no color
|
|
182
|
+
stream = io.StringIO()
|
|
183
|
+
renderer = Renderer(stream=stream)
|
|
184
|
+
assert renderer.color is False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class TestFormatForFile:
|
|
188
|
+
"""Tests for format_for_file function."""
|
|
189
|
+
|
|
190
|
+
def test_no_colors(self) -> None:
|
|
191
|
+
messages = [A11yMessage.ok("TST001", "Test")]
|
|
192
|
+
output = format_for_file(messages)
|
|
193
|
+
assert Colors.OK not in output
|
|
194
|
+
|
|
195
|
+
def test_blank_lines_between(self) -> None:
|
|
196
|
+
messages = [
|
|
197
|
+
A11yMessage.ok("TST001", "Test 1"),
|
|
198
|
+
A11yMessage.ok("TST002", "Test 2"),
|
|
199
|
+
]
|
|
200
|
+
output = format_for_file(messages)
|
|
201
|
+
# Should have blank line between messages
|
|
202
|
+
assert "\n\n" in output
|