diff --git a/pyproject.toml b/pyproject.toml index 1915d9e..bc1b405 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,4 +52,4 @@ include = ["pkgmgr*"] [tool.setuptools.package-data] "pkgmgr.config" = ["*.yml", "*.yaml"] -"pkgmgr.actions.review" = ["*.j2"] +"pkgmgr.actions.review" = ["*.j2", "*.LICENSE"] diff --git a/src/pkgmgr/actions/review/bootstrap-icons.LICENSE b/src/pkgmgr/actions/review/bootstrap-icons.LICENSE new file mode 100644 index 0000000..f952439 --- /dev/null +++ b/src/pkgmgr/actions/review/bootstrap-icons.LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2024 The Bootstrap Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/pkgmgr/actions/review/review.html.j2 b/src/pkgmgr/actions/review/review.html.j2 index 7026ec2..f90343c 100644 --- a/src/pkgmgr/actions/review/review.html.j2 +++ b/src/pkgmgr/actions/review/review.html.j2 @@ -1,5 +1,9 @@ {% set status_letters = {"added": "A", "deleted": "D", "modified": "M", "renamed": "R", "copied": "C", "untracked": "U"} %} {% set signs = {"add": "+", "del": "-", "ctx": "", "meta": ""} %} +{% set icon_paths = { + "arrows-expand": "M1 8a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13A.5.5 0 0 1 1 8M7.646.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 1.707V5.5a.5.5 0 0 1-1 0V1.707L6.354 2.854a.5.5 0 1 1-.708-.708zM8 10a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L7.5 14.293V10.5A.5.5 0 0 1 8 10", + "arrows-collapse": "M1 8a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13A.5.5 0 0 1 1 8m7-8a.5.5 0 0 1 .5.5v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 1 1 .708-.708L7.5 4.293V.5A.5.5 0 0 1 8 0m-.5 11.707-1.146 1.147a.5.5 0 0 1-.708-.708l2-2a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 11.707V15.5a.5.5 0 0 1-1 0z", +} %} {% set style %} :root { color-scheme: light dark; @@ -93,6 +97,7 @@ input[type="search"] { width: 260px; max-width: 100%; padding: 5px 10px; border: input[type="search"]:focus { outline: 2px solid var(--accent); outline-offset: -1px; } button { padding: 5px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--subtle); color: var(--text); font: 500 13px var(--sans); cursor: pointer; } button:hover { border-color: var(--muted); } +button.icon { display: inline-flex; align-items: center; justify-content: center; padding: 6px 8px; } main { padding-top: 24px; padding-bottom: 80px; } .group { margin-bottom: 36px; } h2 { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin: 0 0 12px; font-size: 17px; font-weight: 600; } @@ -187,6 +192,7 @@ details.commit > summary { padding: 10px 14px; } })(); {% endset %} {% macro stat(added, removed) %}+{{ added }}−{{ removed }}{% endmacro %} +{% macro icon(name) %}{% endmacro %} {% macro plural(count, word) %}{{ count }} {{ word }}{{ "" if count == 1 else "s" }}{% endmacro %} {% macro file_block(file, open) %}
@@ -282,8 +288,8 @@ details.commit > summary { padding: 10px 14px; }
- - + +
diff --git a/tests/integration/test_review.py b/tests/integration/test_review.py index 2d436ff..21784b0 100644 --- a/tests/integration/test_review.py +++ b/tests/integration/test_review.py @@ -2,6 +2,7 @@ from __future__ import annotations import io import os +import re import subprocess import tempfile import unittest @@ -160,6 +161,27 @@ class TestReview(unittest.TestCase): self.assertNotIn("big 11", html) self.assertIn("20 more lines not shown", html) + def test_toolbar_buttons_are_labelled_icons(self) -> None: + html = self._render("page.html", None, []) + + for key, label in (("expand", "Expand all"), ("collapse", "Collapse all")): + with self.subTest(button=key): + button = re.search( + rf'', + html, + re.DOTALL, + ) + self.assertIsNotNone(button) + attrs, inner = button.groups() + self.assertIn(f'title="{label}"', attrs) + self.assertIn(f'aria-label="{label}"', attrs) + self.assertRegex( + inner, rf'^]* aria-hidden="true"' + ) + self.assertEqual(re.sub(r"<[^>]+>", "", inner).strip(), "") + self.assertNotIn(" None: plain = os.path.join(self.root, "plain") os.makedirs(plain)