diff --git a/resources/css/components.css b/resources/css/all.css similarity index 60% rename from resources/css/components.css rename to resources/css/all.css index ecf823c8..4cd4da93 100644 --- a/resources/css/components.css +++ b/resources/css/all.css @@ -1,12 +1,40 @@ /* - * The component stylesheets already written without Tailwind (plan step 36), while the rest are - * still in tailwind.css. Each lives in `material.components`, so it can be imported anywhere in - * the entry: the layer statement, not the import order, decides where its rules rank. Step 37 - * replaces this file with all.css. One block per stream, so parallel rewrites merge cleanly. + * Livewire Material for an application that wants everything: the foundation, every layout + * stylesheet and every component stylesheet, each imported once. The blocks below are the same + * groups `components.css` and `layout.css` used before this file replaced both of them (plan step + * 37) — one block per audit stream, so a stylesheet still lands in a predictable place — plus the + * Layout block those two files didn't share. + * + * This is not the recommended way to bring the package's CSS in: importing `all.css` pulls in + * every component's rules whether an application's views render them or not. The recommended path + * is `foundation.css` (the one required import) followed by only the component stylesheets a + * view actually renders — each component file already imports the stylesheets of the components + * it draws, so the list an application writes stays short. `all.css` is for the showcase, for an + * error page rendered without a build, and for an application that would rather not track + * per-component imports at all. + * + * Same shape as every package stylesheet: the layer statement first, then plain imports, nothing + * else at the top level — an `@import` cannot sit inside a `@layer` block, and `layer()` on it + * would nest the layer a second time inside a file that already declares it. */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; +@import './foundation.css'; + +/* Layout */ +@import './layout/spacing.css'; +@import './layout/visibility.css'; +@import './layout/stack.css'; +@import './layout/row.css'; +@import './layout/grid.css'; +@import './layout/surface.css'; +@import './layout/pane.css'; +@import './layout/list-detail.css'; +@import './layout/supporting-pane.css'; +@import './layout/feed.css'; +@import './layout/scaffold.css'; + /* Foundation components */ @import './components/icon.css'; @import './components/shape.css'; @@ -41,6 +69,7 @@ @import './components/textarea.css'; @import './components/select.css'; @import './components/file.css'; +@import './components/selection.css'; @import './components/checkbox.css'; @import './components/radio.css'; @import './components/toggle.css'; diff --git a/resources/css/layout.css b/resources/css/layout.css deleted file mode 100644 index 5a4fa93d..00000000 --- a/resources/css/layout.css +++ /dev/null @@ -1,17 +0,0 @@ -/* - * The layout components (plan step 35): the scaffold, panes, the canonical layouts, surfaces and - * the arrangements inside a pane, each in `material.layout`, with the visibility props in - * `material.visibility`. Step 37 folds this file into all.css. - */ - -@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; - -@import './layout/stack.css'; -@import './layout/row.css'; -@import './layout/grid.css'; -@import './layout/surface.css'; -@import './layout/pane.css'; -@import './layout/list-detail.css'; -@import './layout/supporting-pane.css'; -@import './layout/feed.css'; -@import './layout/scaffold.css'; diff --git a/tests/Feature/Components/ActionStylesheetsTest.php b/tests/Feature/Components/ActionStylesheetsTest.php index eea93dd0..618064f9 100644 --- a/tests/Feature/Components/ActionStylesheetsTest.php +++ b/tests/Feature/Components/ActionStylesheetsTest.php @@ -10,13 +10,13 @@ use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses; * classes (tests/Support/ViewClasses.php), and each has a stylesheet in * `material.components` that imports the stylesheets of the components its view renders, writes * its values from the tokens and its breakpoints as px range queries, and is imported from the - * "Actions and communication" block of components.css. + * "Actions and communication" block of all.css. * * `icon` and `shape` are the two foundation components (``, ``) — no group's view * renders them exclusively, so they were in no group's dataset at all. They follow the same shape, * token and view-class rules as every other component, so they run with this group's checks too; * only "is imported from the actions and communication block" needs to read the two of them from - * components.css's first block ("Foundation components") instead, since that is where they sit. + * all.css's first block ("Foundation components") instead, since that is where they sit. */ dataset('action components', [ 'icon', @@ -95,16 +95,8 @@ it('takes its values from the tokens and its breakpoints in px', function (strin } })->with('action components'); -it('is imported from the actions and communication block of components.css, or icon and shape from the foundation block before it', function (string $name) { - $components = File::get(__DIR__.'/../../../resources/css/components.css'); - - if (in_array($name, ['icon', 'shape'], true)) { - $block = substr($components, (int) strpos($components, '/* Foundation components */')); - $block = substr($block, 0, (int) strpos($block, '/* Actions and communication */')); - } else { - $block = substr($components, (int) strpos($components, '/* Actions and communication */')); - $block = substr($block, 0, (int) strpos($block, '/* Inputs, selection and data */')); - } +it('is imported from the actions and communication block of all.css, or icon and shape from the foundation block before it', function (string $name) { + $block = allCssBlock(in_array($name, ['icon', 'shape'], true) ? 'Foundation components' : 'Actions and communication'); expect($block)->toContain("@import './components/{$name}.css';"); })->with('action components'); diff --git a/tests/Feature/Components/ChipTest.php b/tests/Feature/Components/ChipTest.php index 93dedfcb..6474aff4 100644 --- a/tests/Feature/Components/ChipTest.php +++ b/tests/Feature/Components/ChipTest.php @@ -37,7 +37,7 @@ it('draws a chip at Compose\'s size, corner, type and padding from its styleshee // 16px beside a label and 8px beside an icon, each 1px short for the border. ->toContain('padding-inline: 15px;') ->toContain('padding-inline-start: 7px;') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/chip.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/chip.css';"); }); it('pads a chip without icons by 16px on both sides', function () { @@ -245,7 +245,7 @@ it('draws the set from its stylesheet: 8px between chips, wrapping unless it scr expect($css)->toContain("@import './chip.css';") ->toMatch('/\[data-md-chip-set-row\] \{\s*display: flex;\s*flex-wrap: wrap;\s*gap: var\(--md-sys-measurement-space100\);/') ->toContain('[data-md-chip-set-scroller] > [data-md-chip-set-row] {') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/chip-set.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/chip-set.css';"); }); it('scrolls a chip set on one line with fading edges', function () { diff --git a/tests/Feature/Components/ChoicesTest.php b/tests/Feature/Components/ChoicesTest.php index 2cfbe5a7..56bbc94c 100644 --- a/tests/Feature/Components/ChoicesTest.php +++ b/tests/Feature/Components/ChoicesTest.php @@ -106,5 +106,5 @@ it('brings the stylesheets of what it renders, and draws its empty row', functio ->toContain("@import './chip-set.css';") ->toContain("@import './chip.css';") ->toContain('[data-md-choices-empty] {') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/choices.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/choices.css';"); }); diff --git a/tests/Feature/Components/ContainmentStylesheetsTest.php b/tests/Feature/Components/ContainmentStylesheetsTest.php index 837c8d62..9fccff7c 100644 --- a/tests/Feature/Components/ContainmentStylesheetsTest.php +++ b/tests/Feature/Components/ContainmentStylesheetsTest.php @@ -9,7 +9,7 @@ use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses; * `data-md-*` attributes and no class list of its own beyond the interaction and text classes * (tests/Support/ViewClasses.php), and each has a stylesheet in `material.components` that imports * the stylesheets of the components its view renders, writes its values from the tokens and its - * breakpoints as px range queries, and is imported from the "Containment" block of components.css. + * breakpoints as px range queries, and is imported from the "Containment" block of all.css. * * The dataset grew by one name per component commit, the same rule InputStylesheetsTest.php and * ActionStylesheetsTest.php follow. The error layout, whose view is not a component, has the same @@ -108,12 +108,8 @@ it('takes its values from the tokens and its breakpoints in px', function (strin assertContainmentTokensAndPxBreakpoints(ComponentStylesheet::read($name), "{$name}.css"); })->with('containment components'); -it('is imported from the containment block of components.css', function (string $name) { - $components = File::get(__DIR__.'/../../../resources/css/components.css'); - $block = substr($components, (int) strpos($components, '/* Containment */')); - $block = substr($block, 0, (int) strpos($block, '/* Navigation */')); - - expect($block)->toContain("@import './components/{$name}.css';"); +it('is imported from the containment block of all.css', function (string $name) { + expect(allCssBlock('Containment'))->toContain("@import './components/{$name}.css';"); })->with('containment components'); it('scopes every element-wide selector to a data-md hook', function (string $name) { diff --git a/tests/Feature/Components/DataTest.php b/tests/Feature/Components/DataTest.php index 0253b104..72ef1918 100644 --- a/tests/Feature/Components/DataTest.php +++ b/tests/Feature/Components/DataTest.php @@ -26,7 +26,7 @@ it('draws 52px rows, 36px dense ones, from its stylesheet in the components laye ->toMatch('/\\[data-md-table\\] \\{\\s*--cell-x: 12px;\\s*--cell-y: var\\(--md-sys-measurement-space200\\);/') ->toMatch('/\\[data-md-table\\]\\[data-md-dense\\] \\{\\s*--cell-y: var\\(--md-sys-measurement-space100\\);/') ->toContain('[data-md-table] :where(tbody tr[aria-selected=\'true\'])') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/table.css';") + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/table.css';") ->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('table.css'); }); @@ -112,7 +112,7 @@ it('draws the sort header from its stylesheet, its arrow hidden until the column expect((string) file_get_contents(__DIR__.'/../../../resources/css/components/sort-header.css')) ->toContain('@layer material.components') ->toMatch('/\\[data-md-sort-header\\]:not\\(\\[data-md-active\\]\\) \\[data-md-sort-header-arrow\\] \\{\\s*opacity: 0;/') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/sort-header.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/sort-header.css';"); }); it('draws the paginators from their stylesheet, trading the numbers for "Page n of m" below 600px', function () { @@ -120,5 +120,5 @@ it('draws the paginators from their stylesheet, trading the numbers for "Page n ->toContain('@layer material.components') ->toMatch('/@media \\(width < 600px\\) \\{\\s*\\[data-md-pagination-page\\],\\s*\\[data-md-pagination-range\\] \\{\\s*display: none;/') ->toMatch('/@media \\(width >= 600px\\) \\{\\s*\\[data-md-pagination-compact\\] \\{\\s*display: none;/') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/pagination.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/pagination.css';"); }); diff --git a/tests/Feature/Components/FeedTest.php b/tests/Feature/Components/FeedTest.php index ca46019a..46af4b9c 100644 --- a/tests/Feature/Components/FeedTest.php +++ b/tests/Feature/Components/FeedTest.php @@ -38,5 +38,5 @@ it('keeps one column on a compact window and fills columns from medium, in the l ->toContain("[data-md-feed] {\n display: grid;\n grid-template-columns: minmax(0, 1fr);") ->toContain("@media (width >= 600px) {\n grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--md-min-item, 240px)), 1fr));") ->not->toContain('grid-auto-flow') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/feed.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/feed.css';"); }); diff --git a/tests/Feature/Components/FieldTest.php b/tests/Feature/Components/FieldTest.php index 3c05987c..116b5f74 100644 --- a/tests/Feature/Components/FieldTest.php +++ b/tests/Feature/Components/FieldTest.php @@ -225,7 +225,7 @@ it('draws a form\'s column and its actions from its stylesheet', function () { expect($css)->toContain('@layer material.components') ->toContain('grid-template-columns: minmax(0, 1fr);') ->toContain('gap: var(--md-sys-measurement-space200);') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/form.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/form.css';"); }); it('keeps the field\'s chrome in its own stylesheet in the components layer', function () { @@ -235,7 +235,7 @@ it('keeps the field\'s chrome in its own stylesheet in the components layer', fu ->toContain("@import './icon.css';") ->toContain('[data-md-field-box]') ->not->toMatch('/(?and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/field.css';") + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/field.css';") ->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('field.css'); }); @@ -258,14 +258,14 @@ it('gives the one-line field a stylesheet that brings the field and its icons', expect($css)->toStartWith('/*') ->toContain("@import './field.css';") ->toContain("@import './icon.css';") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/input.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/input.css';"); }); it('gives the password field a stylesheet that brings the field and its icons', function () { expect((string) file_get_contents(__DIR__.'/../../../resources/css/components/password.css')) ->toContain("@import './field.css';") ->toContain("@import './icon.css';") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/password.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/password.css';"); }); it('draws what a textarea changes in the field from its own stylesheet', function () { @@ -276,7 +276,7 @@ it('draws what a textarea changes in the field from its own stylesheet', functio ->toContain('textarea[data-md-field-control][data-md-autogrow]') ->toContain('field-sizing: content;') ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components/field.css'))->not->toContain('textarea[') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/textarea.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/textarea.css';"); }); it('draws what a select changes in the field from its own stylesheet', function () { @@ -286,7 +286,7 @@ it('draws what a select changes in the field from its own stylesheet', function ->toContain('@supports (appearance: base-select)') ->toContain('select[data-md-field-control]:open') ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components/field.css'))->not->toContain('select[') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/select.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/select.css';"); }); it('draws the file picker\'s button from its own stylesheet', function () { @@ -296,5 +296,5 @@ it('draws the file picker\'s button from its own stylesheet', function () { ->toContain("input[type='file'][data-md-field-control]::file-selector-button") ->toContain('background-color: var(--md-sys-color-secondary-container);') ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components/field.css'))->not->toContain("[type='file']") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/file.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/file.css';"); }); diff --git a/tests/Feature/Components/GridTest.php b/tests/Feature/Components/GridTest.php index 073a9ccd..e69aa9f6 100644 --- a/tests/Feature/Components/GridTest.php +++ b/tests/Feature/Components/GridTest.php @@ -75,5 +75,5 @@ it('reads its own column count at each breakpoint in the layout layer', function expect($css)->toContain("@media (width >= {$width}px) {\n --md-columns: var(--md-columns-{$breakpoint}, 1);"); } - expect((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/grid.css';"); + expect((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/grid.css';"); }); diff --git a/tests/Feature/Components/IconTest.php b/tests/Feature/Components/IconTest.php index bb6b524c..d9adabe9 100644 --- a/tests/Feature/Components/IconTest.php +++ b/tests/Feature/Components/IconTest.php @@ -57,7 +57,7 @@ it('draws its size from a stylesheet in the components layer', function () { expect($css)->toContain('@layer material.components') ->toContain('inline-size: var(--md-icon-size, 24px)') ->toContain('[data-md-icon][data-md-mirror-rtl]:dir(rtl)') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/icon.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/icon.css';"); }); it('names the icon for a screen reader when it carries the meaning alone', function () { diff --git a/tests/Feature/Components/InputStylesheetsTest.php b/tests/Feature/Components/InputStylesheetsTest.php index 024c87c2..ba1fb401 100644 --- a/tests/Feature/Components/InputStylesheetsTest.php +++ b/tests/Feature/Components/InputStylesheetsTest.php @@ -10,7 +10,7 @@ use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses; * classes (tests/Support/ViewClasses.php), and each has a stylesheet in * `material.components` that imports the stylesheets of the components its view renders, writes * its values from the tokens and its breakpoints as px range queries, and is imported from the - * "Inputs, selection and data" block of components.css. + * "Inputs, selection and data" block of all.css. * * `pagination` is not in this dataset: its four views live in `resources/views/pagination/**`, not * `resources/views/components/`, so the two checks that read a single `resources/views/components/ @@ -47,10 +47,9 @@ dataset('input components', inputComponents()); /** * The checks that read a stylesheet alone also run on the one no view renders by itself: * selection.css, what the checkbox, radio and switch share (its own file header names them), which - * each of their stylesheets imports. Nothing renders it directly and components.css never imports - * it either — only checkbox.css, radio.css and toggle.css do — so without this its shape and token - * rules would go unchecked, the way navigation-item.css did before NavigationStylesheetsTest.php - * picked it up. + * each of their stylesheets imports too. Nothing renders it directly, so without this its shape + * and token rules would go unchecked, the way navigation-item.css did before + * NavigationStylesheetsTest.php picked it up. */ dataset('input stylesheets', [...inputComponents(), 'selection']); @@ -155,12 +154,8 @@ it('takes its values from the tokens and its breakpoints in px', function (strin assertTokensAndPxBreakpoints(ComponentStylesheet::read($name), "{$name}.css"); })->with('input stylesheets'); -it('is imported from the inputs, selection and data block of components.css', function (string $name) { - $components = File::get(__DIR__.'/../../../resources/css/components.css'); - $block = substr($components, (int) strpos($components, '/* Inputs, selection and data */')); - $block = substr($block, 0, (int) strpos($block, '/* Containment */')); - - expect($block)->toContain("@import './components/{$name}.css';"); +it('is imported from the inputs, selection and data block of all.css', function (string $name) { + expect(allCssBlock('Inputs, selection and data'))->toContain("@import './components/{$name}.css';"); })->with('input components'); // ---- pagination: its views live outside resources/views/components/ -------------------------- @@ -183,12 +178,8 @@ it('takes pagination\'s values from the tokens and its breakpoints in px', funct assertTokensAndPxBreakpoints(ComponentStylesheet::read('pagination'), 'pagination.css'); }); -it('is imported from the inputs, selection and data block of components.css for pagination', function () { - $components = File::get(__DIR__.'/../../../resources/css/components.css'); - $block = substr($components, (int) strpos($components, '/* Inputs, selection and data */')); - $block = substr($block, 0, (int) strpos($block, '/* Containment */')); - - expect($block)->toContain("@import './components/pagination.css';"); +it('is imported from the inputs, selection and data block of all.css for pagination', function () { + expect(allCssBlock('Inputs, selection and data'))->toContain("@import './components/pagination.css';"); }); dataset('pagination views', [ diff --git a/tests/Feature/Components/ListDetailTest.php b/tests/Feature/Components/ListDetailTest.php index 182daa97..4babf90f 100644 --- a/tests/Feature/Components/ListDetailTest.php +++ b/tests/Feature/Components/ListDetailTest.php @@ -88,6 +88,6 @@ it('shows one pane below expanded and both from it, at M3\'s fixed pane widths', ->toContain("@media (width >= 1200px) {\n grid-template-columns: 412px minmax(0, 1fr);") ->toContain("@media (width < 840px) {\n [data-md-list-detail]:not([data-md-selected]) > [data-md-list-detail-pane='detail'],\n [data-md-list-detail][data-md-selected] > [data-md-list-detail-pane='list'] {\n display: none;") ->toContain('[data-md-list-detail-back] [data-md-icon]:dir(rtl)') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/list-detail.css';") + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/list-detail.css';") ->and((string) file_get_contents(__DIR__.'/../../../resources/js/material.js'))->toContain("import './layout.js'"); }); diff --git a/tests/Feature/Components/NavigationStylesheetsTest.php b/tests/Feature/Components/NavigationStylesheetsTest.php index 1d237a90..bd8801a6 100644 --- a/tests/Feature/Components/NavigationStylesheetsTest.php +++ b/tests/Feature/Components/NavigationStylesheetsTest.php @@ -10,7 +10,7 @@ use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses; * classes (tests/Support/ViewClasses.php), and each has a stylesheet in `material.components` that * imports the stylesheets of the components its view renders, writes its values from the tokens * and its breakpoints as px range queries, and is imported from the "Navigation" block of - * components.css. + * all.css. * * The dataset grows by one name per component commit, the same rule ContainmentStylesheetsTest.php * and the earlier groups follow. `` and `` share one stylesheet (tabs.css) and one @@ -148,11 +148,8 @@ it('takes its values from the tokens and its breakpoints in px', function (strin assertNavigationTokensAndPxBreakpoints(ComponentStylesheet::read($name), "{$name}.css"); })->with('navigation stylesheets'); -it('is imported from the navigation block of components.css', function (string $name) { - $components = File::get(__DIR__.'/../../../resources/css/components.css'); - $block = substr($components, (int) strpos($components, '/* Navigation */')); - - expect($block)->toContain("@import './components/{$name}.css';"); +it('is imported from the navigation block of all.css', function (string $name) { + expect(allCssBlock('Navigation'))->toContain("@import './components/{$name}.css';"); })->with('navigation stylesheets'); it('scopes every element-wide selector to a data-md hook', function (string $name) { diff --git a/tests/Feature/Components/OverlayTest.php b/tests/Feature/Components/OverlayTest.php index 2e0242a0..ddc1f86e 100644 --- a/tests/Feature/Components/OverlayTest.php +++ b/tests/Feature/Components/OverlayTest.php @@ -100,7 +100,7 @@ it('divides a scrolling body from its header and actions only while content is h ->toContain('position: absolute;') // A token that reduced motion sets to zero, so there the rule simply appears. ->toContain('transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);') - ->and(File::get(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/modal.css';") + ->and(File::get(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/modal.css';") ->and(File::get(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('dialog.css') ->and(File::get(__DIR__.'/../../../resources/js/material.js'))->toContain("import './dialog.js'") ->and(File::get(__DIR__.'/../../../resources/js/dialog.js')) @@ -273,7 +273,7 @@ it('draws the drawer from a stylesheet imported from the containment block, with ->not->toContain('pane-width') ->not->toContain('pane-close-on-escape') ->not->toContain('data-pane') - ->and(File::get(__DIR__.'/../../../resources/css/components.css')) + ->and(File::get(__DIR__.'/../../../resources/css/all.css')) ->toContain("@import './components/drawer.css';"); }); @@ -305,7 +305,7 @@ it('gives a bottom sheet M3\'s preset heights, cycled from the drag handle', fun }); it('draws the bottom sheet from a stylesheet imported from the containment block and by the menu', function () { - expect(File::get(__DIR__.'/../../../resources/css/components.css')) + expect(File::get(__DIR__.'/../../../resources/css/all.css')) ->toContain("@import './components/bottom-sheet.css';") ->and(File::get(__DIR__.'/../../../resources/css/components/menu.css')) ->toContain("@import './bottom-sheet.css';") diff --git a/tests/Feature/Components/PaneTest.php b/tests/Feature/Components/PaneTest.php index b323bf09..afd223f3 100644 --- a/tests/Feature/Components/PaneTest.php +++ b/tests/Feature/Components/PaneTest.php @@ -87,5 +87,5 @@ it('keeps M3\'s margin on the body, once, in the layout layer', function () { ->toContain("@media (width >= 600px) {\n padding-inline: var(--md-layout-margin, var(--md-sys-measurement-space300));") ->toContain("[data-md-pane-body] > * {\n --md-layout-margin: 0px;") ->toContain("[data-md-pane][data-md-width='narrow'] {\n max-inline-size: 40rem;") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/pane.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/pane.css';"); }); diff --git a/tests/Feature/Components/RowTest.php b/tests/Feature/Components/RowTest.php index af28fb8f..6325b5e2 100644 --- a/tests/Feature/Components/RowTest.php +++ b/tests/Feature/Components/RowTest.php @@ -56,5 +56,5 @@ it('draws the row from a stylesheet in the layout layer, stacking at the breakpo expect($css)->toContain('@layer material.layout') ->toContain("@media (width < 840px) {\n [data-md-row][data-md-stack-below='expanded'] {\n flex-direction: column;") ->toContain("[data-md-row][data-md-stack-below='expanded']:not([data-md-align]) {\n align-items: stretch;") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/row.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/row.css';"); }); diff --git a/tests/Feature/Components/ScaffoldTest.php b/tests/Feature/Components/ScaffoldTest.php index 22fcfc5c..ad2c12eb 100644 --- a/tests/Feature/Components/ScaffoldTest.php +++ b/tests/Feature/Components/ScaffoldTest.php @@ -268,7 +268,7 @@ it('places the FAB at the bottom-end corner, clear of the bar and of a snackbar, // Only the region's own
: one an application nests in the page takes none of this. ->toContain("[data-md-scaffold-content] > main {\n --md-layout-margin: 0px;") ->not->toContain('[data-md-scaffold] main') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/scaffold.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/scaffold.css';"); }); it('imports the stylesheets of the navigation rail, the navigation bar and the snackbar host', function () { diff --git a/tests/Feature/Components/SearchTest.php b/tests/Feature/Components/SearchTest.php index ba177aee..bce71f3a 100644 --- a/tests/Feature/Components/SearchTest.php +++ b/tests/Feature/Components/SearchTest.php @@ -114,6 +114,6 @@ it('draws the bar and the view from its stylesheet, at M3\'s widths', function ( ->toContain('[data-md-search][data-md-full-screen] [data-md-search-view] {') ->toContain("[data-md-search][data-md-trigger='icon']:not([data-md-open]) [data-md-search-bar] {") ->not->toMatch('/\\d(?:\\.\\d+)?rem/') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/search.css';") + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/search.css';") ->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('search.css'); }); diff --git a/tests/Feature/Components/SelectionTest.php b/tests/Feature/Components/SelectionTest.php index ce3bb19a..2b3b539e 100644 --- a/tests/Feature/Components/SelectionTest.php +++ b/tests/Feature/Components/SelectionTest.php @@ -151,7 +151,7 @@ it('draws the checkbox from its stylesheet, on the row the selection controls sh ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components/selection.css')) ->toContain('[data-md-selection-row]') ->toContain('@layer material.components') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/checkbox.css';") + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/checkbox.css';") ->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('selection.css'); }); @@ -160,7 +160,7 @@ it('lays the radio group out from its stylesheet, inline only from 600px', funct expect($css)->toContain("@import './selection.css';") ->toMatch('/@media \\(width >= 600px\\) \\{\\s*\\[data-md-radio\\]\\[data-md-inline\\] \\[data-md-radio-options\\] \\{\\s*display: flex;/') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/radio.css';") + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/radio.css';") ->and((string) $this->blade('')) ->toMatch('/data-md-radio\\s+class="mt-4" style="order: 1"/'); }); @@ -169,5 +169,5 @@ it('draws the switch from its stylesheet', function () { expect((string) file_get_contents(__DIR__.'/../../../resources/css/components/toggle.css')) ->toContain("@import './selection.css';") ->toMatch('/\\[data-md-switch\\] \\{[^}]*width: 52px;[^}]*height: 32px;/') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/toggle.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/toggle.css';"); }); diff --git a/tests/Feature/Components/SliderTest.php b/tests/Feature/Components/SliderTest.php index c6b46486..4d5ca336 100644 --- a/tests/Feature/Components/SliderTest.php +++ b/tests/Feature/Components/SliderTest.php @@ -47,7 +47,7 @@ it('draws M3\'s 44x48 value indicator and puts the stop on the inactive track', ->toMatch('/\[data-md-slider-value\] \{[^}]*min-width: var\(--md-sys-measurement-space600\);[^}]*height: 44px;[^}]*background-color: var\(--md-sys-color-inverse-surface\);[^}]*transform-origin: bottom;/') ->toMatch('/:is\(\[data-md-slider-tick\], \[data-md-slider-stop\]\) \{[^}]*background-color: var\(--slider-on-inactive\);/') ->toContain("@import './icon.css';") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/components.css'))->toContain("@import './components/slider.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/slider.css';"); }); it('snaps and clamps the value to the step grid', function (string $template, string $value) { diff --git a/tests/Feature/Components/StackTest.php b/tests/Feature/Components/StackTest.php index 6e7c7449..3eb3d5c2 100644 --- a/tests/Feature/Components/StackTest.php +++ b/tests/Feature/Components/StackTest.php @@ -32,12 +32,12 @@ it('takes the element, the visibility props and the caller\'s class and style', ]); }); -it('draws the stack from a stylesheet in the layout layer, imported by layout.css', function () { +it('draws the stack from a stylesheet in the layout layer, imported by all.css', function () { $css = (string) file_get_contents(__DIR__.'/../../../resources/css/layout/stack.css'); expect($css)->toContain('@layer material.layout') ->toContain("[data-md-stack] {\n display: flex;\n flex-direction: column;\n gap: var(--md-gap);") // A stack inside another never takes its parent's gap. ->toContain(":where([data-md-stack]) {\n --md-gap: 0px;") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/stack.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/stack.css';"); }); diff --git a/tests/Feature/Components/SupportingPaneTest.php b/tests/Feature/Components/SupportingPaneTest.php index ad949881..06b60b0a 100644 --- a/tests/Feature/Components/SupportingPaneTest.php +++ b/tests/Feature/Components/SupportingPaneTest.php @@ -68,5 +68,5 @@ it('places the supporting pane at M3\'s widths from expanded, in the layout laye ->toContain("@media (width >= 1200px) {\n grid-template-columns: minmax(0, 1fr) 412px;") ->toContain("[data-md-supporting-pane][data-md-width='split'] {\n grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);") ->toContain('inset-block-end: max(var(--material-bottom-bar, 0px), var(--material-safe-bottom, env(safe-area-inset-bottom)));') - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/supporting-pane.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/supporting-pane.css';"); }); diff --git a/tests/Feature/Components/SurfaceTest.php b/tests/Feature/Components/SurfaceTest.php index 23986810..aaee6fc4 100644 --- a/tests/Feature/Components/SurfaceTest.php +++ b/tests/Feature/Components/SurfaceTest.php @@ -58,7 +58,7 @@ it('puts the caller\'s class and style on the surface untouched', function () { ->toMatchArray(['class' => 'upload-zone', 'style' => 'min-height: 12rem;', 'data-test' => 'zone']); }); -it('draws the surface from a stylesheet in the layout layer, imported by layout.css', function () { +it('draws the surface from a stylesheet in the layout layer, imported by all.css', function () { $css = (string) file_get_contents(__DIR__.'/../../../resources/css/layout/surface.css'); expect($css)->toContain('@layer material.layout') @@ -69,5 +69,5 @@ it('draws the surface from a stylesheet in the layout layer, imported by layout. ->toContain("@import './visibility.css';") // A pane on a surface keeps its content off the surface's edge again. ->toContain("[data-md-surface] > * {\n --md-layout-margin: initial;") - ->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/surface.css';"); + ->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/surface.css';"); }); diff --git a/tests/Feature/ErrorPagesTest.php b/tests/Feature/ErrorPagesTest.php index 5ab47386..151f76ec 100644 --- a/tests/Feature/ErrorPagesTest.php +++ b/tests/Feature/ErrorPagesTest.php @@ -273,12 +273,8 @@ it('takes its values from the tokens and its breakpoints in px', function () { } }); -it('is imported from the containment block of components.css', function () { - $components = File::get(__DIR__.'/../../resources/css/components.css'); - $block = substr($components, (int) strpos($components, '/* Containment */')); - $block = substr($block, 0, (int) strpos($block, '/* Navigation */')); - - expect($block)->toContain("@import './components/error-page.css';"); +it('is imported from the containment block of all.css', function () { + expect(allCssBlock('Containment'))->toContain("@import './components/error-page.css';"); }); it('styles the body only when it holds the error layout, and turns the shape on both paths', function () { diff --git a/tests/Feature/StylesheetsTest.php b/tests/Feature/StylesheetsTest.php index 02c160a8..26366516 100644 --- a/tests/Feature/StylesheetsTest.php +++ b/tests/Feature/StylesheetsTest.php @@ -10,8 +10,14 @@ use NoNameWeb\LivewireMaterial\Support\Layout; * would nest the layer twice); then its own `@layer material.` blocks. The only rules outside * a layer are foundation/hidden.css's two `!important` ones. * - * Until the components leave Tailwind, the checks cover the foundation — foundation.css and every - * file it imports, text.css included — and material.css and tailwind.css carry the rest. + * Every package component and layout stylesheet is plain CSS now (plan step 36), so the shape, + * Tailwind-free and breakpoint checks below run over the whole tree `all.css` reaches (foundation, + * layout and components together) rather than the foundation alone — everything but + * `tailwind.css` and `tokens/theme.css`/`tokens/utilities.css`, which stay Tailwind until plan + * step 39 and are simply never reached from `all.css`. A component's own values (colours as + * roles, shadows as elevation levels, and so on) are each group's own concern, in + * tests/Feature/Components/*StylesheetsTest.php; `bundle()`, the PHP bundler `all.css` is built + * for, is tests/Feature/StylesheetsBundleTest.php's. */ const MATERIAL_LAYER_STATEMENT = '@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;'; @@ -170,6 +176,19 @@ function foundationStylesheets(): array return stylesheetTree(stylesheetPath('foundation.css')); } +/** + * Everything all.css reaches: the foundation, every layout stylesheet and every component + * stylesheet, each once. What a check runs over now that every one of them is plain CSS (plan + * step 36) — `tailwind.css` and its two token files are never in this tree, since all.css does + * not import them. + * + * @return list + */ +function allStylesheets(): array +{ + return stylesheetTree(stylesheetPath('all.css')); +} + function stylesheetName(string $file): string { return substr($file, strlen(stylesheetPath()) + 1); @@ -189,12 +208,12 @@ it('brings every foundation file and text.css into the foundation, and nothing o ->and($foundation) ->toContain('tokens/scheme.css', 'tokens/shape.css', 'tokens/elevation.css', 'tokens/motion.css', 'tokens/type.css', 'tokens/state.css', 'tokens/spacing.css', 'tokens/font.css') // The Tailwind half: the theme, the token utilities and the components still written for it. - ->and(array_values(array_intersect($foundation, ['tokens/theme.css', 'tokens/utilities.css', 'tailwind.css', 'material.css'])))->toBe([]) + ->and(array_values(array_intersect($foundation, ['tokens/theme.css', 'tokens/utilities.css', 'tailwind.css'])))->toBe([]) ->and(array_values(preg_grep('/^components\//', $foundation)))->toBe([]); }); -it('opens every foundation stylesheet with a header, the layer statement and plain imports', function () { - foreach (foundationStylesheets() as $file) { +it('opens every stylesheet all.css reaches with a header, the layer statement and plain imports', function () { + foreach (allStylesheets() as $file) { $name = stylesheetName($file); $items = stylesheetItems(File::get($file)); @@ -261,7 +280,7 @@ it('keeps every foundation rule inside its material layer, but the two that hide ])->and($important)->toBe(2); }); -it('defines x-cloak once, where both the foundation and material.css reach it', function () { +it('defines x-cloak once, reached from both the foundation and all.css', function () { $definitions = collect(File::allFiles(stylesheetPath())) ->filter(fn (SplFileInfo $file): bool => $file->getExtension() === 'css') ->filter(fn (SplFileInfo $file): bool => str_contains(stylesheetWithoutComments($file->getContents()), '[x-cloak]')) @@ -269,40 +288,50 @@ it('defines x-cloak once, where both the foundation and material.css reach it', ->values() ->all(); - $material = array_map(stylesheetName(...), stylesheetTree(stylesheetPath('material.css'))); - expect($definitions)->toBe(['foundation/hidden.css']) - ->and($material)->toContain('foundation/hidden.css', 'tailwind.css') - // Imported after Tailwind, the reset would sit in a layer above Tailwind's utilities. - ->and(array_values(array_intersect($material, ['foundation.css', 'foundation/reset.css', 'foundation/base.css', 'foundation/interaction.css', 'text.css'])))->toBe([]); + ->and(foundationStylesheets())->toContain(stylesheetPath('foundation/hidden.css')) + ->and(allStylesheets())->toContain(stylesheetPath('foundation/hidden.css')); }); -it('writes no Tailwind directive in the foundation', function () { - foreach (foundationStylesheets() as $file) { +it('writes no Tailwind directive anywhere all.css reaches', function () { + foreach (allStylesheets() as $file) { expect(stylesheetWithoutComments(File::get($file))) ->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b/', stylesheetName($file)) ->not->toMatch('/--(?:theme|spacing|alpha)\(|\btheme\(|[\'"]tailwindcss[\'"]/', stylesheetName($file)); } }); -it('writes a media query in the foundation only at M3\'s breakpoints, in px', function () { - $queries = collect(foundationStylesheets()) +it('writes a media query, anywhere all.css reaches, only at M3\'s breakpoints, in px', function () { + $queries = collect(allStylesheets()) ->flatMap(function (string $file): array { preg_match_all('/@media\b([^{]*)\{/', stylesheetWithoutComments(File::get($file)), $matches); return array_map(fn (string $query): string => stylesheetName($file).': '.trim($query), $matches[1]); }); - // The foundation's own queries (hover, reduced motion) hold no width; the check is for what - // lands here later. + // Some queries (hover, reduced motion) hold no width at all; the check below is only for the + // ones that do. expect($queries)->not->toBeEmpty(); foreach ($queries as $query) { - preg_match_all('/(\d*\.?\d+)([a-z%]*)/i', substr($query, strpos($query, ': ') + 2), $lengths, PREG_SET_ORDER); + $feature = substr($query, strpos($query, ': ') + 2); + + // The one named exception (docs/reference/m3/components-navigation-selection-inputs.md § + // Time pickers/Behaviour, and tests/Feature/Components/InputStylesheetsTest.php's + // assertBreakpointsInPx()): a viewport *height* in an `orientation` query is not a + // breakpoint, since M3 does not make one of it. A width, in any query, still is. + $orientationHeight = preg_match('/\(\s*orientation\s*:/', $feature) === 1 + && preg_match('/(?:^|[\s:<>=(])(?:min-|max-)?height\b/', $feature) === 1 + && ! str_contains($feature, 'width'); + + preg_match_all('/(\d*\.?\d+)([a-z%]*)/i', $feature, $lengths, PREG_SET_ORDER); foreach ($lengths as [, $number, $unit]) { - expect($unit)->toBe('px', $query) - ->and(in_array($number, ['600', '840', '1200', '1600'], true))->toBeTrue("{$query} is not at an M3 breakpoint"); + expect($unit)->toBe('px', $query); + + if (! $orientationHeight) { + expect(in_array($number, ['600', '840', '1200', '1600'], true))->toBeTrue("{$query} is not at an M3 breakpoint"); + } } } }); @@ -329,31 +358,77 @@ it('declares M3\'s spacing scale as the reference gives it', function () { }); /** - * The layout components' stylesheets: layout.css and everything it imports. + * The layout components' stylesheets: all.css's own `./layout/*.css` imports, each with + * everything it in turn imports (which reaches into `components/`, for the layout files that draw + * with a component, e.g. pane.css's app bar and button). * * @return list */ function layoutStylesheets(): array { - return stylesheetTree(stylesheetPath('layout.css')); + $files = []; + + foreach (stylesheetImports(stylesheetPath('all.css')) as $import) { + if (! str_starts_with($import, './layout/')) { + continue; + } + + foreach (stylesheetTree(stylesheetPath($import)) as $file) { + if (! in_array($file, $files, true)) { + $files[] = $file; + } + } + } + + return $files; } -it('imports a stylesheet for every layout component from layout.css, each beside its view', function () { - $imports = stylesheetImports(stylesheetPath('layout.css')); +it('imports every stylesheet under components/ and layout/ directly, each exactly once', function () { + $imports = stylesheetImports(stylesheetPath('all.css')); + + expect(count($imports))->toBe(count(array_unique($imports)), 'all.css imports something more than once'); + + $expected = collect(File::files(stylesheetPath('components'))) + ->map(fn (SplFileInfo $file): string => "./components/{$file->getFilename()}") + ->merge(collect(File::files(stylesheetPath('layout')))->map(fn (SplFileInfo $file): string => "./layout/{$file->getFilename()}")) + ->sort() + ->values() + ->all(); + + // Every file under both directories is imported directly — a shared one (navigation-item.css, + // selection.css, menu.css included) as much as a component's own — and nothing stale remains. + $actual = collect($imports) + ->filter(fn (string $import): bool => str_starts_with($import, './components/') || str_starts_with($import, './layout/')) + ->sort() + ->values() + ->all(); + + expect($actual)->toBe($expected); +}); + +it('imports a stylesheet for every layout component from all.css, each beside its view', function () { + $imports = array_values(array_filter( + stylesheetImports(stylesheetPath('all.css')), + fn (string $import): bool => str_starts_with($import, './layout/'), + )); $reached = array_map(stylesheetName(...), layoutStylesheets()); $files = collect(File::files(stylesheetPath('layout')))->map(fn (SplFileInfo $file): string => $file->getBasename('.css')); // Two files are shared by the components rather than being one: the spacing props and the - // visibility props. + // visibility props. Both are still imported directly by all.css, like every other layout file + // — only the "has its own component" half of the check below skips them. $components = $files->diff(['spacing', 'visibility'])->values(); expect($components)->not->toBeEmpty() ->and($files->map(fn (string $name): string => "layout/{$name}.css")->diff($reached)->values()->all())->toBe([]); + foreach ($files as $name) { + expect(in_array("./layout/{$name}.css", $imports, true))->toBeTrue("layout/{$name}.css is not imported directly by all.css"); + } + foreach ($components as $name) { - expect(File::exists(__DIR__."/../../resources/views/components/{$name}.blade.php"))->toBeTrue("layout/{$name}.css has no component") - ->and($imports)->toContain("./layout/{$name}.css"); + expect(File::exists(__DIR__."/../../resources/views/components/{$name}.blade.php"))->toBeTrue("layout/{$name}.css has no component"); } }); @@ -389,28 +464,10 @@ it('imports, from every layout stylesheet, the stylesheet of each component its expect($checked)->toBeGreaterThan(0); }); -it('opens every layout stylesheet with a header, the layer statement and plain imports', function () { - foreach (layoutStylesheets() as $file) { - $name = stylesheetName($file); - $items = stylesheetItems(File::get($file)); - - expect(File::get($file))->toStartWith('/*', "{$name} has no header comment") - ->and($items[0]['statement'] ?? null)->toBe(MATERIAL_LAYER_STATEMENT, "{$name} does not open with the layer statement"); - - $blocks = false; - - foreach (array_slice($items, 1) as $item) { - if (isset($item['prelude'])) { - $blocks = true; - - continue; - } - - expect($blocks)->toBeFalse("{$name}: `{$item['statement']}` comes after a block") - ->and($item['statement'])->toMatch('/^@import ([\'"])\.{1,2}\/[\w.\/-]+\.css\1;$/', "{$name}: `{$item['statement']}` is not a plain import"); - } - } -}); +// Every layout stylesheet's header, layer statement and plain-imports shape, and its freedom from +// Tailwind and from a media query off an M3 breakpoint, are covered by the two all.css-wide tests +// above (allStylesheets() reaches every layout file too) — a dedicated layout-only version of +// either would only repeat that same check on a subset already checked. it('keeps every layout rule in material.layout, and the visibility props in material.visibility', function () { // layoutStylesheets() follows every @import, so it now reaches component stylesheets a layout @@ -442,36 +499,6 @@ it('keeps every layout rule in material.layout, and the visibility props in mate } }); -it('writes no Tailwind in the layout stylesheets, and media queries only at M3\'s breakpoints, in px', function () { - $queries = 0; - - // Component stylesheets reached through a layout file (see the test above) are not layout - // stylesheets themselves, and keep their own group's rules (a hover query included). - foreach (layoutStylesheets() as $file) { - $name = stylesheetName($file); - - if (! str_starts_with($name, 'layout/')) { - continue; - } - - $css = stylesheetWithoutComments(File::get($file)); - - expect($css) - ->not->toMatch('/@(?:tailwind|theme|utility|variant|custom-variant|apply|source|config|plugin|reference)\b/', $name) - ->not->toMatch('/--(?:theme|spacing|alpha)\(|\btheme\(|[\'"]tailwindcss[\'"]/', $name); - - preg_match_all('/@media\b([^{]*)\{/', $css, $matches); - - foreach ($matches[1] as $query) { - $queries++; - - expect(trim($query))->toMatch('/^\(width (?:<|>=) (?:600|840|1200|1600)px\)$/', "{$name}: `@media {$query}` is not an M3 breakpoint in px"); - } - } - - expect($queries)->toBeGreaterThan(0); -}); - it('gives every spacing token a gap and a padding, and the layout components read only those', function () { $tokens = array_map( fn (string $property): string => substr($property, strlen('--md-sys-measurement-')), @@ -610,13 +637,16 @@ it('declares the material layers in the Workbench between Tailwind\'s preflight // otherwise beat a rewritten component's padding, and below the utilities. expect(trim($app))->toStartWith('@layer theme, base, material, components, utilities;'); - $foundation = strpos($app, "@import '../../../resources/css/foundation.css';"); + $all = strpos($app, "@import '../../../resources/css/all.css';"); $tailwind = strpos($app, "@import 'tailwindcss'"); $components = strpos($app, "@import '../../../resources/css/tailwind.css';"); - expect($foundation)->toBeInt() - ->and($tailwind)->toBeGreaterThan($foundation) + expect($all)->toBeInt() + ->and($tailwind)->toBeGreaterThan($all) ->and($components)->toBeGreaterThan($tailwind) - // material.css would declare the tokens a second time. - ->and($app)->not->toContain('material.css'); + // material.css declared the tokens a second time; components.css and layout.css are the + // interim lists all.css replaced. + ->and($app)->not->toContain('material.css') + ->not->toContain("resources/css/components.css'") + ->not->toContain("resources/css/layout.css'"); }); diff --git a/tests/Pest.php b/tests/Pest.php index 4d31f207..456e393f 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,6 @@ extend(TestCase::class)->in('Feature', 'Browser'); @@ -24,3 +25,27 @@ function layoutRoot(string $html): array if (($timeout = (int) getenv('BROWSER_TIMEOUT')) > 0) { pest()->browser()->timeout($timeout); } + +/** + * The named block of resources/css/all.css — the content between its `/* Heading *\/` comment and + * the next one, or the end of the file for the last block ("Navigation"). components.css and + * layout.css grouped their imports the same way before plan step 37 folded both into all.css; this + * is the one place that block lookup lives now, so a stylesheet test asks "is this imported from + * the Containment block" without repeating the search in every file that needs it. It is here, + * rather than in tests/Feature/StylesheetsTest.php, because Pest.php is always loaded, whichever + * test file or path is run. + */ +function allCssBlock(string $heading): string +{ + $all = File::get(__DIR__.'/../resources/css/all.css'); + $marker = "/* {$heading} */"; + $start = strpos($all, $marker); + + if ($start === false) { + throw new RuntimeException("all.css has no `{$marker}` block."); + } + + $next = strpos($all, '/*', $start + strlen($marker)); + + return $next === false ? substr($all, $start) : substr($all, $start, $next - $start); +} diff --git a/workbench/resources/css/app.css b/workbench/resources/css/app.css index 9e7d115d..74b26ce2 100644 --- a/workbench/resources/css/app.css +++ b/workbench/resources/css/app.css @@ -2,12 +2,12 @@ in docs/ are not scanned for class names. The layer statement puts the `material` layers above Tailwind's preflight (`base`) and below its `components` and `utilities`: a rewritten component's padding, margin and border would otherwise lose to preflight's `* { padding: 0 }`, which the - material reset already carries, while every utility still outranks the package. tailwind.css - brings the components still written for Tailwind, without declaring the tokens a second time. */ + material reset already carries, while every utility still outranks the package. all.css brings + the foundation, every layout stylesheet and every component stylesheet in one import; tailwind.css + brings the theme and token utilities the showcase still writes as classes, without declaring the + tokens a second time. */ @layer theme, base, material, components, utilities; -@import '../../../resources/css/foundation.css'; -@import '../../../resources/css/layout.css'; -@import '../../../resources/css/components.css'; +@import '../../../resources/css/all.css'; @import 'tailwindcss' source(none); @import '../../../resources/css/tailwind.css'; @import './material-scheme.css';