summaryrefslogtreecommitdiff
path: root/tests/contrast/test_contrasts.py
blob: cfc9b414f4416cf271f5ce3dff527f276a711ff9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""
    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 pygments.token
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_cls):
    return [
        (
            round(
                wcag_contrast_ratio.rgb(
                    hex2rgb(style["bgcolor"] or style_cls.background_color),
                    hex2rgb(style["color"] or "#000000")
                    # we default to black because browsers also do
                ),
                1,
            ),
            ttype,
        )
        for ttype, style in style_cls.list_styles()
        if ttype != pygments.token.Whitespace
    ]


def builtin_styles():
    for style_name in pygments.styles.STYLE_MAP:
        yield (style_name, pygments.styles.get_style_by_name(style_name))


def min_contrasts():
    return {
        name: min(x[0] for x in get_style_contrasts(style))
        for name, style in builtin_styles()
    }


def update_json():
    with open(JSON_FILENAME, "w", encoding="utf-8") as f:
        json.dump(
            min_contrasts(),
            f,
            indent=2,
        )


def test_contrasts(fail_if_improved=True):
    with open(JSON_FILENAME, encoding="utf-8") 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"