Plan step 35: <x-app-shell> becomes <x-scaffold>, M3's and Compose's name for the structure of bars, rails and panes, with no alias; every package view, the showcase, the tests, the skills, README and UPGRADE follow. Its markup and Tailwind classes stay for the navigation rewrite. The new fab slot places the FAB as Compose's Scaffold does - bottom-end, 16px from the edges below medium and 24px from it, clear of the navigation bar and of a snackbar on screen (resources/css/layout/scaffold.css) - and the content region tells a pane inside that the margin is already drawn. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
153 lines
5.5 KiB
PHP
153 lines
5.5 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use NoNameWeb\LivewireMaterial\Showcase\Sections;
|
|
use NoNameWeb\LivewireMaterial\Support\SvgFile;
|
|
|
|
/**
|
|
* Reboot the application with the showcase switched on or off. The routes are mounted
|
|
* while the provider boots, so the flag has to be in the environment before that.
|
|
*/
|
|
function rebootWithShowcase(bool $enabled): void
|
|
{
|
|
$value = $enabled ? 'true' : 'false';
|
|
|
|
putenv("MATERIAL_SHOWCASE={$value}");
|
|
$_ENV['MATERIAL_SHOWCASE'] = $_SERVER['MATERIAL_SHOWCASE'] = $value;
|
|
|
|
test()->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);
|
|
|
|
$this->withoutVite()
|
|
->get("/material/{$key}")
|
|
->assertOk()
|
|
->assertSee("<title>{$section['title']} · Livewire Material</title>", false)
|
|
->assertSee('id="'.$key.'"', false);
|
|
}
|
|
|
|
$this->get('/material/not-a-section')->assertNotFound();
|
|
});
|
|
|
|
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('<x-datepicker>', '<x-button>', '<x-scaffold>')
|
|
->and($index->firstWhere('title', '<x-datepicker>')['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('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, '<path'));
|
|
};
|
|
|
|
$standard = $this->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([]);
|
|
});
|