Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dev-docs/BROWSER_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,11 @@ synthetic provider.
- Tests that need stateful middleware (SSE feeds driven by the test, custom
Vite transform hooks, configs that diverge from the canonical daemon
config) keep their own server and live in the heavy tier.
- Before pointer/keyboard selection in a Monaco diff, wait for its computed
diff decoration (`.line-insert` in the Git fixture), then locate the line.
Plain model text can paint before the worker result inserts deleted-code view
zones. `git-history.test.ts` holds a real worker reply and defers Monaco's
repaint to exercise that geometry change with native pointer checks intact.
Its main-world DOM readiness probe releases the reply and repaint; a direct
native click instead receives the reply at pointer movement and releases
repaint on pointer-up, keeping the stale geometry through mouse-down.
105 changes: 99 additions & 6 deletions packages/ui/tests/browser/git-history.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "node:assert/strict"
import { after, before, test } from "node:test"
import { fileURLToPath } from "node:url"
import { chromium, type Browser } from "playwright"
import { chromium, type Browser, type Page } from "playwright"
import { createFixtureShutdown } from "./fixture-shutdown"
import { armFixtureNavigationBudget } from "./fixture-navigation-budget"
import { createServer, type ViteDevServer } from "vite"
Expand Down Expand Up @@ -38,6 +38,14 @@ const gitFocusBoundaryInitScript = String.raw`(() => {
})()`

let cache: Awaited<ReturnType<typeof createFixtureCache>>

async function clickModifiedDiffLine(page: Page) {
// Plain model text paints before the asynchronous diff adds its deleted-code
// view zones. Selecting those old coordinates can open Monaco's copy menu.
await page.waitForFunction(() => document.querySelector(".git-diff-view .line-insert"))
await page.locator(".git-diff-view .editor.modified .view-line").filter({ hasText: 'title: "Git"' }).click()
}

test("large Changes inventories keep the reader controls responsive", async () => {
const page = await browser.newPage({ viewport: { width: 2000, height: 1120 } })
try {
Expand Down Expand Up @@ -784,6 +792,92 @@ test("real SessionView keeps its composer while file previews are fenced by sess
} finally { await page.close() }
})

test("composer selection waits for the computed diff when its initial paint is deferred", async () => {
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } })
await page.route("**/api/**", route => route.fulfill({ json: {} }))
await page.addInitScript({ content: String.raw`(() => {
const NativeWorker = window.Worker
window.Worker = class extends NativeWorker {
constructor(...args) {
super(...args)
const requests = new Set(), post = this.postMessage.bind(this)
let hold = true
this.postMessage = (message, ...rest) => {
if (message?.method === "$computeDiff") requests.add(message.req)
return post(message, ...rest)
}
this.addEventListener("message", event => {
if (!hold || !requests.delete(event.data?.seq)) return
hold = false
event.stopImmediatePropagation()
window.releaseDiff = () => this.dispatchEvent(new MessageEvent("message", { data: event.data }))
}, true)
}
}
window.deferDiffPaint = () => {
const raf = window.requestAnimationFrame.bind(window), cancel = window.cancelAnimationFrame.bind(window), frames = new Map()
const query = document.querySelector
let id = 0, released = false
window.requestAnimationFrame = callback => { frames.set(--id, callback); return id }
window.cancelAnimationFrame = handle => { if (!frames.delete(handle)) cancel(handle) }
const release = boundary => {
if (released) return
released = true
window.diffPaintReleaseBoundary = boundary
document.querySelector = query
document.removeEventListener("pointermove", onPointerMove, true)
document.removeEventListener("pointerup", onPointerUp, true)
window.requestAnimationFrame = raf
window.cancelAnimationFrame = cancel
for (const callback of frames.values()) raf(callback)
}
// The main-world readiness probe releases painting before it can see
// the real decoration. Without that probe, keep stale DOM through the
// native mouse-down that opens Monaco's deleted-code menu.
document.querySelector = function(selector) {
if (selector === ".git-diff-view .line-insert") {
deliver()
release("readiness")
}
return query.call(this, selector)
}
let delivered = false
const deliver = () => {
if (delivered) return
delivered = true
window.releaseDiff()
}
// With no readiness wait, deliver only after Playwright has measured
// and scrolled the old line, at its real pointer movement to that point.
const onPointerMove = () => deliver()
const onPointerUp = () => release("pointerup")
document.addEventListener("pointermove", onPointerMove, true)
document.addEventListener("pointerup", onPointerUp, true)
}
})()` })
try {
await page.goto(`${url}?session=1`)
await page.getByRole("button", { name: /Changements/ }).click()
await page.getByRole("button", { name: "Aperçu du fichier · src/components/git-panel.tsx", exact: true }).click()
const line = page.locator(".git-diff-view .editor.modified .view-line").filter({ hasText: 'title: "Git"' })
await line.waitFor()
await page.waitForFunction(() => Boolean((window as any).releaseDiff))
assert.equal(await page.locator(".git-diff-view .line-insert").count(), 0, "visible model text precedes diff computation")
// Arm reply delivery at the readiness probe or native pointer movement;
// keep stale DOM until the probe or native pointer-up releases painting.
// Native hit testing and Playwright's actionability frames stay live.
await page.evaluate(() => (window as any).deferDiffPaint())
await clickModifiedDiffLine(page)
await page.keyboard.press("Home")
await page.keyboard.press("Shift+End")
assert.equal(await page.getByRole("menuitem", { name: "Copy changed lines", exact: true }).count(), 0, "selection never opens the deleted-code menu")
assert.equal(await page.evaluate(() => (window as any).diffPaintReleaseBoundary), "readiness")
assert.equal(await page.evaluate(() => (window as any).monaco.editor.getDiffEditors()[0].getModifiedEditor().getSelection().isEmpty()), false)
await page.getByRole("button", { name: "Ajouter au prompt", exact: true }).click()
assert.match(await page.locator(".session-view textarea.prompt-input").inputValue(), /File: src\/components\/git-panel.tsx : 3-3/)
} finally { await page.close() }
})

test("central diff inserts local lines and revision-qualified history into the real session composer", async () => {
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } })
const errors: string[] = []
Expand All @@ -796,8 +890,7 @@ test("central diff inserts local lines and revision-qualified history into the r
await composer.fill("Conserver ce brouillon")
await page.getByRole("button", { name: /Changements/ }).click()
await page.getByRole("button", { name: "Aperçu du fichier · src/components/git-panel.tsx", exact: true }).click()
const modifiedLine = page.locator(".git-diff-view .editor.modified .view-line").filter({ hasText: 'title: "Git"' })
await modifiedLine.click()
await clickModifiedDiffLine(page)
await page.keyboard.press("Home")
await page.keyboard.press("Shift+End")
// Git status reconciliation reopens a fresh, identical target to reload the
Expand Down Expand Up @@ -831,7 +924,7 @@ test("central diff inserts local lines and revision-qualified history into the r

// Native editor keyboard selection must keep the full range, not just the hovered line.
await page.getByRole("button", { name: "Aperçu du fichier · src/styles/panels/git-history.css", exact: true }).click()
await modifiedLine.click()
await clickModifiedDiffLine(page)
await page.keyboard.press("Control+Home")
await page.keyboard.press("ArrowDown")
await page.keyboard.press("ArrowDown")
Expand All @@ -843,7 +936,7 @@ test("central diff inserts local lines and revision-qualified history into the r
await page.getByRole("button", { name: "Commits", exact: true }).first().click()
await page.getByRole("button", { name: /Make Git history the starting point/ }).click()
await page.getByRole("button", { name: "Aperçu du fichier · src/components/git-panel.tsx", exact: true }).click()
await modifiedLine.click()
await clickModifiedDiffLine(page)
await page.keyboard.press("Home")
await page.keyboard.press("Shift+End")
await insert.click()
Expand All @@ -861,7 +954,7 @@ test("central diff inserts local lines and revision-qualified history into the r
await page.getByRole("combobox", { name: "Worktree à consulter" }).selectOption("review")
await page.getByRole("button", { name: /Changements/ }).click()
await page.getByRole("button", { name: "Aperçu du fichier · src/components/git-panel.tsx", exact: true }).click()
await modifiedLine.click()
await clickModifiedDiffLine(page)
await page.keyboard.press("Home")
await page.keyboard.press("Shift+End")
await insert.click()
Expand Down
Loading