diff --git a/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.test.tsx b/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.test.tsx
index 1113bd5bf07..477a9229795 100644
--- a/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.test.tsx
+++ b/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.test.tsx
@@ -34,6 +34,8 @@ const createSnippet = (id: string, name: string): SnippetWithContent => ({
const SNIPPET_A = createSnippet('snippet-a', 'First query')
const SNIPPET_B = createSnippet('snippet-b', 'Second query')
+const UNTITLED_A = createSnippet('untitled-a', 'Untitled query')
+const UNTITLED_B = createSnippet('untitled-b', 'Untitled query')
/** The modal renders the AI title generator, which checks for an OpenAI key when self-hosted. */
const mockOpenAIKeyCheck = () =>
@@ -108,6 +110,47 @@ describe('RenameQueryModal', () => {
expect(screen.getByRole('button', { name: 'Rename query' })).toBeDisabled()
})
+ test('resets for the next snippet when both snippets share a name', async () => {
+ mockOpenAIKeyCheck()
+ const requests = mockUpsert()
+ const onComplete = vi.fn()
+
+ const { rerender } = customRender(
+
+ )
+
+ await userEvent.clear(getNameInput())
+ await userEvent.type(getNameInput(), 'Renamed query')
+ fireEvent.click(screen.getByRole('button', { name: 'Rename query' }))
+ await waitFor(() => expect(onComplete).toHaveBeenCalledOnce())
+
+ // The parent keeps the renamed snippet selected while closing the modal, then reopens it for
+ // a second snippet that still carries the same original name
+ rerender(
+
+ )
+ rerender(
+
+ )
+
+ await waitFor(() => expect(getNameInput()).toHaveValue('Untitled query'))
+
+ await userEvent.clear(getNameInput())
+ await userEvent.type(getNameInput(), 'Second renamed query')
+ fireEvent.click(screen.getByRole('button', { name: 'Rename query' }))
+
+ await waitFor(() => expect(onComplete).toHaveBeenCalledTimes(2))
+ expect(requests).toEqual([
+ { id: 'untitled-a', name: 'Renamed query' },
+ { id: 'untitled-b', name: 'Second renamed query' },
+ ])
+ })
+
test('discards an abandoned edit when cancelled', async () => {
mockOpenAIKeyCheck()
const onCancel = vi.fn()
diff --git a/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx b/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx
index 1ae91dc20c1..8041ebe9b7d 100644
--- a/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx
+++ b/apps/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx
@@ -49,12 +49,13 @@ const formSchema = z.object({
description: z.string().optional(),
})
-export const RenameQueryModal = ({
- snippet = {} as any,
- visible,
- onCancel,
- onComplete,
-}: RenameQueryModalProps) => {
+interface RenameQueryFormProps {
+ snippet: SqlSnippet | Snippet
+ onCancel: () => void
+ onComplete: () => void
+}
+
+const RenameQueryForm = ({ snippet, onCancel, onComplete }: RenameQueryFormProps) => {
const { ref } = useParams()
const router = useRouter()
@@ -145,7 +146,7 @@ export const RenameQueryModal = ({
}
toast.success('Successfully renamed snippet!')
- reset({ name, description }, { keepDirtyValues: false })
+ reset({ name, description })
if (onComplete) onComplete()
} catch (error: any) {
// [Joshen] We probably need some rollback cause all the saving is async
@@ -156,98 +157,105 @@ export const RenameQueryModal = ({
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: { name: name ?? '', description: description ?? '' },
- values: { name: name ?? '', description: description ?? '' },
- resetOptions: { keepDirtyValues: true },
})
const { reset, formState } = form
const { isDirty, isSubmitting } = formState
- const handleCancel = () => {
- onCancel()
- reset(undefined, { keepDirtyValues: false })
- }
-
return (
-