style(13-03): apply Prettier formatting across repo

Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
Lucas Berger
2026-06-11 20:35:18 -04:00
parent 4bc0445173
commit 982438dc10
398 changed files with 19050 additions and 16382 deletions
+101 -107
View File
@@ -23,153 +23,147 @@
* pnpm --filter @familysync/pwa test:e2e
* pnpm --filter @familysync/pwa exec playwright test --project=pixel layout.spec.ts
*/
import { test, expect } from '@playwright/test'
import { test, expect } from '@playwright/test';
// ── Rule 1 + Rule 3 + Rule 4: BottomTabBar tap targets, visibility, accessible names ──
test.describe('Rule 1/3/4 — BottomTabBar tap targets and in-viewport position', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/calendar')
})
await page.goto('/calendar');
});
test('BottomTabBar navigation landmark is visible (Rule 4 — accessible name)', async ({
page,
}) => {
// On mobile profiles the sole navigation landmark is the BottomTabBar nav.
// getByRole succeeds ↔ accessible name exists — doubles as Rule 4 gate.
await expect(
page.getByRole('navigation', { name: 'Main navigation' }),
).toBeVisible()
})
await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();
});
test('Calendar tab meets 44×44px touch-target minimum (Rule 1)', async ({ page }) => {
// Scope to the navigation landmark to stay robust if desktop nav ever appears.
const nav = page.getByRole('navigation', { name: 'Main navigation' })
const calTab = nav.getByRole('link', { name: 'Calendar' })
const box = await calTab.boundingBox()
expect(box, 'Calendar tab bounding box must not be null').not.toBeNull()
expect(box!.width, 'Calendar tab width ≥ 44px').toBeGreaterThanOrEqual(44)
expect(box!.height, 'Calendar tab height ≥ 44px').toBeGreaterThanOrEqual(44)
})
const nav = page.getByRole('navigation', { name: 'Main navigation' });
const calTab = nav.getByRole('link', { name: 'Calendar' });
const box = await calTab.boundingBox();
expect(box, 'Calendar tab bounding box must not be null').not.toBeNull();
expect(box!.width, 'Calendar tab width ≥ 44px').toBeGreaterThanOrEqual(44);
expect(box!.height, 'Calendar tab height ≥ 44px').toBeGreaterThanOrEqual(44);
});
test('Lists tab meets 44×44px touch-target minimum (Rule 1)', async ({ page }) => {
const nav = page.getByRole('navigation', { name: 'Main navigation' })
const listsTab = nav.getByRole('link', { name: 'Lists' })
const box = await listsTab.boundingBox()
expect(box, 'Lists tab bounding box must not be null').not.toBeNull()
expect(box!.width, 'Lists tab width ≥ 44px').toBeGreaterThanOrEqual(44)
expect(box!.height, 'Lists tab height ≥ 44px').toBeGreaterThanOrEqual(44)
})
const nav = page.getByRole('navigation', { name: 'Main navigation' });
const listsTab = nav.getByRole('link', { name: 'Lists' });
const box = await listsTab.boundingBox();
expect(box, 'Lists tab bounding box must not be null').not.toBeNull();
expect(box!.width, 'Lists tab width ≥ 44px').toBeGreaterThanOrEqual(44);
expect(box!.height, 'Lists tab height ≥ 44px').toBeGreaterThanOrEqual(44);
});
test('BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)', async ({ page }) => {
// The bar uses env(safe-area-inset-bottom, 0px). In emulation there is no
// safe-area-inset, so the bar's bottom edge must be ≤ viewport height.
const nav = page.getByRole('navigation', { name: 'Main navigation' })
await expect(nav).toBeVisible()
const box = await nav.boundingBox()
expect(box, 'BottomTabBar bounding box must not be null').not.toBeNull()
const viewportHeight = page.viewportSize()!.height
const nav = page.getByRole('navigation', { name: 'Main navigation' });
await expect(nav).toBeVisible();
const box = await nav.boundingBox();
expect(box, 'BottomTabBar bounding box must not be null').not.toBeNull();
const viewportHeight = page.viewportSize()!.height;
expect(
box!.y + box!.height,
`BottomTabBar bottom edge (${box!.y + box!.height}) must be ≤ viewport height (${viewportHeight})`,
).toBeLessThanOrEqual(viewportHeight)
})
).toBeLessThanOrEqual(viewportHeight);
});
test('PhoneNav header is visible (Rule 3)', async ({ page }) => {
// PhoneNav renders a <header> with exact text "FamilySync" (not a nav landmark).
// Use exact:true to avoid matching the "Install FamilySync" install-prompt text.
await expect(page.getByText('FamilySync', { exact: true })).toBeVisible()
})
await expect(page.getByText('FamilySync', { exact: true })).toBeVisible();
});
test('PhoneNav settings button meets 44×44px touch-target minimum (Rule 1)', async ({
page,
}) => {
test('PhoneNav settings button meets 44×44px touch-target minimum (Rule 1)', async ({ page }) => {
// aria-label: "${displayName} — open settings" (AppNav.tsx PhoneNav)
const settingsBtn = page.getByRole('button', { name: /open settings/i })
const box = await settingsBtn.boundingBox()
expect(box, 'Settings button bounding box must not be null').not.toBeNull()
expect(box!.width, 'Settings button width ≥ 44px').toBeGreaterThanOrEqual(44)
expect(box!.height, 'Settings button height ≥ 44px').toBeGreaterThanOrEqual(44)
})
const settingsBtn = page.getByRole('button', { name: /open settings/i });
const box = await settingsBtn.boundingBox();
expect(box, 'Settings button bounding box must not be null').not.toBeNull();
expect(box!.width, 'Settings button width ≥ 44px').toBeGreaterThanOrEqual(44);
expect(box!.height, 'Settings button height ≥ 44px').toBeGreaterThanOrEqual(44);
});
test('New Event FAB meets 56×56px touch-target minimum (Rule 1)', async ({ page }) => {
// Phone-only FAB — aria-label="New Event", fixed 56×56px (CalendarShell.tsx)
const fab = page.getByRole('button', { name: 'New Event' })
const box = await fab.boundingBox()
expect(box, 'New Event FAB bounding box must not be null').not.toBeNull()
expect(box!.width, 'New Event FAB width ≥ 56px').toBeGreaterThanOrEqual(56)
expect(box!.height, 'New Event FAB height ≥ 56px').toBeGreaterThanOrEqual(56)
})
})
const fab = page.getByRole('button', { name: 'New Event' });
const box = await fab.boundingBox();
expect(box, 'New Event FAB bounding box must not be null').not.toBeNull();
expect(box!.width, 'New Event FAB width ≥ 56px').toBeGreaterThanOrEqual(56);
expect(box!.height, 'New Event FAB height ≥ 56px').toBeGreaterThanOrEqual(56);
});
});
// ── Rule 1/3/4 repeated on /lists ──
test.describe('Rule 1/3/4 — BottomTabBar on /lists', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/lists')
})
await page.goto('/lists');
});
test('BottomTabBar navigation landmark is visible on /lists (Rule 4)', async ({ page }) => {
await expect(
page.getByRole('navigation', { name: 'Main navigation' }),
).toBeVisible()
})
await expect(page.getByRole('navigation', { name: 'Main navigation' })).toBeVisible();
});
test('Calendar tab meets 44×44px on /lists (Rule 1)', async ({ page }) => {
const nav = page.getByRole('navigation', { name: 'Main navigation' })
const calTab = nav.getByRole('link', { name: 'Calendar' })
const box = await calTab.boundingBox()
expect(box).not.toBeNull()
expect(box!.width).toBeGreaterThanOrEqual(44)
expect(box!.height).toBeGreaterThanOrEqual(44)
})
const nav = page.getByRole('navigation', { name: 'Main navigation' });
const calTab = nav.getByRole('link', { name: 'Calendar' });
const box = await calTab.boundingBox();
expect(box).not.toBeNull();
expect(box!.width).toBeGreaterThanOrEqual(44);
expect(box!.height).toBeGreaterThanOrEqual(44);
});
test('Lists tab meets 44×44px on /lists (Rule 1)', async ({ page }) => {
const nav = page.getByRole('navigation', { name: 'Main navigation' })
const listsTab = nav.getByRole('link', { name: 'Lists' })
const box = await listsTab.boundingBox()
expect(box).not.toBeNull()
expect(box!.width).toBeGreaterThanOrEqual(44)
expect(box!.height).toBeGreaterThanOrEqual(44)
})
const nav = page.getByRole('navigation', { name: 'Main navigation' });
const listsTab = nav.getByRole('link', { name: 'Lists' });
const box = await listsTab.boundingBox();
expect(box).not.toBeNull();
expect(box!.width).toBeGreaterThanOrEqual(44);
expect(box!.height).toBeGreaterThanOrEqual(44);
});
test('BottomTabBar is fully in-viewport on /lists (Rule 3)', async ({ page }) => {
const nav = page.getByRole('navigation', { name: 'Main navigation' })
await expect(nav).toBeVisible()
const box = await nav.boundingBox()
expect(box).not.toBeNull()
const viewportHeight = page.viewportSize()!.height
expect(box!.y + box!.height).toBeLessThanOrEqual(viewportHeight)
})
})
const nav = page.getByRole('navigation', { name: 'Main navigation' });
await expect(nav).toBeVisible();
const box = await nav.boundingBox();
expect(box).not.toBeNull();
const viewportHeight = page.viewportSize()!.height;
expect(box!.y + box!.height).toBeLessThanOrEqual(viewportHeight);
});
});
// ── Rule 2: No horizontal overflow ──
test.describe('Rule 2 — No horizontal overflow', () => {
test('no overflow on /calendar', async ({ page }) => {
await page.goto('/calendar')
await page.goto('/calendar');
const overflow = await page.evaluate(() => ({
scrollWidth: document.documentElement.scrollWidth,
clientWidth: document.documentElement.clientWidth,
}))
}));
expect(
overflow.scrollWidth,
`scrollWidth (${overflow.scrollWidth}) must be ≤ clientWidth (${overflow.clientWidth}) on /calendar`,
).toBeLessThanOrEqual(overflow.clientWidth)
})
).toBeLessThanOrEqual(overflow.clientWidth);
});
test('no overflow on /lists', async ({ page }) => {
await page.goto('/lists')
await page.goto('/lists');
const overflow = await page.evaluate(() => ({
scrollWidth: document.documentElement.scrollWidth,
clientWidth: document.documentElement.clientWidth,
}))
}));
expect(
overflow.scrollWidth,
`scrollWidth (${overflow.scrollWidth}) must be ≤ clientWidth (${overflow.clientWidth}) on /lists`,
).toBeLessThanOrEqual(overflow.clientWidth)
})
})
).toBeLessThanOrEqual(overflow.clientWidth);
});
});
// ── Harness self-validation — injected-defect proofs (TEST-01 acceptance bar) ──
//
@@ -182,81 +176,81 @@ test.describe('harness self-validation — injected defects', () => {
test('Rule 1 proof: tap-target assertion fails under 20px injection, passes after removal', async ({
page,
}) => {
await page.goto('/calendar')
await page.goto('/calendar');
// Confirm the nav is visible before injection
const nav = page.getByRole('navigation', { name: 'Main navigation' })
await expect(nav).toBeVisible()
const nav = page.getByRole('navigation', { name: 'Main navigation' });
await expect(nav).toBeVisible();
// INJECT: force BottomTabBar links to 20px height — simulates a broken tap target
const styleHandle = await page.addStyleTag({
content:
'nav[aria-label="Main navigation"] a { min-height: 20px !important; height: 20px !important; max-height: 20px !important; }',
})
});
// Measure WHILE injected — must be < 44px to prove the assertion tracks geometry
const calTab = nav.getByRole('link', { name: 'Calendar' })
const boxWithDefect = await calTab.boundingBox()
const calTab = nav.getByRole('link', { name: 'Calendar' });
const boxWithDefect = await calTab.boundingBox();
expect(
boxWithDefect,
'Calendar tab bounding box must not be null even with defect injected',
).not.toBeNull()
).not.toBeNull();
expect(
boxWithDefect!.height,
'Height must be < 44px with 20px injection (proving measurement tracks rendered geometry)',
).toBeLessThan(44)
).toBeLessThan(44);
// REMOVE the injected style by deleting the <style> element via evaluate
// (styleHandle.evaluate(el => el.remove()) — no page reload), then re-measure — must be ≥ 44px again
await styleHandle.evaluate((el) => (el as Element).remove())
const boxAfterRemoval = await calTab.boundingBox()
await styleHandle.evaluate((el) => (el as Element).remove());
const boxAfterRemoval = await calTab.boundingBox();
expect(
boxAfterRemoval,
'Calendar tab bounding box must not be null after defect removal',
).not.toBeNull()
).not.toBeNull();
expect(
boxAfterRemoval!.height,
'Height must be ≥ 44px after defect style is removed',
).toBeGreaterThanOrEqual(44)
})
).toBeGreaterThanOrEqual(44);
});
test('Rule 2 proof: overflow assertion fails under 2000px injection, passes after removal', async ({
page,
}) => {
await page.goto('/calendar')
await page.goto('/calendar');
// Confirm baseline — no overflow before injection
const baseOverflow = await page.evaluate(() => ({
scrollWidth: document.documentElement.scrollWidth,
clientWidth: document.documentElement.clientWidth,
}))
expect(baseOverflow.scrollWidth).toBeLessThanOrEqual(baseOverflow.clientWidth)
}));
expect(baseOverflow.scrollWidth).toBeLessThanOrEqual(baseOverflow.clientWidth);
// INJECT: force body width to 2000px — simulates Schedule-X overflow defect
const styleHandle = await page.addStyleTag({
content: 'body { width: 2000px !important; }',
})
});
// Measure WHILE injected — scrollWidth must exceed clientWidth
const overflowWithDefect = await page.evaluate(() => ({
scrollWidth: document.documentElement.scrollWidth,
clientWidth: document.documentElement.clientWidth,
}))
}));
expect(
overflowWithDefect.scrollWidth,
`scrollWidth (${overflowWithDefect.scrollWidth}) must be > clientWidth (${overflowWithDefect.clientWidth}) with 2000px injection (proving overflow detection works)`,
).toBeGreaterThan(overflowWithDefect.clientWidth)
).toBeGreaterThan(overflowWithDefect.clientWidth);
// REMOVE the injected style by deleting the <style> element via evaluate
// (styleHandle.evaluate(el => el.remove()) — no page reload) — overflow must clear
await styleHandle.evaluate((el) => (el as Element).remove())
await styleHandle.evaluate((el) => (el as Element).remove());
const overflowAfterRemoval = await page.evaluate(() => ({
scrollWidth: document.documentElement.scrollWidth,
clientWidth: document.documentElement.clientWidth,
}))
}));
expect(
overflowAfterRemoval.scrollWidth,
'scrollWidth must be ≤ clientWidth after 2000px injection is removed',
).toBeLessThanOrEqual(overflowAfterRemoval.clientWidth)
})
})
).toBeLessThanOrEqual(overflowAfterRemoval.clientWidth);
});
});