Skip to content

Commit 4e93384

Browse files
committed
Merge branch 'main' into feat/344
2 parents ea26362 + 579e882 commit 4e93384

File tree

2 files changed

+177
-28
lines changed

2 files changed

+177
-28
lines changed

src/lib/DocsSearchBar.js

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@ import { MeiliSearch } from 'meilisearch'
88
/**
99
* Adds an autocomplete dropdown to an input field
1010
* @function DocsSearchBar
11-
* @param {string} options.hostUrl URL where MeiliSearch instance is hosted
12-
* @param {string} options.apiKey Read-only API key
13-
* @param {string} options.indexUid UID of the index to target
14-
* @param {string} options.inputSelector CSS selector that targets the input
15-
* @param {Object} [options.meilisearchOptions] Options to pass the underlying MeiliSearch client
16-
* @param {Object} [options.autocompleteOptions] Options to pass to the underlying autocomplete instance
11+
* @param {string} options.hostUrl URL where MeiliSearch instance is hosted
12+
* @param {string} options.apiKey Read-only API key
13+
* @param {string} options.indexUid UID of the index to target
14+
* @param {string} options.inputSelector CSS selector that targets the input
15+
* @param {boolean} [options.debug] When set to true, the dropdown will not be closed on blur
16+
* @param {Object} [options.meilisearchOptions] Options to pass the underlying MeiliSearch client
17+
* @param {function} [options.queryDataCallback] This function will be called when querying MeiliSearch
18+
* @param {Object} [options.autocompleteOptions] Options to pass to the underlying autocomplete instance
19+
* @param {function} [options.transformData] An optional function to transform the hits
20+
* @param {function} [options.queryHook] An optional function to transform the query
21+
* @param {function} [options.handleSelected] This function is called when a suggestion is selected
22+
* @param {function} [options.enhancedSearchInput] When set to true, a theme is applied to the search box to improve its appearance
23+
* @param {'column'|'simple'} [options.layout] Layout of the search bar
24+
* @param {boolean} [options.enableDarkMode] Allows you to display the searchbar in dark mode
1725
* @return {Object}
1826
*/
1927
const usage = `Usage:
@@ -22,8 +30,16 @@ const usage = `Usage:
2230
apiKey,
2331
indexUid,
2432
inputSelector,
25-
[ meilisearchOptions ]
26-
[ autocompleteOptions ]
33+
[ debug ],
34+
[ meilisearchOptions ],
35+
[ queryDataCallback ],
36+
[ autocompleteOptions ],
37+
[ transformData ],
38+
[ queryHook ],
39+
[ handleSelected ],
40+
[ enhancedSearchInput ],
41+
[ layout ],
42+
[ enableDarkMode ]
2743
})`
2844
class DocsSearchBar {
2945
constructor({
@@ -196,13 +212,56 @@ class DocsSearchBar {
196212
)
197213
}
198214

199-
if (typeof args.enableDarkMode !== 'boolean') {
215+
DocsSearchBar.typeCheck(
216+
args,
217+
['meilisearchOptions', 'autocompleteOptions'],
218+
'object',
219+
true,
220+
)
221+
222+
DocsSearchBar.typeCheck(
223+
args,
224+
['debug', 'enableDarkMode', 'enhancedSearchInput'],
225+
'boolean',
226+
false,
227+
)
228+
229+
DocsSearchBar.typeCheck(
230+
args,
231+
['queryDataCallback', 'transformData', 'queryHook', 'handleSelected'],
232+
'function',
233+
true,
234+
)
235+
236+
if (args.layout && !['simple', 'columns'].includes(args.layout)) {
200237
throw new Error(
201-
`Error: "enableDarkMode" must be of type: boolean. Found type: ${typeof args.inputSelector}`,
238+
`Error: "layout" must be either 'columns' or 'simple'. Supplied value: ${args.layout}`,
202239
)
203240
}
204241
}
205242

243+
/**
244+
* Checks if the arguments defined in the check variable are of the supplied
245+
* type
246+
* @param {any[]} args all arguments
247+
* @param {string[]} checkArguments array with the argument names to check
248+
* @param {string} type required type for the arguments
249+
* @param {boolean} optional don't check argument if it's falsy
250+
* @returns {void}
251+
*/
252+
static typeCheck(args, checkArguments, type, optional) {
253+
checkArguments
254+
.filter((argument) => !optional || args[argument])
255+
.forEach((argument) => {
256+
const value = args[argument]
257+
if (typeof args[argument] !== type) {
258+
throw new Error(
259+
`Error: "${argument}" must be of type: ${type}. Found type: ${typeof value}`,
260+
)
261+
}
262+
})
263+
}
264+
206265
static injectSearchBox(input) {
207266
input.before(templates.searchBox)
208267
const newInput = input.prev().prev().find('input')

src/lib/__tests__/DocsSearchBar-test.js

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,26 @@ describe('DocsSearchBar', () => {
230230

231231
describe('checkArguments', () => {
232232
let checkArguments
233+
let defaultOptions
234+
233235
beforeEach(() => {
234236
checkArguments = DocsSearchBar.checkArguments
237+
defaultOptions = {
238+
hostUrl: 'https://test.getmeili.com',
239+
apiKey: 'apiKey',
240+
indexUid: 'indexUID',
241+
inputSelector: '#input',
242+
debug: false,
243+
meilisearchOptions: {},
244+
queryDataCallback: null,
245+
autocompleteOptions: {},
246+
transformData: false,
247+
queryHook: false,
248+
handleSelected: false,
249+
enhancedSearchInput: false,
250+
layout: 'columns',
251+
enableDarkMode: false,
252+
}
235253
})
236254

237255
afterEach(() => {
@@ -242,11 +260,8 @@ describe('DocsSearchBar', () => {
242260

243261
it('should throw an error if no hostUrl defined', () => {
244262
// Given
245-
const options = {
246-
apiKey: 'apiKey',
247-
indexUid: 'indexUID',
248-
inputSelector: '#',
249-
}
263+
const options = defaultOptions
264+
delete options.hostUrl
250265

251266
// When
252267
expect(() => {
@@ -255,10 +270,8 @@ describe('DocsSearchBar', () => {
255270
})
256271
it('should throw an error if no inputSelector defined', () => {
257272
// Given
258-
const options = {
259-
hostUrl: 'test.com',
260-
indexUid: 'indexUID',
261-
}
273+
const options = defaultOptions
274+
delete options.inputSelector
262275

263276
// When
264277
expect(() => {
@@ -267,25 +280,102 @@ describe('DocsSearchBar', () => {
267280
})
268281
it('should throw an error if no indexUid defined', () => {
269282
// Given
270-
const options = {
271-
hostUrl: 'test.com',
272-
inputSelector: '#',
273-
}
283+
const options = defaultOptions
284+
delete options.indexUid
274285

275286
// When
276287
expect(() => {
277288
checkArguments(options)
278289
}).toThrow(/^Usage:/)
279290
})
280291
it('should throw an error if no selector matches', () => {
292+
// Given
293+
const options = { ...defaultOptions, inputSelector: '#' }
294+
sinon.stub(DocsSearchBar, 'getInputFromSelector').returns(false)
295+
296+
// When
297+
expect(() => {
298+
checkArguments(options)
299+
}).toThrow(/^Error:/)
300+
})
301+
it('should throw an error if enableDarkMode is not a boolean', () => {
302+
// Given
303+
const options = { ...defaultOptions, enableDarkMode: 'yes' }
304+
305+
// When
306+
expect(() => {
307+
checkArguments(options)
308+
}).toThrow(/^Error:/)
309+
})
310+
it('should throw an error if debug is not a boolean', () => {
311+
// Given
312+
const options = { ...defaultOptions, debug: null }
313+
314+
// When
315+
expect(() => {
316+
checkArguments(options)
317+
}).toThrow(/^Error:/)
318+
})
319+
it('should throw an error if enhancedSearchInput is not a boolean', () => {
320+
// Given
321+
const options = { ...defaultOptions, enhancedSearchInput: 'yes' }
322+
323+
// When
324+
expect(() => {
325+
checkArguments(options)
326+
}).toThrow(/^Error:/)
327+
})
328+
it('should throw an error if meilisearchOptions is not an object', () => {
329+
// Given
330+
const options = { ...defaultOptions, meilisearchOptions: 'not-an-object' }
331+
332+
// When
333+
expect(() => {
334+
checkArguments(options)
335+
}).toThrow(/^Error:/)
336+
})
337+
it('should throw an error if autocompleteOptions is not a object', () => {
281338
// Given
282339
const options = {
283-
hostUrl: 'test.com',
284-
apiKey: 'apiKey',
285-
indexUid: 'indexUID',
286-
inputSelector: '#',
340+
...defaultOptions,
341+
autocompleteOptions: 'not-an-object',
287342
}
288-
sinon.stub(DocsSearchBar, 'getInputFromSelector').returns(false)
343+
344+
// When
345+
expect(() => {
346+
checkArguments(options)
347+
}).toThrow(/^Error:/)
348+
})
349+
it('should throw an error if queryDataCallback is not a function', () => {
350+
// Given
351+
const options = { ...defaultOptions, queryDataCallback: 'not-a-function' }
352+
353+
// When
354+
expect(() => {
355+
checkArguments(options)
356+
}).toThrow(/^Error:/)
357+
})
358+
it('should throw an error if transformData is not a function', () => {
359+
// Given
360+
const options = { ...defaultOptions, transformData: 'not-a-function' }
361+
362+
// When
363+
expect(() => {
364+
checkArguments(options)
365+
}).toThrow(/^Error:/)
366+
})
367+
it('should throw an error if queryHook is not a function', () => {
368+
// Given
369+
const options = { ...defaultOptions, queryHook: 'not-a-function' }
370+
371+
// When
372+
expect(() => {
373+
checkArguments(options)
374+
}).toThrow(/^Error:/)
375+
})
376+
it('should throw an error if handleSelected is not a function', () => {
377+
// Given
378+
const options = { ...defaultOptions, handleSelected: 'not-a-function' }
289379

290380
// When
291381
expect(() => {

0 commit comments

Comments
 (0)