Plan step 23, containment.md § Missing ("Lists: leading video slot").
`<x-list-item video="…">` (or `<x-slot:video>`) draws M3's leading media
in its landscape sizes: 100×56px in a two-line item, 114×64px in a
three-line one (ListTokens' LeadingVideoSmall 56×100dp and
LeadingVideoLarge 64×114dp), with the small corner the leading image
takes. The tallest element sets an item's height, so a video lifts the
item to at least the 72px two-line row — 56 + 2×8 is 72 exactly, and
64 + 2×12 is 88.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
113 lines
6.4 KiB
PHP
113 lines
6.4 KiB
PHP
<?php
|
|
|
|
it('draws a plain list, with dividers on request', function () {
|
|
expect((string) $this->blade('<x-list label="Files" dividers><x-list-item title="a.zip" /></x-list>'))
|
|
->toContain('role="list"')
|
|
->toContain('aria-label="Files"')
|
|
->toContain('data-list="plain"')
|
|
->toContain('data-dividers')
|
|
->not->toContain('divide-y')
|
|
->toContain('role="listitem"')
|
|
->and((string) $this->blade('<x-list><x-list-item title="a.zip" /></x-list>'))->not->toContain('data-dividers')
|
|
->and((string) $this->blade('<x-list segmented dividers><x-list-item title="a.zip" /></x-list>'))->not->toContain('data-dividers');
|
|
});
|
|
|
|
it('draws M3 Expressive\'s segmented list', function () {
|
|
expect((string) $this->blade('<x-list segmented><x-list-item title="a.zip" /></x-list>'))
|
|
->toContain('data-list="segmented"')
|
|
->toContain('gap-0.5');
|
|
});
|
|
|
|
it('grows an item from one to three lines, top-aligning the tallest', function () {
|
|
expect((string) $this->blade('<x-list-item title="a.zip" />'))->toContain('items-center')->toContain('min-h-14 py-2')
|
|
->and((string) $this->blade('<x-list-item title="a.zip" description="248 MB" />'))->toContain('items-center')->toContain('min-h-18 py-2')->toContain('248 MB')
|
|
->and((string) $this->blade('<x-list-item title="a.zip" overline="Protected" description="248 MB" />'))
|
|
->toContain('items-start')
|
|
->toContain('min-h-22 py-3')
|
|
->toContain('Protected')
|
|
->not->toContain('items-center');
|
|
});
|
|
|
|
it('leaves 16px between the item and what leads or trails it', function () {
|
|
expect((string) $this->blade('<x-list-item title="a.zip" icon="folder_zip" />'))->toContain('flex gap-4 px-4');
|
|
});
|
|
|
|
it('draws the expressive icon size in a segmented list', function () {
|
|
expect((string) $this->blade('<x-list segmented><x-list-item title="a" icon="settings" icon-right="chevron_right" /></x-list>'))
|
|
->toContain('size-5')
|
|
->not->toContain('size-6')
|
|
->and((string) $this->blade('<x-list><x-list-item title="a" icon="settings" /></x-list>'))->toContain('size-6');
|
|
});
|
|
|
|
it('leads with an icon, an avatar or an image, and trails with text or an icon', function () {
|
|
expect((string) $this->blade('<x-list-item title="a" icon="folder_zip" trailing="3 days" icon-right="chevron_right" />'))
|
|
->toContain('3 days')
|
|
->and(substr_count((string) $this->blade('<x-list-item title="a" icon="folder_zip" icon-right="chevron_right" />'), '<svg'))->toBe(2)
|
|
->and((string) $this->blade('<x-list-item title="Anna" avatar="AM" />'))->toContain('bg-primary-container')->toContain('>AM</span>')
|
|
->and((string) $this->blade('<x-list-item title="Anna" avatar="/anna.jpg" />'))->toContain('<img src="/anna.jpg"')
|
|
->and((string) $this->blade('<x-list-item title="Photo" image="/p.jpg" />'))->toContain('size-14');
|
|
});
|
|
|
|
it('makes a linked item a row that opens from anywhere', function () {
|
|
expect((string) $this->blade('<x-list-item title="Settings" link="/settings" />'))
|
|
->toContain('data-list-row')
|
|
->toContain('href="/settings"')
|
|
->toContain('data-list-open')
|
|
->toContain('wire:navigate');
|
|
});
|
|
|
|
it('is a list box of options when it is one a person chooses from', function () {
|
|
expect((string) $this->blade('<x-list selectable label="Recipients"><x-list-item title="Anna" :selected="true" /><x-list-item title="Ben" /></x-list>'))
|
|
->toContain('role="listbox"')
|
|
->toMatch('/role="option"\s+aria-selected="true"/')
|
|
->toMatch('/role="option"\s+aria-selected="false"/')
|
|
->not->toContain('aria-multiselectable')
|
|
->and((string) $this->blade('<x-list selection="multi"><x-list-item title="Anna" /></x-list>'))
|
|
->toContain('role="listbox"')
|
|
->toContain('aria-multiselectable="true"')
|
|
->and((string) $this->blade('<x-list><x-list-item title="Anna" :selected="true" /></x-list>'))
|
|
->toContain('role="list"')
|
|
->toMatch('/role="listitem"\s+aria-current="true"/')
|
|
->not->toContain('aria-selected');
|
|
});
|
|
|
|
it('draws a second cue on a selected option, which icon-right replaces', function () {
|
|
expect(substr_count((string) $this->blade('<x-list selectable><x-list-item title="Anna" :selected="true" /></x-list>'), '<svg'))->toBe(1)
|
|
->and(substr_count((string) $this->blade('<x-list selectable><x-list-item title="Anna" :selected="true" icon-right="chevron_right" /></x-list>'), '<svg'))->toBe(1)
|
|
->and(substr_count((string) $this->blade('<x-list selectable><x-list-item title="Anna" /></x-list>'), '<svg'))->toBe(0)
|
|
->and(substr_count((string) $this->blade('<x-list><x-list-item title="Anna" :selected="true" /></x-list>'), '<svg'))->toBe(0);
|
|
});
|
|
|
|
it('leaves a disabled item out of the keyboard and announces it disabled', function () {
|
|
expect((string) $this->blade('<x-list-item title="Settings" link="/settings" icon="settings" disabled />'))
|
|
->toContain('aria-disabled="true"')
|
|
->toContain('pointer-events-none text-on-surface/38')
|
|
->not->toContain('href="/settings"')
|
|
->not->toContain('data-list-open')
|
|
->not->toContain('data-list-row');
|
|
});
|
|
|
|
it('marks a selected item and takes controls in its slots', function () {
|
|
expect((string) $this->blade('<x-list-item title="Anna" :selected="true"><x-slot:leading><input type="checkbox"></x-slot:leading><x-slot:end><button>⋮</button></x-slot:end></x-list-item>'))
|
|
->toContain('data-selected')
|
|
->toContain('<input type="checkbox">')
|
|
->toContain('<button>⋮</button>');
|
|
});
|
|
|
|
it('leads with M3\'s video, in the size the item\'s height calls for', function () {
|
|
expect((string) $this->blade('<x-list-item title="Sunrise" video="/poster.jpg" />'))
|
|
->toContain('h-14 w-25')
|
|
->toContain('rounded-corner-sm')
|
|
->toContain('<img src="/poster.jpg" alt="" />')
|
|
->toContain('min-h-18 py-2')
|
|
->and((string) $this->blade('<x-list-item title="Sunrise" overline="Draft" description="7:48" video="/poster.jpg" />'))
|
|
->toContain('h-16 w-28.5')
|
|
->toContain('min-h-22 py-3')
|
|
->and((string) $this->blade('<x-list-item title="Sunrise"><x-slot:video><video src="/clip.mp4"></video></x-slot:video></x-list-item>'))
|
|
->toContain('<video src="/clip.mp4"></video>')
|
|
->toContain('h-14 w-25')
|
|
->and((string) $this->blade('<x-list-item title="Sunrise" />'))
|
|
->not->toContain('h-14 w-25')
|
|
->toContain('min-h-14 py-2');
|
|
});
|