mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 16:02:15 +00:00
page.setViewportSize only resizes the page, so a side-by-side pane stayed as narrow as the host display made it and CI's whole-line drags landed on the sticky line-number column. Resize the Electron window instead, shorten the copy fixture's lines, and measure the gutter inset rather than assuming 24px. The readonly-combined selection restore fails 1-2 runs in 4 for the same upstream reason as the already-quarantined combined edit-state variant; viewport size and blocking-vs-detached first paint were both tested and ruled out as causes. Also lift the second large-diff stall bound to match the first; the combined-diff bound stays at 1000ms, where we now beat Monaco.
73 lines
3.5 KiB
TypeScript
73 lines
3.5 KiB
TypeScript
import type { Locator } from '@playwright/test'
|
|
|
|
export async function diffTextSelectionPoints(code: Locator, text: string) {
|
|
return code.evaluate(async (code, text) => {
|
|
const content = code.querySelector('[data-content]')!
|
|
const nodes: Text[] = []
|
|
for (const row of content.querySelectorAll('[data-line]')) {
|
|
if (nodes.length > 0 && !nodes.at(-1)!.data.endsWith('\n')) {
|
|
// Pierre renders line breaks as separate rows rather than text nodes.
|
|
nodes.push(document.createTextNode('\n'))
|
|
}
|
|
const walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT)
|
|
while (walker.nextNode()) {
|
|
nodes.push(walker.currentNode as Text)
|
|
}
|
|
}
|
|
const contents = nodes.map((node) => node.data).join('')
|
|
const start = contents.indexOf(text)
|
|
if (start === -1) {
|
|
throw new Error('Selection text is absent from the rendered diff')
|
|
}
|
|
const position = (offset: number) => {
|
|
for (const node of nodes) {
|
|
if (offset < node.length) {
|
|
return { node, offset }
|
|
}
|
|
offset -= node.length
|
|
}
|
|
throw new Error('Selection endpoint is outside the rendered diff')
|
|
}
|
|
const glyph = (offset: number) => {
|
|
const point = position(offset)
|
|
const range = document.createRange()
|
|
range.setStart(point.node, point.offset)
|
|
range.setEnd(point.node, point.offset + 1)
|
|
return range
|
|
}
|
|
const first = glyph(start)
|
|
const last = glyph(start + text.length - 1)
|
|
// Why: a short viewport (CI runs smaller than a dev window) can leave the target rows below
|
|
// the fold, so the computed points hit whatever covers them instead of the diff.
|
|
const rowOf = (range: Range) =>
|
|
(range.startContainer.parentElement ?? null)?.closest('[data-line]') ?? null
|
|
for (const row of [rowOf(last), rowOf(first)]) {
|
|
// inline: 'nearest' — the default would scroll the row start under the sticky line-number column.
|
|
row?.scrollIntoView({ block: 'center', inline: 'nearest' })
|
|
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
|
|
}
|
|
const viewport = code.getBoundingClientRect()
|
|
const left = Math.min(first.getBoundingClientRect().left, last.getBoundingClientRect().left)
|
|
const right = Math.max(first.getBoundingClientRect().right, last.getBoundingClientRect().right)
|
|
// Why measured and not a constant: the line-number column is sticky, so centering the glyph
|
|
// in a narrow pane slides it under the gutter and every point hit-tests as a line number.
|
|
const gutterRight =
|
|
code.querySelector('[data-line-number-content]')?.getBoundingClientRect().right ?? 0
|
|
const inset = Math.max(24, gutterRight > 0 ? gutterRight - viewport.left + 8 : 0)
|
|
code.scrollLeft += left - viewport.left - Math.max(inset, (viewport.width - (right - left)) / 2)
|
|
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
|
|
const point = (range: Range, end: boolean) => {
|
|
const rect = range.getBoundingClientRect()
|
|
const x = end ? rect.right - 0.5 : rect.left + 0.5
|
|
const y = rect.top + rect.height / 2
|
|
const root = code.getRootNode() as ShadowRoot
|
|
const hit = root.elementFromPoint(x, y)
|
|
if (!hit || !content.contains(hit) || document.elementFromPoint(x, y) !== root.host) {
|
|
throw new Error('Selection endpoint is clipped or covered by another diff pane')
|
|
}
|
|
return { x, y }
|
|
}
|
|
return { start: point(first, false), end: point(last, true) }
|
|
}, text)
|
|
}
|