diff options
author | Martin Fischer <martin@push-f.com> | 2021-11-01 15:27:45 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-01 15:27:45 +0100 |
commit | 8cfc6823a7a74d20d0f32000f3b967de770d1a9f (patch) | |
tree | e9b74c0a90e398a60873adaa0c9fbbee5c3ca1c9 | |
parent | 631e8511fa47a5f99468eabfcd418d3cd6cecc25 (diff) | |
download | pygments-git-8cfc6823a7a74d20d0f32000f3b967de770d1a9f.tar.gz |
Prohibit contrast degradation for styles via test (#1919)
Web accessibility is important. Unfortunately currently many pygments
styles have rules with poor contrasts. This commit introduces a test
case that fails if the minimum contrast of a style gets worse, e.g:
E AssertionError: contrast degradation for style 'borland'
E The following rules have a contrast lower than the required 2.9:
E
E * 1.90 Token.Text.Whitespace
E * 2.80 Token.Generic.Heading
E * 2.30 Token.Generic.Subheading
E
E assert not 1.9 < 2.9
This is accomplished by storing the current minimum contrasts in
./tests/contrast/min_contrasts.json.
When you improve a minimum contrast the test fails with:
E AssertionError: congrats, you improved a contrast! please run ./scripts/update_contrasts.py
E assert not 1.9 > 0.9
Running the script as instructed updates the JSON file, making the test pass.
New styles are required to meet the WCAG AA contrast minimum of 4.5.
First commit to address #1718.
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rwxr-xr-x | scripts/update_contrasts.py | 21 | ||||
-rw-r--r-- | tests/contrast/min_contrasts.json | 43 | ||||
-rw-r--r-- | tests/contrast/test_contrasts.py | 96 | ||||
-rw-r--r-- | tox.ini | 1 |
6 files changed, 163 insertions, 0 deletions
@@ -246,5 +246,6 @@ Other contributors, listed alphabetically, are: * Fabian Neumann -- CDDL lexer * Thomas Duboucher -- CDDL lexer * Philipp Imhof -- Pango Markup formatter +* Martin Fischer -- WCAG contrast testing Many thanks for all contributions! diff --git a/requirements.txt b/requirements.txt index 49a627f2..7240ed5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ pytest>=6.0 pyflakes pylint tox +wcag-contrast-ratio diff --git a/scripts/update_contrasts.py b/scripts/update_contrasts.py new file mode 100755 index 00000000..156bc5cb --- /dev/null +++ b/scripts/update_contrasts.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +""" + Updates tests/contrast/min_contrasts.json + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Whenever you have improved the minimum contrast of a style you should run + this script, so that the test_contrasts.py test prevents future degredations. +""" + +import os +import sys + +# always prefer Pygments from source if exists +srcpath = os.path.join(os.path.dirname(__file__), "..") +if os.path.isdir(os.path.join(srcpath, "pygments")): + sys.path.insert(0, srcpath) + +import tests.contrast.test_contrasts + +tests.contrast.test_contrasts.test_contrasts(fail_if_improved=False) +tests.contrast.test_contrasts.update_json() diff --git a/tests/contrast/min_contrasts.json b/tests/contrast/min_contrasts.json new file mode 100644 index 00000000..e215a26c --- /dev/null +++ b/tests/contrast/min_contrasts.json @@ -0,0 +1,43 @@ +{ + "default": 1.8, + "emacs": 1.8, + "friendly": 1.7, + "colorful": 1.9, + "autumn": 1.9, + "murphy": 1.4, + "manni": 1.4, + "material": 1.6, + "monokai": 1.4, + "perldoc": 1.6, + "pastie": 1.9, + "borland": 1.9, + "trac": 1.9, + "native": 2.1, + "fruity": 1.9, + "bw": 21.0, + "vim": 1.3, + "vs": 3.6, + "tango": 1.0, + "rrt": 1.0, + "xcode": 5.1, + "igor": 3.6, + "paraiso-light": 1.3, + "paraiso-dark": 1.3, + "lovelace": 3.1, + "algol": 3.5, + "algol_nu": 3.5, + "arduino": 2.6, + "rainbow_dash": 1.0, + "abap": 2.5, + "solarized-dark": 2.8, + "solarized-light": 2.5, + "sas": 1.9, + "stata": 1.9, + "stata-light": 1.9, + "stata-dark": 1.4, + "inkpot": 1.0, + "zenburn": 1.6, + "gruvbox-dark": 1.0, + "gruvbox-light": 1.0, + "dracula": 1.4 +} diff --git a/tests/contrast/test_contrasts.py b/tests/contrast/test_contrasts.py new file mode 100644 index 00000000..d503a0c4 --- /dev/null +++ b/tests/contrast/test_contrasts.py @@ -0,0 +1,96 @@ +""" + Contrast Tests + ~~~~~~~~~~ + + Pygments styles should be accessible to people with suboptimal vision. + This test ensures that the minimum contrast of styles does not degrade, + and that every rule of a new style fulfills the WCAG AA standard.[1] + + [1]: https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html +""" + +import json +import os +import sys + +import pygments.styles +import wcag_contrast_ratio + +JSON_FILENAME = os.path.join(os.path.dirname(__file__), "min_contrasts.json") +WCAG_AA_CONTRAST = 4.5 + + +def hex2rgb(hexstr): + hexstr = hexstr.lstrip("#") + r = int(hexstr[:2], 16) / 255 + g = int(hexstr[2:4], 16) / 255 + b = int(hexstr[4:], 16) / 255 + return (r, g, b) + + +def get_style_contrasts(style): + bgcolor = hex2rgb(style.background_color) + return [ + ( + round( + wcag_contrast_ratio.rgb( + bgcolor, + hex2rgb(style["color"] or "#000000") + # we default to black because browsers also do + ), + 1, + ), + ttype, + ) + for ttype, style in style.list_styles() + ] + + +def builtin_styles(): + for style_name in pygments.styles.STYLE_MAP: + yield (style_name, pygments.styles.get_style_by_name(style_name)) + + +def update_json(): + with open(JSON_FILENAME, "w") as f: + json.dump( + { + name: min(x[0] for x in get_style_contrasts(style)) + for name, style in builtin_styles() + }, + f, + indent=2, + ) + + +def test_contrasts(fail_if_improved=True): + with open(JSON_FILENAME) as f: + previous_contrasts = json.load(f) + + for style_name in pygments.styles.STYLE_MAP: + style = pygments.styles.get_style_by_name(style_name) + contrasts = get_style_contrasts(style) + min_contrast = min([x[0] for x in contrasts]) + + bar = previous_contrasts.get(style_name, WCAG_AA_CONTRAST) + + assert not min_contrast < bar, ( + "contrast degradation for style '{}'\n" + "The following rules have a contrast lower than the required {}:\n\n" + "{}\n" + ).format( + style_name, + bar, + "\n".join( + [ + "* {:.2f} {}".format(contrast, ttype) + for contrast, ttype in contrasts + if contrast < bar + ] + ), + ) + + if fail_if_improved: + assert ( + not min_contrast > bar + ), "congrats, you improved a contrast! please run ./scripts/update_contrasts.py" @@ -5,6 +5,7 @@ envlist = py35, py36, py37, py38, py39, py310, pypy3, lint deps = pytest pytest-cov + wcag-contrast-ratio commands = pytest {posargs} |