diff --git a/bin/tests/e2e/alpine.spec.ts b/bin/tests/e2e/alpine.spec.ts new file mode 100644 index 0000000..a751f4a --- /dev/null +++ b/bin/tests/e2e/alpine.spec.ts @@ -0,0 +1,47 @@ +import { expect, test } from '@wordpress/e2e-test-utils-playwright'; +import type { Page } from '@playwright/test'; +import { homeUrl, runId } from '../../support/site'; + +/** + * What a visitor sees before Alpine starts. With the theme's scripts held back, as they + * are for a moment on every load, nothing Alpine hides with x-show may be on screen: + * every such element carries x-cloak, which the theme's stylesheet hides until Alpine + * removes it. Without that, the cart drawer, the mobile menu, the add-to-cart modal and + * the product accordion flashed on each page load. + */ + +type Product = { id: number; permalink: string }; + +let product: Product; + +test.beforeAll(async ({ requestUtils }) => { + product = await requestUtils.rest({ + method: 'POST', + path: '/wc/v3/products', + data: { name: `E2E cloak ${runId}`, type: 'simple', regular_price: '5.00', status: 'publish', description: 'Described.' }, + }); +}); + +test.afterAll(async ({ requestUtils }) => { + await requestUtils.rest({ method: 'DELETE', path: `/wc/v3/products/${product.id}`, params: { force: true } }); +}); + +test.use({ storageState: { cookies: [], origins: [] } }); + +async function visibleBeforeAlpine(page: Page, url: string): Promise { + // The theme's built scripts, Alpine among them; its stylesheet still loads. + await page.route(/\/build\/theme\/[^/]+\/assets\/.+\.js(\?.*)?$/, (route) => route.abort()); + await page.goto(url); + + return page.evaluate(() => [...document.querySelectorAll('[x-show]')] + .filter((element) => element.checkVisibility()) + .map((element) => `<${element.tagName.toLowerCase()} x-show="${element.getAttribute('x-show')}">`)); +} + +test('the home page shows nothing Alpine hides, before Alpine starts', async ({ page }) => { + expect(await visibleBeforeAlpine(page, homeUrl('/'))).toEqual([]); +}); + +test('a product page shows nothing Alpine hides, before Alpine starts', async ({ page }) => { + expect(await visibleBeforeAlpine(page, product.permalink)).toEqual([]); +}); diff --git a/resources/assets/css/app.css b/resources/assets/css/app.css index 26d6584..597a9f7 100644 --- a/resources/assets/css/app.css +++ b/resources/assets/css/app.css @@ -16,6 +16,12 @@ @import './frontend/components/wc-blocks.css'; @import './frontend/layout/single-product.css'; +/* Alpine removes x-cloak once it has initialised an element. Until then, + whatever it hides with x-show stays hidden instead of flashing on load. */ +[x-cloak] { + display: none !important; +} + @theme { /* Primary action color — buttons, links, active states */ --color-primary: var(--wp--preset--color--primary, #1f2937); diff --git a/resources/views/parts/header/cart-drawer.blade.php b/resources/views/parts/header/cart-drawer.blade.php index ee708fd..2cacb4f 100644 --- a/resources/views/parts/header/cart-drawer.blade.php +++ b/resources/views/parts/header/cart-drawer.blade.php @@ -4,7 +4,7 @@ x-on:panel-close.window="open = false" @keydown.window.escape="if (open) { window.dispatchEvent(new CustomEvent('panel-close')); }" class="fixed inset-y-0 right-0 pl-4 sm:pl-10 max-w-full flex z-40"> -