mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
### Context Adds context menu to the explorer nav to allow users to delete notebooks or chats from there (instead of having to open the notebook / chat first, then select delete from the header) <img width="304" height="339" alt="image" src="https://github.com/user-attachments/assets/2ad0f21c-0d49-407c-97a2-ef11ae59e280" /> ### Changes involved: - Consolidates the rendering of explorer nav items into a single component that both `ExplorerNavNotebook`, `ExplorerNavHome` and `ExplorerNavChats` use. - We were previously rendering a link for notebook, and a button for chats. But chats' button was just calling `router.push` under the hood. - Opting to use a Context to handle the delete functionality such that we can render the confirmation modal just once at the layout level since there's multiple places that have this delete functionality - notebook tab, chat tab, context menu in each of the explorer navs ### To test - [ ] Verify that navigating around the explorer is status quo - [ ] Verify that you can delete a notebook/chat from within the notebook/chat tab - [ ] Verify that you can delete a notebook/chat from the explorer nav - [ ] Verify that after deleting a notebook/chat from either locations, the tabs should clear <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Unified chat and notebook navigation with direct links and active-item highlighting. - Added context-menu options to delete chats and notebooks with confirmation and status notifications. - Added double-click support for keeping recently viewed chats and notebooks open as tabs. - Deleted items and their related tabs are removed automatically. - **Tests** - Updated Explorer navigation tests for the unified link-based item behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
import { fireEvent, screen } from '@testing-library/react'
|
|
import type { PropsWithChildren } from 'react'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import { ExplorerNavChats } from './ExplorerNavChats'
|
|
import { customRender } from '@/tests/lib/custom-render'
|
|
|
|
vi.mock('common', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('common')>()
|
|
return { ...actual, useParams: () => ({ id: 'recent-chat' }) }
|
|
})
|
|
|
|
vi.mock('next/router', () => ({
|
|
useRouter: () => ({ pathname: '/project/[ref]/explorer/chat/[id]' }),
|
|
}))
|
|
|
|
vi.mock('./ExplorerLayout.constants', () => ({
|
|
ExplorerNavResourceWrapper: ({
|
|
children,
|
|
search,
|
|
setSearch,
|
|
}: PropsWithChildren<{ search: string; setSearch: (value: string) => void }>) => (
|
|
<div>
|
|
<input
|
|
aria-label="Search chats"
|
|
value={search}
|
|
onChange={(event) => setSearch(event.target.value)}
|
|
/>
|
|
{children}
|
|
</div>
|
|
),
|
|
rowClassName: (isActive: boolean) => (isActive ? 'active' : 'inactive'),
|
|
}))
|
|
|
|
vi.mock('@/components/interfaces/Explorer/hooks', () => ({
|
|
useCreateChat: () => ({ openChat: vi.fn() }),
|
|
}))
|
|
|
|
vi.mock('./ExplorerProvider', () => ({
|
|
useExplorerDeleteItem: () => ({ confirmDelete: vi.fn() }),
|
|
}))
|
|
|
|
vi.mock('@/state/ai-assistant-state', () => ({
|
|
useAiAssistantChatList: () => [
|
|
{ id: 'old-chat', name: 'Older investigation', updatedAt: new Date('2026-01-01') },
|
|
{ id: 'missing-date', name: 'Rehydrated chat' },
|
|
{ id: 'recent-chat', name: 'Recent investigation', updatedAt: new Date('2026-02-01') },
|
|
{
|
|
id: 'support',
|
|
name: 'Support conversation',
|
|
updatedAt: new Date('2026-03-01'),
|
|
supportMetadata: { isSupportChat: true },
|
|
},
|
|
],
|
|
}))
|
|
|
|
describe('ExplorerNavChats', () => {
|
|
it('filters support chats, safely sorts rehydrated chats, and marks the route active', () => {
|
|
customRender(<ExplorerNavChats />)
|
|
|
|
const chatLinks = screen.getAllByRole('link')
|
|
expect(chatLinks.map((link) => link.textContent)).toEqual([
|
|
'Recent investigation',
|
|
'Older investigation',
|
|
'Rehydrated chat',
|
|
])
|
|
expect(chatLinks[0]).toHaveClass('active')
|
|
expect(screen.queryByText('Support conversation')).not.toBeInTheDocument()
|
|
|
|
fireEvent.change(screen.getByRole('textbox', { name: 'Search chats' }), {
|
|
target: { value: 'rehydrated' },
|
|
})
|
|
|
|
expect(screen.getByRole('link')).toHaveTextContent('Rehydrated chat')
|
|
expect(screen.queryByText('Recent investigation')).not.toBeInTheDocument()
|
|
})
|
|
})
|