Pin every written-out copy of the rail's collapsed conditions
Plan step 36, navigation review. The Tailwind-era rail-collapsed: variant is now plain selectors in 26 rules, each repeated in five window bands, across navigation-rail.css, navigation-rail-item.css, navigation-rail-section.css and layout/scaffold.css; the tests pinned a few of them. A new test walks every rule whose :where() names the rail and fails when a copy drifts from the seven conditions for its band, or when a rule is drawn collapsed in some bands but not all five. ComponentStylesheet gains rules() for it. 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
cbd058f948
commit
f31acf82d7
@@ -228,3 +228,70 @@ it('draws the item\'s own state layer split between the item and its indicator',
|
||||
->and($item->declarations('[data-md-navigation-rail-item][data-md-navigation-rail-item] [data-md-navigation-indicator]::before'))
|
||||
->toBe(['background-color' => 'var(--md-sys-color-on-secondary-container)', 'opacity' => 'var(--navigation-indicator-layer)']);
|
||||
});
|
||||
|
||||
/**
|
||||
* Plain CSS has no variant to name "drawn collapsed" once, so every rule with a collapsed shape
|
||||
* writes the conditions out: navigation-rail.css, navigation-rail-item.css,
|
||||
* navigation-rail-section.css, and layout/scaffold.css for the actions row inside the rail's footer.
|
||||
* This walks every copy in those files — not one pinned rule — and fails if any drifts from the
|
||||
* seven conditions the Tailwind-era `rail-collapsed:` variant had, or if a rule has its collapsed
|
||||
* shape in some window bands but not in all five.
|
||||
*/
|
||||
dataset('stylesheets that draw a collapsed rail', [
|
||||
'navigation-rail',
|
||||
'navigation-rail-item',
|
||||
'navigation-rail-section',
|
||||
'../layout/scaffold',
|
||||
]);
|
||||
|
||||
it('writes "collapsed" with the same seven conditions in every copy, in every band', function (string $name) {
|
||||
$closed = ':not([data-md-open])';
|
||||
$bands = [
|
||||
'always' => ["[data-md-navigation-rail='collapsed']", "[data-rail='collapsed'] [data-md-navigation-rail='collapsible']{$closed}", "[data-md-navigation-rail='modal']{$closed}"],
|
||||
'@media (width < 600px)' => ["[data-md-navigation-rail='collapsible']{$closed}"],
|
||||
'@media (width < 840px)' => ["[data-md-navigation-rail='adaptive']{$closed}"],
|
||||
'@media (840px <= width < 1200px)' => [":is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']{$closed}"],
|
||||
'@media (width >= 1200px)' => ["[data-rail='collapsed'] [data-md-navigation-rail='adaptive']{$closed}"],
|
||||
];
|
||||
|
||||
// A condition matches the rail itself and everything in it; the scaffold's actions row can only
|
||||
// ever be inside a rail, so its copy keeps the descendant half alone.
|
||||
$both = fn (array $conditions): array => array_merge(...array_map(fn (string $condition): array => [$condition, "{$condition} *"], $conditions));
|
||||
$inside = fn (array $conditions): array => array_map(fn (string $condition): string => "{$condition} *", $conditions);
|
||||
|
||||
$groups = [];
|
||||
|
||||
foreach (ComponentStylesheet::read($name)->rules() as $rule) {
|
||||
preg_match_all('/:where\(((?:[^()]++|\((?1)\))*)\)/', $rule['selector'], $wheres, PREG_SET_ORDER);
|
||||
$wheres = array_filter($wheres, fn (array $where): bool => str_contains($where[1], 'data-md-navigation-rail='));
|
||||
|
||||
if ($wheres === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$media = array_values(array_filter($rule['at'], fn (string $at): bool => str_starts_with($at, '@media')));
|
||||
$band = match ($media) {
|
||||
[] => 'always',
|
||||
['@media (width >= 840px)', '@media (width < 1200px)'] => '@media (840px <= width < 1200px)',
|
||||
default => implode(' › ', $media),
|
||||
};
|
||||
|
||||
expect(array_key_exists($band, $bands))->toBeTrue("{$name}.css: `{$rule['selector']}` sits in {$band}, which is no band of the rail's");
|
||||
|
||||
foreach ($wheres as [$where, $conditions]) {
|
||||
$written = array_map(trim(...), preg_split('/,(?![^()]*\))/', $conditions));
|
||||
|
||||
expect(in_array($written, [$both($bands[$band]), $inside($bands[$band])], true))
|
||||
->toBeTrue("{$name}.css: `{$where}` in {$band} is not the rail's collapsed conditions for that band");
|
||||
}
|
||||
|
||||
$key = str_replace(array_column($wheres, 0), '', $rule['selector']).' '.json_encode($rule['declarations']);
|
||||
$groups[$key][] = $band;
|
||||
}
|
||||
|
||||
expect($groups)->not->toBeEmpty();
|
||||
|
||||
foreach ($groups as $key => $found) {
|
||||
expect(array_values(array_unique($found)))->toEqualCanonicalizing(array_keys($bands), "{$name}.css: {$key} is drawn collapsed in some bands only");
|
||||
}
|
||||
})->with('stylesheets that draw a collapsed rail');
|
||||
|
||||
@@ -117,6 +117,17 @@ final class ComponentStylesheet
|
||||
return $declarations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every rule, nesting resolved, in source order: for a test that has to walk all of them rather
|
||||
* than name one.
|
||||
*
|
||||
* @return list<array{selector: string, at: list<string>, declarations: array<string, string>}>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return $this->rules;
|
||||
}
|
||||
|
||||
public function has(string $selector, array $at = []): bool
|
||||
{
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user