mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 02:57:36 +00:00
test: prove diff reveal cancels a paging rail jump (#29585)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
+101
-106
@@ -44,7 +44,6 @@ import {
|
||||
layout,
|
||||
marker,
|
||||
overrideLayoutProperty,
|
||||
scrollTranscript,
|
||||
session,
|
||||
stubLayout,
|
||||
stubResizeObserver
|
||||
@@ -270,8 +269,10 @@ describe('jumping from the rail while following the end', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: prompt }))
|
||||
if (loaded) {
|
||||
expect(Math.abs(rowOffsetFromViewportTop(prompt))).toBeLessThanOrEqual(2)
|
||||
await frame()
|
||||
expect(Math.abs(rowOffsetFromViewportTop(prompt))).toBeLessThanOrEqual(2)
|
||||
}
|
||||
await settle(60)
|
||||
await settle(loaded ? 59 : 60)
|
||||
|
||||
expect(HTMLElement.prototype.scrollTo).toHaveBeenCalled()
|
||||
expect(HTMLElement.prototype.scrollTo).not.toHaveBeenCalledWith(
|
||||
@@ -424,115 +425,109 @@ describe('jumping from the rail while following the end', () => {
|
||||
expect(distanceFromBottom()).toBe(0)
|
||||
})
|
||||
|
||||
it('lands a jump to a loaded message within one frame', async () => {
|
||||
render(<PagedTranscript />)
|
||||
await settle(10)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'prompt-45' }))
|
||||
await frame()
|
||||
|
||||
// A smooth scroll would still be easing in here, a few pixels from where it started.
|
||||
expect(Math.abs(rowOffsetFromViewportTop('prompt-45'))).toBeLessThanOrEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('revealing a diff while a rail jump pages', () => {
|
||||
let restoreLayout = (): void => {}
|
||||
beforeEach(() => {
|
||||
restoreLayout = stubLayout()
|
||||
})
|
||||
afterEach(() => {
|
||||
restoreLayout()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
function journalItem(itemId: string, body: AgentJournalItemBody, sequence: number) {
|
||||
return { itemId, body, sequence, observedAt: sequence * 1000, revision: 1 }
|
||||
}
|
||||
function prompt(itemId: string, text: string, sequence: number) {
|
||||
return journalItem(
|
||||
itemId,
|
||||
{ kind: 'message', role: 'user', blocks: [{ type: 'text', text }] },
|
||||
sequence
|
||||
)
|
||||
}
|
||||
|
||||
const patch = '@@ -1 +1 @@\n-before\n+after'
|
||||
const OLDER = prompt('older', 'Oldest prompt', 1)
|
||||
const LOADED: AgentJournalRenderItem[] = [
|
||||
prompt('user', 'Edit it', 2),
|
||||
journalItem(
|
||||
'diff',
|
||||
{
|
||||
kind: 'diff',
|
||||
path: 'src/a.ts',
|
||||
patch: { head: patch, truncated: false, digest: 'fixture', byteLength: patch.length }
|
||||
},
|
||||
3
|
||||
),
|
||||
prompt('user-2', 'Second prompt', 4),
|
||||
prompt('user-3', 'Third prompt', 5),
|
||||
...Array.from({ length: 200 }, (_, index) =>
|
||||
journalItem(
|
||||
`tail-${index}`,
|
||||
{ kind: 'message', role: 'assistant', blocks: [{ type: 'text', text: `marker-${index}` }] },
|
||||
index + 6
|
||||
describe('revealing a diff while a rail jump pages', () => {
|
||||
function journalItem(itemId: string, body: AgentJournalItemBody, sequence: number) {
|
||||
return { itemId, body, sequence, observedAt: sequence * 1000, revision: 1 }
|
||||
}
|
||||
function prompt(itemId: string, text: string, sequence: number) {
|
||||
return journalItem(
|
||||
itemId,
|
||||
{ kind: 'message', role: 'user', blocks: [{ type: 'text', text }] },
|
||||
sequence
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
function DiffTranscript({ holdPage }: { holdPage: () => Promise<void> }): React.JSX.Element {
|
||||
const [loadedOlder, setLoadedOlder] = useState(false)
|
||||
const items = useMemo(() => (loadedOlder ? [OLDER, ...LOADED] : LOADED), [loadedOlder])
|
||||
const loadEarlier = useCallback(async (): Promise<NativeChatOlderPageResult> => {
|
||||
await holdPage()
|
||||
setLoadedOlder(true)
|
||||
return 'applied'
|
||||
}, [holdPage])
|
||||
return (
|
||||
<NativeChatMessageList
|
||||
session={{
|
||||
...session(projectStructuredItemsToNativeChat(items)),
|
||||
hasMore: !loadedOlder,
|
||||
loadEarlier
|
||||
}}
|
||||
journalItems={items}
|
||||
railOutline={loadedOlder ? [] : [{ id: 'older', text: 'Oldest prompt', hasImages: false }]}
|
||||
isWorking={false}
|
||||
expandSignal={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const patch = '@@ -1 +1 @@\n-before\n+after'
|
||||
const OLDER = prompt('older', 'Oldest prompt', 1)
|
||||
const LOADED: AgentJournalRenderItem[] = [
|
||||
prompt('user', 'Edit it', 2),
|
||||
journalItem(
|
||||
'diff',
|
||||
{
|
||||
kind: 'diff',
|
||||
path: 'src/a.ts',
|
||||
patch: { head: patch, truncated: false, digest: 'fixture', byteLength: patch.length }
|
||||
},
|
||||
3
|
||||
),
|
||||
...Array.from({ length: 200 }, (_, index) =>
|
||||
journalItem(
|
||||
`tail-${index}`,
|
||||
{
|
||||
kind: 'message',
|
||||
role: 'assistant',
|
||||
blocks: [{ type: 'text', text: `marker-${index}` }]
|
||||
},
|
||||
index + 4
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
it('keeps the diff in view when its page lands', async () => {
|
||||
let release = (): void => {}
|
||||
const holdPage = vi.fn(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
release = resolve
|
||||
})
|
||||
)
|
||||
const scrollTo = vi.fn()
|
||||
vi.spyOn(HTMLElement.prototype, 'scrollTo').mockImplementation(scrollTo)
|
||||
const { container } = render(<DiffTranscript holdPage={holdPage} />)
|
||||
function DiffTranscript({ holdPage }: { holdPage: () => Promise<void> }): React.JSX.Element {
|
||||
const [loadedOlder, setLoadedOlder] = useState(false)
|
||||
const items = useMemo(() => (loadedOlder ? [OLDER, ...LOADED] : LOADED), [loadedOlder])
|
||||
const loadEarlier = useCallback(async (): Promise<NativeChatOlderPageResult> => {
|
||||
await holdPage()
|
||||
setLoadedOlder(true)
|
||||
return 'applied'
|
||||
}, [holdPage])
|
||||
return (
|
||||
<NativeChatMessageList
|
||||
session={{
|
||||
...session(projectStructuredItemsToNativeChat(items)),
|
||||
hasMore: !loadedOlder,
|
||||
loadEarlier
|
||||
}}
|
||||
journalItems={items}
|
||||
railOutline={
|
||||
loadedOlder ? [] : [{ id: 'older', text: 'Oldest prompt', hasImages: false }]
|
||||
}
|
||||
isWorking={false}
|
||||
expandSignal={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Oldest prompt' }))
|
||||
// Anti-vacuous: the older page is in flight.
|
||||
expect(holdPage).toHaveBeenCalledTimes(1)
|
||||
fireEvent.click(screen.getByRole('button', { name: /1 changed file/ }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /src\/a.ts/ }))
|
||||
scrollTranscript(container, 6000)
|
||||
expect(screen.getByText('Edited')).toBeInTheDocument()
|
||||
scrollTo.mockClear()
|
||||
function expectDiffInView(): void {
|
||||
expect(
|
||||
screen.getByRole('button', { name: /^Edited .*a\.ts(?:\s|$)/, expanded: true })
|
||||
).toBeInTheDocument()
|
||||
const row = screen.getByText('Edited').closest<HTMLElement>('[data-index]')
|
||||
if (!row) {
|
||||
throw new Error('revealed diff has no mounted row')
|
||||
}
|
||||
const top = layout.aboveTranscriptPx + row.offsetTop - scroller().scrollTop
|
||||
expect(row.offsetHeight).toBeGreaterThan(0)
|
||||
expect(top).toBeGreaterThanOrEqual(-2)
|
||||
expect(top).toBeLessThan(scroller().clientHeight - 2)
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
release()
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
||||
it('keeps the diff in view when its page lands', async () => {
|
||||
const pages = holdFirstPage()
|
||||
render(<DiffTranscript holdPage={pages.holdPage} />)
|
||||
await settle(10)
|
||||
expect(distanceFromBottom()).toBe(0)
|
||||
expect(screen.queryByText('Edited')).toBeNull()
|
||||
|
||||
await pickUnloadedWhilePaging('Oldest prompt')
|
||||
expect(pages.asked()).toBe(1)
|
||||
expect(screen.getByRole('button', { name: 'Oldest prompt' })).toHaveAttribute(
|
||||
'aria-busy',
|
||||
'true'
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /1 changed file/ }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /src\/a.ts/ }))
|
||||
expect(screen.getByRole('button', { name: 'Oldest prompt' })).not.toHaveAttribute('aria-busy')
|
||||
await settle(40)
|
||||
expectDiffInView()
|
||||
|
||||
pages.release()
|
||||
await settle(60)
|
||||
|
||||
expect(pages.asked()).toBe(1)
|
||||
expect(screen.getByRole('button', { name: 'Oldest prompt' })).not.toHaveAttribute('aria-busy')
|
||||
expectDiffInView()
|
||||
})
|
||||
|
||||
// The abandoned jump would have taken the pin and smooth-scrolled up to the oldest
|
||||
// prompt; the prepend's own anchoring is an instant write.
|
||||
expect(scrollTo.mock.calls.filter(([options]) => options?.behavior === 'smooth')).toEqual([])
|
||||
expect(screen.getByText('Edited')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user