164 lines
6.8 KiB
JavaScript
164 lines
6.8 KiB
JavaScript
/**
|
|
* Portrait PWA layout tests.
|
|
*
|
|
* Verifies that the Settings panel (and other list-pane views) are fully
|
|
* accessible in portrait orientation when the app is running as a PWA
|
|
* (standalone display mode).
|
|
*
|
|
* Because Playwright cannot set display-mode:standalone natively, we inject
|
|
* a small CSS override that activates the portrait media query rule-set via
|
|
* a data attribute, and also add the .portrait-pwa class that the JS watcher
|
|
* would normally add. This exercises the same CSS paths that fire in the real
|
|
* installed PWA.
|
|
*/
|
|
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
// Portrait dimensions (iPhone 12 / similar)
|
|
const PORTRAIT_WIDTH = 390;
|
|
const PORTRAIT_HEIGHT = 844;
|
|
const LANDSCAPE_WIDTH = 844;
|
|
const LANDSCAPE_HEIGHT = 390;
|
|
|
|
/**
|
|
* Inject an override so the portrait media-query CSS fires without needing
|
|
* an actual installed PWA. We piggyback on a custom data attribute.
|
|
*/
|
|
async function enablePortraitPwaMode(page) {
|
|
await page.addStyleTag({
|
|
content: `
|
|
/* Mirror (display-mode:standalone) and (orientation:portrait) rules for testing */
|
|
[data-portrait-pwa-test] .app { grid-template-columns: 1fr; }
|
|
[data-portrait-pwa-test] .sidebar-toggle { display: inline-flex; }
|
|
[data-portrait-pwa-test] .sidebar {
|
|
position: fixed; top:0; left:0; bottom:0;
|
|
width: min(86vw, 300px); max-width:300px; z-index:100;
|
|
border-right:1px solid var(--line-soft); box-shadow:var(--shadow);
|
|
transform:translateX(-100%); transition:transform 0.28s var(--ease);
|
|
}
|
|
[data-portrait-pwa-test] .app.sidebar-open .sidebar { transform:translateX(0); }
|
|
[data-portrait-pwa-test] .app.sidebar-open .sidebar-backdrop { display:block; opacity:1; }
|
|
[data-portrait-pwa-test] .topbar { position:sticky; top:0; z-index:50; }
|
|
[data-portrait-pwa-test] .body { flex-direction:column; overflow:hidden; }
|
|
[data-portrait-pwa-test] .player-pane { flex:none; width:100%; overflow-y:visible; }
|
|
[data-portrait-pwa-test] .player-stage { width:100%; aspect-ratio:16/9; }
|
|
[data-portrait-pwa-test] .btn-row { flex-wrap:wrap; gap:7px; }
|
|
[data-portrait-pwa-test] .now-meta { flex-direction:column; gap:12px; margin-top:14px; }
|
|
[data-portrait-pwa-test] .np-actions { flex-wrap:wrap; gap:6px; }
|
|
[data-portrait-pwa-test] .list-pane {
|
|
flex:1; min-height:0; width:100%;
|
|
border-left:none; border-top:1px solid var(--line-soft);
|
|
overflow-y:auto; overflow-x:hidden;
|
|
padding-bottom: 60px;
|
|
}
|
|
[data-portrait-pwa-test] .cards { max-height:none; overflow-y:visible; padding-bottom:0; }
|
|
[data-portrait-pwa-test] .settings { padding-bottom:20px; }
|
|
`,
|
|
});
|
|
await page.evaluate(() => {
|
|
document.documentElement.setAttribute('data-portrait-pwa-test', '1');
|
|
// Also add the JS class that setupPortraitPwaWatcher would add
|
|
const app = document.querySelector('.app');
|
|
if (app) app.classList.add('portrait-pwa');
|
|
});
|
|
}
|
|
|
|
test.describe('Portrait PWA layout', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.setViewportSize({ width: PORTRAIT_WIDTH, height: PORTRAIT_HEIGHT });
|
|
await page.goto('/');
|
|
// Wait for app JS to initialise
|
|
await page.waitForSelector('.app', { state: 'attached' });
|
|
await enablePortraitPwaMode(page);
|
|
});
|
|
|
|
test('app starts in portrait without horizontal scroll', async ({ page }) => {
|
|
const bodyScrollWidth = await page.evaluate(() => document.body.scrollWidth);
|
|
const viewportWidth = await page.evaluate(() => window.innerWidth);
|
|
expect(bodyScrollWidth).toBeLessThanOrEqual(viewportWidth + 2); // allow 2px rounding
|
|
});
|
|
|
|
test('Settings panel is accessible in portrait', async ({ page }) => {
|
|
// Open the sidebar drawer (hamburger)
|
|
await page.click('#sidebarToggle');
|
|
await page.waitForTimeout(350); // drawer animation
|
|
|
|
// Navigate to Settings
|
|
await page.click('[data-view="settings"]');
|
|
await page.waitForTimeout(300);
|
|
|
|
// Settings content must exist inside the list pane
|
|
const settingsEl = await page.locator('.settings');
|
|
await expect(settingsEl).toBeVisible();
|
|
|
|
// The settings element must not be clipped — its bounding box bottom
|
|
// should be reachable (list-pane scrolls to accommodate it)
|
|
const listPane = page.locator('.list-pane');
|
|
await expect(listPane).toBeVisible();
|
|
|
|
const listPaneBox = await listPane.boundingBox();
|
|
expect(listPaneBox).not.toBeNull();
|
|
expect(listPaneBox.height).toBeGreaterThan(50); // must have meaningful height
|
|
});
|
|
|
|
test('Settings panel scrolls to bottom without overlap', async ({ page }) => {
|
|
await page.click('#sidebarToggle');
|
|
await page.waitForTimeout(350);
|
|
await page.click('[data-view="settings"]');
|
|
await page.waitForTimeout(300);
|
|
|
|
// Scroll list-pane to the bottom
|
|
await page.evaluate(() => {
|
|
const lp = document.querySelector('.list-pane');
|
|
if (lp) lp.scrollTop = lp.scrollHeight;
|
|
});
|
|
await page.waitForTimeout(100);
|
|
|
|
// After scrolling, the last setting group should be visible
|
|
const lastGroup = page.locator('.set-group').last();
|
|
await expect(lastGroup).toBeVisible();
|
|
});
|
|
|
|
test('switching landscape to portrait restores layout', async ({ page }) => {
|
|
// Start in landscape
|
|
await page.setViewportSize({ width: LANDSCAPE_WIDTH, height: LANDSCAPE_HEIGHT });
|
|
await page.waitForTimeout(200);
|
|
|
|
// Switch back to portrait
|
|
await page.setViewportSize({ width: PORTRAIT_WIDTH, height: PORTRAIT_HEIGHT });
|
|
await page.waitForTimeout(300);
|
|
|
|
// App should not have horizontal overflow
|
|
const bodyScrollWidth = await page.evaluate(() => document.body.scrollWidth);
|
|
const viewportWidth = await page.evaluate(() => window.innerWidth);
|
|
expect(bodyScrollWidth).toBeLessThanOrEqual(viewportWidth + 2);
|
|
|
|
// List pane should be visible and have height
|
|
const listPane = page.locator('.list-pane');
|
|
await expect(listPane).toBeVisible();
|
|
const box = await listPane.boundingBox();
|
|
expect(box.height).toBeGreaterThan(50);
|
|
});
|
|
|
|
test('landscape mode is unaffected — sidebar column visible at wide viewport', async ({ page }) => {
|
|
// Remove the portrait-pwa test overrides
|
|
await page.evaluate(() => {
|
|
document.documentElement.removeAttribute('data-portrait-pwa-test');
|
|
const app = document.querySelector('.app');
|
|
if (app) app.classList.remove('portrait-pwa');
|
|
});
|
|
|
|
// Switch to a wide desktop viewport
|
|
await page.setViewportSize({ width: 1280, height: 800 });
|
|
await page.waitForTimeout(200);
|
|
|
|
// In landscape desktop, the sidebar should be visible (grid column)
|
|
const sidebar = page.locator('.sidebar');
|
|
await expect(sidebar).toBeVisible();
|
|
|
|
// list-pane should have a fixed width (452px desktop)
|
|
const listPaneBox = await page.locator('.list-pane').boundingBox();
|
|
expect(listPaneBox.width).toBeGreaterThan(200);
|
|
});
|
|
});
|