refreshApplication(); } afterEach(function () { rebootWithShowcase(true); }); it('mounts the showcase when enabled', function () { $this->withoutVite() ->get('/material') ->assertOk() ->assertSee('Livewire Material'); }); it('gives every section a page of its own, linked from the overview and the rail', function () { $overview = $this->withoutVite()->get('/material')->assertOk(); foreach (Sections::all() as $key => $section) { $overview->assertSee(route('livewire-material.section', $key), false); $html = $this->withoutVite() ->get("/material/{$key}") ->assertOk() ->assertSee("{$section['title']} · Livewire Material", false) ->assertSee('id="'.$key.'"', false) ->getContent(); // The page's

names the section; the section's own

is for a screen reader only, // so the title is not drawn twice. preg_match_all('/]*>/', $html, $headings); expect($headings[0][0] ?? null)->toBe('

', "{$key}'s own

is drawn"); } $this->get('/material/not-a-section')->assertNotFound(); }); it('gives each canonical layout a page of its own, linked from the Layout section', function () { foreach (['list-detail' => 'List-detail', 'supporting-pane' => 'Supporting pane', 'feed' => 'Feed'] as $page => $title) { test()->withoutVite()->get("/material/layout/{$page}") ->assertOk() ->assertSee("{$title} · Layout · Livewire Material", false) ->assertSee('id="example-'.$page.'"', false) // Its own navigation reaches the other three pages, including itself. ->assertSee(route('livewire-material.layout', 'list-detail', false), false) ->assertSee(route('livewire-material.layout', 'supporting-pane', false), false) ->assertSee(route('livewire-material.layout', 'feed', false), false); } test()->withoutVite()->get('/material/layout') ->assertOk() ->assertSee(route('livewire-material.layout', 'list-detail', false), false) ->assertSee(route('livewire-material.layout', 'supporting-pane', false), false) ->assertSee(route('livewire-material.layout', 'feed', false), false); }); it('indexes every section, example and component for the search, each linking to a place that exists', function () { $index = collect($this->getJson('/material/search.json')->assertOk()->json()); expect($index->where('kind', 'Section')->pluck('title')->all()) ->toBe(collect(Sections::all())->pluck('title')->all()) ->and($index->where('kind', 'Component')->pluck('title')->all()) ->toContain('', '', '') ->and($index->firstWhere('title', '')['url'])->toBe('/material/pickers'); $index->where('kind', 'Example') ->groupBy(fn (array $entry): string => strtok($entry['url'], '#')) ->each(function ($examples, string $page): void { $html = $this->withoutVite()->get($page)->assertOk()->getContent(); foreach ($examples as $example) { expect($html)->toContain('id="'.substr($example['url'], strpos($example['url'], '#') + 1).'"'); } }); }); it('indexes exactly the examples each page renders, so an example the index misses fails here', function () { $index = collect($this->getJson('/material/search.json')->assertOk()->json())->where('kind', 'Example'); $pages = collect(Sections::all())->keys()->map(fn (string $key): string => route('livewire-material.section', $key, false)) ->merge(collect(Sections::layoutPages())->except('layout')->pluck('url')); foreach ($pages as $page) { $html = $this->withoutVite()->get($page)->assertOk()->getContent(); preg_match_all('/<[a-z]+\b[^>]*\bdata-md-showcase-example\b[^>]*>/', $html, $tags); $rendered = collect($tags[0]) ->map(fn (string $tag): ?string => preg_match('/\bid="([^"]+)"/', $tag, $id) === 1 ? $id[1] : null) ->filter() ->sort() ->values() ->all(); $indexed = $index->filter(fn (array $entry): bool => strtok($entry['url'], '#') === $page) ->map(fn (array $entry): string => substr($entry['url'], strpos($entry['url'], '#') + 1)) ->sort() ->values() ->all(); expect($indexed)->toBe($rendered, "{$page}: the search index and the page disagree on its examples"); } }); it('writes every example as an application writes it', function () { $files = collect(File::glob(__DIR__.'/../../resources/views/showcase/sections/*.blade.php')) ->merge(File::glob(__DIR__.'/../../resources/views/showcase/layout/*.blade.php')); $examples = $files->flatMap(function (string $file): array { preg_match_all("/<<<'BLADE'\n(.*?)^\s*BLADE[,;]/sm", File::get($file), $matches); return array_map(fn (string $code): array => [basename($file), $code], $matches[1]); }); expect($examples)->not->toBeEmpty(); foreach ($examples as [$file, $code]) { // The example component rewrites unprefixed tags to a configured prefix; a namespaced tag // would show an application a form it does not write, and escape that rewrite. expect(str_contains($code, 'x-livewire-material::'))->toBeFalse("{$file} writes a namespaced tag in an example"); // Nor a hook only showcase.css draws, which would do nothing in the application's page. expect(str_contains($code, 'data-md-showcase'))->toBeFalse("{$file} writes a showcase hook in an example"); // A class is a text or interaction class, or one of the application's own that // showcase.css stands in for, each named in its header. preg_match_all('/class="([^"]*)"/', $code, $classes); foreach (preg_split('/\s+/', implode(' ', $classes[1]), -1, PREG_SPLIT_NO_EMPTY) as $class) { expect(str_starts_with($class, 'md-') || in_array($class, ['showcase-w-narrow', 'showcase-ink-tertiary', 'showcase-ink-secondary'], true)) ->toBeTrue("{$file} writes the class `{$class}` in an example"); } } }); it('does not mount the showcase when disabled', function () { rebootWithShowcase(false); $this->get('/material')->assertNotFound(); }); it('compiles every package view with the showcase off, as view:cache does in production', function () { rebootWithShowcase(false); $compiled = sys_get_temp_dir().'/livewire-material-view-cache-'.uniqid(); config(['view.compiled' => $compiled]); File::ensureDirectoryExists($compiled); try { $this->artisan('view:cache')->assertSuccessful(); } finally { File::deleteDirectory($compiled); } }); it('serves a symbol for the icon search', function () { $this->get('/material/symbols/filled/favorite.svg') ->assertOk() ->assertHeader('Content-Type', 'image/svg+xml') ->assertSee('viewBox="0 -960 960 960"', false); }); it('serves the 24 cut by default and the 20 cut on request', function () { $geometry = function (string $folder): string { $svg = trim((string) file_get_contents(__DIR__."/../../resources/svg/symbols/{$folder}/home.svg")); return substr($svg, (int) strpos($svg, 'get('/material/symbols/outlined/home.svg')->assertOk()->getContent(); $dense = $this->get('/material/symbols/outlined/home.svg?optical=20')->assertOk()->getContent(); expect($dense)->not->toBe($standard) ->and($standard)->toContain($geometry('outlined')) ->and($dense)->toContain($geometry('outlined-20')) ->and($this->get('/material/symbols/outlined/home.svg?optical=40')->getContent())->toBe($standard); }); it('answers 404 for a symbol or style that does not exist', function () { $this->get('/material/symbols/outlined/not_a_symbol_at_all.svg')->assertNotFound(); $this->get('/material/symbols/sharp/favorite.svg')->assertNotFound(); }); it('lists the symbol names for the icon search', function () { $this->getJson('/material/symbols.json') ->assertOk() ->assertJsonFragment(['calendar_month']) ->assertJsonCount(count(SvgFile::symbolNames())); }); it('previews an error page through the exception handler', function () { $this->withoutVite() ->get('/material/errors/404') ->assertNotFound() ->assertSee('Page not found'); $this->get('/material/errors/418')->assertNotFound(); }); it('previews a sample mail in the package theme', function () { $this->get('/material/mail') ->assertOk() ->assertSee('class="button button-primary"', false) ->assertSee('border-radius: 9999px', false) ->assertSee('Unsubscribe'); }); it('shows every component somewhere in the showcase', function () { $showcase = collect(File::allFiles(__DIR__.'/../../resources/views/showcase')) ->map(fn (SplFileInfo $file): string => $file->getContents()) ->implode("\n"); $absent = collect(File::files(__DIR__.'/../../resources/views/components')) ->map(fn (SplFileInfo $file): string => $file->getBasename('.blade.php')) ->reject(fn (string $name): bool => preg_match('/(?:<|<)x-(?:livewire-material::)?'.preg_quote($name, '/').'(?:[\s\/>]|>)/', $showcase) === 1) ->values() ->all(); expect($absent)->toBe([]); });