Share one view class rule between the stylesheet tests
Plan step 36 review. The brief and step 34 let a package view write the
md-* text classes beside the three interaction classes, but both
stylesheet tests allowed only the latter, and each kept its own copy of
the check. tests/Support/ViewClasses.php is now the one rule: the
interaction classes, the text classes read from text.css, and a caller's
class handed on whole. It also closes the holes the copies left: a
single-quoted :class, an echo mixed into a class list, a hint-, icon- or
box-class literal, a 'class' => entry that is not a plain literal, and
->class('…') as a string.
InputStylesheetsTest's exemptions narrow too: the autofill transition is
skipped in field.css alone; an orientation query may name any px height
but never a width off a breakpoint; a box-shadow must be none, an
elevation level, or a 0 0 0 ring in a colour role.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
d46b07c13c
commit
a3eef657cc
@@ -2,10 +2,12 @@
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses;
|
||||
|
||||
/**
|
||||
* The actions and communication components, rewritten without Tailwind (plan step 36): each view
|
||||
* renders `data-md-*` attributes and no class list of its own, and each has a stylesheet in
|
||||
* renders `data-md-*` attributes and no class list of its own beyond the interaction and text
|
||||
* classes (tests/Support/ViewClasses.php), and each has a stylesheet in
|
||||
* `material.components` that imports the stylesheets of the components its view renders, writes
|
||||
* its values from the tokens and its breakpoints as px range queries, and is imported from the
|
||||
* "Actions and communication" block of components.css.
|
||||
@@ -61,56 +63,8 @@ it('imports the stylesheet of every component its view renders', function (strin
|
||||
expect(array_values(array_diff($rendered, ComponentStylesheet::read($name)->imports())))->toBe([]);
|
||||
})->with('action components');
|
||||
|
||||
it('writes no class list into the view but the interaction classes', function (string $name) {
|
||||
$view = File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php");
|
||||
$allowed = ['md-state-layer', 'md-focus-ring', 'md-touch-target'];
|
||||
|
||||
// `@class()`, `Arr::toCssClasses` maps and Alpine's `:class` binding are never how a class
|
||||
// reaches the page here; the interaction classes and a caller's own class are the only source.
|
||||
expect($view)->not->toMatch('/@class\(|toCssClasses|(?:x-bind)?:class=/');
|
||||
|
||||
// A literal `class="…"` may hold only the interaction classes — any subset, any order, with a
|
||||
// simple `@if (…) … @endif` around one token — or a caller's value forwarded whole (`hint-class`,
|
||||
// `icon-class`: `class="{{ $…}}"`).
|
||||
preg_match_all('/(?<![\w:-])class=(["\'])(.*?)\1/s', $view, $classAttributes, PREG_SET_ORDER);
|
||||
|
||||
foreach ($classAttributes as [, , $content]) {
|
||||
if (preg_match('/^\{\{\s*\$/', trim($content)) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stripped = (string) preg_replace('/\{\{.*?\}\}|@if\s*\([^()]*\)|@unless\s*\([^()]*\)|@else|@endif|@endunless/s', ' ', $content);
|
||||
$tokens = array_values(array_filter(preg_split('/\s+/', trim($stripped))));
|
||||
|
||||
expect(array_diff($tokens, $allowed))->toBe([], "{$name}.blade.php writes class=\"{$content}\", not only the interaction classes");
|
||||
}
|
||||
|
||||
// `$attributes->class([…])` merges the interaction classes with the caller's own, per the
|
||||
// brief: "merges through $attributes->class() only where the root takes the caller's class".
|
||||
// Only the array's own items are checked — a condition may nest its own brackets
|
||||
// (`in_array($size, ['xs', 'sm'], true)`), which are no class list of the view's writing.
|
||||
preg_match_all('/->class\(\[(.*?)\]\)/s', $view, $classCalls, PREG_SET_ORDER);
|
||||
|
||||
foreach ($classCalls as [, $args]) {
|
||||
$depth = 0;
|
||||
|
||||
for ($i = 0, $length = strlen($args); $i < $length; $i++) {
|
||||
$char = $args[$i];
|
||||
|
||||
if ($char === '[' || $char === '(') {
|
||||
$depth++;
|
||||
} elseif ($char === ']' || $char === ')') {
|
||||
$depth--;
|
||||
} elseif ($depth === 0 && $char === "'") {
|
||||
$end = strpos($args, "'", $i + 1);
|
||||
$item = substr($args, $i + 1, $end - $i - 1);
|
||||
|
||||
expect(in_array($item, $allowed, true))->toBeTrue("{$name}.blade.php: ->class() writes [{$item}], not an interaction class");
|
||||
|
||||
$i = $end;
|
||||
}
|
||||
}
|
||||
}
|
||||
it('writes no class list into the view but the interaction and text classes', function (string $name) {
|
||||
expect(ViewClasses::violations(File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php")))->toBe([]);
|
||||
})->with('action components');
|
||||
|
||||
it('takes its values from the tokens and its breakpoints in px', function (string $name) {
|
||||
|
||||
Reference in New Issue
Block a user