Skip to content
Open
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
56 changes: 56 additions & 0 deletions frontend/src/components/Chat/FindingEvidenceDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,62 @@ it('does not keep a cancelled selection when the picker reopens', async () => {
expect(screen.getByRole('button', { name: 'Attach attack' })).toBeDisabled()
})

it('restarts pagination and search when the picker reopens', async () => {
const user = userEvent.setup()
;(operationsApi.listFindings as jest.Mock).mockResolvedValue({
items: [FINDING], has_more: true, next_offset: 20,
})
renderDialog()
await waitFor(() => { expect(screen.getByRole('button', { name: 'Link to finding' })).toBeEnabled() })
await user.click(screen.getByRole('button', { name: 'Link to finding' }))
await screen.findByRole('radio')
await user.click(screen.getByRole('button', { name: 'Next findings' }))
await waitFor(() => {
expect(operationsApi.listFindings).toHaveBeenLastCalledWith('saved', { limit: 20, offset: 20, title: '' })
})

await user.type(screen.getByRole('textbox'), 'Proof')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search reset is covered now, but the page is already back at offset 0 before the dialog closes. The search handler calls setOffset(0), and the next assertion confirms it. That means reopening no longer has a later page to reset. I checked that removing only setOffset(0) from the reopening block still leaves this test passing.

Could we click Next findings after the Proof search settles, check that the request uses offset: 20 and title: 'Proof', then cancel and reopen? The final request should use offset: 0 and title: ''. Keeping a separate pagination-only reopen test would also protect the original bug.

await waitFor(() => {
expect(operationsApi.listFindings).toHaveBeenLastCalledWith('saved', { limit: 20, offset: 0, title: 'Proof' })
})
await user.click(screen.getByRole('button', { name: 'Next findings' }))
await waitFor(() => {
expect(operationsApi.listFindings).toHaveBeenLastCalledWith('saved', { limit: 20, offset: 20, title: 'Proof' })
})

await user.click(screen.getByRole('button', { name: 'Cancel' }))
await waitFor(() => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument() })
await user.click(screen.getByRole('button', { name: 'Link to finding' }))
await screen.findByRole('radio')
expect(screen.getByRole('textbox')).toHaveValue('')
await waitFor(() => {
expect(operationsApi.listFindings).toHaveBeenLastCalledWith('saved', { limit: 20, offset: 0, title: '' })
})
})

it('restarts pagination when the picker reopens without a search', async () => {
const user = userEvent.setup()
;(operationsApi.listFindings as jest.Mock).mockResolvedValue({
items: [FINDING], has_more: true, next_offset: 20,
})
renderDialog()
await waitFor(() => { expect(screen.getByRole('button', { name: 'Link to finding' })).toBeEnabled() })
await user.click(screen.getByRole('button', { name: 'Link to finding' }))
await screen.findByRole('radio')
await user.click(screen.getByRole('button', { name: 'Next findings' }))
await waitFor(() => {
expect(operationsApi.listFindings).toHaveBeenLastCalledWith('saved', { limit: 20, offset: 20, title: '' })
})

await user.click(screen.getByRole('button', { name: 'Cancel' }))
await waitFor(() => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument() })
await user.click(screen.getByRole('button', { name: 'Link to finding' }))
await screen.findByRole('radio')
await waitFor(() => {
expect(operationsApi.listFindings).toHaveBeenLastCalledWith('saved', { limit: 20, offset: 0, title: '' })
Comment thread
romanlutz marked this conversation as resolved.
Resolved
})
})

it('drops the selection when the viewed attack changes while the picker is open', async () => {
const user = userEvent.setup()
const { rerender } = render(<FluentProvider theme={webLightTheme}><MemoryRouter>
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/Chat/FindingEvidenceDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ export default function FindingEvidenceDialog({
const newFindingRef = useRef<HTMLButtonElement>(null)
const wasOpen = useRef(false)
const wasCreating = useRef(false)
// Each opening starts without a previous selection or result.
// Each opening starts without a previous selection or result, and back at the
// first page of an unfiltered finding list, so a later opening never resumes
// the previous opening's pagination or search.
if (open !== shownOpen) {
setShownOpen(open)
if (open) { setPicked(null); setNotice(null); setAttachError('') }
if (open) { setPicked(null); setNotice(null); setAttachError(''); setOffset(0); setTitle('') }
}
// Evidence can come from any attack; the user picks whose findings to link, defaulting to the attack's own Operation.
const operation = chosen?.key === viewKey ? chosen.operation
Expand Down
Loading