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
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:
@@ -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) --------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user