diff --git a/src/features/collections/components/CollectionTab.tsx b/src/features/collections/components/CollectionTab.tsx index ad030df..51930ed 100644 --- a/src/features/collections/components/CollectionTab.tsx +++ b/src/features/collections/components/CollectionTab.tsx @@ -62,6 +62,7 @@ import { getVisibleFields, documentsToJson, getErrorMessage, + resolveSimpleQueryPath, } from '../../../shared/utils'; import { generateJsQueryFromSimpleParams } from '../../../shared/utils/queryUtils'; @@ -425,23 +426,6 @@ const CollectionTab: React.FC = ({ } }, [viewMode, documents]); - // F5 keyboard shortcut - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'F5') { - e.preventDefault(); - if (queryModeRef.current === 'js') { - executeJsQuery(); - } else { - loadDocuments(); - } - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [executeJsQuery, loadDocuments]); - // Computed values const allFields = useMemo(() => extractAllFields(documents), [documents]); const queryFields = useMemo(() => extractQueryableFields(documents), [documents]); @@ -460,26 +444,52 @@ const CollectionTab: React.FC = ({ ); // Event Handlers + const handleSimpleQuery = useCallback(async () => { + const queryPath = resolveSimpleQueryPath(collectionPathInput, documentPath || collectionPath); + if (!queryPath) { + showMessage?.('Enter a valid Firestore collection or document path.', 'error'); + return; + } + + if (queryPath.shouldOpenPath) { + onOpenCollection?.(queryPath.path, firestoreDatabaseId); + return; + } + + await loadDocuments(); + }, [ + collectionPathInput, + collectionPath, + documentPath, + firestoreDatabaseId, + loadDocuments, + onOpenCollection, + showMessage, + ]); + const handleRunQuery = useCallback(async () => { if (queryMode === 'simple') { - await loadDocuments(); + await handleSimpleQuery(); } else { await executeJsQuery(); } - }, [queryMode, loadDocuments, executeJsQuery]); + }, [queryMode, handleSimpleQuery, executeJsQuery]); - const handleOpenCollectionPath = useCallback(() => { - const nextPath = collectionPathInput.trim().replace(/^\/+|\/+$/g, ''); - const segments = nextPath.split('/'); - - if (!nextPath || segments.some((segment) => !segment)) { - showMessage?.('Enter a valid Firestore collection or document path.', 'error'); - return; - } + // F5 follows the same behavior as the Run button, including the edited Simple Query path. + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key !== 'F5') return; + event.preventDefault(); + if (queryModeRef.current === 'js') { + void executeJsQuery(); + } else { + void handleSimpleQuery(); + } + }; - if (nextPath === (documentPath || collectionPath)) return; - onOpenCollection?.(nextPath, firestoreDatabaseId); - }, [collectionPathInput, collectionPath, documentPath, firestoreDatabaseId, onOpenCollection, showMessage]); + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [executeJsQuery, handleSimpleQuery]); const handleToggleFavorite = useCallback(() => { dispatch( @@ -757,7 +767,7 @@ const CollectionTab: React.FC = ({ onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); - handleOpenCollectionPath(); + void handleSimpleQuery(); } }} placeholder="Collection path" diff --git a/src/shared/utils/collectionUtils.test.ts b/src/shared/utils/collectionUtils.test.ts index 2ac03a9..434918d 100644 --- a/src/shared/utils/collectionUtils.test.ts +++ b/src/shared/utils/collectionUtils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { extractQueryableFields } from './collectionUtils'; +import { extractQueryableFields, normalizeFirestorePath, resolveSimpleQueryPath } from './collectionUtils'; describe('extractQueryableFields', () => { it('includes top-level and nested map field paths', () => { @@ -34,3 +34,27 @@ describe('extractQueryableFields', () => { ).toEqual(['createdAt', 'location', 'tags']); }); }); + +describe('normalizeFirestorePath', () => { + it('preserves a nested document path entered in Simple Query', () => { + expect(normalizeFirestorePath(' /content-plans/plan-id/ads/ad-id/ ')).toBe('content-plans/plan-id/ads/ad-id'); + }); + + it('rejects empty Firestore path segments', () => { + expect(normalizeFirestorePath('content-plans//ads')).toBeNull(); + }); + + it('opens the path currently entered in Simple Query instead of the tab collection', () => { + expect(resolveSimpleQueryPath('content-plans/plan-id/ads/ad-id', 'content-plans')).toEqual({ + path: 'content-plans/plan-id/ads/ad-id', + shouldOpenPath: true, + }); + }); + + it('refreshes the current tab when the entered path has not changed', () => { + expect(resolveSimpleQueryPath(' content-plans ', 'content-plans')).toEqual({ + path: 'content-plans', + shouldOpenPath: false, + }); + }); +}); diff --git a/src/shared/utils/collectionUtils.ts b/src/shared/utils/collectionUtils.ts index 39179a1..eaa60f4 100644 --- a/src/shared/utils/collectionUtils.ts +++ b/src/shared/utils/collectionUtils.ts @@ -224,3 +224,22 @@ export const createDefaultSortConfig = (): SortConfig => ({ field: '', direction: 'asc', }); + +/** + * Normalizes a Firestore collection or document path entered by the user. + * Returns null when the path contains an empty segment. + */ +export const normalizeFirestorePath = (path: string): string | null => { + const normalizedPath = path.trim().replace(/^\/+|\/+$/g, ''); + if (!normalizedPath || normalizedPath.split('/').some((segment) => !segment)) return null; + return normalizedPath; +}; + +/** Decides whether a Simple Query should refresh the current tab or open the entered path. */ +export const resolveSimpleQueryPath = ( + inputPath: string, + currentPath: string, +): { path: string; shouldOpenPath: boolean } | null => { + const path = normalizeFirestorePath(inputPath); + return path ? { path, shouldOpenPath: path !== currentPath } : null; +}; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index 513bd7c..a44445a 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -39,6 +39,8 @@ export { documentsToJson, createEmptyFilter, createDefaultSortConfig, + normalizeFirestorePath, + resolveSimpleQueryPath, } from './collectionUtils'; // Common utilities