'); 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(''))->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('')) ->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( 'Zugangsdaten' ); // 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( 'Zugangsdaten' ); 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('//s', $source, $matches); foreach ($matches[0] as $element) { $withoutIconSlot = preg_replace('/.*?<\/x-slot:icon>/s', '', $element); if (str_contains((string) $withoutIconSlot, 'getPathname()); } } } expect($offenders)->toBe([]); });