Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

feat(search-client): Add support for Custom Search Clients #1216

Merged
merged 15 commits into from
May 17, 2018
Merged
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
11 changes: 6 additions & 5 deletions packages/react-instantsearch/src/core/InstantSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ function validateNextProps(props, nextProps) {
* @propType {string} apiKey - Your Algolia search-only API key.
* @propType {string} indexName - Main index in which to search.
* @propType {boolean} [refresh=false] - Flag to activate when the cache needs to be cleared so that the front-end is updated when a change occurs in the index.
* @propType {object} [algoliaClient] - Provide a custom Algolia client instead of the internal one.
* @propType {object} [algoliaClient] - Provide a custom Algolia client instead of the internal one (deprecated in favor of `searchClient`).
* @propType {object} [searchClient] - Provide a custom search client.
* @propType {func} [onSearchStateChange] - Function to be called everytime a new search is done. Useful for [URL Routing](guide/Routing.html).
* @propType {object} [searchState] - Object to inject some search state. Switches the InstantSearch component in controlled mode. Useful for [URL Routing](guide/Routing.html).
* @propType {func} [createURL] - Function to call when creating links, useful for [URL Routing](guide/Routing.html).
Expand Down Expand Up @@ -59,7 +60,7 @@ class InstantSearch extends Component {

this.aisManager = createInstantSearchManager({
indexName: props.indexName,
algoliaClient: props.algoliaClient,
searchClient: props.searchClient,
initialState,
resultsState: props.resultsState,
stalledSearchDelay: props.stalledSearchDelay,
Expand All @@ -79,8 +80,8 @@ class InstantSearch extends Component {
}
}

if (this.props.algoliaClient !== nextProps.algoliaClient) {
this.aisManager.updateClient(nextProps.algoliaClient);
if (this.props.searchClient !== nextProps.searchClient) {
this.aisManager.updateClient(nextProps.searchClient);
}

if (this.isControlled) {
Expand Down Expand Up @@ -177,7 +178,7 @@ InstantSearch.propTypes = {
// @TODO: These props are currently constant.
indexName: PropTypes.string.isRequired,

algoliaClient: PropTypes.object.isRequired,
searchClient: PropTypes.object.isRequired,

createURL: PropTypes.func,

Expand Down
6 changes: 3 additions & 3 deletions packages/react-instantsearch/src/core/InstantSearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const DEFAULT_PROPS = {
appId: 'foo',
apiKey: 'bar',
indexName: 'foobar',
algoliaClient: {},
searchClient: {},
root: {
Root: 'div',
},
Expand Down Expand Up @@ -114,7 +114,7 @@ describe('InstantSearch', () => {
expect(createInstantSearchManager.mock.calls[0][0]).toEqual({
indexName: DEFAULT_PROPS.indexName,
initialState: {},
algoliaClient: {},
searchClient: {},
stalledSearchDelay: 200,
});
});
Expand All @@ -135,7 +135,7 @@ describe('InstantSearch', () => {
expect(ism.updateClient.mock.calls).toHaveLength(0);
wrapper.setProps({
...DEFAULT_PROPS,
algoliaClient: {},
searchClient: {},
});

expect(ism.updateClient.mock.calls).toHaveLength(1);
Expand Down
32 changes: 29 additions & 3 deletions packages/react-instantsearch/src/core/createInstantSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function createInstantSearch(defaultAlgoliaClient, root) {
return class CreateInstantSearch extends Component {
static propTypes = {
algoliaClient: PropTypes.object,
searchClient: PropTypes.object,
appId: PropTypes.string,
apiKey: PropTypes.string,
children: PropTypes.oneOfType([
Expand Down Expand Up @@ -42,24 +43,48 @@ export default function createInstantSearch(defaultAlgoliaClient, root) {
constructor(...args) {
super(...args);

if (this.props.searchClient) {
if (this.props.appId || this.props.apiKey || this.props.algoliaClient) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can cover those cases with tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I messed up with my rebase. Will add it back!

throw new Error(
'react-instantsearch:: `searchClient` cannot be used with `appId`, `apiKey` or `algoliaClient`.'
);
}
}

if (this.props.algoliaClient) {
// eslint-disable-next-line no-console
console.warn(
'`algoliaClient` option was renamed `searchClient`. Please use this new option before the next major version.'
);
}

this.client =
this.props.searchClient ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can cover this case with a test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

this.props.algoliaClient ||
defaultAlgoliaClient(this.props.appId, this.props.apiKey);

this.client.addAlgoliaAgent(`react-instantsearch ${version}`);
if (typeof this.client.addAlgoliaAgent === 'function') {
this.client.addAlgoliaAgent(`react-instantsearch ${version}`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can cover this case with a test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

componentWillReceiveProps(nextProps) {
const props = this.props;
if (nextProps.algoliaClient) {

if (nextProps.searchClient) {
this.client = nextProps.searchClient;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can cover this case with a test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! (I messed up with my rebase)

} else if (nextProps.algoliaClient) {
this.client = nextProps.algoliaClient;
} else if (
props.appId !== nextProps.appId ||
props.apiKey !== nextProps.apiKey
) {
this.client = defaultAlgoliaClient(nextProps.appId, nextProps.apiKey);
}
this.client.addAlgoliaAgent(`react-instantsearch ${version}`);

if (typeof this.client.addAlgoliaAgent === 'function') {
this.client.addAlgoliaAgent(`react-instantsearch ${version}`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can cover this case with a test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

render() {
Expand All @@ -71,6 +96,7 @@ export default function createInstantSearch(defaultAlgoliaClient, root) {
onSearchStateChange={this.props.onSearchStateChange}
onSearchParameters={this.props.onSearchParameters}
root={this.props.root}
searchClient={this.client}
algoliaClient={this.client}
refresh={this.props.refresh}
resultsState={this.props.resultsState}
Expand Down
123 changes: 119 additions & 4 deletions packages/react-instantsearch/src/core/createInstantSearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ describe('createInstantSearch', () => {
<CustomInstantSearch appId="app" apiKey="key" indexName="name" />
);

// eslint-disable-next-line no-shadow
const { algoliaClient, ...propsWithoutClient } = wrapper.props();
const {
algoliaClient, // eslint-disable-line no-shadow
searchClient,
...propsWithoutClient
} = wrapper.props();

expect(wrapper.is(InstantSearch)).toBe(true);
expect(propsWithoutClient).toMatchSnapshot();
expect(wrapper.props().algoliaClient).toBe(algoliaClient);
expect(wrapper.props().searchClient).toBe(searchClient);
});

it('creates an algolia client using the provided factory', () => {
Expand All @@ -43,6 +47,45 @@ describe('createInstantSearch', () => {
);
});

it('throws if algoliaClient is given with searchClient', () => {
const trigger = () =>
shallow(
<CustomInstantSearch
indexName="name"
searchClient={algoliaClient}
algoliaClient={algoliaClient}
/>
);

expect(() => trigger()).toThrow();
});

it('throws if appId is given with searchClient', () => {
const trigger = () =>
shallow(
<CustomInstantSearch
indexName="name"
appId="appId"
searchClient={algoliaClient}
/>
);

expect(() => trigger()).toThrow();
});

it('throws if apiKey is given with searchClient', () => {
const trigger = () =>
shallow(
<CustomInstantSearch
indexName="name"
apiKey="apiKey"
searchClient={algoliaClient}
/>
);

expect(() => trigger()).toThrow();
});

it('updates the algoliaClient when appId or apiKey changes', () => {
const wrapper = shallow(
<CustomInstantSearch appId="app" apiKey="key" indexName="name" />
Expand All @@ -56,6 +99,16 @@ describe('createInstantSearch', () => {
expect(algoliaClientFactory.mock.calls[2]).toEqual(['app', 'key2']);
});

it('uses the provided searchClient', () => {
const wrapper = shallow(
<CustomInstantSearch searchClient={algoliaClient} indexName="name" />
);

expect(algoliaClientFactory).toHaveBeenCalledTimes(0);
expect(algoliaClient.addAlgoliaAgent).toHaveBeenCalledTimes(1);
expect(wrapper.props().searchClient).toBe(algoliaClient);
});

it('uses the provided algoliaClient', () => {
const wrapper = shallow(
<CustomInstantSearch algoliaClient={algoliaClient} indexName="name" />
Expand All @@ -66,6 +119,22 @@ describe('createInstantSearch', () => {
expect(wrapper.props().algoliaClient).toBe(algoliaClient);
});

it('does not throw if searchClient does not have a `addAlgoliaAgent()` method', () => {
const client = {};
const trigger = () =>
shallow(<CustomInstantSearch indexName="name" searchClient={client} />);

expect(() => trigger()).not.toThrow();
});

it('does not throw if algoliaClient does not have a `addAlgoliaAgent()` method', () => {
const client = {};
const trigger = () =>
shallow(<CustomInstantSearch indexName="name" algoliaClient={client} />);

expect(() => trigger()).not.toThrow();
});

it('updates the algoliaClient when provided algoliaClient is passed down', () => {
const newAlgoliaClient = {
addAlgoliaAgent: jest.fn(),
Expand All @@ -85,6 +154,49 @@ describe('createInstantSearch', () => {
expect(newAlgoliaClient.addAlgoliaAgent).toHaveBeenCalledTimes(1);
});

it('updates the searchClient when provided searchClient is passed down', () => {
const newAlgoliaClient = {
addAlgoliaAgent: jest.fn(),
};

const wrapper = shallow(
<CustomInstantSearch searchClient={algoliaClient} indexName="name" />
);

expect(algoliaClient.addAlgoliaAgent).toHaveBeenCalledTimes(1);

wrapper.setProps({
searchClient: newAlgoliaClient,
});

expect(wrapper.props().searchClient).toBe(newAlgoliaClient);
expect(newAlgoliaClient.addAlgoliaAgent).toHaveBeenCalledTimes(1);
});

it('does not throw when algoliaClient gets updated and does not have a `addAlgoliaAgent()` method', () => {
const client = {};
const makeWrapper = () =>
shallow(<CustomInstantSearch indexName="name" algoliaClient={client} />);

expect(() => {
makeWrapper().setProps({
algoliaClient: client,
});
}).not.toThrow();
});

it('does not throw when searchClient gets updated and does not have a `addAlgoliaAgent()` method', () => {
const client = {};
const makeWrapper = () =>
shallow(<CustomInstantSearch indexName="name" searchClient={client} />);

expect(() => {
makeWrapper().setProps({
searchClient: client,
});
}).not.toThrow();
});

it('expect to create InstantSearch with a custom root props', () => {
const root = {
Root: 'span',
Expand All @@ -99,8 +211,11 @@ describe('createInstantSearch', () => {
<CustomInstantSearch indexName="name" root={root} />
);

// eslint-disable-next-line no-shadow, no-unused-vars
const { algoliaClient, ...propsWithoutClient } = wrapper.props();
const {
algoliaClient, // eslint-disable-line no-shadow, no-unused-vars
searchClient, // eslint-disable-line no-unused-vars
...propsWithoutClient
} = wrapper.props();

expect(wrapper.props().root).toEqual(root);
expect(propsWithoutClient).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('createInstantSearchManager', () => {
indexName: 'first',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

ism.widgetsManager.registerWidget({
Expand Down Expand Up @@ -122,7 +122,7 @@ describe('createInstantSearchManager', () => {
indexName: 'first',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

// <SearchBox defaultRefinement="query" />
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('createInstantSearchManager', () => {
indexName: 'first',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

ism.widgetsManager.registerWidget({
Expand Down Expand Up @@ -241,7 +241,7 @@ describe('createInstantSearchManager', () => {
indexName: 'first',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

ism.widgetsManager.registerWidget({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('createInstantSearchManager errors', () => {
indexName: 'index',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

ism.widgetsManager.registerWidget({
Expand Down Expand Up @@ -70,7 +70,7 @@ describe('createInstantSearchManager errors', () => {
indexName: 'index',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

ism.onExternalStateUpdate({});
Expand All @@ -95,7 +95,7 @@ describe('createInstantSearchManager errors', () => {
indexName: 'index',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

ism.onSearchForFacetValues({ facetName: 'facetName', query: 'query' });
Expand All @@ -118,7 +118,7 @@ describe('createInstantSearchManager errors', () => {
indexName: 'index',
initialState: {},
searchParameters: {},
algoliaClient: client,
searchClient: client,
});

ism.widgetsManager.registerWidget({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import highlightTags from './highlightTags.js';
export default function createInstantSearchManager({
indexName,
initialState = {},
algoliaClient,
searchClient,
resultsState,
stalledSearchDelay,
}) {
Expand All @@ -27,7 +27,7 @@ export default function createInstantSearchManager({

let stalledSearchTimer = null;

const helper = algoliasearchHelper(algoliaClient, indexName, baseSP);
const helper = algoliasearchHelper(searchClient, indexName, baseSP);
helper.on('result', handleSearchSuccess);
helper.on('error', handleSearchError);
helper.on('search', handleNewSearch);
Expand Down
Loading