diff --git a/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.test.tsx b/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.test.tsx new file mode 100644 index 00000000000..79d443b8f87 --- /dev/null +++ b/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.test.tsx @@ -0,0 +1,103 @@ +import { QueryClient } from '@tanstack/react-query' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { LOCAL_STORAGE_KEYS } from 'common' +import mockRouter from 'next-router-mock' +import type { ReactNode } from 'react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { ExplorerLayout } from './ExplorerLayout' +import { createTabsState, TabsStateContext } from '@/state/tabs' +import { customRender } from '@/tests/lib/custom-render' + +vi.mock('next/navigation', () => ({ + usePathname: () => mockRouter.asPath, +})) + +vi.mock('../ProjectLayout', () => ({ + ProjectLayoutWithAuth: ({ children }: { children: ReactNode }) => <>{children}, +})) + +vi.mock('@/components/interfaces/Account/Preferences/useExplorerPreferences', () => ({ + useExplorerPreferences: () => ({ home: 'home', hasCompletedOnboarding: true, isReady: true }), +})) + +vi.mock('@/components/interfaces/Explorer/ExplorerQueryTabCoordinator', () => ({ + ExplorerQueryTabCoordinator: () => null, +})) + +vi.mock('@/components/interfaces/Explorer/ExplorerNotebookTabCoordinator', () => ({ + ExplorerNotebookTabCoordinator: () => null, +})) + +vi.mock('@/components/interfaces/Explorer/hooks', () => ({ + useCreateChat: () => ({ createChat: vi.fn() }), + useCreateNotebook: () => ({ createNotebook: vi.fn() }), + useCreateQuery: () => ({ createQuery: vi.fn() }), +})) + +describe('Explorer Home tab navigation', () => { + beforeEach(() => { + vi.restoreAllMocks() + localStorage.clear() + mockRouter.setCurrentUrl({ + pathname: '/project/[ref]/explorer/query/[id]', + query: { ref: 'default', id: 'query-1' }, + }) + }) + + it.each(['mouse', 'keyboard'] as const)( + 'clears the saved Explorer destination before navigating Home with the %s', + async (input) => { + const user = userEvent.setup() + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: Infinity } }, + }) + const historyKey = ['localStorage', LOCAL_STORAGE_KEYS.DASHBOARD_HISTORY('default')] + const otherHistory = { editor: 'table-1', sql: 'snippet-1' } + queryClient.setQueryData(historyKey, { + ...otherHistory, + explorer: { type: 'query', id: 'query-1' }, + }) + + const tabs = createTabsState('default') + tabs.addTab({ + id: 'query-query-1', + type: 'query', + label: 'My query', + metadata: { queryId: 'query-1' }, + isPreview: false, + }) + let historyAtNavigation: unknown + const push = vi.spyOn(mockRouter, 'push') + push.mockImplementationOnce(async () => { + historyAtNavigation = queryClient.getQueryData(historyKey) + return true + }) + + customRender( + + Explorer content + , + { queryClient } + ) + + if (input === 'mouse') { + await user.click(screen.getByRole('tab', { name: 'Open Explorer home' })) + } else { + await user.tab() + expect(screen.getByRole('tab', { name: 'My query' })).toHaveFocus() + await user.keyboard('{ArrowLeft}') + } + + expect(push).toHaveBeenCalledWith('/project/default/explorer') + expect(historyAtNavigation).toEqual(otherHistory) + expect(queryClient.getQueryData(historyKey)).toEqual(otherHistory) + expect(screen.getByRole('tab', { name: 'Open Explorer home' })).toHaveAttribute( + 'aria-selected', + 'true' + ) + expect(tabs.tabsMap['query-query-1']).toBeDefined() + } + ) +}) diff --git a/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx b/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx index da87c153c7f..f73170c15a1 100644 --- a/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx +++ b/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx @@ -28,6 +28,7 @@ import { useCreateNotebook, useCreateQuery, } from '@/components/interfaces/Explorer/hooks' +import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory' import { useIsTemporarySqlEditorVisit } from '@/hooks/misc/useIsTemporarySqlEditorVisit' import { useTrack } from '@/lib/telemetry/track' import { @@ -46,6 +47,7 @@ export interface ExplorerLayoutProps extends ComponentProps { const { ref } = useParams() const tabs = useTabsStateSnapshot() + const { setLastVisitedExplorerTab } = useDashboardHistory() const { home, hasCompletedOnboarding, isReady } = useExplorerPreferences() const shouldShowHomeTab = isReady && (!hasCompletedOnboarding || home === 'home') @@ -68,6 +70,10 @@ export const ExplorerLayout = ({ browserTitle, children, title }: ExplorerLayout entity: browserTitle?.entity ?? activeTabLabel, } + const handleTabChange = (id: string) => { + if (id === EXPLORER_HOME_TAB_ID) setLastVisitedExplorerTab(undefined) + } + return ( : undefined} newTabButton={} + onTabChange={handleTabChange} />
{children}
diff --git a/apps/studio/components/layouts/Tabs/Tabs.tsx b/apps/studio/components/layouts/Tabs/Tabs.tsx index 05022bc102c..36839849c4c 100644 --- a/apps/studio/components/layouts/Tabs/Tabs.tsx +++ b/apps/studio/components/layouts/Tabs/Tabs.tsx @@ -41,6 +41,7 @@ interface EditorTabsProps { customTabs?: ReactNode newTabButton?: ReactNode isCollapseButtonHidden?: boolean + onTabChange?: (id: string) => void } // [Joshen] Will be adjusting this component to support Explorer @@ -49,6 +50,7 @@ export const EditorTabs = ({ customTabs, newTabButton, isCollapseButtonHidden, + onTabChange, }: EditorTabsProps) => { const { ref } = useParams() const router = useRouter() @@ -168,6 +170,7 @@ export const EditorTabs = ({ } const handleTabChange = (id: string) => { + onTabChange?.(id) tabs.handleTabNavigation(id, router) }