chore: import upstream snapshot with attribution
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
Tests for the check-css-class-prefix pre-commit hook.
|
||||
|
||||
Specifically guards against the regex regression fixed in PR #3103, where
|
||||
`(?<![\\w/])\\.` matched only the first class in a compound selector like
|
||||
`.class1.class2` because the leading dot of the second class was preceded
|
||||
by a word character. That hid 159+ unprefixed compound state classes such
|
||||
as `.ldr-foo.selected` or `.ldr-foo.loading` from the prefix check.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
HOOK_SCRIPT = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ ".pre-commit-hooks"
|
||||
/ "check-css-class-prefix.py"
|
||||
)
|
||||
|
||||
|
||||
def _run_hook(content: str, filename: str) -> subprocess.CompletedProcess:
|
||||
"""Write content to a temp file and run the hook against it."""
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = Path(d) / filename
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content)
|
||||
return subprocess.run(
|
||||
[sys.executable, str(HOOK_SCRIPT), str(path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
class TestCompoundSelectorsCSS:
|
||||
"""The fix in PR #3103: every class in a compound selector must be checked."""
|
||||
|
||||
def test_compound_selector_first_class_unprefixed_is_caught(self):
|
||||
"""`.bare.ldr-other` must fail on `.bare`."""
|
||||
result = _run_hook(".bare.ldr-other { color: red; }\n", "x.css")
|
||||
assert result.returncode == 1
|
||||
assert "'.bare'" in result.stdout
|
||||
|
||||
def test_compound_selector_second_class_unprefixed_is_caught(self):
|
||||
"""`.ldr-foo.selected` was previously silently passing — must fail now."""
|
||||
result = _run_hook(".ldr-foo.selected { color: red; }\n", "x.css")
|
||||
assert result.returncode == 1
|
||||
assert "'.selected'" in result.stdout
|
||||
|
||||
def test_compound_selector_three_classes_all_validated(self):
|
||||
"""`.ldr-a.bare.ldr-c` must fail on the middle bare class."""
|
||||
result = _run_hook(".ldr-a.bare.ldr-c { color: red; }\n", "x.css")
|
||||
assert result.returncode == 1
|
||||
assert "'.bare'" in result.stdout
|
||||
|
||||
def test_compound_selector_all_prefixed_passes(self):
|
||||
"""`.ldr-foo.ldr-bar` is valid — all classes have the prefix."""
|
||||
result = _run_hook(".ldr-foo.ldr-bar { color: red; }\n", "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_compound_with_pseudo_class_validates_real_classes(self):
|
||||
"""`.ldr-foo.bare:hover` must fail on `.bare`, not on `:hover`."""
|
||||
result = _run_hook(".ldr-foo.bare:hover { color: red; }\n", "x.css")
|
||||
assert result.returncode == 1
|
||||
assert "'.bare'" in result.stdout
|
||||
assert "'.hover'" not in result.stdout
|
||||
|
||||
|
||||
class TestDescendantSelectorsCSS:
|
||||
"""Space-separated selectors must continue to validate each class."""
|
||||
|
||||
def test_descendant_selector_with_unprefixed_child_fails(self):
|
||||
result = _run_hook(".ldr-parent .bare { color: red; }\n", "x.css")
|
||||
assert result.returncode == 1
|
||||
assert "'.bare'" in result.stdout
|
||||
|
||||
def test_descendant_selector_all_prefixed_passes(self):
|
||||
result = _run_hook(".ldr-parent .ldr-child { color: red; }\n", "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
class TestUrlsAreNotMatched:
|
||||
"""The hook's lookbehind must continue to skip dots inside URL hostnames."""
|
||||
|
||||
def test_url_in_background_image_is_not_flagged(self):
|
||||
css = (
|
||||
".ldr-foo {\n"
|
||||
" background: url(https://www.example.com/spinner.svg);\n"
|
||||
"}\n"
|
||||
)
|
||||
result = _run_hook(css, "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_unquoted_url_with_dotted_path_is_not_flagged(self):
|
||||
css = ".ldr-foo { background: url(www.w3.org/path); }\n"
|
||||
result = _run_hook(css, "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
class TestAllowlistedClasses:
|
||||
"""Bootstrap and other framework classes must pass the prefix check."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"selector",
|
||||
[
|
||||
".container",
|
||||
".form-group",
|
||||
".btn-primary",
|
||||
".active",
|
||||
".disabled",
|
||||
".visually-hidden",
|
||||
".bi-search",
|
||||
".fa-spinner",
|
||||
],
|
||||
)
|
||||
def test_allowed_framework_class_passes(self, selector: str):
|
||||
result = _run_hook(f"{selector} {{ color: red; }}\n", "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_compound_with_allowlisted_modifier_passes(self):
|
||||
"""`.ldr-foo.active` must pass — `active` is allowlisted."""
|
||||
result = _run_hook(".ldr-foo.active { color: red; }\n", "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
class TestCommentsAndImportsSkipped:
|
||||
"""Lines that are CSS comments or @import should not be scanned."""
|
||||
|
||||
def test_unprefixed_class_inside_comment_is_ignored(self):
|
||||
css = "/* .bare-class is mentioned but not defined here */\n"
|
||||
result = _run_hook(css, "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_import_statement_is_skipped(self):
|
||||
css = '@import url("./other.bare-thing.css");\n'
|
||||
result = _run_hook(css, "x.css")
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
class TestStyleTagInHTML:
|
||||
"""The HTML scanner uses the same regex inside <style> blocks."""
|
||||
|
||||
def test_compound_selector_in_style_tag_is_caught(self):
|
||||
html = (
|
||||
"<html><head><style>\n"
|
||||
".ldr-foo.bare { color: red; }\n"
|
||||
"</style></head></html>\n"
|
||||
)
|
||||
result = _run_hook(html, "x.html")
|
||||
assert result.returncode == 1
|
||||
assert "'.bare'" in result.stdout
|
||||
assert "in style tag" in result.stdout
|
||||
|
||||
def test_compound_selector_outside_style_tag_falls_through_to_attr_check(
|
||||
self,
|
||||
):
|
||||
"""A `.ldr-foo.bare` literal outside <style> isn't a CSS rule definition;
|
||||
the attribute scanner only inspects class="..." attributes, so it
|
||||
shouldn't flag this content as either a CSS or HTML class issue."""
|
||||
html = '<div data-css=".ldr-foo.bare">x</div>\n'
|
||||
result = _run_hook(html, "x.html")
|
||||
assert result.returncode == 0
|
||||
Reference in New Issue
Block a user