mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
c37e756983
## Context Currently in the SQL Editor, toggling "favourite" for a snippet doesn't persist unless you manually save the snippet (which expects a change in the snippet's content before allowing so) - which is a bit of an odd UX This used to work before we introduced manual saving which is currently the default behaviour for the SQL Editor - `addFavorite` and `removeFavorite` would add to the `needsSaving` queue which the editor's save scheduler will subscribe and trigger the save. However the save scheduler doesn't subscribe to the queue for manual saving mode ([ref](https://github.com/supabase/supabase/blob/master/apps/studio/state/sql-editor/sql-editor-save-scheduler.ts#L90)) - hence toggling favourite on a snippet never triggers a PATCH request. Am opting to immediately trigger a PATCH request when toggling favourites which is a bit more of an expected UX imo One thing to note is that favoriting a snippet essentially does a save on the snippet - which means the contents will be persisted as well, although i think this is alright ## To test - [ ] Verify that toggling favourite for a SQL snippet persists immediately - Can verify by checking the context menu CTA to see if it's changed from "Add to favourites" to "Remove from favourites" or vice versa <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Improvements** - Favoriting or unfavoriting SQL snippets now saves immediately. - Favorite changes are handled consistently across the SQL Editor, including the utility panel and snippet navigation. - Pending content saves are coordinated to prevent favorite changes from being overwritten. - If saving a favorite fails, the previous favorite state is restored and an error notification is shown. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
import { untrustedSql } from '@supabase/pg-meta'
|
|
import { beforeEach, describe, expect, it } from 'vitest'
|
|
|
|
import { sqlEditorState } from './sql-editor-state'
|
|
import type { SnippetWithContent } from '@/data/content/sql-folders-query'
|
|
import { untrustedLogSql } from '@/data/logs/safe-analytics-sql'
|
|
|
|
function makeLogSnippet(id: string): SnippetWithContent {
|
|
return {
|
|
id,
|
|
name: 'My Logs Query',
|
|
description: '',
|
|
visibility: 'user',
|
|
project_id: 42,
|
|
owner_id: 7,
|
|
folder_id: null,
|
|
favorite: false,
|
|
status: 'saved',
|
|
inserted_at: '2024-01-01T00:00:00.000Z',
|
|
updated_at: '2024-01-01T00:00:00.000Z',
|
|
type: 'log_sql',
|
|
content: {
|
|
content_id: id,
|
|
schema_version: '1',
|
|
unchecked_sql: untrustedLogSql('select * from logs'),
|
|
},
|
|
}
|
|
}
|
|
|
|
function makeSnippet(
|
|
id: string,
|
|
overrides: Omit<Partial<SnippetWithContent>, 'content' | 'type'> = {}
|
|
): SnippetWithContent {
|
|
return {
|
|
id,
|
|
name: 'My Query',
|
|
description: 'A description',
|
|
visibility: 'user',
|
|
project_id: 42,
|
|
owner_id: 7,
|
|
folder_id: null,
|
|
favorite: false,
|
|
status: 'saved',
|
|
inserted_at: '2024-01-01T00:00:00.000Z',
|
|
updated_at: '2024-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
type: 'sql',
|
|
content: {
|
|
content_id: id,
|
|
schema_version: '1',
|
|
unchecked_sql: untrustedSql('SELECT * FROM users;'),
|
|
},
|
|
}
|
|
}
|
|
|
|
describe('addFavorite / removeFavorite', () => {
|
|
beforeEach(() => {
|
|
// sqlEditorState is a module-level singleton, so reset the state these tests touch
|
|
for (const id of Object.keys(sqlEditorState.snippets)) {
|
|
delete sqlEditorState.snippets[id]
|
|
}
|
|
sqlEditorState.needsSaving.clear()
|
|
})
|
|
|
|
it('marks a loaded snippet as favorite without queueing it for saving', () => {
|
|
sqlEditorState.addSnippet({ projectRef: 'ref', snippet: makeSnippet('snippet-1') })
|
|
|
|
sqlEditorState.addFavorite('snippet-1')
|
|
|
|
expect(sqlEditorState.snippets['snippet-1'].snippet.favorite).toBe(true)
|
|
// Favorites persist immediately via the save coordinator, not the queue.
|
|
expect(sqlEditorState.needsSaving.has('snippet-1')).toBe(false)
|
|
})
|
|
|
|
it('unmarks a favorited snippet without queueing it for saving', () => {
|
|
sqlEditorState.addSnippet({
|
|
projectRef: 'ref',
|
|
snippet: makeSnippet('snippet-1', { favorite: true }),
|
|
})
|
|
|
|
sqlEditorState.removeFavorite('snippet-1')
|
|
|
|
expect(sqlEditorState.snippets['snippet-1'].snippet.favorite).toBe(false)
|
|
expect(sqlEditorState.needsSaving.has('snippet-1')).toBe(false)
|
|
})
|
|
|
|
it('ignores addFavorite for a snippet that is not in the store', () => {
|
|
expect(() => sqlEditorState.addFavorite('missing')).not.toThrow()
|
|
expect(sqlEditorState.needsSaving.has('missing')).toBe(false)
|
|
})
|
|
|
|
it('ignores removeFavorite for a snippet that is not in the store', () => {
|
|
expect(() => sqlEditorState.removeFavorite('missing')).not.toThrow()
|
|
expect(sqlEditorState.needsSaving.has('missing')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('setSql — source-aware branding', () => {
|
|
beforeEach(() => {
|
|
for (const id of Object.keys(sqlEditorState.snippets)) {
|
|
delete sqlEditorState.snippets[id]
|
|
}
|
|
sqlEditorState.needsSaving.clear()
|
|
})
|
|
|
|
it('updates the SQL of a database snippet and marks it for saving', () => {
|
|
sqlEditorState.addSnippet({ projectRef: 'ref', snippet: makeSnippet('db-1') })
|
|
|
|
sqlEditorState.setSql({ id: 'db-1', sql: 'select 2' })
|
|
|
|
expect(sqlEditorState.snippets['db-1'].snippet.content?.unchecked_sql).toBe('select 2')
|
|
expect(sqlEditorState.needsSaving.has('db-1')).toBe(true)
|
|
})
|
|
|
|
it('updates the SQL of a logs snippet and marks it for saving', () => {
|
|
sqlEditorState.addSnippet({ projectRef: 'ref', snippet: makeLogSnippet('logs-1') })
|
|
|
|
sqlEditorState.setSql({ id: 'logs-1', sql: 'select count(*) from logs' })
|
|
|
|
expect(sqlEditorState.snippets['logs-1'].snippet.content?.unchecked_sql).toBe(
|
|
'select count(*) from logs'
|
|
)
|
|
expect(sqlEditorState.needsSaving.has('logs-1')).toBe(true)
|
|
})
|
|
})
|