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

Commit

Permalink
feat(search-client): Add support for Custom Search Clients (#1216)
Browse files Browse the repository at this point in the history
* feat: Add support for `searchClient`

* feat: Add stories for `searchClient`

* feat: Add `searchClient` in `createInstantSearchManager`

* test: Replace `algoliaClient` with `searchClient`

* test: Updage snapshot with `searchClient`

* test: Add tests for `searchClient`

* style: Remove ESLint rule for `console`

* feat: Throw if API collision on the server

* test: Ignore `searchClient` in snapshot

* feat: Remove `algoliaClient` from InstantSearch implementation

* test: Test omitting `addAlgoliaAgent()` doesn't throw

* docs(algoliaClient): Deprecate `algoliaClient` in favor of `searchClient`

* test(createInstantSearchServer): Add tests for API collision

* test(createInstantSearchServer): Test `algoliaClient` and `searchClient` usages
  • Loading branch information
francoischalifour authored and samouss committed May 17, 2018
1 parent d0f319e commit 174cc28
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 44 deletions.
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) {
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 ||
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}`);
}
}

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

if (nextProps.searchClient) {
this.client = nextProps.searchClient;
} 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}`);
}
}

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

0 comments on commit 174cc28

Please sign in to comment.