diff --git a/.gitignore b/.gitignore index 37876f84..983fb768 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,12 @@ authn.localhost-access.log authn.localhost-access1.log bedrock static/images/logo-*.png + +# Playwright +/test-results +/playwright-report +/blob-report +/test/e2e/gallery + +# local working notes / manual-test artifacts +scratchpad diff --git a/CHANGELOG.md b/CHANGELOG.md index 78fc0cc5..957ba9b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # authn.io ChangeLog +## 7.4.4 - 2026-06-dd + +### Fixed +- Fix the root cause of the first party dialog's horizontal overflow + instead of clipping it. The dialog header keeps its full-bleed + edge-to-edge border (a negative side-margin that cancels the panel + padding, with no overflow). The overflow came from the in-flow body + dividers, which reuse the same negative side-margin but sit inside a + wrapper narrower than the panel, so the margin pushed past its edge. + Neutralize the side margins on those body dividers only — they read as + inset rules with no overhang — and remove the temporary + `overflow-x: hidden` workaround. Adds a layout test asserting the + header border bleeds edge-to-edge so the bleed cannot regress silently. +- Target the wallet hint list by its own class for the 1p flex sizing, + rather than the generic `.wrm-flex-column-stretch` (which upstream + also applies to a wrapper around the integrated list), so the sizing + and scrolling apply only to the intended element. + +### Added +- Add an automated visual/layout test suite for the first party wallet + chooser dialog. A dev-only `/test/wallet-chooser` harness route + renders the dialog with fake state across wallet counts and the + cross-device QR section; `npm run test:e2e` asserts layout invariants + on desktop Chromium, WebKit, and Firefox plus emulated iPhone and + Pixel phone sizes, and `npm run gallery` produces a browsable + screenshot gallery. + ## 7.4.3 - 2026-06-12 ### Fixed diff --git a/README.md b/README.md index 30f8fcc4..06f244d0 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,48 @@ Access the server at the following URL: * https://authn.localhost:33443/ +## Testing + +The first party wallet chooser dialog has an automated visual/layout test +suite. It drives a dev-only harness route +(`/test/wallet-chooser?hints=N&qr=1`) that renders the dialog with fake state, +so the layout can be exercised across wallet counts (0, 1, 5, 15) and the +cross-device QR section without any CHAPI registration, wallets, or popups. The +harness route is excluded from production builds. + +Install the browser engines once: + + npx playwright install chromium webkit firefox + +Run the layout regression suite: + + npm run test:e2e + +This asserts structural invariants — the expected wallets render, no horizontal +or window scrollbar appears, the header stays pinned while the list scrolls, and +the panel fills the popup — rather than comparing pixels. It starts the dev +server automatically and runs five projects: desktop Chromium, WebKit, and +Firefox at the 500px popup width, plus emulated **iPhone 15** and **Pixel 7**. +The phone projects matter because on a phone the popup is clamped to the screen +width and crosses the dialog's 430px "small screen" CSS breakpoint, exercising +layout branches the desktop width does not. + +Run a single project with, e.g., `npm run test:e2e -- --project=iphone`. The +project names are `chromium`, `webkit`, `firefox`, `iphone`, and +`android-pixel`. + +Generate a browsable screenshot gallery of every state (wallet count × theme × +engine, collapsed and expanded), with an `index.html` contact sheet: + + npm run gallery + +Output is written to `test/e2e/gallery/` (git-ignored). + +> **Brave:** the Chromium engine covers Brave's rendering (Brave is Chromium +> plus "shields", which do not affect the dialog CSS). Brave's storage/shields +> behavior is a CHAPI plumbing concern, verified in the manual mobile pass, not +> by this layout suite. Real mobile/Safari-on-iOS is likewise a manual pass. + ## Production Full instructions for running this code in production are beyond the scope of diff --git a/package-lock.json b/package-lock.json index e38e10b9..19e04bc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ }, "devDependencies": { "@digitalbazaar/eslint-config": "^8.0.1", + "@playwright/test": "^1.60.0", "eslint": "^9.39.4" }, "engines": { @@ -2624,6 +2625,22 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@sindresorhus/base62": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", @@ -5195,6 +5212,21 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6763,6 +6795,38 @@ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", "license": "MIT" }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", diff --git a/package.json b/package.json index 9daf9986..1a025b9d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ }, "scripts": { "start": "node authn.localhost.js", - "lint": "eslint" + "lint": "eslint", + "test:e2e": "playwright test wallet-chooser", + "gallery": "playwright test gallery && node test/e2e/build-gallery-index.js" }, "repository": { "type": "git", @@ -37,6 +39,7 @@ }, "devDependencies": { "@digitalbazaar/eslint-config": "^8.0.1", + "@playwright/test": "^1.60.0", "eslint": "^9.39.4" }, "engines": { diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000..3a05242b --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,60 @@ +/*! + * New BSD License (3-clause) + * Copyright (c) 2026, Digital Bazaar, Inc. + * All rights reserved. + */ +import {defineConfig, devices} from '@playwright/test'; + +const BASE_URL = 'https://authn.localhost:33443'; + +export default defineConfig({ + testDir: './test/e2e', + // the dialog suite is layout-only and deterministic; fail fast in CI + forbidOnly: !!process.env.CI, + retries: 0, + reporter: 'list', + use: { + baseURL: BASE_URL, + // the dev server uses a self-signed localhost certificate + ignoreHTTPSErrors: true, + screenshot: 'off', + trace: 'on-first-retry' + }, + // the wallet chooser popups are 500px wide; emulate that as the default + // viewport so layout matches the real popup + projects: [ + { + name: 'chromium', + use: {...devices['Desktop Chrome'], viewport: {width: 500, height: 640}} + }, + { + name: 'webkit', + use: {...devices['Desktop Safari'], viewport: {width: 500, height: 640}} + }, + { + name: 'firefox', + use: {...devices['Desktop Firefox'], viewport: {width: 500, height: 640}} + }, + // phone-sized projects: on a phone the popup is clamped to the screen + // width (narrower than the 500px desktop popup) and crosses the + // dialog's 430px "small screen" CSS breakpoint, so these exercise + // layout branches the desktop projects do not. Device descriptors also + // set a touch-capable, mobile-UA context. + { + name: 'iphone', + use: {...devices['iPhone 15']} + }, + { + name: 'android-pixel', + use: {...devices['Pixel 7']} + } + ], + // start the authn.io dev server automatically; reuse one already running + webServer: { + command: 'node authn.localhost.js', + url: `${BASE_URL}/test/wallet-chooser?hints=1`, + ignoreHTTPSErrors: true, + reuseExistingServer: true, + timeout: 120 * 1000 + } +}); diff --git a/test/e2e/build-gallery-index.js b/test/e2e/build-gallery-index.js new file mode 100644 index 00000000..3262d947 --- /dev/null +++ b/test/e2e/build-gallery-index.js @@ -0,0 +1,71 @@ +/*! + * New BSD License (3-clause) + * Copyright (c) 2026, Digital Bazaar, Inc. + * All rights reserved. + */ +import {fileURLToPath} from 'node:url'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +/* Builds an `index.html` contact sheet from the PNGs the gallery spec wrote +to `test/e2e/gallery///`. Run after `playwright test gallery` +(the `gallery` npm script chains them). Filesystem-driven so it sees every +engine's output regardless of which Playwright worker produced it. */ + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const GALLERY_DIR = path.join(__dirname, 'gallery'); + +async function findPngs(dir, base = dir) { + const out = []; + let entries; + try { + entries = await fs.readdir(dir, {withFileTypes: true}); + } catch { + return out; + } + for(const entry of entries) { + const full = path.join(dir, entry.name); + if(entry.isDirectory()) { + out.push(...await findPngs(full, base)); + } else if(entry.name.endsWith('.png')) { + out.push(path.relative(base, full)); + } + } + return out; +} + +const pngs = (await findPngs(GALLERY_DIR)).sort((a, b) => a.localeCompare(b)); +if(pngs.length === 0) { + console.error('No gallery PNGs found. Run `playwright test gallery` first.'); + process.exit(1); +} + +const cards = pngs.map(rel => { + // rel = //.png + const [engine, theme, file] = rel.split(path.sep); + const label = file.replace(/\.png$/, '').replace(/-/g, ' '); + return ` +
+ ${label} +
${engine} / ${theme} — ${label}
+
`; +}).join(''); + +const html = ` + +authn.io wallet chooser gallery + +

authn.io wallet chooser — ${pngs.length} shots

+
${cards}
`; + +const indexPath = path.join(GALLERY_DIR, 'index.html'); +await fs.writeFile(indexPath, html); +console.log(`Gallery contact sheet: ${indexPath}`); diff --git a/test/e2e/gallery.spec.js b/test/e2e/gallery.spec.js new file mode 100644 index 00000000..44265761 --- /dev/null +++ b/test/e2e/gallery.spec.js @@ -0,0 +1,74 @@ +/*! + * New BSD License (3-clause) + * Copyright (c) 2026, Digital Bazaar, Inc. + * All rights reserved. + */ +import {expect, test} from '@playwright/test'; +import {fileURLToPath} from 'node:url'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +/* Generates a labelled, human-viewable gallery of the first party wallet +chooser dialog across wallet counts, the cross-device QR section, themes, and +viewports — the screenshots we otherwise capture by hand for review. Output +goes to `test/e2e/gallery///.png` plus an `index.html` +contact sheet. This is NOT a regression gate (see `wallet-chooser.spec.js` for +that); run it with `npm run gallery`. */ + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const GALLERY_DIR = path.join(__dirname, 'gallery'); + +const STATES = [ + {name: 'hints-0-qr', query: 'hints=0&qr=1', label: '0 wallets + QR'}, + {name: 'hints-1-qr', query: 'hints=1&qr=1', label: '1 wallet + QR'}, + {name: 'hints-5-qr', query: 'hints=5&qr=1', label: '5 wallets + QR'}, + {name: 'hints-5-noqr', query: 'hints=5&qr=0', label: '5 wallets, no QR'}, + {name: 'hints-15-qr', query: 'hints=15&qr=1', label: '15 wallets + QR'} +]; +const THEMES = ['light', 'dark']; + +test.describe('gallery', () => { + for(const theme of THEMES) { + test.describe(theme, () => { + test.use({colorScheme: theme}); + + for(const state of STATES) { + test(`${state.label}`, async ({page}, testInfo) => { + const engine = testInfo.project.name; + await page.goto(`/test/wallet-chooser?${state.query}`); + await page.locator('.wrm-modal-1p .wrm-modal-content').waitFor(); + + const dir = path.join(GALLERY_DIR, engine, theme); + await fs.mkdir(dir, {recursive: true}); + + // collapsed (default) shot + await page.screenshot({path: path.join(dir, `${state.name}.png`)}); + + // for states with the expander, also capture it expanded + const toggle = page.locator('.cross-device-toggle'); + if(await toggle.count() > 0) { + await toggle.click(); + const qr = page.locator('img[alt*="QR"]'); + await expect(qr).toBeVisible(); + await page.screenshot( + {path: path.join(dir, `${state.name}-expanded.png`)}); + + // when the expanded QR sits below the fold (many wallets on a + // short/phone viewport), also capture a shot scrolled to the + // QR, so the gallery shows it is reachable + const qrInView = await qr.evaluate(el => { + const r = el.getBoundingClientRect(); + return r.bottom <= window.innerHeight && r.top >= 0; + }); + if(!qrInView) { + await qr.scrollIntoViewIfNeeded(); + await expect(qr).toBeInViewport(); + await page.screenshot( + {path: path.join(dir, `${state.name}-expanded-scrolled.png`)}); + } + } + }); + } + }); + } +}); diff --git a/test/e2e/wallet-chooser.spec.js b/test/e2e/wallet-chooser.spec.js new file mode 100644 index 00000000..7cfef742 --- /dev/null +++ b/test/e2e/wallet-chooser.spec.js @@ -0,0 +1,157 @@ +/*! + * New BSD License (3-clause) + * Copyright (c) 2026, Digital Bazaar, Inc. + * All rights reserved. + */ +import {expect, test} from '@playwright/test'; + +/* Geometric-invariant tests for the first party wallet chooser dialog, +exercised via the dev-only `/test/wallet-chooser` harness route. These assert +structural properties (no horizontal scroll, header pinned, panel fills the +popup) rather than pixels, so they catch the layout regressions seen in the +7.4.x series without screenshot-diff noise. */ + +// the states the dialog must handle, keyed for readable test titles +const STATES = [ + {name: '0 wallets + QR', query: 'hints=0&qr=1', hints: 0, qr: true}, + {name: '1 wallet + QR', query: 'hints=1&qr=1', hints: 1, qr: true}, + {name: '5 wallets + QR', query: 'hints=5&qr=1', hints: 5, qr: true}, + {name: '5 wallets, no QR', query: 'hints=5&qr=0', hints: 5, qr: false}, + {name: '15 wallets + QR', query: 'hints=15&qr=1', hints: 15, qr: true} +]; + +async function gotoChooser(page, query) { + await page.goto(`/test/wallet-chooser?${query}`); + // wait for the dialog content to render + await page.locator('.wrm-modal-1p .wrm-modal-content').waitFor(); +} + +for(const state of STATES) { + test.describe(state.name, () => { + test.beforeEach(async ({page}) => gotoChooser(page, state.query)); + + test('renders the expected number of wallet hints', async ({page}) => { + await expect(page.locator('.wrm-hint-list .wrm-selectable')) + .toHaveCount(state.hints); + }); + + test('shows no horizontal scrollbar', async ({page}) => { + // the 7.4.1 regression produced a horizontal scrollbar on the 1p + // content. Assert the document does not scroll horizontally. + const scrolls = await page.evaluate(() => + document.documentElement.scrollWidth > window.innerWidth); + expect(scrolls).toBe(false); + }); + + // Stricter than the scrollbar check: detects the horizontal OVERFLOW + // condition itself (an element whose content is wider than its box), + // not just the resulting scrollbar — so an `overflow-x: hidden` + // band-aid cannot make it a false pass. This guards the root-cause + // fix that neutralizes the header's `margin: -15px` overhang. + test('has no horizontal overflow', async ({page}) => { + const offenders = await page.evaluate(() => { + const out = []; + for(const el of document.querySelectorAll('.wrm-modal-1p *')) { + // only inspect elements with a real content box. Inline + // elements (e.g. , ) report `clientWidth: 0` but + // a nonzero `scrollWidth`, which Firefox surfaces and Chromium + // does not; that difference is not overflow, so skip them. + const cs = getComputedStyle(el); + if(cs.display === 'inline' || el.clientWidth === 0) { + continue; + } + if(el.scrollWidth - el.clientWidth > 1) { + out.push(`${el.className || el.tagName} ` + + `(scrollW ${el.scrollWidth} > clientW ${el.clientWidth})`); + } + } + return out; + }); + expect(offenders, offenders.join('; ')).toEqual([]); + }); + + test('does not scroll the popup window', async ({page}) => { + // only an inner region may scroll; the window itself must not (that + // scrolled the header/Close button away and stacked scrollbars) + const windowScrolls = await page.evaluate(() => + document.documentElement.scrollHeight > window.innerHeight + 1); + expect(windowScrolls).toBe(false); + }); + + test('keeps the header pinned while the list scrolls', async ({page}) => { + const header = page.locator('.wrm-modal-1p .wrm-modal-content-header') + .first(); + const before = await header.boundingBox(); + // scroll the wallet list to its end, if it scrolls at all + await page.evaluate(() => { + const list = document.querySelector('.wrm-hint-list'); + if(list) { + list.scrollTop = list.scrollHeight; + } + }); + const after = await header.boundingBox(); + expect(after.y).toBeCloseTo(before.y, 0); + }); + + test('panel fills the popup width (no background bleed)', + async ({page}) => { + // the content panel must span the full popup width; gaps showed the + // page background as dark bands + const fills = await page.evaluate(() => { + const panel = document.querySelector( + '.wrm-modal-1p .wrm-modal-content'); + return Math.abs(panel.getBoundingClientRect().width - + window.innerWidth) <= 1; + }); + expect(fills).toBe(true); + }); + + test('header border bleeds edge to edge', async ({page}) => { + // the dialog header ("Choose a Wallet") is full-bleed by design: its + // bottom border spans the popup edge to edge, distinguishing it from + // an inset in-page separator. A regression once removed the + // negative side margins that produce the bleed, shrinking the + // border to the padded content width; the geometric-overflow checks + // did not catch it because no overflow remained. This guards the + // bleed directly. The header is the panel's direct-child header (not + // the empty separator div the body reuses the class for). + const flush = await page.evaluate(() => { + const header = document.querySelector( + '.wrm-modal-1p .wrm-modal-content > .wrm-modal-content-header'); + const panel = document.querySelector( + '.wrm-modal-1p .wrm-modal-content'); + const h = header.getBoundingClientRect(); + const p = panel.getBoundingClientRect(); + return Math.abs(h.left - p.left) <= 1 && + Math.abs(h.right - p.right) <= 1; + }); + expect(flush).toBe(true); + }); + + if(state.qr && state.hints > 0) { + test('shows the cross-device expander, collapsed', async ({page}) => { + await expect(page.locator('.cross-device-toggle')).toBeVisible(); + await expect(page.locator('img[alt*="QR"]')).toHaveCount(0); + }); + + test('expands the QR code when the prompt is clicked', async ({page}) => { + await page.locator('.cross-device-toggle').click(); + await expect(page.locator('img[alt*="QR"]')).toBeVisible(); + }); + } + + if(state.qr && state.hints === 0) { + test('shows the QR code immediately, no expander', async ({page}) => { + await expect(page.locator('.cross-device-toggle')).toHaveCount(0); + await expect(page.locator('img[alt*="QR"]')).toBeVisible(); + }); + } + + if(!state.qr) { + test('shows no cross-device section', async ({page}) => { + await expect(page.locator('.cross-device-toggle')).toHaveCount(0); + await expect(page.locator('img[alt*="QR"]')).toHaveCount(0); + }); + } + }); +} diff --git a/web/app.less b/web/app.less index a661c1db..a0f37cce 100644 --- a/web/app.less +++ b/web/app.less @@ -85,6 +85,27 @@ stacked a second scrollbar on top of the hint list's own) and keeps the QR section visible as an affordance at any popup height */ .wrm-modal.wrm-modal-1p { + /* The dialog header (a direct child of the padded `.wrm-modal-content`) + keeps upstream's `margin: -15px` full-bleed trick: its negative side + margins exactly cancel the panel's 15px padding, so its bottom border + spans the popup edge-to-edge with no overflow. The body's in-flow + dividers reuse the same trick — the `.wrm-separator` rows (inline + `margin: 15px -15px 0`) and the empty `.wrm-modal-content-header` div + the greeting uses as a divider — but they live inside the body + wrapper, a content-box flex column 15px narrower than the panel on + each side, so their negative side margins push 15px past the + wrapper's right edge: real horizontal overflow (the 7.4.1 + `overflow-x: hidden` band-aid only hid it). Each is a full-width 1px + rule that already stretches to fill its box, so dropping the side + overhang leaves it ending at the panel padding edge — reading as an + inset divider, with no overflow and no hack. `!important` beats the + element's inline margin. */ + .wrm-modal-content-header + div .wrm-separator, + .wrm-modal-content-header + div .wrm-modal-content-header { + margin-left: 0 !important; + margin-right: 0 !important; + } + /* make the content panel exactly fill the popup — never grow with content and never come up short (the page background would show through as black bands in dark mode); `border-box` + @@ -106,15 +127,13 @@ } /* the body slot wrapper (the header's sibling) absorbs the - remaining height as a flex column; `overflow-y: auto` is only a - fallback for windows too short to fit even the fixed parts */ + remaining height as a flex column */ > .wrm-modal-content-header + div { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; overflow-y: auto; - overflow-x: hidden; /* fixed (non-scrolling) parts: greeting, separator, etc. */ > * { @@ -139,25 +158,16 @@ flex-shrink: 0; } - /* the wallet list region is the only shrinkable part, down - to ~one wallet row (below that, the body fallback scroll - takes over); the list itself scrolls — never a wrapper - around it, which would nest two scrollbars */ - > .wrm-flex-column-stretch { - flex-shrink: 1; - min-height: 62px; - - &:not(.wrm-hint-list) { - overflow-y: hidden; - } - } - + /* the wallet list is the only shrinkable part and the only + scroller; it grows with available space and shrinks to ~one + wallet row. Target it by its own class — NOT the generic + `.wrm-flex-column-stretch`, which upstream also applies to a + wrapper div around the integrated list, so styling that + class reached the wrong element and nested scrollers. */ .wrm-hint-list { flex: 1 1 auto; - /* always show at least ~one wallet row */ min-height: 62px; - /* grow with available space instead of the fixed 200px - cap used in the 3p dialog */ + /* grow instead of the fixed 200px cap used in the 3p dialog */ max-height: none; } } diff --git a/web/router.js b/web/router.js index 11a3deab..2ed0ed58 100644 --- a/web/router.js +++ b/web/router.js @@ -30,6 +30,15 @@ export async function createRouter() { /* webpackChunkName: "FirstPartyMediatorPage" */ './routes/FirstPartyMediatorPage.vue'), meta: {title: 'Allow Wallet'} - }] + }, + // dev-only harness for exercising the wallet chooser dialog layout + // without CHAPI; excluded from production builds + ...(process.env.NODE_ENV !== 'production' ? [{ + path: '/test/wallet-chooser', + component: () => import( + /* webpackChunkName: "TestWalletChooser" */ + './routes/TestWalletChooser.vue'), + meta: {title: 'Test Wallet Chooser'} + }] : [])] }); } diff --git a/web/routes/TestWalletChooser.vue b/web/routes/TestWalletChooser.vue new file mode 100644 index 00000000..46106dcf --- /dev/null +++ b/web/routes/TestWalletChooser.vue @@ -0,0 +1,103 @@ + + + + +