mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 22:06:17 -08:00
test(site): [SITE-07] freeze console contracts and lifecycle failures
This commit is contained in:
1 parent
7cf6f33130
commit
6cd2d0ffbe
16 files changed
+1881
-12
No files matched your search
@@ -395,3 +395,11 @@ The immediate-call ordering probe also remains intact. Record the chosen flags
|
||||
alongside artifacts: diagnostic overhead may change an intermittent race, and
|
||||
passing the event control must not replace the original failure evidence. The
|
||||
combination attachment includes the selected modes and optional native trace.
|
||||
|
||||
`site-console.spec.js` characterizes the site's original console bundle, its
|
||||
React/ReactDOM/Parcel globals, returned component, object logs, Clear and hover
|
||||
scroll behavior. Common contracts run against the current and frozen input.
|
||||
Three explicitly historical cases reproduce pending-scroll, multiple-viewer hook
|
||||
ownership and native Error-message defects. Passing those cases is not proof of
|
||||
candidate fixes. SITE-CONSOLE-01 must add candidate remediation assertions and
|
||||
main-editor integration checks; see `refactor/console-modernization.md`.
|
||||
@@ -0,0 +1,174 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const frozen = fs.readFileSync('refactor/baselines/site-vendor/console-original.js')
|
||||
expect(createHash('sha256').update(frozen).digest('hex')).toBe('e00bbf82bf08c452825690372ded39a9b60f4baf438f0e7ce8300e9bc4629c03')
|
||||
async function setup(page, testInfo, historical) {
|
||||
await page.route('**/site-console-fixture', route => route.fulfill({
|
||||
contentType: 'text/html; charset=utf-8',
|
||||
body: '<!doctype html><html><head><meta charset="utf-8"></head><body><div id="first" style="height:220px;width:640px"></div><div id="second" style="height:220px;width:640px"></div></body></html>',
|
||||
}))
|
||||
if (historical)
|
||||
await page.route('**/assets/js/console.js', route => route.fulfill({ contentType: 'text/javascript', body: frozen }))
|
||||
await page.goto('/site-console-fixture')
|
||||
await page.evaluate(() => {
|
||||
window.originalConsole = { ...window.console }
|
||||
window.scrollTimers = new Set()
|
||||
window.scrollFired = 0
|
||||
const schedule = window.setTimeout
|
||||
const cancel = window.clearTimeout
|
||||
window.setTimeout = (callback, delay, ...args) => {
|
||||
if (delay !== 200 || typeof callback !== 'function')
|
||||
return schedule(callback, delay, ...args)
|
||||
const id = schedule(() => {
|
||||
window.scrollTimers.delete(id)
|
||||
window.scrollFired++
|
||||
Reflect.apply(callback, window, args)
|
||||
}, delay)
|
||||
window.scrollTimers.add(id)
|
||||
return id
|
||||
}
|
||||
window.clearTimeout = (id) => {
|
||||
window.scrollTimers.delete(id)
|
||||
cancel(id)
|
||||
}
|
||||
})
|
||||
await page.addScriptTag({ url: '/assets/js/console.js' })
|
||||
await page.evaluate(() => {
|
||||
window.viewer = window.consoleLog(document.querySelector('#first'))
|
||||
})
|
||||
await testInfo.attach('console-input', { contentType: 'application/json', body: JSON.stringify({
|
||||
historical,
|
||||
path: historical ? 'refactor/baselines/site-vendor/console-original.js' : 'docs/assets/js/console.js',
|
||||
sha256: createHash('sha256').update(historical ? frozen : fs.readFileSync('docs/assets/js/console.js')).digest('hex'),
|
||||
}) })
|
||||
}
|
||||
|
||||
for (const historical of [false, true]) {
|
||||
const scope = historical ? 'historical' : 'current'
|
||||
test(`${scope} console globals, returned component and repeated mount retain their interfaces`, async ({ page }, testInfo) => {
|
||||
await setup(page, testInfo, historical)
|
||||
expect(await page.evaluate(() => {
|
||||
const first = document.querySelector('#first')
|
||||
const hook = window.console.log
|
||||
return {
|
||||
react: window.React.version,
|
||||
dom: window.ReactDOM.version,
|
||||
loader: typeof window.parcelRequire,
|
||||
entry: window.parcelRequire('Focm').default === window.consoleLog,
|
||||
reactModule: window.parcelRequire('n8MK') === window.React,
|
||||
sameComponent: window.consoleLog(first) === window.viewer,
|
||||
sameHook: window.console.log === hook,
|
||||
methods: ['add', 'onClear', 'onMouseEnter', 'onMouseLeave', 'componentWillUnmount', 'render'].every(name => typeof window.viewer[name] === 'function'),
|
||||
state: { count: window.viewer.state.logs.length, hover: window.viewer.state.hover },
|
||||
}
|
||||
})).toEqual({ react: '17.0.2', dom: '17.0.2', loader: 'function', entry: true, reactModule: true, sameComponent: true, sameHook: true, methods: true, state: { count: 0, hover: false } })
|
||||
await expect(page.locator('.console-header-number')).toHaveText('0')
|
||||
expect(await page.evaluate(() => window.ReactDOM.unmountComponentAtNode(document.querySelector('#first')))).toBe(true)
|
||||
expect(await page.evaluate(() => ({ restored: window.console.log === window.originalConsole.log, feed: 'feed' in window.console }))).toEqual({ restored: true, feed: false })
|
||||
})
|
||||
|
||||
test(`${scope} console renders levels and raw objects; Clear resets the visible log count`, async ({ page }, testInfo) => {
|
||||
await setup(page, testInfo, historical)
|
||||
await page.evaluate(() => {
|
||||
window.sample = { message: 'structured-value', nested: { answer: 42 } }
|
||||
window.sample.self = window.sample
|
||||
window.console.log('plain-message', window.sample)
|
||||
window.console.info('info-message')
|
||||
window.console.warn('warning-message')
|
||||
window.console.error('error-message', new Error('native-error'))
|
||||
})
|
||||
await expect(page.locator('.console-header-number')).toHaveText('4')
|
||||
await expect(page.locator('.console-component')).toContainText('plain-message')
|
||||
await expect(page.locator('.console-component')).toContainText('warning-message')
|
||||
await expect(page.locator('.console-component')).toContainText('error-message')
|
||||
expect(await page.evaluate(() => ({ methods: window.viewer.state.logs.map(log => log.method), objectIdentity: window.viewer.state.logs[0].data[1] === window.sample }))).toEqual({ methods: ['log', 'info', 'warn', 'error'], objectIdentity: true })
|
||||
await page.locator('.console-header-right').click()
|
||||
await expect(page.locator('.console-header-number')).toHaveText('0')
|
||||
await expect(page.locator('.console-component')).toHaveText('')
|
||||
})
|
||||
|
||||
test(`${scope} console scrolls new logs and preserves the pointer hover pause`, async ({ page }, testInfo) => {
|
||||
await setup(page, testInfo, historical)
|
||||
await page.mouse.move(900, 600)
|
||||
await page.evaluate(() => {
|
||||
for (let i = 0; i < 40; i++)
|
||||
window.console.log(`scroll-row-${i}`)
|
||||
})
|
||||
await expect(page.locator('.console-header-number')).toHaveText('40')
|
||||
await page.waitForFunction(() => window.scrollFired > 0)
|
||||
await expect.poll(() => page.locator('.console-component').evaluate(node => node.scrollTop)).toBeGreaterThan(0)
|
||||
await page.locator('.console-component').hover()
|
||||
await page.waitForFunction(() => window.viewer.state.hover)
|
||||
await page.evaluate(() => {
|
||||
const node = document.querySelector('.console-component')
|
||||
node.style.scrollBehavior = 'auto'
|
||||
node.scrollTop = 0
|
||||
window.console.log('hover-retains-position')
|
||||
})
|
||||
await expect(page.locator('.console-header-number')).toHaveText('41')
|
||||
expect(await page.evaluate(() => ({ top: document.querySelector('.console-component').scrollTop, timers: window.scrollTimers.size }))).toEqual({ top: 0, timers: 0 })
|
||||
await page.mouse.move(900, 600)
|
||||
await page.waitForFunction(() => !window.viewer.state.hover)
|
||||
await page.evaluate(() => window.console.log('resume-scrolling'))
|
||||
await expect(page.locator('.console-header-number')).toHaveText('42')
|
||||
await expect.poll(() => page.locator('.console-component').evaluate(node => node.scrollTop)).toBeGreaterThan(0)
|
||||
})
|
||||
}
|
||||
|
||||
test('frozen historical console leaves its scheduled scroll callback after unmount', async ({ page }, testInfo) => {
|
||||
await setup(page, testInfo, true)
|
||||
await page.evaluate(() => {
|
||||
// Keep the native callback pending until after the synchronous unmount.
|
||||
window.viewer.add({ method: 'log', data: ['pending-scroll'] })
|
||||
window.pendingBeforeUnmount = window.scrollTimers.size
|
||||
window.ReactDOM.unmountComponentAtNode(document.querySelector('#first'))
|
||||
window.pendingAfterUnmount = window.scrollTimers.size
|
||||
})
|
||||
expect(await page.evaluate(() => ({ before: window.pendingBeforeUnmount, after: window.pendingAfterUnmount }))).toEqual({ before: 1, after: 1 })
|
||||
await page.waitForFunction(() => window.scrollFired === 1)
|
||||
await expect(page.locator('#first')).toBeEmpty()
|
||||
})
|
||||
|
||||
test('frozen historical console loses the remaining viewer when another viewer unmounts', async ({ page }, testInfo) => {
|
||||
await setup(page, testInfo, true)
|
||||
await page.evaluate(() => {
|
||||
window.otherViewer = window.consoleLog(document.querySelector('#second'))
|
||||
window.console.log('both-mounted')
|
||||
})
|
||||
await expect(page.locator('#first .console-header-number')).toHaveText('1')
|
||||
await expect(page.locator('#second .console-header-number')).toHaveText('1')
|
||||
await page.evaluate(() => {
|
||||
window.ReactDOM.unmountComponentAtNode(document.querySelector('#first'))
|
||||
window.console.log('remaining-viewer-loses-log')
|
||||
})
|
||||
await page.waitForFunction(() => window.viewer.state.logs.length === 1 && !('feed' in window.console))
|
||||
// Drain the same console-feed macrotask queue before inspecting the survivor.
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 0)))
|
||||
expect(await page.evaluate(() => window.otherViewer.state.logs.map(log => log.data[0]))).toEqual(['both-mounted'])
|
||||
})
|
||||
|
||||
test('frozen historical console error-only rendering loses the message on Firefox and WebKit', async ({ page, browserName }, testInfo) => {
|
||||
await setup(page, testInfo, true)
|
||||
await page.evaluate(() => {
|
||||
window.nativeError = new Error('native-error-message')
|
||||
window.console.error(window.nativeError)
|
||||
})
|
||||
await expect(page.locator('.console-header-number')).toHaveText('1')
|
||||
const result = await page.evaluate(() => ({
|
||||
parsed: window.viewer.state.logs[0].data[0],
|
||||
message: window.nativeError.message,
|
||||
stack: window.nativeError.stack,
|
||||
text: document.querySelector('.console-component').textContent,
|
||||
}))
|
||||
expect(result.parsed).toBe(result.stack)
|
||||
expect(result.message).toBe('native-error-message')
|
||||
for (const line of result.stack.trim().split('\n'))
|
||||
expect(result.text).toContain(line.trim())
|
||||
if (browserName === 'chromium')
|
||||
expect(result.text).toContain(result.message)
|
||||
else
|
||||
expect(result.text).not.toContain(result.message)
|
||||
await testInfo.attach('historical-error-rendering', { contentType: 'application/json', body: JSON.stringify(result) })
|
||||
})
|
||||
Reference in new issue
Block a user