Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 24 commits into from
Apr 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b085d77
feat(search-client): Add support and checks for `searchClient`
francoischalifour Apr 17, 2018
a700991
test(search-client): Add tests for `searchClient`
francoischalifour Apr 17, 2018
76c242e
refactor(search-client): Refactor code
francoischalifour Apr 18, 2018
113a56c
feat: Throw if `searchClient` doesn't implement `search()`
francoischalifour Apr 18, 2018
af50f97
test: Test `searchClient` throws error if no `search()` method
francoischalifour Apr 18, 2018
3c4da58
feat: Deprecate `createAlgoliaClient`
francoischalifour Apr 18, 2018
fd78e86
test: Test error message thrown
francoischalifour Apr 18, 2018
1b50bb4
test: Snapshot test the search client method input
francoischalifour Apr 18, 2018
dae8d23
fix: Deep copy of the search client
francoischalifour Apr 18, 2018
9fc0eb9
ci(lint): Rename FIXME to THROWAWAY for eslint to not fail
francoischalifour Apr 18, 2018
28c8f0e
refactor(searchClient): Clone `searchClient` with `Object.create()`
francoischalifour Apr 19, 2018
0bc3cf7
docs(searchClient): Add docs for `searchClient`
francoischalifour Apr 19, 2018
d6ce1c7
docs: Replace `br` with new lines
francoischalifour Apr 19, 2018
462b0c1
docs: Document why we need `Object.create()`
francoischalifour Apr 20, 2018
de182ab
docs(guide): Add guide "Prepare for v3" (#2905)
francoischalifour Apr 23, 2018
2de38bb
docs: Link guide to `searchClient`
francoischalifour Apr 25, 2018
35cba06
Merge branch 'feat/2.8' into feat/search-client-support
francoischalifour Apr 26, 2018
627d7f2
refactor: Update client assignation
francoischalifour Apr 26, 2018
517c109
test: Update `client.search()`
francoischalifour Apr 26, 2018
2b4d47b
chore: Add lock file
francoischalifour Apr 26, 2018
837c47e
revert: Add lock file
francoischalifour Apr 26, 2018
ae351c8
feat: Change usage message for search clients
francoischalifour Apr 26, 2018
dca3fab
test: Update usage
francoischalifour Apr 26, 2018
fa0ca7f
feat: Update warning for `createAlgoliaClient`
francoischalifour Apr 26, 2018
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
8 changes: 8 additions & 0 deletions docgen/src/data/communityHeader.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
{
"name": "Integrate with Angular (2+)",
"url": "guides/angular-integration.html"
},
{
"name": "Prepare for v3",
"url": "guides/prepare-for-v3.html"
}
]
},
Expand Down Expand Up @@ -144,6 +148,10 @@
"name": "Migrate from V1",
"url": "guides/migration.html"
},
{
"name": "Prepare for v3",
"url": "guides/prepare-for-v3.html"
},
{
"name": "instantsearch()",
"url": "instantsearch.html"
Expand Down
50 changes: 50 additions & 0 deletions docgen/src/guides/prepare-for-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: Prepare for v3
mainTitle: Guides
layout: main.pug
category: guides
withHeadings: true
navWeight: 0
editable: true
githubSource: docgen/src/guides/prepare-for-v3.md
---

Starting with 2.8.0, we are introducing changes and deprecations to prepare for the upcoming release of InstantSearch.js 3.

## Initializing InstantSearch

InstantSearch has always worked with the [Algolia search client](https://github.com/algolia/algoliasearch-client-javascript) behind the scenes. This will no longer be the default for InstantSearch.js 3. To prepare this future migration, you can start using the new options now.

### Current usage

1. [Import `InstantSearch.js`](https://community.algolia.com/instantsearch.js/v2/getting-started.html#install-instantsearchjs)
2. Initialize InstantSearch

```javascript
const search = instantsearch({
appId: 'appId',
apiKey: 'apiKey',
indexName: 'indexName',
});

search.start();
```

### New usage

1. [Import `algoliasearch`](https://github.com/algolia/algoliasearch-client-javascript)
2. [Import `InstantSearch.js`](https://community.algolia.com/instantsearch.js/v2/getting-started.html#install-instantsearchjs)
3. Initialize InstantSearch with the `searchClient` option

```javascript
const search = instantsearch({
indexName: 'indexName',
searchClient: algoliasearch('appId', 'apiKey'),
});

search.start();
```

## Deprecations

* [`createAlgoliaClient`](https://community.algolia.com/instantsearch.js/v2/instantsearch.html#struct-InstantSearchOptions-createAlgoliaClient) becomes deprecated in favor of [`searchClient`](https://community.algolia.com/instantsearch.js/v2/instantsearch.html#struct-InstantSearchOptions-searchClient)
1 change: 1 addition & 0 deletions docgen/src/guides/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mainTitle: Guides
layout: main.pug
category: guides
name: routing
navWeight: 5
withHeadings: true
editable: true
githubSource: docgen/src/guides/routing.md
Expand Down
92 changes: 71 additions & 21 deletions src/lib/InstantSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,39 @@ function defaultCreateURL() {
const defaultCreateAlgoliaClient = (defaultAlgoliasearch, appId, apiKey) =>
defaultAlgoliasearch(appId, apiKey);

const checkOptions = ({
appId,
apiKey,
indexName,
createAlgoliaClient,
searchClient,
}) => {
if (!searchClient) {
if (appId === null || apiKey === null || indexName === null) {
const usage = `
Usage: instantsearch({
appId: 'my_application_id',
apiKey: 'my_search_api_key',
indexName: 'my_index_name'
});`;
throw new Error(usage);
}
} else if (
searchClient &&
(indexName === null ||
appId !== null ||
apiKey !== null ||
createAlgoliaClient !== defaultCreateAlgoliaClient)
) {
const usage = `
Usage: instantsearch({
indexName: 'my_index_name',
searchClient: algoliasearch('appId', 'apiKey')
});`;
throw new Error(usage);
}
};

/**
* Widgets are the building blocks of InstantSearch.js. Any
* valid widget must have at least a `render` or a `init` function.
Expand All @@ -41,30 +74,39 @@ const defaultCreateAlgoliaClient = (defaultAlgoliasearch, appId, apiKey) =>
* @fires Instantsearch#render This event is triggered each time a render is done
*/
class InstantSearch extends EventEmitter {
constructor({
appId = null,
apiKey = null,
indexName = null,
numberLocale,
searchParameters = {},
urlSync = null,
routing = null,
searchFunction,
createAlgoliaClient = defaultCreateAlgoliaClient,
stalledSearchDelay = 200,
}) {
constructor(options) {
super();
if (appId === null || apiKey === null || indexName === null) {
const usage = `
Usage: instantsearch({
appId: 'my_application_id',
apiKey: 'my_search_api_key',
indexName: 'my_index_name'
});`;
throw new Error(usage);

const {
appId = null,
apiKey = null,
indexName = null,
numberLocale,
searchParameters = {},
urlSync = null,
routing = null,
searchFunction,
createAlgoliaClient = defaultCreateAlgoliaClient,
stalledSearchDelay = 200,
searchClient = null,
} = options;

checkOptions({
appId,
apiKey,
indexName,
createAlgoliaClient,
searchClient,
});

if (searchClient && typeof searchClient.search !== 'function') {
throw new Error(
'InstantSearch configuration error: `searchClient` must implement a `search(requests)` method.'
);
}

const client = createAlgoliaClient(algoliasearch, appId, apiKey);
const client =
searchClient || createAlgoliaClient(algoliasearch, appId, apiKey);

if (typeof client.addAlgoliaAgent === 'function') {
client.addAlgoliaAgent(`instantsearch.js ${version}`);
Expand Down Expand Up @@ -115,6 +157,14 @@ Usage: instantsearch({
...ROUTING_DEFAULT_OPTIONS,
...routing,
};

if (options.createAlgoliaClient) {
// eslint-disable-next-line no-console
console.warn(`
InstantSearch.js: \`createAlgoliaClient\` option is deprecated and will be removed in the next major version.
Please use \`searchClient\` instead: https://community.algolia.com/instantsearch.js/v2/instantsearch.html#struct-InstantSearchOptions-searchClient.
To help you migrate, please refer to the migration guide: https://community.algolia.com/instantsearch.js/v2/guides/prepare-for-v3.html`);
}
}

/**
Expand Down
31 changes: 31 additions & 0 deletions src/lib/__tests__/__snapshots__/search-client-test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`InstantSearch Search Client Lifecycle calls the provided searchFunction when used 1`] = `
Array [
Object {
"indexName": "",
"params": Object {
"facets": Array [],
"page": 0,
"query": "test",
"tagFilters": "",
},
},
]
`;

exports[`InstantSearch Search Client Lifecycle gets called on search 1`] = `
Array [
Object {
"indexName": "",
"params": Object {
"facets": Array [],
"page": 0,
"query": "",
"tagFilters": "",
},
},
]
`;

exports[`InstantSearch Search Client Properties throws if no \`search()\` method 1`] = `"InstantSearch configuration error: \`searchClient\` must implement a \`search(requests)\` method."`;
62 changes: 62 additions & 0 deletions src/lib/__tests__/api-collision.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint no-new: off */
import InstantSearch from '../InstantSearch';

const usage = `
Usage: instantsearch({
indexName: 'my_index_name',
searchClient: algoliasearch('appId', 'apiKey')
});`;

// THROWAWAY: Test suite to remove once the next major version is released
describe('InstantSearch API collision', () => {
describe('with search client', () => {
const appId = 'appId';
const apiKey = 'apiKey';
const indexName = 'indexName';
const searchClient = { search() {} };

it('and indexName', () => {
expect(() => {
new InstantSearch({
indexName,
searchClient,
});
}).not.toThrow();
});

it('and nothing else', () => {
expect(() => {
new InstantSearch({
searchClient,
});
}).toThrow(usage);
});

it('and appId', () => {
expect(() => {
new InstantSearch({
appId,
searchClient,
});
}).toThrow(usage);
});

it('and apiKey', () => {
expect(() => {
new InstantSearch({
apiKey,
searchClient,
});
}).toThrow(usage);
});

it('and createAlgoliaClient', () => {
expect(() => {
new InstantSearch({
createAlgoliaClient: () => {},
searchClient,
});
}).toThrow(usage);
});
});
});
62 changes: 62 additions & 0 deletions src/lib/__tests__/search-client-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import InstantSearch from '../InstantSearch';

describe('InstantSearch Search Client', () => {
describe('Properties', () => {
it('throws if no `search()` method', () => {
expect(() => {
// eslint-disable-next-line no-new
new InstantSearch({
indexName: '',
searchClient: {},
});
}).toThrowErrorMatchingSnapshot();
});
});

describe('Lifecycle', () => {
it('gets called on search', () => {
const searchClientSpy = {
search: jest.fn(() => Promise.resolve({ results: [{}] })),
};

const search = new InstantSearch({
indexName: '',
searchClient: searchClientSpy,
});

expect(searchClientSpy.search).not.toHaveBeenCalled();

search.start();

expect(search.helper.state.query).toBe('');
expect(searchClientSpy.search).toHaveBeenCalledTimes(1);
expect(searchClientSpy.search.mock.calls[0][0]).toMatchSnapshot();
});

it('calls the provided searchFunction when used', () => {
const searchFunctionSpy = jest.fn(h => {
h.setQuery('test').search();
});

const searchClientSpy = {
search: jest.fn(() => Promise.resolve({ results: [{}] })),
};

const search = new InstantSearch({
indexName: '',
searchFunction: searchFunctionSpy,
searchClient: searchClientSpy,
});

expect(searchFunctionSpy).not.toHaveBeenCalled();
expect(searchClientSpy.search).not.toHaveBeenCalled();

search.start();

expect(searchFunctionSpy).toHaveBeenCalledTimes(1);
expect(search.helper.state.query).toBe('test');
expect(searchClientSpy.search).toHaveBeenCalledTimes(1);
expect(searchClientSpy.search.mock.calls[0][0]).toMatchSnapshot();
});
});
});
Loading