mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
375 lines
14 KiB
TypeScript
375 lines
14 KiB
TypeScript
import { execFileSync } from 'node:child_process'
|
||
import { mkdirSync, writeFileSync } from 'node:fs'
|
||
import path from 'node:path'
|
||
import type { Page } from '@stablyai/playwright-test'
|
||
import { expect, test } from './helpers/orca-app'
|
||
import { pressShortcut } from './helpers/shortcuts'
|
||
import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
||
|
||
// Exercise real gutter ownership and pointer tracking: the range must survive composer open without starting Monaco selection.
|
||
|
||
const BAND = '.orca-diff-comment-range-highlight'
|
||
const COMPOSER = '.orca-diff-comment-popover'
|
||
const COMPOSER_LABEL = '.orca-diff-comment-popover-label'
|
||
const COMPOSER_TEXTAREA = '.orca-diff-comment-popover-textarea'
|
||
|
||
type StoredNote = { startLine?: number; lineNumber: number; body: string }
|
||
|
||
// Commit a baseline: untracked files open without the diff gutter this gesture needs.
|
||
const SEED_BASELINE = 'export const seed = true\n'
|
||
const SEED_LINE_COUNT = 24
|
||
// Wait for the highest tested line so first-cell paint cannot hide an unloaded modified model.
|
||
const SEED_SYNC_LINE = 9
|
||
|
||
async function seedDiffFile(page: Page, worktreeId: string, relative: string): Promise<void> {
|
||
const { worktreePath, storeRel } = await page.evaluate(
|
||
({ wId, rel }) => {
|
||
const store = window.__store
|
||
if (!store) {
|
||
throw new Error('window.__store is not available - is the app in dev mode?')
|
||
}
|
||
const worktree = Object.values(store.getState().worktreesByRepo)
|
||
.flat()
|
||
.find((entry) => entry.id === wId)
|
||
if (!worktree) {
|
||
throw new Error('active worktree not found')
|
||
}
|
||
const separator = worktree.path.includes('\\') ? '\\' : '/'
|
||
return { worktreePath: worktree.path, storeRel: rel.split('/').join(separator) }
|
||
},
|
||
{ wId: worktreeId, rel: relative }
|
||
)
|
||
|
||
// Commit before rewriting, idempotently across reruns on the worker-scoped repo.
|
||
const absolutePath = path.join(worktreePath, ...relative.split('/'))
|
||
mkdirSync(path.dirname(absolutePath), { recursive: true })
|
||
writeFileSync(absolutePath, SEED_BASELINE)
|
||
const porcelain = execFileSync('git', ['status', '--porcelain', '--', relative], {
|
||
cwd: worktreePath,
|
||
encoding: 'utf8'
|
||
})
|
||
if (porcelain.trim() !== '') {
|
||
execFileSync('git', ['add', '--', relative], { cwd: worktreePath, stdio: 'pipe' })
|
||
execFileSync('git', ['commit', '-m', `e2e seed ${relative} for diff-note-range`], {
|
||
cwd: worktreePath,
|
||
stdio: 'pipe'
|
||
})
|
||
}
|
||
const lines = Array.from(
|
||
{ length: SEED_LINE_COUNT },
|
||
(_, index) => `export const line${String(index + 1).padStart(2, '0')} = ${index + 1}`
|
||
)
|
||
writeFileSync(absolutePath, `${lines.join('\n')}\n`)
|
||
|
||
await page.evaluate(
|
||
async ({ wId, rel }) => {
|
||
const store = window.__store
|
||
if (!store) {
|
||
throw new Error('window.__store is not available - is the app in dev mode?')
|
||
}
|
||
const state = store.getState()
|
||
const worktree = Object.values(state.worktreesByRepo)
|
||
.flat()
|
||
.find((entry) => entry.id === wId)
|
||
if (!worktree) {
|
||
throw new Error('active worktree not found')
|
||
}
|
||
const separator = worktree.path.includes('\\') ? '\\' : '/'
|
||
// Inline view keeps modified line numbers in the gesture’s margin.
|
||
await state.updateSettings({ diffDefaultView: 'inline' })
|
||
state.openDiff(wId, `${worktree.path}${separator}${rel}`, rel, 'typescript', false)
|
||
},
|
||
{ wId: worktreeId, rel: storeRel }
|
||
)
|
||
await expect(
|
||
page.locator('.modified-in-monaco-diff-editor .margin .line-numbers').first(),
|
||
'diff gutter never rendered'
|
||
).toBeVisible({ timeout: 20_000 })
|
||
await expect
|
||
.poll(async () => hasGutterLine(page, SEED_SYNC_LINE), {
|
||
timeout: 20_000,
|
||
message: 'modified diff content never rendered its gutter lines'
|
||
})
|
||
.toBe(true)
|
||
}
|
||
|
||
async function readGutterPoint(
|
||
page: Page,
|
||
lineNumber: number
|
||
): Promise<{ x: number; y: number } | null> {
|
||
return page.evaluate((target: number) => {
|
||
const editor = document.querySelector('.monaco-editor.modified-in-monaco-diff-editor')
|
||
if (!editor) {
|
||
return null
|
||
}
|
||
for (const cell of editor.querySelectorAll('.margin .line-numbers')) {
|
||
if (Number.parseInt(cell.textContent?.trim() ?? '', 10) === target) {
|
||
const rect = cell.getBoundingClientRect()
|
||
return { x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) }
|
||
}
|
||
}
|
||
return null
|
||
}, lineNumber)
|
||
}
|
||
|
||
async function hasGutterLine(page: Page, lineNumber: number): Promise<boolean> {
|
||
return (await readGutterPoint(page, lineNumber)) !== null
|
||
}
|
||
|
||
// Centre of a line's number cell — the column the "+" lives in and the gesture starts from.
|
||
async function gutterPoint(page: Page, lineNumber: number): Promise<{ x: number; y: number }> {
|
||
let point: { x: number; y: number } | null = null
|
||
await expect
|
||
.poll(
|
||
async () => {
|
||
point = await readGutterPoint(page, lineNumber)
|
||
return point
|
||
},
|
||
{
|
||
timeout: 20_000,
|
||
message: `line ${lineNumber} is not rendered in the modified gutter`
|
||
}
|
||
)
|
||
.not.toBeNull()
|
||
if (point == null) {
|
||
throw new Error(`line ${lineNumber} is not rendered in the modified gutter`)
|
||
}
|
||
return point
|
||
}
|
||
|
||
// Box of the "+" affordance, which only exists while its line is hovered.
|
||
async function revealAddButton(
|
||
page: Page,
|
||
lineNumber: number
|
||
): Promise<{ x: number; y: number; top: string }> {
|
||
const line = await gutterPoint(page, lineNumber)
|
||
await page.mouse.move(line.x + 260, line.y)
|
||
const button = page.locator('.orca-diff-comment-add-btn')
|
||
await expect(button, 'the "+" never appeared on the hovered line').toBeVisible()
|
||
const box = await button.boundingBox()
|
||
if (!box) {
|
||
throw new Error('the "+" has no box')
|
||
}
|
||
const top = await button.evaluate((node) => {
|
||
if (!(node instanceof HTMLElement)) {
|
||
throw new Error('the add button is not an HTMLElement')
|
||
}
|
||
return node.style.top
|
||
})
|
||
return {
|
||
x: Math.round(box.x + box.width / 2),
|
||
y: Math.round(box.y + box.height / 2),
|
||
top
|
||
}
|
||
}
|
||
|
||
async function readNotes(page: Page, worktreeId: string): Promise<StoredNote[]> {
|
||
return page.evaluate((wId: string) => {
|
||
const worktree = Object.values(window.__store?.getState().worktreesByRepo ?? {})
|
||
.flat()
|
||
.find((entry) => entry.id === wId)
|
||
return (worktree?.diffComments ?? []).map((comment) => ({
|
||
startLine: comment.startLine,
|
||
lineNumber: comment.lineNumber,
|
||
body: comment.body
|
||
}))
|
||
}, worktreeId)
|
||
}
|
||
|
||
async function submitNote(page: Page, body: string): Promise<void> {
|
||
await page.locator(COMPOSER_TEXTAREA).fill(body)
|
||
await page.keyboard.press('Enter')
|
||
await expect(page.locator(COMPOSER), 'composer stayed open after submit').toHaveCount(0, {
|
||
timeout: 10_000
|
||
})
|
||
}
|
||
|
||
test.describe('Diff note line range', () => {
|
||
test.beforeEach(async ({ orcaPage }) => {
|
||
await waitForSessionReady(orcaPage)
|
||
await waitForActiveWorktree(orcaPage)
|
||
})
|
||
|
||
test('dragging the gutter selects a range, keeps it lit, and saves one ranged note', async ({
|
||
orcaPage
|
||
}) => {
|
||
const worktreeId = await waitForActiveWorktree(orcaPage)
|
||
await seedDiffFile(orcaPage, worktreeId, 'src/diff-note-range-drag.ts')
|
||
|
||
const from = await gutterPoint(orcaPage, 4)
|
||
const to = await gutterPoint(orcaPage, 9)
|
||
|
||
await orcaPage.mouse.move(from.x, from.y)
|
||
await orcaPage.mouse.down()
|
||
await expect(orcaPage.locator(BAND)).toHaveCount(1)
|
||
|
||
await orcaPage.mouse.move(to.x, (from.y + to.y) / 2)
|
||
await orcaPage.mouse.move(to.x, to.y)
|
||
await expect(
|
||
orcaPage.locator(BAND),
|
||
'the band did not follow the pointer while the button was held'
|
||
).toHaveCount(6)
|
||
|
||
await orcaPage.mouse.up()
|
||
|
||
await expect(orcaPage.locator(COMPOSER_LABEL)).toHaveText('Lines 4-9')
|
||
await expect(
|
||
orcaPage.locator(BAND),
|
||
'the band should stay lit while the note is being written'
|
||
).toHaveCount(6)
|
||
// The gutter press belongs to us, so Monaco never started a text selection under it.
|
||
expect(await orcaPage.evaluate(() => window.getSelection()?.toString() ?? '')).toBe('')
|
||
|
||
await submitNote(orcaPage, 'Collapse these six lines into a loop.')
|
||
|
||
expect(await readNotes(orcaPage, worktreeId)).toEqual([
|
||
{ startLine: 4, lineNumber: 9, body: 'Collapse these six lines into a loop.' }
|
||
])
|
||
const card = orcaPage.locator('.orca-diff-comment-card').first()
|
||
await expect(card, 'saved note card did not render').toBeVisible({ timeout: 15_000 })
|
||
await expect(card).toContainText('lines 4-9')
|
||
await expect(orcaPage.locator(BAND)).toHaveCount(0)
|
||
})
|
||
|
||
// Drag upward to prove anchor/focus stay distinct and commit in document order.
|
||
test('dragging the gutter upward commits the same range as dragging down', async ({
|
||
orcaPage
|
||
}) => {
|
||
const worktreeId = await waitForActiveWorktree(orcaPage)
|
||
await seedDiffFile(orcaPage, worktreeId, 'src/diff-note-range-drag-up.ts')
|
||
|
||
const from = await gutterPoint(orcaPage, 9)
|
||
const to = await gutterPoint(orcaPage, 4)
|
||
|
||
await orcaPage.mouse.move(from.x, from.y)
|
||
await orcaPage.mouse.down()
|
||
await expect(orcaPage.locator(BAND)).toHaveCount(1)
|
||
|
||
await orcaPage.mouse.move(to.x, (from.y + to.y) / 2)
|
||
await orcaPage.mouse.move(to.x, to.y)
|
||
await expect(
|
||
orcaPage.locator(BAND),
|
||
'the band did not grow upward while the button was held'
|
||
).toHaveCount(6)
|
||
|
||
await orcaPage.mouse.up()
|
||
|
||
await expect(orcaPage.locator(COMPOSER_LABEL)).toHaveText('Lines 4-9')
|
||
expect(await orcaPage.evaluate(() => window.getSelection()?.toString() ?? '')).toBe('')
|
||
|
||
await submitNote(orcaPage, 'Dragged bottom to top.')
|
||
|
||
expect(await readNotes(orcaPage, worktreeId)).toEqual([
|
||
{ startLine: 4, lineNumber: 9, body: 'Dragged bottom to top.' }
|
||
])
|
||
await expect(orcaPage.locator('.orca-diff-comment-card').first()).toContainText('lines 4-9')
|
||
})
|
||
|
||
// Start on the non-Monaco + node to exercise the hit-test path that previously lost the whole drag.
|
||
test('dragging from the "+" itself selects a range and the button rides the selection', async ({
|
||
orcaPage
|
||
}) => {
|
||
const worktreeId = await waitForActiveWorktree(orcaPage)
|
||
await seedDiffFile(orcaPage, worktreeId, 'src/diff-note-range-button-drag.ts')
|
||
|
||
const button = await revealAddButton(orcaPage, 4)
|
||
const to = await gutterPoint(orcaPage, 9)
|
||
|
||
await orcaPage.mouse.move(button.x, button.y)
|
||
await orcaPage.mouse.down()
|
||
await orcaPage.mouse.move(button.x, (button.y + to.y) / 2)
|
||
await orcaPage.mouse.move(button.x, to.y)
|
||
|
||
await expect(
|
||
orcaPage.locator(BAND),
|
||
'the drag stalled because the pointer stayed over the "+"'
|
||
).toHaveCount(6)
|
||
await expect
|
||
.poll(
|
||
async () =>
|
||
orcaPage.locator('.orca-diff-comment-add-btn').evaluate((node) => {
|
||
if (!(node instanceof HTMLElement)) {
|
||
throw new Error('the add button is not an HTMLElement')
|
||
}
|
||
return node.style.top
|
||
}),
|
||
{ message: 'the "+" did not ride the growing end of the selection' }
|
||
)
|
||
.not.toBe(button.top)
|
||
|
||
await orcaPage.mouse.up()
|
||
await expect(orcaPage.locator(COMPOSER_LABEL)).toHaveText('Lines 4-9')
|
||
await submitNote(orcaPage, 'Dragged straight off the plus button.')
|
||
|
||
expect(await readNotes(orcaPage, worktreeId)).toEqual([
|
||
{ startLine: 4, lineNumber: 9, body: 'Dragged straight off the plus button.' }
|
||
])
|
||
})
|
||
|
||
test('a press with no drag still saves the single-line note it always did', async ({
|
||
orcaPage
|
||
}) => {
|
||
const worktreeId = await waitForActiveWorktree(orcaPage)
|
||
await seedDiffFile(orcaPage, worktreeId, 'src/diff-note-range-click.ts')
|
||
|
||
const point = await gutterPoint(orcaPage, 6)
|
||
await orcaPage.mouse.move(point.x, point.y)
|
||
await orcaPage.mouse.down()
|
||
await orcaPage.mouse.up()
|
||
|
||
await expect(orcaPage.locator(COMPOSER_LABEL)).toHaveText('Line 6')
|
||
await expect(orcaPage.locator(BAND)).toHaveCount(1)
|
||
|
||
await submitNote(orcaPage, 'Single line still works.')
|
||
|
||
expect(await readNotes(orcaPage, worktreeId)).toEqual([
|
||
{ startLine: undefined, lineNumber: 6, body: 'Single line still works.' }
|
||
])
|
||
await expect(orcaPage.locator('.orca-diff-comment-card').first()).toContainText('line 6')
|
||
})
|
||
|
||
test('Escape abandons a drag without opening a composer', async ({ orcaPage }) => {
|
||
const worktreeId = await waitForActiveWorktree(orcaPage)
|
||
await seedDiffFile(orcaPage, worktreeId, 'src/diff-note-range-escape.ts')
|
||
|
||
const from = await gutterPoint(orcaPage, 3)
|
||
const to = await gutterPoint(orcaPage, 8)
|
||
await orcaPage.mouse.move(from.x, from.y)
|
||
await orcaPage.mouse.down()
|
||
await orcaPage.mouse.move(to.x, to.y)
|
||
await expect(orcaPage.locator(BAND)).toHaveCount(6)
|
||
|
||
await orcaPage.keyboard.press('Escape')
|
||
await expect(orcaPage.locator(BAND), 'Escape left the band behind').toHaveCount(0)
|
||
|
||
await orcaPage.mouse.up()
|
||
await expect(
|
||
orcaPage.locator(COMPOSER),
|
||
'a cancelled drag must not open a composer on release'
|
||
).toHaveCount(0)
|
||
expect(await readNotes(orcaPage, worktreeId)).toEqual([])
|
||
})
|
||
|
||
test('the Add Review Note chord turns an editor selection into a ranged note', async ({
|
||
orcaPage
|
||
}) => {
|
||
const worktreeId = await waitForActiveWorktree(orcaPage)
|
||
await seedDiffFile(orcaPage, worktreeId, 'src/diff-note-range-chord.ts')
|
||
|
||
// Click the code column, not the gutter: Monaco still owns that side.
|
||
const line = await gutterPoint(orcaPage, 5)
|
||
await orcaPage.mouse.click(line.x + 220, line.y)
|
||
await orcaPage.keyboard.press('Shift+ArrowDown')
|
||
await orcaPage.keyboard.press('Shift+ArrowDown')
|
||
|
||
await pressShortcut(orcaPage, 'KeyA', { shift: true })
|
||
|
||
await expect(orcaPage.locator(COMPOSER_LABEL)).toHaveText('Lines 5-7')
|
||
await submitNote(orcaPage, 'Range from the keyboard.')
|
||
|
||
expect(await readNotes(orcaPage, worktreeId)).toEqual([
|
||
{ startLine: 5, lineNumber: 7, body: 'Range from the keyboard.' }
|
||
])
|
||
})
|
||
})
|