Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,19 @@ describe('Multiple Connections Sidebar Component', function () {
expect(screen.queryByText('No results found.')).to.be.null;
});

it('should show "No results found." when the search pattern is invalid', async function () {
await renderAndWaitForNavigationTree();

const searchInput = screen.getByRole('searchbox', { name: 'Search' });
userEvent.type(searchInput, '(');

await waitFor(() => {
expect(screen.getByTestId('no-search-results')).to.be.visible;
expect(screen.getByText('No results found.')).to.be.visible;
});
expect(screen.queryByRole('tree')).to.be.null;
});

context('and performing actions', function () {
beforeEach(async function () {
await renderAndWaitForNavigationTree({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function MultipleConnectionSidebar({
string | undefined
>(undefined);
const [connectionsFilter, setConnectionsFilter] = useState<ConnectionsFilter>(
{ regex: null, excludeInactive: false }
{ regex: null, searchText: '', excludeInactive: false }
);
const [connectionInfoModalConnectionId, setConnectionInfoModalConnectionId] =
useState<string | undefined>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default function NavigationItemsFilter({
(event) => {
onFilterChange((filter) => ({
...filter,
searchText: event.target.value,
regex: createRegExp(event.target.value),
}));
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe('useFilteredConnections', function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand All @@ -175,7 +175,7 @@ describe('useFilteredConnections', function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand All @@ -197,7 +197,7 @@ describe('useFilteredConnections', function () {
{
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: true },
filter: { regex: null, searchText: '', excludeInactive: true },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand All @@ -211,7 +211,7 @@ describe('useFilteredConnections', function () {

rerender({
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
});
Expand All @@ -225,7 +225,7 @@ describe('useFilteredConnections', function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand Down Expand Up @@ -256,7 +256,7 @@ describe('useFilteredConnections', function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand Down Expand Up @@ -308,7 +308,7 @@ describe('useFilteredConnections', function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand Down Expand Up @@ -344,7 +344,7 @@ describe('useFilteredConnections', function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand Down Expand Up @@ -390,7 +390,7 @@ describe('useFilteredConnections', function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand Down Expand Up @@ -423,7 +423,7 @@ describe('useFilteredConnections', function () {
{
initialProps: {
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
Expand Down Expand Up @@ -457,7 +457,7 @@ describe('useFilteredConnections', function () {
];
rerender({
connections: newConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
});
Expand All @@ -473,7 +473,7 @@ describe('useFilteredConnections', function () {
// now pretend again that connection2 is connected
rerender({
connections: mockSidebarConnections,
filter: { regex: null, excludeInactive: false },
filter: { regex: null, searchText: '', excludeInactive: false },
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
});
Expand All @@ -497,6 +497,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('_connection', 'i'), // match everything basically
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -515,6 +516,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('disconnected_connection', 'i'), // match disconnected one
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -533,6 +535,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('db_ready_1_1', 'i'), // match first database basically
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand Down Expand Up @@ -576,6 +579,7 @@ describe('useFilteredConnections', function () {
],
filter: {
regex: new RegExp('Matching', 'i'), // this matches connection as well as database
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -596,6 +600,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('coll_ready_2_1', 'i'), // match second db's collection
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand Down Expand Up @@ -627,6 +632,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('ready_2_1', 'i'), // this matches 1 database and 1 collection
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -648,6 +654,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('ready_1_1\\.coll_ready_shared_name', 'i'), // this matches only coll_ready_shared_name collection in ready_1_1 database
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -669,6 +676,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('coll_ready_1_1', 'i'),
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -687,6 +695,25 @@ describe('useFilteredConnections', function () {
});
});

it('should return no connections when search text is non-empty but regex is invalid', async function () {
const { result } = renderHookWithContext(useFilteredConnections, {
initialProps: {
connections: mockSidebarConnections,
filter: {
regex: null,
searchText: '(',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
onDatabaseExpand: onDatabaseExpandStub,
},
});

await waitFor(() => {
expect(result.current.filtered).to.deep.equal([]);
});
});

context('excluding inactive connections', function () {
it('should match only connected collections items', function () {
const { result, rerender } = renderHookWithContext(
Expand All @@ -696,6 +723,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('connection_1'),
searchText: '',
excludeInactive: true,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -712,6 +740,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('connection_1'),
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -734,6 +763,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: null as RegExp | null,
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -755,6 +785,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('coll_ready_1_1', 'i'),
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -781,6 +812,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('coll_ready_1_1', 'i') as RegExp | null,
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -802,6 +834,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: null,
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand All @@ -826,6 +859,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('coll_ready_1_1', 'i'),
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand Down Expand Up @@ -861,6 +895,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('coll_ready_1_1', 'i'),
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand Down Expand Up @@ -902,6 +937,7 @@ describe('useFilteredConnections', function () {
connections: mockSidebarConnections,
filter: {
regex: new RegExp('coll_ready_1_1', 'i'),
searchText: '',
excludeInactive: false,
},
fetchAllCollections: fetchAllCollectionsStub,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ import type {
SidebarNotConnectedConnection,
} from '@mongodb-js/compass-connections-navigation';
import { type ConnectionInfo } from '@mongodb-js/connection-info';
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useReducer,
useRef,
} from 'react';

/** Matches nothing; used when the user entered a non-empty search that is not a valid RegExp. */
const NEVER_MATCH_REGEX = /^$/;

type ExpandedDatabases = Record<
SidebarDatabase['_id'],
Expand Down Expand Up @@ -394,6 +404,11 @@ function filteredConnectionsToSidebarConnection(

export type ConnectionsFilter = {
regex: RegExp | null;
/**
* Raw search field text. When non-empty and `regex` is null (invalid pattern),
* filtering still runs so the UI can show an empty state instead of the full list.
*/
searchText?: string;
excludeInactive: boolean;
};

Expand Down Expand Up @@ -429,10 +444,19 @@ export const useFilteredConnections = ({
// filter updates
// connections change often, but the effect only uses connections if the filter is active
// so we use this conditional dependency to avoid too many calls
const connectionsWhenFiltering =
(filter.regex || filter.excludeInactive) && connections;
useEffect(() => {
if (!filter.regex && !filter.excludeInactive) {
const searchText = filter.searchText ?? '';
const invalidSearchPattern = searchText.length > 0 && filter.regex === null;
const isFilteringActive =
!!filter.regex || filter.excludeInactive || invalidSearchPattern;
const connectionsWhenFiltering = isFilteringActive && connections;
const filterRegexForReducer = invalidSearchPattern
? NEVER_MATCH_REGEX
: filter.regex;

// useLayoutEffect so the filtered list and empty state update in the same frame as
// the search input, avoiding a brief flash of unfiltered results.
useLayoutEffect(() => {
if (!isFilteringActive) {
dispatch({ type: CLEAR_FILTER });
} else if (connectionsWhenFiltering) {
// the above check is extra just to please TS
Expand All @@ -445,12 +469,13 @@ export const useFilteredConnections = ({
dispatch({
type: FILTER_CONNECTIONS,
connections: connectionsWhenFiltering,
filterRegex: filter.regex,
filterRegex: filterRegexForReducer,
excludeInactive: filter.excludeInactive,
});
}
}, [
filter.regex,
isFilteringActive,
filterRegexForReducer,
filter.excludeInactive,
connectionsWhenFiltering,
fetchAllCollections,
Expand Down
Loading