mirror of
https://github.com/supabase/supabase.git
synced 2026-06-29 03:50:30 -04:00
4c011cf9c0
## Problem Deleting a custom report waited for the API round trip before updating the UI. The confirmation modal showed a loading spinner, the report stayed visible in the sidebar until the request resolved, and the interaction felt sluggish. ## Fix The delete now applies optimistically. On confirm, the report is removed from the sidebar immediately and the user is navigated away. The actual delete runs in the background. If it fails, the cached list is rolled back to its previous state and an error toast is shown. The optimistic behavior lives inside `useContentDeleteMutation` (via `onMutate` snapshot + `onError` rollback), so any current or future caller of that hook gets it for free, no per-call wiring required. ## How to test - Open a project with at least one custom report - Click the kebab menu on a report and choose Delete report, then confirm - Expected result: the report disappears from the sidebar instantly and a success toast appears - To test rollback: throttle/offline the network or force the delete endpoint to fail, then delete again - Expected result: the report reappears in the sidebar and an error toast is shown <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Deletion flows now provide explicit loading, success and error feedback; UI updates immediately on delete and will restore if the action fails. * **Removals** * Removed the reports menu and individual report menu item UI components (affects report-level rename/delete dropdowns and related menu navigation). * **Tests** * Added tests covering content deletion behavior, multiple-deletion cases, and data integrity after removals. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { removeContentFromList } from './content-delete-mutation'
|
|
import type { ContentData } from './content-query'
|
|
|
|
const makeListData = (ids: string[]): ContentData => ({
|
|
cursor: 'next-cursor',
|
|
content: ids.map((id) => ({ id, name: id, type: 'report' })) as ContentData['content'],
|
|
})
|
|
|
|
describe('removeContentFromList', () => {
|
|
it('removes a single matching id', () => {
|
|
const result = removeContentFromList(makeListData(['a', 'b', 'c']), ['b'])
|
|
expect(result.content.map((item) => item.id)).toEqual(['a', 'c'])
|
|
})
|
|
|
|
it('removes multiple matching ids', () => {
|
|
const result = removeContentFromList(makeListData(['a', 'b', 'c']), ['a', 'c'])
|
|
expect(result.content.map((item) => item.id)).toEqual(['b'])
|
|
})
|
|
|
|
it('leaves the list unchanged when no ids match', () => {
|
|
const result = removeContentFromList(makeListData(['a', 'b']), ['x'])
|
|
expect(result.content.map((item) => item.id)).toEqual(['a', 'b'])
|
|
})
|
|
|
|
it('preserves sibling fields such as cursor', () => {
|
|
const result = removeContentFromList(makeListData(['a']), ['a'])
|
|
expect(result.cursor).toBe('next-cursor')
|
|
})
|
|
|
|
it('does not mutate the input data', () => {
|
|
const input = makeListData(['a', 'b'])
|
|
removeContentFromList(input, ['a'])
|
|
expect(input.content.map((item) => item.id)).toEqual(['a', 'b'])
|
|
})
|
|
})
|