CluPilotCloud/tests/Feature/IconLayoutTest.php

104 lines
4.0 KiB
PHP

<?php
use Illuminate\Support\Facades\Blade;
/**
* An icon sits BESIDE its label, at the size the call site asked for.
*
* Both halves of that sentence were broken at once in the console sidebar: the
* "Zugangsdaten" entry rendered the lock above the word instead of next to it,
* and at 20px instead of the 16px it was written as. The two causes are
* independent, so they are tested independently.
*/
it('renders an icon at the size the call site asked for', function () {
// Not a style preference: `.size-4` and `.size-5` have the same
// specificity, so the one Tailwind emits LATER wins — and it emits size-4
// first. A component that always merges its own size therefore overrides
// every caller that picked a smaller one.
$html = Blade::render('<x-ui.icon name="lock" class="size-4" />');
expect($html)->toContain('size-4')->not->toContain('size-5');
});
it('falls back to its own size when the call site names none', function () {
expect(Blade::render('<x-ui.icon name="lock" />'))->toContain('size-5');
});
it('keeps an icon on the line it was written on', function () {
// Tailwind's preflight makes every svg display:block. Inside an inline
// parent that pushes the following text onto a second line, which is how a
// one-line navigation entry became two lines tall.
expect(Blade::render('<x-ui.icon name="lock" />'))
->toContain('inline-block')
->toContain('shrink-0');
});
it('keeps a navigation entry on one line whichever slot the icon uses', function () {
// Blade::render leaves a slot's output buffer open when a component is
// rendered outside a request, which PHPUnit reports as a risky test. The
// markup it returns is complete; only the buffer needs tidying.
$render = function (string $template): string {
$level = ob_get_level();
try {
return Blade::render($template);
} finally {
while (ob_get_level() > $level) {
ob_end_clean();
}
}
};
$viaSlot = $render(
'<x-ui.nav-item href="/x"><x-slot:icon><x-ui.icon name="lock" /></x-slot:icon>Zugangsdaten</x-ui.nav-item>'
);
// The mistake that caused the report: the icon handed to the DEFAULT slot
// rather than the icon slot. The layout must not depend on which was used.
$viaDefaultSlot = $render(
'<x-ui.nav-item href="/x"><x-ui.icon name="lock" />Zugangsdaten</x-ui.nav-item>'
);
foreach ([$viaSlot, $viaDefaultSlot] as $html) {
expect($html)
->toContain('items-center')
->toContain('Zugangsdaten')
// The label wrapper is a flex row of its own, so a stray icon lands
// next to the text rather than above it.
->toContain('flex min-w-0 items-center gap-3');
}
});
it('puts navigation icons in the icon slot everywhere', function () {
// Belt to the component's braces. The component now survives an icon in the
// wrong slot, but the wrong slot also skips the shrink-0 wrapper and reads
// as an accident to the next person editing the file.
$offenders = [];
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(resource_path('views'), FilesystemIterator::SKIP_DOTS)
);
foreach ($files as $file) {
if (! str_ends_with($file->getFilename(), '.blade.php')) {
continue;
}
$source = (string) file_get_contents($file->getPathname());
// Each nav-item element, from its opening tag to its closing one.
preg_match_all('/<x-ui\.nav-item\b.*?<\/x-ui\.nav-item>/s', $source, $matches);
foreach ($matches[0] as $element) {
$withoutIconSlot = preg_replace('/<x-slot:icon>.*?<\/x-slot:icon>/s', '', $element);
if (str_contains((string) $withoutIconSlot, '<x-ui.icon')) {
$offenders[] = str_replace(resource_path('views').'/', '', $file->getPathname());
}
}
}
expect($offenders)->toBe([]);
});