blade('')) ->toContain('role="list"') ->toContain('aria-label="Files"') ->toContain('data-md-list="plain"') ->toContain('data-md-dividers') ->toContain('role="listitem"') ->and((string) $this->blade(''))->not->toContain('data-md-dividers') ->and((string) $this->blade(''))->not->toContain('data-md-dividers'); }); it('draws M3 Expressive\'s segmented list', function () { expect((string) $this->blade('')) ->toContain('data-md-list="segmented"'); }); it('grows an item from one to three lines, top-aligning the tallest', function () { expect((string) $this->blade(''))->toContain('data-md-lines="1"') ->and((string) $this->blade(''))->toContain('data-md-lines="2"')->toContain('248 MB') ->and((string) $this->blade('')) ->toContain('data-md-lines="3"') ->toContain('Protected'); }); it('leaves 16px between the item and what leads or trails it', function () { $css = ComponentStylesheet::read('list-item'); expect($css->declarations('[data-md-list-item]')) ->toHaveKey('gap', 'var(--md-sys-measurement-space200)') ->toHaveKey('padding-inline', 'var(--md-sys-measurement-space200)'); }); it('draws the expressive icon size in a segmented list', function () { expect((string) $this->blade('')) ->toContain('style="--md-icon-size: 20px"') ->and((string) $this->blade('')) ->not->toContain('--md-icon-size: 20px'); }); it('leads with an icon, an avatar or an image, and trails with text or an icon', function () { expect((string) $this->blade('')) ->toContain('3 days') ->and(substr_count((string) $this->blade(''), 'toBe(2) ->and((string) $this->blade(''))->toContain('data-md-list-item-avatar')->toContain('>AM') ->and((string) $this->blade(''))->toContain('and((string) $this->blade(''))->toContain('data-md-list-item-image'); }); it('makes a linked item a row that opens from anywhere', function () { expect((string) $this->blade('')) ->toContain('data-md-list-row') ->toContain('href="/settings"') ->toContain('data-md-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('')) ->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('')) ->toContain('role="listbox"') ->toContain('aria-multiselectable="true"') ->and((string) $this->blade('')) ->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(''), 'toBe(1) ->and(substr_count((string) $this->blade(''), 'toBe(1) ->and(substr_count((string) $this->blade(''), 'toBe(0) ->and(substr_count((string) $this->blade(''), 'toBe(0); }); it('leaves a disabled item out of the keyboard and announces it disabled', function () { expect((string) $this->blade('')) ->toContain('aria-disabled="true"') ->not->toContain('href="/settings"') ->not->toContain('data-md-list-open') ->not->toContain('data-md-list-row'); }); it('marks a selected item and takes controls in its slots', function () { expect((string) $this->blade('')) ->toContain('data-md-selected') ->toContain('data-md-list-item-leading') ->toContain('') ->toContain('data-md-list-item-end') ->toContain(''); }); it('leads with M3\'s video, in the size the item\'s height calls for', function () { expect((string) $this->blade('')) ->toContain('data-md-list-item-video') ->toContain('data-md-size="sm"') ->toContain('') ->toContain('data-md-lines="2"') ->and((string) $this->blade('')) ->toContain('data-md-size="lg"') ->toContain('data-md-lines="3"') ->and((string) $this->blade('')) ->toContain('') ->toContain('data-md-size="sm"') ->and((string) $this->blade('')) ->not->toContain('data-md-list-item-video') ->toContain('data-md-lines="1"'); }); it('lays a row\'s state layer over its fill, so a selected or segmented row keeps its colour under the tint', function () { $css = ComponentStylesheet::read('list-item'); $hover = '[data-md-list-row]:not([data-md-card]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover))'; $press = '[data-md-list-row]:not([data-md-card]):active:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):active))'; expect($css->declarations($hover, ['@media (hover: hover)'])) ->toBe(['background-color' => 'color-mix(in srgb, currentColor calc(var(--md-sys-state-hover-state-layer-opacity) * 100%), var(--md-list-row-fill, transparent))']) ->and($css->declarations($press)) ->toBe(['background-color' => 'color-mix(in srgb, currentColor calc(var(--md-sys-state-pressed-state-layer-opacity) * 100%), var(--md-list-row-fill, transparent))']) ->and($css->declarations('[data-md-list-row]:not([data-md-card]):has([data-md-list-open]:focus-visible)')) ->toHaveKey('background-color', 'color-mix(in srgb, currentColor calc(var(--md-sys-state-focus-state-layer-opacity) * 100%), var(--md-list-row-fill, transparent))') ->and($css->declarations(':where([data-md-list-row])'))->toBe(['--md-list-row-fill' => 'transparent']) ->and($css->declarations("[data-md-list='segmented'] > [data-md-list-item]"))->toHaveKey('--md-list-row-fill', 'var(--md-sys-color-surface)') ->and($css->declarations('[data-md-list-item][data-md-selected]'))->toHaveKey('--md-list-row-fill', 'var(--md-sys-color-secondary-container)') ->and(File::get(ComponentStylesheet::path('table')))->toContain('--md-list-row-fill: var(--md-sys-color-secondary-container);'); // The segmented fill comes first: on the same weight, a selected tile's secondary-container wins. expect(strpos($css->css, "[data-md-list='segmented'] > [data-md-list-item] {"))->toBeLessThan(strpos($css->css, '[data-md-list-item][data-md-selected] {')); }); it('inks a disabled item as a whole at the disabled-content opacity', function () { expect(ComponentStylesheet::read('list-item')->declarations("[data-md-list-item][aria-disabled='true']"))->toBe([ 'pointer-events' => 'none', 'color' => 'color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent)', ]); });