feat(coverage): class_references + scan_archive — each template-bearing file's class tokens (static class=/className=, Vue :class object/array/ternary, React className={…}, Svelte class:x) read in the same tar walk as definitions; the CSS consumer map's extractor (milestone 302 step 1, #2934)
CI & Build / Plugin hooks (push) Failing after 1s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 12s
CI & Build / integration (push) Successful in 29s
CI & Build / Python tests (push) Successful in 1m2s
CI & Build / Build & push image (push) Successful in 29s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 13:57:13 -04:00
co-authored by Claude Fable 5
parent 31383bcebe
commit dffbf43d84
2 changed files with 184 additions and 2 deletions
+77
View File
@@ -16,7 +16,10 @@ import pytest
import pytest_asyncio
from scribe.services.coverage import (
ArchiveScan,
class_references,
coverage_line,
scan_archive,
extract_shapes,
largest_gaps,
scannable,
@@ -118,6 +121,80 @@ def test_shapes_from_archive_strips_the_wrapper_and_gates_files():
assert shapes_from_archive(_tarball(TREE)) == TREE_SHAPES
# --- unit: template class references — the CSS consumer map (milestone 302) --
def test_class_references_reads_vue_static_and_dynamic_forms_only():
"""A template's class attributes name the classes it consumes: the static
`class=`, the Vue dynamic object/array/ternary forms (string literals and
bare object keys), never a selector in <style>, a `class Foo` in
<script>, a `querySelector('.x')`, or a look-alike attribute."""
vue = (
"<template>\n"
' <div class="card card--wide" :class="{ active: isOpen, \'is-error\': err }">\n'
' <span :class="[ \'pill\', cond ? \'pill-on\' : \'pill-off\', other ]" />\n'
' <p class="card" v-bind:class="open ? openCls : \'closed\'">{{ t }}</p>\n'
' <i data-class="nope" headerClass="nope2" />\n'
" </div>\n"
"</template>\n"
'<script setup lang="ts">\n'
"class Foo {}\n"
"const el = document.querySelector('.zap')\n"
"</script>\n"
"<style scoped>\n"
".card { color: red; }\n"
".zap { color: blue; }\n"
"</style>\n"
)
assert class_references("a/B.vue", vue) == {
"card": 2, "card--wide": 1, "active": 1, "is-error": 1,
"pill": 1, "pill-on": 1, "pill-off": 1, "closed": 1,
}
def test_class_references_reads_react_svelte_and_server_templates():
tsx = (
"export function X({ on }: { on: boolean }) {\n"
' return <button className="btn btn-primary" data-x="y">\n'
" <i className={on ? 'tab tab-on' : 'tab'} />\n"
" <b className={`chip ${on ? 'chip-on' : ''} chip-sm`} />\n"
" <u className={cn({ pill: on, 'pill-off': !on })} />\n"
" </button>\n"
"}\n"
)
# A template literal's static text counts; its `${…}` hole is unknowable
# (chip-on sits inside the hole's own ternary and is NOT claimed).
assert class_references("a/x.tsx", tsx) == {
"btn": 1, "btn-primary": 1, "tab": 2, "tab-on": 1,
"chip": 1, "chip-sm": 1, "pill": 1, "pill-off": 1,
}
assert class_references("a/y.svelte", '<div class:active={on} class="row">') == {
"row": 1, "active": 1,
}
# A server-side interpolation contributes no token; a literal class inside
# a template conditional still does.
html = '<div class="row {{ cls }} col-2 {% if x %}y{% endif %}">'
assert class_references("t/p.html", html) == {"row": 1, "col-2": 1, "y": 1}
# Not a template-bearing file: nothing, however it reads.
assert class_references("a/z.py", 'html = \'<div class="row">\'') == {}
def test_scan_archive_returns_definitions_and_references_from_one_walk():
tree = dict(TREE)
tree["web/Card.vue"] = (
b'<template><div class="btn card">x</div></template>\n'
b"<style scoped>\n.card {\n color: red;\n}\n</style>\n"
)
scan = scan_archive(_tarball(tree))
assert isinstance(scan, ArchiveScan)
assert [(d.path, d.kind, d.name) for d in scan.definitions] == TREE_SHAPES + [
("web/Card.vue", "css", "card"),
]
# Only files whose markup names a class appear; the .py/.css files don't.
assert scan.references == {"web/Card.vue": {"btn": 1, "card": 1}}
assert shapes_from_archive(_tarball(tree)) == [(d.path, d.kind, d.name) for d in scan.definitions]
# --- unit: the covering predicate (lives with the ledger since #2788) --------