|
|
|
|
|
/**
|
|
|
|
|
|
* One-shot Playwright probe to verify the Toast dismiss flow emits
|
|
|
|
|
|
* `data-event*` perceptual signals on the item element.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Usage:
|
|
|
|
|
|
* 1. `npm run dev` in another terminal (defaults to port 5173)
|
|
|
|
|
|
* 2. `node --import tsx/esm scripts/probe-toast-dismiss.ts`
|
|
|
|
|
|
*
|
|
|
|
|
|
* Output: list of mutations seen during the dismiss interaction. If the
|
|
|
|
|
|
* runtime + semantic engine are wired correctly, you should see at minimum:
|
|
|
|
|
|
* - data-event="dismiss" added
|
|
|
|
|
|
* - data-event-id="sig-N" added
|
|
|
|
|
|
* - data-event-phase="active" added
|
|
|
|
|
|
* - data-event-family="emerge" added
|
|
|
|
|
|
* …followed shortly after by their removal as the cleanup tick runs.
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { chromium } from 'playwright'
|
|
|
|
|
|
|
|
|
|
|
|
const URL = process.env.PROBE_URL ?? 'http://localhost:5173/test/soma/toast'
|
|
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
|
const browser = await chromium.launch()
|
|
|
|
|
|
const page = await browser.newPage()
|
|
|
|
|
|
|
|
|
|
|
|
const consoleLines: string[] = []
|
|
|
|
|
|
page.on('console', (msg) => consoleLines.push(`[${msg.type()}] ${msg.text()}`))
|
|
|
|
|
|
|
|
|
|
|
|
await page.goto(URL, { waitUntil: 'networkidle' })
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Trigger a default toast (the demo page has an "Add default" button).
|
|
|
|
|
|
const triggerBtn = page.getByRole('button', { name: /^default$/i }).first()
|
|
|
|
|
|
await triggerBtn.click()
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Wait for the item to mount.
|
|
|
|
|
|
const item = page.locator('[data-toast-item]').first()
|
|
|
|
|
|
await item.waitFor({ state: 'attached', timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Install a MutationObserver in the page context that watches the item.
|
|
|
|
|
|
await page.evaluate(() => {
|
|
|
|
|
|
const item = document.querySelector('[data-toast-item]') as HTMLElement | null
|
|
|
|
|
|
if (!item) {
|
|
|
|
|
|
;(window as unknown as Record<string, unknown>).__probeResult = {
|
|
|
|
|
|
error: 'item not found'
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const captured: Array<{ time: number; attr: string; oldValue: string | null; newValue: string | null }> = []
|
|
|
|
|
|
const start = performance.now()
|
|
|
|
|
|
const observer = new MutationObserver((mutations) => {
|
|
|
|
|
|
for (const m of mutations) {
|
|
|
|
|
|
if (m.type !== 'attributes' || !m.attributeName) continue
|
|
|
|
|
|
captured.push({
|
|
|
|
|
|
time: Math.round(performance.now() - start),
|
|
|
|
|
|
attr: m.attributeName,
|
|
|
|
|
|
oldValue: m.oldValue,
|
|
|
|
|
|
newValue: (m.target as HTMLElement).getAttribute(m.attributeName)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
observer.observe(item, { attributes: true, attributeOldValue: true })
|
|
|
|
|
|
;(window as unknown as Record<string, unknown>).__probeStop = () => {
|
|
|
|
|
|
observer.disconnect()
|
|
|
|
|
|
return captured
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Click the close button (× inside the toast item).
|
|
|
|
|
|
const closeBtn = item.locator('[data-toast-close]').first()
|
|
|
|
|
|
await closeBtn.click()
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Wait long enough for the dismiss + cleanup to complete (~5 frames).
|
|
|
|
|
|
await page.waitForTimeout(200)
|
|
|
|
|
|
|
|
|
|
|
|
// 6. Pull the captured mutations out.
|
|
|
|
|
|
const result = (await page.evaluate(() => {
|
|
|
|
|
|
const stop = (window as unknown as { __probeStop?: () => unknown; __probeResult?: unknown })
|
|
|
|
|
|
.__probeStop
|
|
|
|
|
|
const errResult = (window as unknown as { __probeResult?: unknown }).__probeResult
|
|
|
|
|
|
if (errResult) return errResult
|
|
|
|
|
|
return stop ? stop() : { error: 'observer not installed' }
|
|
|
|
|
|
})) as Array<{ time: number; attr: string; oldValue: string | null; newValue: string | null }>
|
|
|
|
|
|
|
|
|
|
|
|
console.log('\n=== Console messages ===')
|
|
|
|
|
|
for (const line of consoleLines) console.log(line)
|
|
|
|
|
|
|
|
|
|
|
|
console.log('\n=== Mutations on the toast item during dismiss ===')
|
|
|
|
|
|
if (Array.isArray(result) && result.length === 0) {
|
|
|
|
|
|
console.log('(none)')
|
|
|
|
|
|
} else if (Array.isArray(result)) {
|
|
|
|
|
|
for (const m of result) {
|
|
|
|
|
|
console.log(`+${m.time}ms ${m.attr}: ${JSON.stringify(m.oldValue)} → ${JSON.stringify(m.newValue)}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
const sawSignal = result.some((m) => m.attr === 'data-event' && m.newValue === 'dismiss')
|
|
|
|
|
|
console.log(`\nSignal observed: ${sawSignal ? '✅ YES' : '❌ NO'}`)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('error result:', result)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await browser.close()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main().catch((err) => {
|
|
|
|
|
|
console.error(err)
|
|
|
|
|
|
process.exit(1)
|
|
|
|
|
|
})
|