diff --git a/scripts/lint-artifact.mjs b/scripts/lint-artifact.mjs index eec5ab0..f8252e6 100644 --- a/scripts/lint-artifact.mjs +++ b/scripts/lint-artifact.mjs @@ -137,6 +137,8 @@ function lintOne(file) { // --- clipboard guard --- if (/navigator\.clipboard\.writeText/.test(html)) { + const hasHelper = /(?:const|function|let|var)\s+writeClipboard\b/.test(html); + if (!hasHelper) fails.push("navigator.clipboard.writeText appears without a writeClipboard helper in the same file"); const guarded = /navigator\.clipboard\s*&&\s*navigator\.clipboard\.writeText|navigator\.clipboard\?\.writeText/.test(html); if (!guarded) fails.push("navigator.clipboard.writeText called without guarding navigator.clipboard first"); } diff --git a/tests/test_linters.py b/tests/test_linters.py index b72fc66..3d40fe9 100644 --- a/tests/test_linters.py +++ b/tests/test_linters.py @@ -417,6 +417,35 @@ def test_lint_artifact_against_dropped_copy_prompt_fails(tmp_path): assert "copy-as-prompt" in r.stdout +def test_lint_artifact_unguarded_clipboard_fails(tmp_path): + bad = tmp_path / "unguarded.html" + bad.write_text( + _META_HEAD + + '
' + + "" + + "" + ) + r = _run("scripts/lint-artifact.mjs", str(bad)) + assert r.returncode == 1 + assert "writeClipboard" in r.stdout or "guard" in r.stdout.lower() + + +def test_lint_artifact_writeClipboard_helper_passes(tmp_path): + ok = tmp_path / "helper.html" + ok.write_text( + _META_HEAD + + '
' + + "" + ) + r = _run("scripts/lint-artifact.mjs", str(ok)) + assert r.returncode == 0, r.stdout + r.stderr + + def test_lint_artifact_against_dropped_script_fails(tmp_path): src = (REPO / "examples" / "dashboard.html").read_text() bad = tmp_path / "dropped-script.html"