Exhibit A-1 Evidence: commit 243f153
Committed · 243f153af27873262ac44161b49048c8b107f1eb
The booking button was off the screen on phones
The repository itself is private; this page carries the commit as it is recorded there.
Commit message
The change
Text files only, as recorded in git. Lines starting with + were added, lines with − removed.
Files changed
---
e2e/tests/a1-mobile-layout.spec.ts | 12 ++++++++++++
site/wp-content/themes/clinicpress/assets/js/navigation.js | 2 --
site/wp-content/themes/clinicpress/header.php | 5 +++--
site/wp-content/themes/clinicpress/style.css | 9 +++++++--
4 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/e2e/tests/a1-mobile-layout.spec.ts b/e2e/tests/a1-mobile-layout.spec.ts
index e2ad17d..181ef66 100644
--- a/e2e/tests/a1-mobile-layout.spec.ts
+++ b/e2e/tests/a1-mobile-layout.spec.ts
@@ -41,3 +41,15 @@ test('A-1 after: the menu opens from its toggle and reaches the team page', asyn
await expect(page.getByRole('heading', { level: 3, name: 'Grace Han' })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
});
+
+test('A-1 after: the header does not shift when the page finishes loading (CLS)', async ({ page }) => {
+ await page.addInitScript(() => {
+ (window as any).__cls = 0;
+ new PerformanceObserver((list) => {
+ for (const e of list.getEntries() as any[]) if (!e.hadRecentInput) (window as any).__cls += e.value;
+ }).observe({ type: 'layout-shift', buffered: true });
+ });
+ await page.goto(AFTER + '/', { waitUntil: 'networkidle' });
+ await page.waitForTimeout(500);
+ expect(await page.evaluate(() => (window as any).__cls)).toBeLessThan(0.01);
+});
diff --git a/site/wp-content/themes/clinicpress/assets/js/navigation.js b/site/wp-content/themes/clinicpress/assets/js/navigation.js
index 1c3996a..8f0b04b 100644
--- a/site/wp-content/themes/clinicpress/assets/js/navigation.js
+++ b/site/wp-content/themes/clinicpress/assets/js/navigation.js
@@ -7,8 +7,6 @@
if ( ! toggle || ! nav ) {
return;
}
- toggle.hidden = false;
- nav.classList.add( 'is-collapsible' );
toggle.addEventListener( 'click', function () {
var open = toggle.getAttribute( 'aria-expanded' ) === 'true';
toggle.setAttribute( 'aria-expanded', open ? 'false' : 'true' );
diff --git a/site/wp-content/themes/clinicpress/header.php b/site/wp-content/themes/clinicpress/header.php
index 0d74ccc..093628e 100644
--- a/site/wp-content/themes/clinicpress/header.php
+++ b/site/wp-content/themes/clinicpress/header.php
@@ -7,10 +7,11 @@
defined( 'ABSPATH' ) || exit;
?><!doctype html>
-<html <?php language_attributes(); ?>>
+<html <?php language_attributes(); ?> class="no-js">
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
+<script>document.documentElement.className = document.documentElement.className.replace( 'no-js', 'js' );</script>
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
@@ -29,7 +30,7 @@ defined( 'ABSPATH' ) || exit;
<header class="site-header">
<div class="wrap header-inner">
<?php clinicpress_brand(); ?>
- <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="site-navigation" hidden><?php esc_html_e( 'Menu', 'clinicpress' ); ?></button>
+ <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="site-navigation"><?php esc_html_e( 'Menu', 'clinicpress' ); ?></button>
<nav id="site-navigation" class="main-nav" aria-label="<?php esc_attr_e( 'Primary', 'clinicpress' ); ?>">
<?php
wp_nav_menu(
diff --git a/site/wp-content/themes/clinicpress/style.css b/site/wp-content/themes/clinicpress/style.css
index c02dcad..dd1b7f6 100644
--- a/site/wp-content/themes/clinicpress/style.css
+++ b/site/wp-content/themes/clinicpress/style.css
@@ -387,6 +387,11 @@ p {
margin-bottom: 0.4em;
}
+/* The toggle exists only when the script can run; the class is set in <head>, before the first paint. */
+.no-js .menu-toggle {
+ display: none;
+}
+
@media (min-width: 901px) {
.menu-toggle {
display: none;
@@ -395,13 +400,13 @@ p {
/* Small screens: the menu collapses behind its toggle once the script has run. */
@media (max-width: 900px) {
- .main-nav.is-collapsible {
+ .js .main-nav {
display: none;
flex-basis: 100%;
order: 4;
}
- .main-nav.is-collapsible.is-open {
+ .js .main-nav.is-open {
display: block;
}
The proving test
e2e/tests/a1-mobile-layout.spec.ts
e2e/tests/a1-mobile-layout.spec.ts, as committed.
import { test, expect } from '@playwright/test';
import { BEFORE, AFTER } from './helpers';
// Exhibit A-1. On a 390px phone the BEFORE page scrolls sideways and the booking button sits off screen;
// the AFTER page fits the screen, the button is in view and the menu opens behind its toggle.
test.use({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true });
async function layout(page: import('@playwright/test').Page) {
const cta = page.locator('.site-header .header-cta');
const box = await cta.boundingBox();
const width = await page.evaluate(() => document.documentElement.scrollWidth);
return { width, box };
}
test('A-1 before: the page is wider than the phone and the booking button is off screen', async ({ page }) => {
await page.goto(BEFORE + '/');
const { width, box } = await layout(page);
expect(width).toBeGreaterThan(390);
expect(box).not.toBeNull();
expect(box!.x + box!.width).toBeGreaterThan(390);
});
test('A-1 after: the page fits 390px, the booking button is in view and leads to the form', async ({ page }) => {
await page.goto(AFTER + '/');
const { width, box } = await layout(page);
expect(width).toBeLessThanOrEqual(390);
expect(box!.x).toBeGreaterThanOrEqual(0);
expect(box!.x + box!.width).toBeLessThanOrEqual(390);
await page.locator('.site-header .header-cta').click();
await expect(page.getByRole('heading', { level: 1, name: 'Request an appointment' })).toBeVisible();
});
test('A-1 after: the menu opens from its toggle and reaches the team page', async ({ page }) => {
await page.goto(AFTER + '/');
const toggle = page.getByRole('button', { name: 'Menu' });
await expect(toggle).toBeVisible();
await expect(page.getByRole('navigation', { name: 'Primary' }).getByRole('link', { name: 'Our team' })).toBeHidden();
await toggle.click();
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
await page.getByRole('navigation', { name: 'Primary' }).getByRole('link', { name: 'Our team' }).click();
await expect(page.getByRole('heading', { level: 3, name: 'Grace Han' })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
});
test('A-1 after: the header does not shift when the page finishes loading (CLS)', async ({ page }) => {
await page.addInitScript(() => {
(window as any).__cls = 0;
new PerformanceObserver((list) => {
for (const e of list.getEntries() as any[]) if (!e.hadRecentInput) (window as any).__cls += e.value;
}).observe({ type: 'layout-shift', buffered: true });
});
await page.goto(AFTER + '/', { waitUntil: 'networkidle' });
await page.waitForTimeout(500);
expect(await page.evaluate(() => (window as any).__cls)).toBeLessThan(0.01);
});