diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx
index 45b2a9ca..afef0ebe 100644
--- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx
+++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx
@@ -7862,6 +7862,13 @@ describe('QueryEditor external SQL save', () => {
createdAt: 100,
},
];
+ storeState.saveQuery.mockImplementationOnce(async (savedQuery: SavedQuery) => {
+ storeState.savedQueries = storeState.savedQueries.map((item) => (
+ item.id === savedQuery.id ? savedQuery : item
+ ));
+ storeSubscribers.forEach((subscriber) => subscriber());
+ return savedQuery;
+ });
let renderer!: ReactTestRenderer;
await act(async () => {
@@ -7875,7 +7882,7 @@ describe('QueryEditor external SQL save', () => {
expect(getQueryTabDraft('tab-1')).toBe('select 3;');
await act(async () => {
- findButton(renderer!, '保存').props.onClick();
+ await findButton(renderer!, '保存').props.onClick();
});
expect(backendApp.WriteSQLFile).not.toHaveBeenCalled();
@@ -7887,75 +7894,6 @@ describe('QueryEditor external SQL save', () => {
dbName: 'main',
createdAt: 100,
}));
- });
-
- it('keeps untitled fallback when the new query tab title is localized', async () => {
- setCurrentLanguage('en-US');
-
- let renderer!: ReactTestRenderer;
- await act(async () => {
- renderer = create();
- });
-
- editorState.value = 'select 8;';
-
- await act(async () => {
- findButton(renderer!, 'Save').props.onClick();
- });
-
- expect(storeState.saveQuery).toHaveBeenCalledWith(expect.objectContaining({
- id: 'saved-1',
- name: 'Untitled query',
- sql: 'select 8;',
- connectionId: 'conn-1',
- dbName: 'main',
- }));
- });
-
- it('keeps untitled fallback after a language switch when the tab title came from another locale', async () => {
- setCurrentLanguage('ja-JP');
-
- let renderer!: ReactTestRenderer;
- await act(async () => {
- renderer = create();
- });
-
- editorState.value = 'select 10;';
-
- await act(async () => {
- findButton(renderer!, '保存').props.onClick();
- });
-
- expect(storeState.saveQuery).toHaveBeenCalledWith(expect.objectContaining({
- id: 'saved-1',
- storeState.saveQuery.mockImplementationOnce(async (savedQuery: SavedQuery) => {
- storeState.savedQueries = storeState.savedQueries.map((item) => (
- item.id === savedQuery.id ? savedQuery : item
- ));
- storeSubscribers.forEach((subscriber) => subscriber());
- return savedQuery;
- });
- name: '無題のクエリ',
- sql: 'select 10;',
- connectionId: 'conn-1',
- dbName: 'main',
- }));
- });
-
- it('keeps untitled fallback for database-scoped new query titles after a language switch', async () => {
- setCurrentLanguage('ja-JP');
-
- let renderer!: ReactTestRenderer;
- await act(async () => {
- renderer = create();
- });
-
- editorState.value = 'select 11;';
-
- await act(async () => {
- await findButton(renderer!, '保存').props.onClick();
- });
-
expect(getQueryTabDraft('tab-1')).toBe('');
});
@@ -8165,6 +8103,68 @@ describe('QueryEditor external SQL save', () => {
});
expect(getQueryTabDraft('tab-1')).toBe('select 3;');
+ });
+
+ it('keeps untitled fallback when the new query tab title is localized', async () => {
+ setCurrentLanguage('en-US');
+
+ let renderer!: ReactTestRenderer;
+ await act(async () => {
+ renderer = create();
+ });
+
+ editorState.value = 'select 8;';
+
+ await act(async () => {
+ findButton(renderer!, 'Save').props.onClick();
+ });
+
+ expect(storeState.saveQuery).toHaveBeenCalledWith(expect.objectContaining({
+ id: 'saved-1',
+ name: 'Untitled query',
+ sql: 'select 8;',
+ connectionId: 'conn-1',
+ dbName: 'main',
+ }));
+ });
+
+ it('keeps untitled fallback after a language switch when the tab title came from another locale', async () => {
+ setCurrentLanguage('ja-JP');
+
+ let renderer!: ReactTestRenderer;
+ await act(async () => {
+ renderer = create();
+ });
+
+ editorState.value = 'select 10;';
+
+ await act(async () => {
+ findButton(renderer!, '保存').props.onClick();
+ });
+
+ expect(storeState.saveQuery).toHaveBeenCalledWith(expect.objectContaining({
+ id: 'saved-1',
+ name: '無題のクエリ',
+ sql: 'select 10;',
+ connectionId: 'conn-1',
+ dbName: 'main',
+ }));
+ });
+
+ it('keeps untitled fallback for database-scoped new query titles after a language switch', async () => {
+ setCurrentLanguage('ja-JP');
+
+ let renderer!: ReactTestRenderer;
+ await act(async () => {
+ renderer = create();
+ });
+
+ editorState.value = 'select 11;';
+
+ await act(async () => {
+ findButton(renderer!, '保存').props.onClick();
+ });
+
expect(storeState.saveQuery).toHaveBeenCalledWith(expect.objectContaining({
id: 'saved-1',
name: '無題のクエリ',
diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx
index 52167f71..2ea8528b 100644
--- a/frontend/src/components/QueryEditor.tsx
+++ b/frontend/src/components/QueryEditor.tsx
@@ -1346,12 +1346,12 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const toggleQueryResultsPanelActionRef = useRef(null);
const lastExternalQueryRef = useRef(getTabQueryValue(tab));
const lastLocalQueryRef = useRef(query);
+ const saveOperationQueueRef = useRef>(Promise.resolve());
+ const queryEditorMountedRef = useRef(true);
const imeCompositionFallbackRef = useRef<{
editor: any;
valueBefore: string;
selectionBefore: any;
- const saveOperationQueueRef = useRef>(Promise.resolve());
- const queryEditorMountedRef = useRef(true);
positionBefore: { lineNumber: number; column: number } | null;
committedText: string;
} | null>(null);
@@ -1869,10 +1869,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
return savedQueries.find((item) => item.id === tabId) || null;
}, [savedQueries, tab.id, tab.savedQueryId]);
- const syncQueryDraft = useCallback((nextQuery: string) => {
- const next = String(nextQuery ?? '');
- lastLocalQueryRef.current = next;
- persistQueryTabDraftSnapshot(draftSnapshotTab, next, {
useEffect(() => {
queryEditorMountedRef.current = true;
return () => {
@@ -1889,6 +1885,10 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
return queued;
}, []);
+ const syncQueryDraft = useCallback((nextQuery: string) => {
+ const next = String(nextQuery ?? '');
+ lastLocalQueryRef.current = next;
+ persistQueryTabDraftSnapshot(draftSnapshotTab, next, {
connectionId: currentConnectionIdRef.current,
dbName: currentDbRef.current,
});
@@ -8238,6 +8238,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const persistQuery = async (payload: { id: string; name: string; createdAt?: number }): Promise => {
const sql = getCurrentQuery();
+ lastLocalQueryRef.current = sql;
const saved = {
id: payload.id,
name: payload.name,
@@ -8284,7 +8285,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const openSaveQueryModal = (mode: 'save' | 'rename') => {
setSaveModalMode(mode);
- lastLocalQueryRef.current = sql;
saveForm.setFieldsValue({ name: currentSavedQuery?.name || resolveDefaultQueryName() });
setIsSaveModalOpen(true);
};
@@ -8665,6 +8665,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
name: String(values.name || '').trim() || translate('query_editor.save_modal.unnamed'),
createdAt: existed?.createdAt,
});
+ if (!applied) {
+ return;
+ }
message.success(translate(
saveModalMode === 'rename' ? 'query_editor.message.renamed' : 'query_editor.message.saved'
));
@@ -8688,9 +8691,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
currentResultSets,
activeResultKeyRef.current,
isV2Ui,
- if (!applied) {
- return;
- }
);
const nextResultSets = currentResultSets.filter(result => result.key !== key);
const nextActiveKey = currentActiveKey && currentActiveKey !== key