Skip to content

Commit a2a800b

Browse files
fix(instantsearch): update usage errors (#3543)
1 parent 41f7bd9 commit a2a800b

File tree

3 files changed

+166
-31
lines changed

3 files changed

+166
-31
lines changed

src/lib/InstantSearch.js

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import simpleMapping from './stateMappings/simple';
88
import historyRouter from './routers/history';
99
import version from './version';
1010
import createHelpers from './createHelpers';
11+
import { createDocumentationMessageGenerator } from './utils';
12+
13+
const withUsage = createDocumentationMessageGenerator({
14+
name: 'instantsearch',
15+
});
1116

1217
const ROUTING_DEFAULT_OPTIONS = {
1318
stateMapping: simpleMapping(),
@@ -47,22 +52,27 @@ class InstantSearch extends EventEmitter {
4752
searchClient = null,
4853
} = options;
4954

50-
if (indexName === null || searchClient === null) {
51-
throw new Error(`Usage: instantsearch({
52-
indexName: 'indexName',
53-
searchClient: algoliasearch('appId', 'apiKey')
54-
});`);
55+
if (indexName === null) {
56+
throw new Error(withUsage('The `indexName` option is required.'));
57+
}
58+
59+
if (searchClient === null) {
60+
throw new Error(withUsage('The `searchClient` option is required.'));
5561
}
5662

5763
if (typeof options.urlSync !== 'undefined') {
5864
throw new Error(
59-
'InstantSearch.js V3: `urlSync` option has been removed. You can now use the new `routing` option'
65+
withUsage(
66+
'The `urlSync` option was removed in InstantSearch.js 3. You may want to use the `routing` option.'
67+
)
6068
);
6169
}
6270

6371
if (typeof searchClient.search !== 'function') {
6472
throw new Error(
65-
'The search client must implement a `search(requests)` method.'
73+
`The \`searchClient\` must implement a \`search\` method.
74+
75+
See: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js/`
6676
);
6777
}
6878

@@ -117,7 +127,9 @@ class InstantSearch extends EventEmitter {
117127
addWidgets(widgets) {
118128
if (!Array.isArray(widgets)) {
119129
throw new Error(
120-
'You need to provide an array of widgets or call `addWidget()`'
130+
withUsage(
131+
'The `addWidgets` method expects an array of widgets. Please use `addWidget`.'
132+
)
121133
);
122134
}
123135

@@ -130,7 +142,9 @@ class InstantSearch extends EventEmitter {
130142
widgets.forEach(widget => {
131143
// Add the widget to the list of widget
132144
if (widget.render === undefined && widget.init === undefined) {
133-
throw new Error('Widget definition missing render or init method');
145+
throw new Error(`The widget definition expects a \`render\` and/or an \`init\` method.
146+
147+
See: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/`);
134148
}
135149

136150
this.widgets.push(widget);
@@ -185,7 +199,9 @@ class InstantSearch extends EventEmitter {
185199
removeWidgets(widgets) {
186200
if (!Array.isArray(widgets)) {
187201
throw new Error(
188-
'You need to provide an array of widgets or call `removeWidget()`'
202+
withUsage(
203+
'The `removeWidgets` method expects an array of widgets. Please use `removeWidget`.'
204+
)
189205
);
190206
}
191207

@@ -195,7 +211,9 @@ class InstantSearch extends EventEmitter {
195211
typeof widget.dispose !== 'function'
196212
) {
197213
throw new Error(
198-
'The widget you tried to remove does not implement the dispose method, therefore it is not possible to remove this widget'
214+
`The \`dispose\` method is required to remove the widget.
215+
216+
See: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/#the-widget-lifecycle-and-api`
199217
);
200218
}
201219

@@ -249,7 +267,11 @@ class InstantSearch extends EventEmitter {
249267
* @return {undefined} Does not return anything
250268
*/
251269
start() {
252-
if (this.started) throw new Error('start() has been already called once');
270+
if (this.started) {
271+
throw new Error(
272+
withUsage('The `start` method has already been called once.')
273+
);
274+
}
253275

254276
let searchParametersFromUrl;
255277

@@ -349,7 +371,9 @@ class InstantSearch extends EventEmitter {
349371

350372
createURL(params) {
351373
if (!this._createURL) {
352-
throw new Error('You need to call start() before calling createURL()');
374+
throw new Error(
375+
'The `start` method needs to be called before `createURL`.'
376+
);
353377
}
354378
return this._createURL(this.helper.state.setQueryParameters(params));
355379
}

src/lib/__tests__/InstantSearch-test.js

Lines changed: 124 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,125 @@ jest.mock('algoliasearch-helper', () => {
1616
});
1717
});
1818

19+
describe('Usage', () => {
20+
it('throws without indexName', () => {
21+
expect(() => {
22+
// eslint-disable-next-line no-new
23+
new InstantSearch({ indexName: undefined });
24+
}).toThrowErrorMatchingInlineSnapshot(`
25+
"The \`indexName\` option is required.
26+
27+
See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
28+
`);
29+
});
30+
31+
it('throws without searchClient', () => {
32+
expect(() => {
33+
// eslint-disable-next-line no-new
34+
new InstantSearch({ indexName: 'indexName', searchClient: undefined });
35+
}).toThrowErrorMatchingInlineSnapshot(`
36+
"The \`searchClient\` option is required.
37+
38+
See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
39+
`);
40+
});
41+
42+
it('throws if searchClient does not implement a search method', () => {
43+
expect(() => {
44+
// eslint-disable-next-line no-new
45+
new InstantSearch({ indexName: 'indexName', searchClient: {} });
46+
}).toThrowErrorMatchingInlineSnapshot(`
47+
"The \`searchClient\` must implement a \`search\` method.
48+
49+
See: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js/"
50+
`);
51+
});
52+
53+
it('throws if addWidgets is called with a single widget', () => {
54+
expect(() => {
55+
const search = new InstantSearch({
56+
indexName: 'indexName',
57+
searchClient: { search: () => {} },
58+
});
59+
search.addWidgets({});
60+
}).toThrowErrorMatchingInlineSnapshot(`
61+
"The \`addWidgets\` method expects an array of widgets. Please use \`addWidget\`.
62+
63+
See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
64+
`);
65+
});
66+
67+
it('throws if a widget without render or init method is added', () => {
68+
const widgets = [{ render: undefined, init: undefined }];
69+
70+
expect(() => {
71+
const search = new InstantSearch({
72+
indexName: 'indexName',
73+
searchClient: { search: () => {} },
74+
});
75+
search.addWidgets(widgets);
76+
}).toThrowErrorMatchingInlineSnapshot(`
77+
"The widget definition expects a \`render\` and/or an \`init\` method.
78+
79+
See: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/"
80+
`);
81+
});
82+
83+
it('does not throw with a widget having a init method', () => {
84+
const widgets = [{ init: () => {} }];
85+
86+
expect(() => {
87+
const search = new InstantSearch({
88+
indexName: 'indexName',
89+
searchClient: { search: () => {} },
90+
});
91+
search.addWidgets(widgets);
92+
}).not.toThrow();
93+
});
94+
95+
it('does not throw with a widget having a render method', () => {
96+
const widgets = [{ render: () => {} }];
97+
98+
expect(() => {
99+
const search = new InstantSearch({
100+
indexName: 'indexName',
101+
searchClient: { search: () => {} },
102+
});
103+
search.addWidgets(widgets);
104+
}).not.toThrow();
105+
});
106+
107+
it('throws if removeWidgets is called with a single widget', () => {
108+
expect(() => {
109+
const search = new InstantSearch({
110+
indexName: 'indexName',
111+
searchClient: { search: () => {} },
112+
});
113+
search.removeWidgets({});
114+
}).toThrowErrorMatchingInlineSnapshot(`
115+
"The \`removeWidgets\` method expects an array of widgets. Please use \`removeWidget\`.
116+
117+
See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
118+
`);
119+
});
120+
121+
it('throws if a widget without dispose method is removed', () => {
122+
const widgets = [{ init: () => {}, render: () => {}, dispose: undefined }];
123+
124+
expect(() => {
125+
const search = new InstantSearch({
126+
indexName: 'indexName',
127+
searchClient: { search: () => {} },
128+
});
129+
search.removeWidgets(widgets);
130+
}).toThrowErrorMatchingInlineSnapshot(`
131+
"The \`dispose\` method is required to remove the widget.
132+
133+
See: https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/js/#the-widget-lifecycle-and-api"
134+
`);
135+
});
136+
});
137+
19138
describe('InstantSearch lifecycle', () => {
20139
let algoliasearch;
21140
let client;
@@ -67,20 +186,6 @@ describe('InstantSearch lifecycle', () => {
67186
expect(algoliaSearchHelper).not.toHaveBeenCalled();
68187
});
69188

70-
describe('when adding a widget without render and init', () => {
71-
let widget;
72-
73-
beforeEach(() => {
74-
widget = {};
75-
});
76-
77-
it('throw an error', () => {
78-
expect(() => {
79-
search.addWidget(widget);
80-
}).toThrow('Widget definition missing render or init method');
81-
});
82-
});
83-
84189
it('does not fail when passing same references inside multiple searchParameters props', () => {
85190
const disjunctiveFacetsRefinements = { fruits: ['apple'] };
86191
const facetsRefinements = disjunctiveFacetsRefinements;
@@ -663,7 +768,9 @@ it('Does not allow to start twice', () => {
663768
expect(() => instance.start()).not.toThrow();
664769
expect(() => {
665770
instance.start();
666-
}).toThrowErrorMatchingInlineSnapshot(
667-
`"start() has been already called once"`
668-
);
771+
}).toThrowErrorMatchingInlineSnapshot(`
772+
"The \`start\` method has already been called once.
773+
774+
See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
775+
`);
669776
});

src/lib/__tests__/__snapshots__/search-client-test.js.snap

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ Array [
2828
]
2929
`;
3030

31-
exports[`InstantSearch Search Client Properties throws if no \`search()\` method 1`] = `"The search client must implement a \`search(requests)\` method."`;
31+
exports[`InstantSearch Search Client Properties throws if no \`search()\` method 1`] = `
32+
"The \`searchClient\` must implement a \`search\` method.
33+
34+
See: https://www.algolia.com/doc/guides/building-search-ui/going-further/backend-search/in-depth/backend-instantsearch/js/"
35+
`;

0 commit comments

Comments
 (0)