mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## Summary - require an explicit choice before saving a notebook that diverged while dirty - let users save over assistant changes or discard their local edits, with deleted notebooks recreating safely - keep dismissals side-effect free and close deleted notebook tabs when edits are discarded ## Testing - pnpm --filter studio exec vitest run components/interfaces/Explorer/__tests__/ExplorerNotebookTab.assistant-cache-invalidation.test.tsx data/content/notebooks/notebook-cache.test.ts --reporter=dot - pnpm --filter studio typecheck <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added conflict handling when server-side notebook changes overlap with local edits. - Users can overwrite, recreate, discard, or dismiss changes through a confirmation dialog. - Deleted notebooks can be recreated when saved, while discarded deleted notebooks are automatically removed from open tabs. - Conflict dialogs remain open while an action is in progress. - **Bug Fixes** - Improved notebook cache cleanup to remove stale and unsaved notebook data reliably. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
import { QueryClient } from '@tanstack/react-query'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
|
|
import { evictNotebookFromCaches } from './notebook-cache'
|
|
import { contentKeys } from '@/data/content/keys'
|
|
import { notebooksState } from '@/state/notebooks/notebooks-state'
|
|
import type { Notebook } from '@/state/notebooks/types'
|
|
|
|
const PROJECT_REF = 'default'
|
|
const NOTEBOOK_ID = 'notebook-cache-test'
|
|
|
|
const NOTEBOOK: Notebook = {
|
|
id: NOTEBOOK_ID,
|
|
type: 'notebook',
|
|
name: 'Test notebook',
|
|
visibility: 'project',
|
|
favorite: false,
|
|
owner_id: 1,
|
|
project_id: 1,
|
|
content: { schema_version: 1, cells: [] },
|
|
}
|
|
|
|
const seedNotebook = (status: 'new' | 'saved') => {
|
|
delete notebooksState.notebooks[NOTEBOOK_ID]
|
|
if (status === 'new') {
|
|
notebooksState.addNotebook({ projectRef: PROJECT_REF, notebook: NOTEBOOK })
|
|
} else {
|
|
notebooksState.setNotebook({ projectRef: PROJECT_REF, notebook: NOTEBOOK })
|
|
}
|
|
}
|
|
|
|
const seedQueryData = (queryClient: QueryClient) =>
|
|
queryClient.setQueryData(contentKeys.resource(PROJECT_REF, NOTEBOOK_ID), { id: NOTEBOOK_ID })
|
|
|
|
afterEach(() => {
|
|
delete notebooksState.notebooks[NOTEBOOK_ID]
|
|
notebooksState.needsSaving.clear()
|
|
})
|
|
|
|
describe('evictNotebookFromCaches', () => {
|
|
it('removes a saved notebook from the store and drops its cache entry', async () => {
|
|
seedNotebook('saved')
|
|
const queryClient = new QueryClient()
|
|
seedQueryData(queryClient)
|
|
|
|
const evicted = await evictNotebookFromCaches({
|
|
queryClient,
|
|
projectRef: PROJECT_REF,
|
|
id: NOTEBOOK_ID,
|
|
})
|
|
|
|
expect(evicted).toBe(true)
|
|
expect(notebooksState.notebooks[NOTEBOOK_ID]).toBeUndefined()
|
|
expect(queryClient.getQueryData(contentKeys.resource(PROJECT_REF, NOTEBOOK_ID))).toBeUndefined()
|
|
})
|
|
|
|
it('removes an unsaved notebook and its cache entry after its caller confirms discard', async () => {
|
|
seedNotebook('new')
|
|
const queryClient = new QueryClient()
|
|
seedQueryData(queryClient)
|
|
|
|
const evicted = await evictNotebookFromCaches({
|
|
queryClient,
|
|
projectRef: PROJECT_REF,
|
|
id: NOTEBOOK_ID,
|
|
})
|
|
|
|
expect(evicted).toBe(true)
|
|
expect(notebooksState.notebooks[NOTEBOOK_ID]).toBeUndefined()
|
|
expect(queryClient.getQueryData(contentKeys.resource(PROJECT_REF, NOTEBOOK_ID))).toBeUndefined()
|
|
})
|
|
|
|
it('drops a stale cache entry when the notebook is not present in the store', async () => {
|
|
const queryClient = new QueryClient()
|
|
seedQueryData(queryClient)
|
|
|
|
const evicted = await evictNotebookFromCaches({
|
|
queryClient,
|
|
projectRef: PROJECT_REF,
|
|
id: NOTEBOOK_ID,
|
|
})
|
|
|
|
expect(evicted).toBe(true)
|
|
expect(queryClient.getQueryData(contentKeys.resource(PROJECT_REF, NOTEBOOK_ID))).toBeUndefined()
|
|
})
|
|
})
|