Skip to content
Merged
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
79 changes: 78 additions & 1 deletion web/src/components/SessionList.directory-action.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { ReactNode } from 'react'
import type { SessionSummary } from '@/types/api'
import { I18nProvider } from '@/lib/i18n-context'
Expand Down Expand Up @@ -104,6 +104,83 @@ describe('SessionList directory action', () => {
})
})

describe('SessionList time filter', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date(2026, 6, 18, 12))
})

afterEach(() => {
vi.useRealTimers()
})

it('filters after selecting a start and end date', () => {
const recent = makeSession({
id: 'recent',
updatedAt: Date.now(),
metadata: { path: '/work/recent', name: 'Recent session' }
})
const old = makeSession({
id: 'old',
updatedAt: new Date(2020, 0, 1).getTime(),
metadata: { path: '/work/old', name: 'Old session' }
})

renderWithProviders(
<SessionList
sessions={[recent, old]}
selectedSessionId={null}
onSelect={vi.fn()}
onNewSession={vi.fn()}
onRefresh={vi.fn()}
isLoading={false}
renderHeader={false}
api={null}
/>
)

expect(screen.getByRole('button', { name: /Recent session/ })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /Old session/ })).toBeInTheDocument()

fireEvent.click(screen.getByRole('button', { name: 'Filter sessions by last activity' }))
fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 17).toLocaleDateString() }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MINOR] This hard-coded July 2026 selection is coupled to the picker’s current-month default and the session’s Date.now() timestamp. When the test runs outside July 17-18, 2026, the date buttons will not exist or the recent session will be outside the selected range. The same issue appears in the second added test’s July 2026 clicks.

Suggested fix:

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

describe('SessionList time filter', () => {
    beforeEach(() => {
        vi.useFakeTimers()
        vi.setSystemTime(new Date(2026, 6, 18, 12))
    })

    afterEach(() => {
        vi.useRealTimers()
    })

    // existing July 2026 assertions stay deterministic
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 672cc71 by pinning the system clock with Vitest fake timers for the date-range UI tests. Focused tests and the web typecheck pass.

fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 18).toLocaleDateString() }))

expect(screen.getByRole('button', { name: /Recent session/ })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: /Old session/ })).toBeNull()
})

it('uses the first calendar click as start and the second as end', () => {
const session = makeSession({
id: 'session-1',
updatedAt: Date.now(),
metadata: { path: '/work/hapi', name: 'Session' }
})

renderWithProviders(
<SessionList
sessions={[session]}
selectedSessionId={null}
onSelect={vi.fn()}
onNewSession={vi.fn()}
onRefresh={vi.fn()}
isLoading={false}
renderHeader={false}
api={null}
/>
)

const filterButton = screen.getByRole('button', { name: 'Filter sessions by last activity' })
fireEvent.click(filterButton)
fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 1).toLocaleDateString() }))
expect(screen.getByText('Select end date')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 18).toLocaleDateString() }))

expect(filterButton).toHaveAttribute('aria-expanded', 'false')
expect(filterButton).toHaveAttribute('title', '2026-07-01 – 2026-07-18')
})
})

describe('SessionList action menu parity', () => {
it.each([
['running', true],
Expand Down
19 changes: 19 additions & 0 deletions web/src/components/SessionList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
deduplicateSessionsByAgentId,
expandSelectedSessionCollapseOverrides,
filterActiveSessionsOnly,
getSessionTimeRange,
getNextSessionVisibleCount,
getSessionDedupKey,
getWorktreeSessionLabel,
Expand All @@ -12,6 +13,7 @@ import {
normalizeSearch,
prepareSidebarSessions,
sessionMatchesQuery,
sessionMatchesTimeRange,
shouldShowSessionInSidebar
} from './SessionList'

Expand Down Expand Up @@ -308,6 +310,23 @@ describe('session list search helpers', () => {
})
})

describe('session list time filter helpers', () => {
it('treats the selected end date as inclusive in local time', () => {
const range = getSessionTimeRange('2026-07-01', '2026-07-18')
expect(range).toEqual({
start: new Date(2026, 6, 1).getTime(),
end: new Date(2026, 6, 19).getTime()
})
expect(sessionMatchesTimeRange(makeSession({ id: 'inside', updatedAt: new Date(2026, 6, 18, 23, 59).getTime() }), range)).toBe(true)
expect(sessionMatchesTimeRange(makeSession({ id: 'outside', updatedAt: new Date(2026, 6, 19).getTime() }), range)).toBe(false)
})

it('does not filter until both dates are selected', () => {
expect(getSessionTimeRange('', '')).toBeNull()
expect(getSessionTimeRange('2026-07-01', '')).toBeNull()
})
})

describe('getVisibleSessionPreview', () => {
it('keeps selected and pending sessions inside the collapsed preview without promoting them', () => {
const sessions = Array.from({ length: 6 }, (_, index) => makeSession({
Expand Down
Loading
Loading