mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
Persist last visited explorer tab (#50557)
## Context Saves the last visited explorer tab via `useDashboardHistory`, such that landing back on `/explorer` will open the last visited page. Similar behaviour to Table Editor and SQL Editor Would also be useful when going between the SQL Editor and Explorer to bring snippets over <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Explorer now remembers and restores the last visited query, chat, or notebook tab. * Automatically returns to the Explorer home screen when a saved tab is unavailable. * Displays a loading state while the last visited tab is being restored. * **Bug Fixes** * Closing deleted chat tabs now clears their saved history. * **Tests** * Added coverage for Explorer tab restoration, loading states, and stale history cleanup. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
f8257f2146
commit
ad30c04e0e
9 files changed
+357
-25
No files matched your search
@@ -21,7 +21,8 @@ export const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
|
||||
const isLoggedIn = useIsLoggedIn()
|
||||
const isUserMFAEnabled = useIsMFAEnabled()
|
||||
|
||||
const { setLastVisitedSnippet, setLastVisitedTable } = useDashboardHistory()
|
||||
const { setLastVisitedSnippet, setLastVisitedTable, setLastVisitedExplorerTab } =
|
||||
useDashboardHistory()
|
||||
const { lastVisitedOrganization, setLastVisitedOrganization } = useLastVisitedOrganization()
|
||||
|
||||
const DEFAULT_HOME = IS_PLATFORM
|
||||
@@ -97,10 +98,22 @@ export const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
|
||||
setLastVisitedSnippet(id)
|
||||
} else if (router.pathname.endsWith('/editor/[id]')) {
|
||||
setLastVisitedTable(id)
|
||||
} else if (router.pathname.endsWith('/explorer/notebook/[id]')) {
|
||||
setLastVisitedExplorerTab({ type: 'notebook', id })
|
||||
} else if (router.pathname.endsWith('/explorer/query/[id]')) {
|
||||
setLastVisitedExplorerTab({ type: 'query', id })
|
||||
} else if (router.pathname.endsWith('/explorer/chat/[id]')) {
|
||||
setLastVisitedExplorerTab({ type: 'chat', id })
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ref, id])
|
||||
}, [
|
||||
ref,
|
||||
id,
|
||||
router.pathname,
|
||||
setLastVisitedSnippet,
|
||||
setLastVisitedTable,
|
||||
setLastVisitedExplorerTab,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (organization) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ExplorerChatToolbar } from './ExplorerChatToolbar'
|
||||
import { useCreateChat } from './hooks'
|
||||
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
|
||||
import { AssistantChat } from '@/components/ui/AIAssistantPanel/AssistantChat'
|
||||
import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
|
||||
import { useAiAssistantState, useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
||||
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
|
||||
import { createTabId, useTabsStateSnapshot } from '@/state/tabs'
|
||||
@@ -20,6 +21,7 @@ export const ExplorerChatTab = () => {
|
||||
const aiAssistantState = useAiAssistantState()
|
||||
const { createChat, openChat } = useCreateChat()
|
||||
const { activeSidebar } = useSidebarManagerSnapshot()
|
||||
const { setLastVisitedExplorerTab } = useDashboardHistory()
|
||||
const chat = id ? aiAssistant.chats[id] : undefined
|
||||
const chatInstance = id ? aiAssistant.chatInstances[id] : undefined
|
||||
const tabId = id ? createTabId('chat', { id }) : undefined
|
||||
@@ -37,7 +39,7 @@ export const ExplorerChatTab = () => {
|
||||
id: tabId,
|
||||
router,
|
||||
editor: 'explorer',
|
||||
onClearDashboardHistory: () => {},
|
||||
onClearDashboardHistory: () => setLastVisitedExplorerTab(undefined),
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export const ExplorerHomeTab = () => {
|
||||
return <ExplorerHomeContent />
|
||||
}
|
||||
|
||||
const ExplorerHomeLoading = () => (
|
||||
export const ExplorerHomeLoading = () => (
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Opening Explorer"
|
||||
|
||||
@@ -35,7 +35,7 @@ export const useLoadNotebook = ({ id, projectRef }: { id?: string; projectRef?:
|
||||
const hasLoadedNotebook =
|
||||
isCurrentProjectNotebook && currentNotebook?.notebook.content !== undefined
|
||||
|
||||
const { data, error, isError } = useNotebookQuery(
|
||||
const { data, error, isError, isLoading } = useNotebookQuery(
|
||||
{ projectRef, id },
|
||||
{
|
||||
retry: false,
|
||||
@@ -82,7 +82,7 @@ export const useLoadNotebook = ({ id, projectRef }: { id?: string; projectRef?:
|
||||
mergeNotebook()
|
||||
}, [projectRef, id, data, isNotFound, !!profile, !!project])
|
||||
|
||||
return { isNotFound: isNotFound && !isCurrentProjectNotebook }
|
||||
return { isNotFound: isNotFound && !isCurrentProjectNotebook, isLoading }
|
||||
}
|
||||
|
||||
export const useCreateNotebook = () => {
|
||||
|
||||
@@ -52,7 +52,8 @@ export const EditorTabs = ({
|
||||
}: EditorTabsProps) => {
|
||||
const { ref } = useParams()
|
||||
const router = useRouter()
|
||||
const { setLastVisitedSnippet, setLastVisitedTable } = useDashboardHistory()
|
||||
const { setLastVisitedSnippet, setLastVisitedTable, setLastVisitedExplorerTab } =
|
||||
useDashboardHistory()
|
||||
|
||||
const editor = useEditorType()
|
||||
const tabs = useTabsStateSnapshot()
|
||||
@@ -97,6 +98,9 @@ export const EditorTabs = ({
|
||||
if (editor === 'sql') {
|
||||
setLastVisitedSnippet(undefined)
|
||||
}
|
||||
if (editor === 'explorer') {
|
||||
setLastVisitedExplorerTab(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
// Runs `performClose` immediately unless one of the tabs' registered close
|
||||
|
||||
@@ -64,4 +64,41 @@ describe('useDashboardHistory', () => {
|
||||
expect(result.current.history.editor).toBe('table-1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('setLastVisitedExplorerTab', () => {
|
||||
it('stores the last visited Explorer tab', async () => {
|
||||
const { result } = await renderDashboardHistory()
|
||||
|
||||
act(() => result.current.setLastVisitedExplorerTab({ type: 'notebook', id: 'notebook-1' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.history.explorer).toEqual({ type: 'notebook', id: 'notebook-1' })
|
||||
)
|
||||
})
|
||||
|
||||
it('clears the last visited Explorer tab when called with undefined', async () => {
|
||||
const { result } = await renderDashboardHistory()
|
||||
|
||||
act(() => result.current.setLastVisitedExplorerTab({ type: 'chat', id: 'chat-1' }))
|
||||
await waitFor(() => expect(result.current.history.explorer).toBeDefined())
|
||||
|
||||
act(() => result.current.setLastVisitedExplorerTab(undefined))
|
||||
|
||||
await waitFor(() => expect(result.current.history.explorer).toBeUndefined())
|
||||
})
|
||||
|
||||
it('does not touch the SQL or table editor history', async () => {
|
||||
const { result } = await renderDashboardHistory()
|
||||
|
||||
act(() => result.current.setLastVisitedSnippet('snippet-a'))
|
||||
await waitFor(() => expect(result.current.history.sql).toBe('snippet-a'))
|
||||
|
||||
act(() => result.current.setLastVisitedExplorerTab({ type: 'query', id: 'query-1' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.history.explorer).toEqual({ type: 'query', id: 'query-1' })
|
||||
)
|
||||
expect(result.current.history.sql).toBe('snippet-a')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,11 @@
|
||||
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { useLocalStorageQuery } from './useLocalStorage'
|
||||
|
||||
type DashboardHistory = { editor?: string; sql?: string }
|
||||
const DEFAULT_HISTORY = { editor: undefined, sql: undefined }
|
||||
type ExplorerHistoryEntry = { type: 'notebook' | 'query' | 'chat'; id: string }
|
||||
type DashboardHistory = { editor?: string; sql?: string; explorer?: ExplorerHistoryEntry }
|
||||
const DEFAULT_HISTORY = { editor: undefined, sql: undefined, explorer: undefined }
|
||||
|
||||
export const useDashboardHistory = () => {
|
||||
// [Joshen] History should always refer to the project that the user is currently on
|
||||
@@ -14,30 +16,47 @@ export const useDashboardHistory = () => {
|
||||
DEFAULT_HISTORY
|
||||
)
|
||||
|
||||
const setLastVisitedTable = (id?: string) => {
|
||||
setHistory({ ...history, editor: id })
|
||||
}
|
||||
const setLastVisitedTable = useCallback(
|
||||
(id?: string) => {
|
||||
setHistory((current) => ({ ...current, editor: id }))
|
||||
},
|
||||
[setHistory]
|
||||
)
|
||||
|
||||
const setLastVisitedSnippet = (id?: string) => {
|
||||
setHistory({ ...history, sql: id })
|
||||
}
|
||||
const setLastVisitedSnippet = useCallback(
|
||||
(id?: string) => {
|
||||
setHistory((current) => ({ ...current, sql: id }))
|
||||
},
|
||||
[setHistory]
|
||||
)
|
||||
|
||||
const setLastVisitedExplorerTab = useCallback(
|
||||
(entry?: ExplorerHistoryEntry) => {
|
||||
setHistory((current) => ({ ...current, explorer: entry }))
|
||||
},
|
||||
[setHistory]
|
||||
)
|
||||
|
||||
/**
|
||||
* Purge the last-visited snippet when it's one of the deleted snippets, so that
|
||||
* navigating back to the SQL editor doesn't resurrect a deleted snippet.
|
||||
*/
|
||||
const clearSnippetsFromHistory = (ids: string[]) => {
|
||||
setHistory((current) =>
|
||||
current.sql !== undefined && ids.includes(current.sql)
|
||||
? { ...current, sql: undefined }
|
||||
: current
|
||||
)
|
||||
}
|
||||
const clearSnippetsFromHistory = useCallback(
|
||||
(ids: string[]) => {
|
||||
setHistory((current) =>
|
||||
current.sql !== undefined && ids.includes(current.sql)
|
||||
? { ...current, sql: undefined }
|
||||
: current
|
||||
)
|
||||
},
|
||||
[setHistory]
|
||||
)
|
||||
|
||||
return {
|
||||
history,
|
||||
setLastVisitedTable,
|
||||
setLastVisitedSnippet,
|
||||
setLastVisitedExplorerTab,
|
||||
clearSnippetsFromHistory,
|
||||
isHistoryLoaded: isSuccess,
|
||||
}
|
||||
|
||||
@@ -1,20 +1,103 @@
|
||||
import { useParams } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useEffectEvent } from 'react'
|
||||
|
||||
import { ExplorerHomeTab } from '@/components/interfaces/Explorer/ExplorerHomeTab'
|
||||
import {
|
||||
ExplorerHomeLoading,
|
||||
ExplorerHomeTab,
|
||||
} from '@/components/interfaces/Explorer/ExplorerHomeTab'
|
||||
import { useLoadNotebook } from '@/components/interfaces/Explorer/hooks'
|
||||
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
|
||||
import { ExplorerLayout } from '@/components/layouts/ExplorerLayout/ExplorerLayout'
|
||||
import { useDashboardHistory } from '@/hooks/misc/useDashboardHistory'
|
||||
import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
|
||||
import { explorerQueryState } from '@/state/explorer-query'
|
||||
import { EXPLORER_HOME_TAB, useTabsStateSnapshot } from '@/state/tabs'
|
||||
import type { NextPageWithLayout } from '@/types'
|
||||
|
||||
const ProjectExplorerPage: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref: projectRef } = useParams()
|
||||
const tabs = useTabsStateSnapshot()
|
||||
const aiAssistant = useAiAssistantStateSnapshot()
|
||||
const { history, isHistoryLoaded, setLastVisitedExplorerTab } = useDashboardHistory()
|
||||
|
||||
const lastVisited = history.explorer
|
||||
const lastVisitedNotebookId = lastVisited?.type === 'notebook' ? lastVisited.id : undefined
|
||||
const { isNotFound: isNotebookNotFound, isLoading: isNotebookLoading } = useLoadNotebook({
|
||||
id: lastVisitedNotebookId,
|
||||
projectRef,
|
||||
})
|
||||
|
||||
const activateHomeTab = useEffectEvent(() => {
|
||||
tabs.activatePinnedTab(EXPLORER_HOME_TAB)
|
||||
})
|
||||
|
||||
useEffect(() => activateHomeTab(), [])
|
||||
// Redirects to the last opened Explorer tab if it still exists, otherwise falls
|
||||
// back to (and stays on) the Home tab. Existence is checked per tab type: query
|
||||
// drafts and chats are local and resolve synchronously, notebooks require the
|
||||
// server round trip in `useLoadNotebook` above to resolve first.
|
||||
const goToLastVisitedTab = useEffectEvent(() => {
|
||||
if (!projectRef) return
|
||||
|
||||
if (!lastVisited) {
|
||||
activateHomeTab()
|
||||
return
|
||||
}
|
||||
|
||||
if (lastVisited.type === 'query') {
|
||||
const exists = explorerQueryState.restoreDraft({ id: lastVisited.id, projectRef })
|
||||
if (exists) {
|
||||
router.replace(`/project/${projectRef}/explorer/query/${lastVisited.id}`)
|
||||
} else {
|
||||
setLastVisitedExplorerTab(undefined)
|
||||
activateHomeTab()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (lastVisited.type === 'chat') {
|
||||
if (!aiAssistant.isInitialized) return
|
||||
|
||||
if (aiAssistant.chats[lastVisited.id]) {
|
||||
router.replace(`/project/${projectRef}/explorer/chat/${lastVisited.id}`)
|
||||
} else {
|
||||
setLastVisitedExplorerTab(undefined)
|
||||
activateHomeTab()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// notebook
|
||||
if (isNotebookLoading) return
|
||||
|
||||
if (isNotebookNotFound) {
|
||||
setLastVisitedExplorerTab(undefined)
|
||||
activateHomeTab()
|
||||
} else {
|
||||
router.replace(`/project/${projectRef}/explorer/notebook/${lastVisited.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isHistoryLoaded) goToLastVisitedTab()
|
||||
}, [
|
||||
isHistoryLoaded,
|
||||
projectRef,
|
||||
lastVisited?.type,
|
||||
lastVisited?.id,
|
||||
aiAssistant.isInitialized,
|
||||
isNotebookLoading,
|
||||
isNotebookNotFound,
|
||||
])
|
||||
|
||||
const isCheckingLastTab =
|
||||
!projectRef ||
|
||||
!isHistoryLoaded ||
|
||||
(lastVisited?.type === 'chat' && !aiAssistant.isInitialized) ||
|
||||
(lastVisited?.type === 'notebook' && isNotebookLoading)
|
||||
|
||||
if (isCheckingLastTab) return <ExplorerHomeLoading />
|
||||
return <ExplorerHomeTab />
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import { screen } from '@testing-library/react'
|
||||
import mockRouter from 'next-router-mock'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import ProjectExplorerPage from '@/pages/project/[ref]/explorer/index'
|
||||
import { customRender } from '@/tests/lib/custom-render'
|
||||
|
||||
// `common`'s useParams and `next/router` are globally mocked in vitestSetup.ts
|
||||
// to `{ ref: 'default' }` and `next-router-mock` respectively.
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
activatePinnedTab: vi.fn(),
|
||||
assistantSnapshot: vi.fn(),
|
||||
restoreDraft: vi.fn(),
|
||||
setLastVisitedExplorerTab: vi.fn(),
|
||||
useDashboardHistory: vi.fn(),
|
||||
useLoadNotebook: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/misc/useDashboardHistory', () => ({
|
||||
useDashboardHistory: () => mocks.useDashboardHistory(),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/interfaces/Explorer/hooks', () => ({
|
||||
useLoadNotebook: () => mocks.useLoadNotebook(),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/interfaces/Explorer/ExplorerHomeTab', () => ({
|
||||
ExplorerHomeTab: () => <div>Explorer home</div>,
|
||||
ExplorerHomeLoading: () => <div>Checking last tab</div>,
|
||||
}))
|
||||
|
||||
vi.mock('@/state/explorer-query', () => ({
|
||||
explorerQueryState: { restoreDraft: (...args: unknown[]) => mocks.restoreDraft(...args) },
|
||||
}))
|
||||
|
||||
vi.mock('@/state/ai-assistant-state', () => ({
|
||||
useAiAssistantStateSnapshot: () => mocks.assistantSnapshot(),
|
||||
}))
|
||||
|
||||
vi.mock('@/state/tabs', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/state/tabs')>()
|
||||
return {
|
||||
...actual,
|
||||
useTabsStateSnapshot: () => ({ activatePinnedTab: mocks.activatePinnedTab }),
|
||||
}
|
||||
})
|
||||
|
||||
const mockHistory = (explorer?: { type: 'notebook' | 'query' | 'chat'; id: string }) => {
|
||||
mocks.useDashboardHistory.mockReturnValue({
|
||||
history: { explorer },
|
||||
isHistoryLoaded: true,
|
||||
setLastVisitedExplorerTab: mocks.setLastVisitedExplorerTab,
|
||||
})
|
||||
}
|
||||
|
||||
describe('ProjectExplorerPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockRouter.setCurrentUrl('/project/default/explorer')
|
||||
vi.spyOn(mockRouter, 'replace')
|
||||
mocks.useLoadNotebook.mockReturnValue({ isNotFound: false, isLoading: false })
|
||||
mocks.assistantSnapshot.mockReturnValue({ isInitialized: true, chats: {} })
|
||||
})
|
||||
|
||||
it('shows the checking state until dashboard history has loaded', () => {
|
||||
mocks.useDashboardHistory.mockReturnValue({
|
||||
history: {},
|
||||
isHistoryLoaded: false,
|
||||
setLastVisitedExplorerTab: mocks.setLastVisitedExplorerTab,
|
||||
})
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mocks.activatePinnedTab).not.toHaveBeenCalled()
|
||||
expect(screen.getByText('Checking last tab')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('activates the Home tab when there is no last visited Explorer tab', () => {
|
||||
mockHistory(undefined)
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mocks.activatePinnedTab).toHaveBeenCalled()
|
||||
expect(mockRouter.replace).not.toHaveBeenCalled()
|
||||
expect(screen.getByText('Explorer home')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('redirects to the last visited query tab when its draft still exists', () => {
|
||||
mocks.restoreDraft.mockReturnValue(true)
|
||||
mockHistory({ type: 'query', id: 'query-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mocks.restoreDraft).toHaveBeenCalledWith({ id: 'query-1', projectRef: 'default' })
|
||||
expect(mockRouter.replace).toHaveBeenCalledWith('/project/default/explorer/query/query-1')
|
||||
expect(mocks.activatePinnedTab).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('falls back to Home and forgets the entry when the query draft is gone', () => {
|
||||
mocks.restoreDraft.mockReturnValue(false)
|
||||
mockHistory({ type: 'query', id: 'query-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mockRouter.replace).not.toHaveBeenCalled()
|
||||
expect(mocks.setLastVisitedExplorerTab).toHaveBeenCalledWith(undefined)
|
||||
expect(mocks.activatePinnedTab).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('redirects to the last visited chat tab when the chat still exists', () => {
|
||||
mocks.assistantSnapshot.mockReturnValue({
|
||||
isInitialized: true,
|
||||
chats: { 'chat-1': { id: 'chat-1', name: 'Investigate' } },
|
||||
})
|
||||
mockHistory({ type: 'chat', id: 'chat-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mockRouter.replace).toHaveBeenCalledWith('/project/default/explorer/chat/chat-1')
|
||||
})
|
||||
|
||||
it('waits for chat hydration before deciding', () => {
|
||||
mocks.assistantSnapshot.mockReturnValue({ isInitialized: false, chats: {} })
|
||||
mockHistory({ type: 'chat', id: 'chat-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mockRouter.replace).not.toHaveBeenCalled()
|
||||
expect(mocks.activatePinnedTab).not.toHaveBeenCalled()
|
||||
expect(screen.getByText('Checking last tab')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('falls back to Home when the last visited chat was deleted', () => {
|
||||
mocks.assistantSnapshot.mockReturnValue({ isInitialized: true, chats: {} })
|
||||
mockHistory({ type: 'chat', id: 'chat-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mockRouter.replace).not.toHaveBeenCalled()
|
||||
expect(mocks.setLastVisitedExplorerTab).toHaveBeenCalledWith(undefined)
|
||||
expect(mocks.activatePinnedTab).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows the checking state while the last visited notebook is still loading', () => {
|
||||
mocks.useLoadNotebook.mockReturnValue({ isNotFound: false, isLoading: true })
|
||||
mockHistory({ type: 'notebook', id: 'notebook-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mockRouter.replace).not.toHaveBeenCalled()
|
||||
expect(screen.getByText('Checking last tab')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('redirects to the last visited notebook once it is confirmed to exist', () => {
|
||||
mocks.useLoadNotebook.mockReturnValue({ isNotFound: false, isLoading: false })
|
||||
mockHistory({ type: 'notebook', id: 'notebook-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mockRouter.replace).toHaveBeenCalledWith('/project/default/explorer/notebook/notebook-1')
|
||||
})
|
||||
|
||||
it('falls back to Home when the last visited notebook no longer exists', () => {
|
||||
mocks.useLoadNotebook.mockReturnValue({ isNotFound: true, isLoading: false })
|
||||
mockHistory({ type: 'notebook', id: 'notebook-1' })
|
||||
|
||||
customRender(<ProjectExplorerPage dehydratedState={undefined} />)
|
||||
|
||||
expect(mockRouter.replace).not.toHaveBeenCalled()
|
||||
expect(mocks.setLastVisitedExplorerTab).toHaveBeenCalledWith(undefined)
|
||||
expect(mocks.activatePinnedTab).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user