Skip to content

Commit

Permalink
feat(stage-wizard): implement text search wizard COMPASS-6669 (#5001)
Browse files Browse the repository at this point in the history
  • Loading branch information
mabaasit authored Oct 19, 2023
1 parent 709d949 commit 20ae018
Show file tree
Hide file tree
Showing 7 changed files with 769 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import BasicGroupUseCase from './group/basic-group';
import GroupWithStatistics from './group/group-with-statistics';
import MatchUseCase from './match/match';
import GroupWithSubset from './group/group-with-subset';
import TextSearch from './search/text-search';
import type { FieldSchema } from '../../../utils/get-schema';

export type StageWizardFields = FieldSchema[];
Expand All @@ -21,6 +22,7 @@ export type StageWizardUseCase = {
stageOperator: string;
wizardComponent: React.FunctionComponent<WizardComponentProps>;
serverVersion?: string;
isAtlasOnly?: boolean;
};

export const STAGE_WIZARD_USE_CASES: StageWizardUseCase[] = [
Expand Down Expand Up @@ -63,10 +65,17 @@ export const STAGE_WIZARD_USE_CASES: StageWizardUseCase[] = [
},
{
id: 'group-with-subset',
title: 'Return a subset of values based on their order or rank',
title: 'Return a subset of values based on their order or rank',
stageOperator: '$group',
wizardComponent: GroupWithSubset,
},
{
id: 'text-search',
title: 'Search for a text field across all documents in a collection',
stageOperator: '$search',
wizardComponent: TextSearch,
isAtlasOnly: true,
},
];

export { UseCaseCard };
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React, { type ComponentProps } from 'react';
import { TextSearch } from './text-search';
import { screen, render } from '@testing-library/react';
import { expect } from 'chai';
import sinon from 'sinon';
import {
setComboboxValue,
setInputElementValue,
setMultiSelectComboboxValues,
setSelectValue,
} from '../../../../../test/form-helper';
import { MULTI_SELECT_LABEL } from '../field-combobox';

const FIELDS = [
{ name: 'a', type: 'string' },
{ name: 'b', type: 'string' },
{ name: 'c', type: 'string' },
] as any;

const SEARCH_INDEXES = [{ name: 'index1' }, { name: 'index2' }] as any;

const renderTextSearch = (
props: Partial<ComponentProps<typeof TextSearch>> = {}
) => {
render(
<TextSearch
fields={[]}
indexes={[]}
indexesStatus={'INITIAL'}
onFetchIndexes={() => {}}
onChange={() => {}}
{...props}
/>
);
};

describe('TextSearch', function () {
const onChangeSpy = sinon.spy();

beforeEach(function () {
renderTextSearch({
onChange: onChangeSpy,
fields: FIELDS,
indexes: SEARCH_INDEXES,
indexesStatus: 'READY',
});
});

afterEach(function () {
onChangeSpy.resetHistory();
});

it('should render the component', () => {
expect(screen.getByText('Perform a')).to.exist;
expect(screen.getByText('with maxEdits')).to.exist;
expect(screen.getByText('for all documents where')).to.exist;
expect(screen.getByText('contains')).to.exist;
expect(screen.getByText('using')).to.exist;
});

context('calls onChange', function () {
it('for text search with fields', () => {
setSelectValue(/select search type/i, 'text-search');
setSelectValue(/select search path/i, 'field names');
setMultiSelectComboboxValues(new RegExp(MULTI_SELECT_LABEL, 'i'), [
'a',
'c',
]);
setInputElementValue(/text/i, 'abc');
setComboboxValue(/select or type a search index/i, 'index1');

expect(onChangeSpy.lastCall.firstArg).to.equal(
JSON.stringify({
index: 'index1',
text: {
query: 'abc',
path: ['a', 'c'],
},
})
);
expect(onChangeSpy.lastCall.lastArg).to.be.null;
});

it('for text search with wildcard', () => {
setSelectValue(/select search type/i, 'text-search');
setSelectValue(/select search path/i, 'wildcard');
setInputElementValue(/Wildcard/i, 'path.*');

setInputElementValue(/text/i, 'abc');
setComboboxValue(/select or type a search index/i, 'index1');

expect(onChangeSpy.lastCall.firstArg).to.equal(
JSON.stringify({
index: 'index1',
text: {
query: 'abc',
path: {
wildcard: 'path.*',
},
},
})
);
expect(onChangeSpy.lastCall.lastArg).to.be.null;
});

it('for fuzzy search with fields', () => {
setSelectValue(/select search type/i, 'fuzzy-search');
setInputElementValue(/maxEdits/i, '1');

setSelectValue(/select search path/i, 'field names');
setMultiSelectComboboxValues(new RegExp(MULTI_SELECT_LABEL, 'i'), [
'a',
'b',
]);

setInputElementValue(/text/i, 'def');
setComboboxValue(/select or type a search index/i, 'index2');

expect(onChangeSpy.lastCall.firstArg).to.equal(
JSON.stringify({
index: 'index2',
text: {
query: 'def',
path: ['a', 'b'],
fuzzy: {
maxEdits: 1,
},
},
})
);
expect(onChangeSpy.lastCall.lastArg).to.be.null;
});

it('for fuzzy search with wildcard', () => {
setSelectValue(/select search type/i, 'fuzzy-search');
setInputElementValue(/maxEdits/i, '2');

setSelectValue(/select search path/i, 'wildcard');
setInputElementValue(/wildcard/i, 'path.*');

setInputElementValue(/text/i, 'xyz');
setComboboxValue(/select or type a search index/i, 'index2');

expect(onChangeSpy.lastCall.firstArg).to.equal(
JSON.stringify({
index: 'index2',
text: {
query: 'xyz',
path: {
wildcard: 'path.*',
},
fuzzy: {
maxEdits: 2,
},
},
})
);
expect(onChangeSpy.lastCall.lastArg).to.be.null;
});
});

context('validation', function () {
it('should validate maxEdits', function () {
setSelectValue(/select search type/i, 'fuzzy-search');
{
setInputElementValue(/maxEdits/i, '0');
expect(onChangeSpy.lastCall.lastArg).to.be.an.instanceOf(Error);
}
{
setInputElementValue(/maxEdits/i, '3');
expect(onChangeSpy.lastCall.lastArg).to.be.an.instanceOf(Error);
}
});

it('should validate fields', function () {
setSelectValue(/select search path/i, 'field names');
expect(onChangeSpy.lastCall.lastArg).to.be.an.instanceOf(Error);
});

it('should validate search term', function () {
setInputElementValue(/text/i, 'xyz');
setInputElementValue(/text/i, '');
expect(onChangeSpy.lastCall.lastArg).to.be.an.instanceOf(Error);
});
});
});
Loading

0 comments on commit 20ae018

Please sign in to comment.