Skip to content
Merged
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
86 changes: 85 additions & 1 deletion frontend/testing/unit/pages/Findings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ vi.mock('../../../src/utils/date', async (importOriginal: any) => {
}
})

const mockScrollToIndex = vi.fn()

vi.mock('@tanstack/react-virtual', async (importOriginal) => {
const actual = await importOriginal<any>()
return {
...actual,
useVirtualizer: (options: any) => {
const virtualizer = actual.useVirtualizer(options)
return {
...virtualizer,
scrollToIndex: mockScrollToIndex,
}
},
}
})

// @tanstack/react-virtual needs ResizeObserver + scrollHeight in jsdom
if (typeof global.ResizeObserver === 'undefined') {
global.ResizeObserver = class ResizeObserver {
Expand All @@ -34,7 +50,6 @@ if (typeof global.ResizeObserver === 'undefined') {
} as any
}


Object.defineProperty(HTMLElement.prototype, 'scrollHeight', { configurable: true, value: 800 })
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 600 })

Expand Down Expand Up @@ -451,3 +466,72 @@ describe('Findings — severity legend help affordance', () => {
)
})
})

describe('Findings — virtualizer scrolling', () => {
beforeEach(() => {
vi.clearAllMocks()
localStorage.clear()
})

it('changing the selected finding triggers the expected scroll call', async () => {
const findings = [
makeFinding({ id: 'f1', title: 'Finding Alpha', severity: 'critical' }),
makeFinding({ id: 'f2', title: 'Finding Beta', severity: 'high' }),
makeFinding({ id: 'f3', title: 'Finding Gamma', severity: 'medium' }),
]
vi.mocked(getFindings).mockResolvedValue({ findings })

render(<Findings />)
await waitFor(() => expect(screen.queryByText('Synchronizing findings feed...')).not.toBeInTheDocument())

// Clear initial scroll call on mount
mockScrollToIndex.mockClear()

// Click Finding Beta to select it
const betaOption = await screen.findByRole('option', { name: /Finding Beta/i })
await userEvent.click(betaOption)

// Assert it called scrollToIndex with correct index (1)
expect(mockScrollToIndex).toHaveBeenCalledWith(1, { align: 'auto', behavior: 'smooth' })
})

it('filtering/sorting does not scroll to stale indexes', async () => {
const findings = [
makeFinding({ id: 'f1', title: 'Finding Alpha', severity: 'critical' }),
makeFinding({ id: 'f2', title: 'Finding Beta', severity: 'high' }),
makeFinding({ id: 'f3', title: 'Finding Gamma', severity: 'medium' }),
]
vi.mocked(getFindings).mockResolvedValue({ findings })

render(<Findings />)
await waitFor(() => expect(screen.queryByText('Synchronizing findings feed...')).not.toBeInTheDocument())

// Select Beta (triggers scroll) and then clear mocks
const betaOption = await screen.findByRole('option', { name: /Finding Beta/i })
await userEvent.click(betaOption)
expect(mockScrollToIndex).toHaveBeenCalled()
mockScrollToIndex.mockClear()

// Filter using search query that includes Beta
const searchInput = screen.getByPlaceholderText(/Title, target, CVE/i)
await userEvent.type(searchInput, 'Beta')

// Expect no scrollToIndex call because selectedFindingId did not change
expect(mockScrollToIndex).not.toHaveBeenCalled()

// Clear search query
await userEvent.clear(searchInput)
expect(mockScrollToIndex).not.toHaveBeenCalled()

// Change sorting mode (while Beta remains visible/selected)
const selects = screen.getAllByRole('combobox')
const sortSelect = selects.find((s) =>
Array.from(s.querySelectorAll('option')).some((o) => /Newest First/i.test(o.textContent || '')),
)
expect(sortSelect).toBeDefined()
await userEvent.selectOptions(sortSelect!, 'newest')

// Expect no scrollToIndex call
expect(mockScrollToIndex).not.toHaveBeenCalled()
})
})
Loading