Compact request activity into a live summary and modal history
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
// Fixture-only action feedback and accessible dialog checks; no live API writes.
|
||||
const assert = require('node:assert/strict')
|
||||
const { chromium } = require(process.env.REVIEW_PLAYWRIGHT || 'playwright')
|
||||
const base = process.env.REVIEW_BASE || 'http://127.0.0.1:3101'
|
||||
;(async () => {
|
||||
const browser = await chromium.launch({ headless: true })
|
||||
try {
|
||||
const context = await browser.newContext()
|
||||
await context.addCookies([{ name: 'magent_logged_in', value: '1', url: base }])
|
||||
let downloading = true, finished = false
|
||||
const events = [
|
||||
{ id: '1', service: 'Magent', state: 'complete', message: 'Your action has been received.' },
|
||||
{ id: '2', service: 'Radarr', state: 'active', message: 'Checking whether the replacement has been downloaded…' },
|
||||
]
|
||||
let releaseAction
|
||||
await context.route('**/api/**', async (route) => {
|
||||
const path = new URL(route.request().url()).pathname
|
||||
const reply = (json) => route.fulfill({ json })
|
||||
if (path === '/api/auth/me') return reply({ username: 'Member', role: 'user' })
|
||||
if (path.endsWith('/snapshot')) return reply({ request_id: '12', title: 'Activity review', request_type: 'movie', state: 'DOWNLOADING', timeline: [], actions: [], presentation: {
|
||||
status: { label: 'Download in progress', meaning: 'Your movie is downloading.' },
|
||||
download: { visible: downloading, summary: 'Downloading (1 active).' },
|
||||
nextStep: { title: 'Let the download finish', description: 'No action needed.', actionIds: [] }, pipeline: [],
|
||||
} })
|
||||
if (path.endsWith('/actions/recheck')) { await new Promise((resolve) => { releaseAction = resolve }); return reply({ message: 'Request checked.' }) }
|
||||
if (path.includes('/operations/')) return reply({ id: 'fixture', label: 'Recheck request status', status: finished ? 'complete' : 'running', events: finished ? [...events, { id: '3', service: 'Magent', state: 'complete', message: 'This check has finished. Your movie is still downloading.' }] : events })
|
||||
if (path.includes('/events/stream')) return route.fulfill({ contentType: 'text/event-stream', body: ': fixture\n\n' })
|
||||
return reply({ navigation: { showRequests: true }, services: [] })
|
||||
})
|
||||
const page = await context.newPage()
|
||||
const errors = []
|
||||
page.on('pageerror', (error) => errors.push(error.message))
|
||||
for (const width of [1440, 710, 390]) {
|
||||
await page.setViewportSize({ width, height: 960 })
|
||||
for (downloading of [true, false]) {
|
||||
finished = false
|
||||
await page.goto(base + '/requests/12')
|
||||
await page.getByRole('button', { name: 'Recheck request', exact: true }).click({ timeout: 5000 }).catch(async (error) => {
|
||||
console.error(errors, (await page.locator('body').innerText()).slice(0, 1600)); throw error
|
||||
})
|
||||
const card = page.locator('.latest-activity')
|
||||
await card.locator('.latest-activity-message').filter({ hasText: events[1].message }).waitFor()
|
||||
assert.equal(await card.getByText(events[0].message, { exact: true }).isVisible(), false)
|
||||
if (width === 1440 && downloading) {
|
||||
const activityBox = await card.boundingBox()
|
||||
const downloadBox = await page.locator('.request-overview-block').filter({ hasText: 'Current download state' }).boundingBox()
|
||||
assert.ok(activityBox.x > downloadBox.x && Math.abs(activityBox.y - downloadBox.y) < 2, 'Activity must use the spare right-hand quadrant')
|
||||
}
|
||||
const before = await page.locator('.request-next-step').boundingBox()
|
||||
await card.getByRole('button').click()
|
||||
const dialog = page.getByRole('dialog', { name: 'Recheck request status' })
|
||||
await dialog.waitFor()
|
||||
await dialog.getByText(events[0].message, { exact: true }).waitFor()
|
||||
assert.equal(await page.evaluate(() => document.body.style.overflow), 'hidden')
|
||||
const after = await page.locator('.request-next-step').boundingBox()
|
||||
assert.equal(before.y, after.y, 'Opening the dialog must not expand the page')
|
||||
await page.keyboard.press('Escape')
|
||||
await dialog.waitFor({ state: 'hidden' })
|
||||
assert.equal(await card.getByRole('button').evaluate((el) => el === document.activeElement), true)
|
||||
finished = true; releaseAction()
|
||||
await card.getByText('Finished', { exact: true }).waitFor()
|
||||
await card.getByRole('button').click()
|
||||
await dialog.getByText('This check has finished. Your movie is still downloading.', { exact: true }).waitFor()
|
||||
if (process.env.REVIEW_DIR) await page.screenshot({ path: `${process.env.REVIEW_DIR}/activity-${width}-${downloading}.png` })
|
||||
await dialog.getByRole('button', { name: 'Dismiss activity' }).click()
|
||||
await card.waitFor({ state: 'hidden' })
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false)
|
||||
assert.notEqual(await page.evaluate(() => document.body.style.overflow), 'hidden')
|
||||
}
|
||||
}
|
||||
assert.deepEqual(errors, [])
|
||||
console.log('PASS: right-hand activity card, latest event only, live updates, modal history, Escape/focus return, dismissal, desktop/tablet/mobile; all API calls mocked.')
|
||||
} finally { await browser.close() }
|
||||
})().catch((error) => { console.error(error); process.exitCode = 1 })
|
||||
Reference in New Issue
Block a user