diff --git a/docs/mobile-testing-report.md b/docs/mobile-testing-report.md index 5b37f7b3..b5c20064 100644 --- a/docs/mobile-testing-report.md +++ b/docs/mobile-testing-report.md @@ -156,8 +156,8 @@ There is no dedicated help button in the mobile UI. Help is accessible via: | Breakpoint | Class | Description | |------------|-------|-------------| -| < 430px | `device-mobile` | Phone - most features hidden/simplified | -| 430-768px | `device-tablet` | Tablet - intermediate layout | +| < 600px | `device-mobile` | Phone - most features hidden/simplified | +| 600-768px | `device-tablet` | Tablet - intermediate layout | | > 768px | `device-desktop` | Desktop - full features | Touch devices also get `touch-device` class regardless of screen size. diff --git a/test/mobile/device-matrix.test.ts b/test/mobile/device-matrix.test.ts index 1e240122..3370b7e2 100644 --- a/test/mobile/device-matrix.test.ts +++ b/test/mobile/device-matrix.test.ts @@ -15,12 +15,7 @@ import { getCSSProperty, getCSSNumericValue, } from './helpers/assertions.js'; -import { - REPRESENTATIVE_DEVICES, - DEVICE_REGISTRY, - type DeviceEntry, - type DeviceCategory, -} from './devices.js'; +import { REPRESENTATIVE_DEVICES, DEVICE_REGISTRY, type DeviceEntry, type DeviceCategory } from './devices.js'; const PORT = PORTS.DEVICE_MATRIX; const BASE_URL = `http://localhost:${PORT}`; @@ -37,10 +32,7 @@ const PHONE_HIDDEN_SELECTORS = [ ]; // Visible only on phones -const PHONE_ONLY_SELECTORS = [ - SELECTORS.SETTINGS_MOBILE, - SELECTORS.CASE_MOBILE, -]; +const PHONE_ONLY_SELECTORS = [SELECTORS.SETTINGS_MOBILE, SELECTORS.CASE_MOBILE]; describe('Device Matrix', () => { beforeAll(async () => { @@ -54,98 +46,97 @@ describe('Device Matrix', () => { // ─── Representative Devices ─────────────────────────────────────────────── - describe.each( - Object.entries(REPRESENTATIVE_DEVICES) as [DeviceCategory, DeviceEntry][], - )('Representative: %s', (category, device) => { - let context: BrowserContext; - let page: Page; + describe.each(Object.entries(REPRESENTATIVE_DEVICES) as [DeviceCategory, DeviceEntry][])( + 'Representative: %s', + (category, device) => { + let context: BrowserContext; + let page: Page; - beforeAll(async () => { - ({ context, page } = await createDevicePage(device, BASE_URL)); - }); + beforeAll(async () => { + ({ context, page } = await createDevicePage(device, BASE_URL)); + }); - afterAll(async () => { - await context.close(); - }); + afterAll(async () => { + await context.close(); + }); - it(`has correct device class for ${device.name} (${device.viewport.width}px)`, async () => { - await assertDeviceClasses(page, device.viewport.width); - }); + it(`has correct device class for ${device.name} (${device.viewport.width}px)`, async () => { + await assertDeviceClasses(page, device.viewport.width); + }); - it('no horizontal overflow', async () => { - await assertNoHorizontalOverflow(page); - }); + it('no horizontal overflow', async () => { + await assertNoHorizontalOverflow(page); + }); - it('header positioning matches breakpoint', async () => { - const { width } = device.viewport; - const position = await getCSSProperty(page, SELECTORS.HEADER, 'position'); - if (width <= BREAKPOINTS.TABLET_MAX) { - // Phone + tablet (max-width: 768px includes 768): fixed header - expect(position).toBe('fixed'); - const top = await getCSSProperty(page, SELECTORS.HEADER, 'top'); - expect(parseFloat(top)).toBe(0); - } else { - // Desktop: relative header (not fixed) - expect(position).toBe('relative'); - } - }); - - it('toolbar positioning matches breakpoint', async () => { - const { width } = device.viewport; - const position = await getCSSProperty(page, SELECTORS.TOOLBAR, 'position'); - if (width <= BREAKPOINTS.PHONE_MAX) { - // Phone (max-width: 599px): fixed toolbar - expect(position).toBe('fixed'); - } else { - // Tablet/desktop: relative toolbar - expect(position).toBe('relative'); - } - }); - - it('correct elements hidden/visible for breakpoint', async () => { - const { width } = device.viewport; - const isPhone = width < BREAKPOINTS.PHONE_MAX; - // Skip strict assertions for devices at the phone/tablet boundary (±10px) - const atBoundary = Math.abs(width - BREAKPOINTS.PHONE_MAX) <= 10 - || Math.abs(width - BREAKPOINTS.TABLET_MAX) <= 10; - - if (atBoundary) return; - - if (isPhone) { - // Phone: certain elements hidden, mobile buttons visible - for (const sel of PHONE_HIDDEN_SELECTORS) { - await assertHidden(page, sel); + it('header positioning matches breakpoint', async () => { + const { width } = device.viewport; + const position = await getCSSProperty(page, SELECTORS.HEADER, 'position'); + if (width <= BREAKPOINTS.TABLET_MAX) { + // Phone + tablet (max-width: 768px includes 768): fixed header + expect(position).toBe('fixed'); + const top = await getCSSProperty(page, SELECTORS.HEADER, 'top'); + expect(parseFloat(top)).toBe(0); + } else { + // Desktop: relative header (not fixed) + expect(position).toBe('relative'); } - for (const sel of PHONE_ONLY_SELECTORS) { - await assertVisible(page, sel); - } - } else { - // Tablet/desktop: phone-hidden elements should be visible, mobile buttons hidden - for (const sel of PHONE_ONLY_SELECTORS) { - await assertHidden(page, sel); - } - } - }); + }); - it('touch targets pass minimum size', async () => { - const violations = await assertAccessibleTouchTargets(page); - // Log violations for debugging but allow a small number - if (violations.length > 0) { - console.warn( - `[${device.name}] Touch target violations (${violations.length}):\n` + - violations.map(v => ` ${v.selector}: ${v.width}x${v.height}px`).join('\n'), - ); - } - // Larger viewports show more UI elements, so allow more violations. - // Known violators: notification action buttons (26x26), some icon buttons. - const { width } = device.viewport; - // Larger viewports show more elements; scale threshold accordingly - const maxViolations = width >= BREAKPOINTS.TABLET_MAX ? 25 - : width >= BREAKPOINTS.PHONE_MAX ? 20 - : 15; - expect(violations.length).toBeLessThanOrEqual(maxViolations); - }); - }); + it('toolbar positioning matches breakpoint', async () => { + const { width } = device.viewport; + const position = await getCSSProperty(page, SELECTORS.TOOLBAR, 'position'); + if (width < BREAKPOINTS.PHONE_MAX) { + // Phone (max-width: 599px): fixed toolbar + expect(position).toBe('fixed'); + } else { + // Tablet/desktop: relative toolbar + expect(position).toBe('relative'); + } + }); + + it('correct elements hidden/visible for breakpoint', async () => { + const { width } = device.viewport; + const isPhone = width < BREAKPOINTS.PHONE_MAX; + // Skip strict assertions for devices at the phone/tablet boundary (±10px) + const atBoundary = + Math.abs(width - BREAKPOINTS.PHONE_MAX) <= 10 || Math.abs(width - BREAKPOINTS.TABLET_MAX) <= 10; + + if (atBoundary) return; + + if (isPhone) { + // Phone: certain elements hidden, mobile buttons visible + for (const sel of PHONE_HIDDEN_SELECTORS) { + await assertHidden(page, sel); + } + for (const sel of PHONE_ONLY_SELECTORS) { + await assertVisible(page, sel); + } + } else { + // Tablet/desktop: phone-hidden elements should be visible, mobile buttons hidden + for (const sel of PHONE_ONLY_SELECTORS) { + await assertHidden(page, sel); + } + } + }); + + it('touch targets pass minimum size', async () => { + const violations = await assertAccessibleTouchTargets(page); + // Log violations for debugging but allow a small number + if (violations.length > 0) { + console.warn( + `[${device.name}] Touch target violations (${violations.length}):\n` + + violations.map((v) => ` ${v.selector}: ${v.width}x${v.height}px`).join('\n') + ); + } + // Larger viewports show more UI elements, so allow more violations. + // Known violators: notification action buttons (26x26), some icon buttons. + const { width } = device.viewport; + // Larger viewports show more elements; scale threshold accordingly + const maxViolations = width >= BREAKPOINTS.TABLET_MAX ? 25 : width >= BREAKPOINTS.PHONE_MAX ? 20 : 15; + expect(violations.length).toBeLessThanOrEqual(maxViolations); + }); + } + ); // ─── Full Device Matrix (skip with CI_QUICK=1) ─────────────────────────── diff --git a/test/mobile/helpers/constants.ts b/test/mobile/helpers/constants.ts index f155196d..bdd6198b 100644 --- a/test/mobile/helpers/constants.ts +++ b/test/mobile/helpers/constants.ts @@ -59,7 +59,7 @@ export const SELECTORS = { // Device breakpoints (match app.js MobileDetection) export const BREAKPOINTS = { - PHONE_MAX: 599, + PHONE_MAX: 600, TABLET_MAX: 768, } as const; diff --git a/test/mobile/snapshots/keyboard-430w.png b/test/mobile/snapshots/keyboard-430w.png deleted file mode 100644 index a6b079dc..00000000 Binary files a/test/mobile/snapshots/keyboard-430w.png and /dev/null differ diff --git a/test/mobile/snapshots/landing-430w.png b/test/mobile/snapshots/landing-430w.png deleted file mode 100644 index 18c3a6a6..00000000 Binary files a/test/mobile/snapshots/landing-430w.png and /dev/null differ diff --git a/test/mobile/snapshots/settings-430w.png b/test/mobile/snapshots/settings-430w.png deleted file mode 100644 index 6812a274..00000000 Binary files a/test/mobile/snapshots/settings-430w.png and /dev/null differ