<x-card> always titled itself with an <h3>, so a card straight under a page's <h1> skipped a level, which SealShare's download page did with no way round it from the application. `heading` takes h2 to h6, or div or p for a title that is not a heading, as <x-pane> and <x-app-bar> already do; h3 stays the default. The skill also says a card holds one subject, as M3 does (plan step 46). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
103 lines
5.8 KiB
PHP
103 lines
5.8 KiB
PHP
<?php
|
|
|
|
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
|
|
|
it('draws M3\'s three cards, each naming its variant for its own colour and per-state elevation', function (string $variant) {
|
|
$html = (string) $this->blade("<x-card variant=\"{$variant}\">Body</x-card>");
|
|
$css = ComponentStylesheet::read('card');
|
|
|
|
expect($html)->toContain("data-md-card=\"{$variant}\"")
|
|
->and($css->has("[data-md-card='{$variant}']"))->toBeTrue();
|
|
})->with(['filled', 'elevated', 'outlined']);
|
|
|
|
it('is filled unless it knows the variant', function () {
|
|
expect((string) $this->blade('<x-card variant="glass">Body</x-card>'))->toContain('data-md-card="filled"');
|
|
});
|
|
|
|
it('lays out a header, menu, media, body and actions', function () {
|
|
$html = (string) $this->blade(<<<'BLADE'
|
|
<x-card title="holiday-photos.zip" subtitle="248 MB" separator>
|
|
<x-slot:figure><img src="/x.png" alt=""></x-slot:figure>
|
|
<x-slot:menu><button>⋮</button></x-slot:menu>
|
|
Expires in 3 days.
|
|
<x-slot:actions><button>Copy link</button></x-slot:actions>
|
|
</x-card>
|
|
BLADE);
|
|
|
|
expect($html)
|
|
->toContain('data-md-card-figure')
|
|
->toContain('<img src="/x.png" alt="">')
|
|
->toContain('<h3 data-md-card-title>holiday-photos.zip</h3>')
|
|
->toContain('data-md-card-menu')
|
|
->toContain('248 MB')
|
|
->toContain('<button>⋮</button>')
|
|
->toContain('role="separator"')
|
|
->toContain('data-md-card-content')
|
|
->toContain('Expires in 3 days.')
|
|
->toContain('data-md-card-actions')
|
|
->toContain('<button>Copy link</button>')
|
|
->and(strpos($html, 'Expires'))->toBeLessThan(strpos($html, 'Copy link'));
|
|
});
|
|
|
|
it('passes row attributes through and renders the shared interaction classes for a row', function () {
|
|
$html = (string) $this->blade('<x-card data-md-list-row><a href="/s/1" data-md-list-open>Open</a></x-card>');
|
|
|
|
expect($html)
|
|
->toContain('data-md-list-row')
|
|
->toContain('data-md-list-open')
|
|
->toContain('class="md-state-layer md-focus-ring"')
|
|
// Any card can be carried, so each draws the shared layer, which card.css keeps off one
|
|
// that is neither a row nor dragged.
|
|
->and((string) $this->blade('<x-card title="Share">Body</x-card>'))
|
|
->toContain('class="md-state-layer"')
|
|
->not->toContain('md-focus-ring')
|
|
->and(ComponentStylesheet::read('card')->declarations('[data-md-card]:not([data-md-list-row], [data-md-dragged])::before'))
|
|
->toBe(['display' => 'none']);
|
|
});
|
|
|
|
it('makes a directly actionable card the one tab stop, named by its title', function () {
|
|
$html = (string) $this->blade('<x-card actionable title="holiday-photos.zip"><a href="/s/1" data-md-list-open tabindex="-1">Open</a></x-card>');
|
|
|
|
expect($html)
|
|
->toContain('data-md-list-row')
|
|
->toContain('data-md-list-actionable')
|
|
->toContain('tabindex="0"')
|
|
->toContain('role="button"')
|
|
->toContain('aria-label="holiday-photos.zip"')
|
|
->toContain('class="md-state-layer md-focus-ring"')
|
|
->and((string) $this->blade('<x-card actionable role="link" title="Share">Body</x-card>'))
|
|
->toContain('role="link"')
|
|
->and(substr_count((string) $this->blade('<x-card actionable role="link" title="Share">Body</x-card>'), 'role="link"'))->toBe(1)
|
|
->and((string) $this->blade('<x-card title="Share">Body</x-card>'))
|
|
->not->toContain('data-md-list-actionable')
|
|
->not->toContain('tabindex');
|
|
});
|
|
|
|
it('draws M3\'s per-state elevation and dragged state from the tokens, never a hand-made shadow', function () {
|
|
$css = ComponentStylesheet::read('card');
|
|
|
|
expect((string) $this->blade('<x-card data-md-dragged variant="elevated">Body</x-card>'))->toContain('data-md-dragged')
|
|
->and($css->declarations('[data-md-card][data-md-dragged]'))->toBe(['box-shadow' => 'var(--md-sys-elevation-3)'])
|
|
->and($css->declarations("[data-md-card='elevated'][data-md-dragged]"))->toBe(['box-shadow' => 'var(--md-sys-elevation-4)'])
|
|
->and($css->declarations("[data-md-card='elevated']"))->toHaveKey('box-shadow', 'var(--md-sys-elevation-1)')
|
|
->and($css->has('[data-md-card][data-md-list-row]:not([data-md-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover))', ['@media (hover: hover)']))->toBeTrue()
|
|
->and($css->declarations("[data-md-card='elevated'][data-md-list-row]:not([data-md-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover))", ['@media (hover: hover)']))->toBe(['box-shadow' => 'var(--md-sys-elevation-2)'])
|
|
->and($css->declarations('[data-md-card][data-md-list-row]:not([data-md-dragged]):hover:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover)::before', ['@media (hover: hover)']))->toBe(['opacity' => '0']);
|
|
});
|
|
|
|
it('rings the row from the card itself when actionable, or from a has() when its opener is focused', function () {
|
|
$css = ComponentStylesheet::read('card');
|
|
|
|
expect($css->declarations('[data-md-card][data-md-list-row]:has([data-md-list-open]:focus-visible)'))
|
|
->toBe(['outline' => '3px solid var(--md-sys-color-secondary)', 'outline-offset' => '2px'])
|
|
->and($css->declarations('[data-md-card][data-md-list-row] [data-md-list-open]:focus-visible'))
|
|
->toBe(['outline' => 'none']);
|
|
});
|
|
|
|
it('titles the card at the heading level the page\'s outline needs', function () {
|
|
expect((string) $this->blade('<x-card title="Shared files" heading="h2">Body</x-card>'))
|
|
->toContain('<h2 data-md-card-title>Shared files</h2>')
|
|
->and((string) $this->blade('<x-card title="Shared files" heading="span">Body</x-card>'))
|
|
->toContain('<h3 data-md-card-title>Shared files</h3>');
|
|
});
|