class([
'relative flex flex-col overflow-hidden rounded-corner-md text-on-surface',
'bg-surface-container-highest' => $variant === 'filled',
diff --git a/resources/views/showcase/sections/containment.blade.php b/resources/views/showcase/sections/containment.blade.php
index 1c810806..4683f294 100644
--- a/resources/views/showcase/sections/containment.blade.php
+++ b/resources/views/showcase/sections/containment.blade.php
@@ -26,6 +26,15 @@
BLADE,
+ 'A directly actionable card: the card is the tab stop' => <<<'BLADE'
+
+ contract.pdf
+ 1.2 MB · downloaded twice
+
+
+
+
+ BLADE,
'Lists' => <<<'BLADE'
diff --git a/tests/Browser/ContainmentTest.php b/tests/Browser/ContainmentTest.php
index 0c4c2562..4aaa6a31 100644
--- a/tests/Browser/ContainmentTest.php
+++ b/tests/Browser/ContainmentTest.php
@@ -214,12 +214,28 @@ it('dismisses a bottom sheet dragged down past a quarter of its height', functio
it('opens a row\'s opener from a press anywhere on the row, but not from its own buttons', function () {
$page = containment();
- $page->script("window.__opened = 0; document.querySelector('#containment [data-card][data-list-row] [data-list-open]').addEventListener('click', (event) => { event.preventDefault(); window.__opened++ })");
+ $row = '#containment [data-card][data-list-row]:not([data-list-actionable])';
- $page->click('#containment [data-card][data-list-row] p')
+ $page->script("window.__opened = 0; document.querySelector('{$row} [data-list-open]').addEventListener('click', (event) => { event.preventDefault(); window.__opened++ })");
+
+ $page->click("{$row} p")
->assertScript('window.__opened === 1');
- $page->click('#containment [data-card][data-list-row] button:has-text("Copy link")')
+ $page->click("{$row} button:has-text(\"Copy link\")")
+ ->assertScript('window.__opened === 1');
+});
+
+it('makes a directly actionable card the one tab stop, answering Enter', function () {
+ $card = '#containment [data-card][data-list-actionable]';
+
+ $page = containment();
+
+ $page->script("window.__opened = 0; document.querySelector('{$card} [data-list-open]').addEventListener('click', (event) => { event.preventDefault(); window.__opened++ })");
+
+ $page->script("document.querySelector('{$card}').focus()")
+ ->assertScript("document.activeElement.matches('{$card}')");
+
+ $page->keys($card, 'Enter')
->assertScript('window.__opened === 1');
});
diff --git a/tests/Feature/Components/CardTest.php b/tests/Feature/Components/CardTest.php
index 46b508fa..043069e0 100644
--- a/tests/Feature/Components/CardTest.php
+++ b/tests/Feature/Components/CardTest.php
@@ -1,7 +1,10 @@
blade("Body"))->toContain($classes)->toContain('rounded-corner-md')->toContain('data-card');
+it('draws M3\'s three cards, each naming its variant for its per-state elevation', function (string $variant, string $classes) {
+ expect((string) $this->blade("Body"))
+ ->toContain($classes)
+ ->toContain('rounded-corner-md')
+ ->toContain("data-card=\"{$variant}\"");
})->with([
'filled' => ['filled', 'bg-surface-container-highest'],
'elevated' => ['elevated', 'bg-surface-container-low shadow-elevation-1'],
@@ -9,7 +12,9 @@ it('draws M3\'s three cards', function (string $variant, string $classes) {
]);
it('is filled unless it knows the variant', function () {
- expect((string) $this->blade('Body'))->toContain('bg-surface-container-highest');
+ expect((string) $this->blade('Body'))
+ ->toContain('bg-surface-container-highest')
+ ->toContain('data-card="filled"');
});
it('lays out a header, menu, media, body and actions', function () {
@@ -38,3 +43,20 @@ it('passes row attributes through', function () {
->toContain('data-list-row')
->toContain('data-list-open');
});
+
+it('makes a directly actionable card the one tab stop, named by its title', function () {
+ $html = (string) $this->blade('Open');
+
+ expect($html)
+ ->toContain('data-list-row')
+ ->toContain('data-list-actionable')
+ ->toContain('tabindex="0"')
+ ->toContain('role="button"')
+ ->toContain('aria-label="holiday-photos.zip"')
+ ->and((string) $this->blade('Body'))
+ ->toContain('role="link"')
+ ->and(substr_count((string) $this->blade('Body'), 'role="link"'))->toBe(1)
+ ->and((string) $this->blade('Body'))
+ ->not->toContain('data-list-actionable')
+ ->not->toContain('tabindex');
+});