diff --git a/scripts/playground.html b/scripts/playground.html
index 948e8a31..f99447b3 100644
--- a/scripts/playground.html
+++ b/scripts/playground.html
@@ -1,61 +1,51 @@
-
-
+
+
-
-
Search thanks to the assets served at http://127.0.0.1:8080/
-
Do not forget to hard refresh: cmd + maj + R
-
-
-
-
-
-
-
-
-
+
+
Assets are served from http://127.0.0.1:8080/
+
+
-
-
-
-
+
-
+
diff --git a/scripts/serve b/scripts/serve
index 15dc6477..fe939e63 100755
--- a/scripts/serve
+++ b/scripts/serve
@@ -3,9 +3,9 @@
yarn run build:css
echo "Find all files at http://127.0.0.1:8080/";
-open scripts/playground.html
parallelshell \
'webpack --config webpack.serve.config.babel.js -w' \
'onchange "./src/styles/*.scss" -- yarn run build:css' \
- 'live-server ./dist/cdn' \
+ 'live-server --no-browser ./dist/cdn' \
+ "${BROWSER} scripts/playground.html"
diff --git a/scripts/test-watch b/scripts/test-watch
index 63b84fce..429a46cc 100755
--- a/scripts/test-watch
+++ b/scripts/test-watch
@@ -3,5 +3,6 @@ set -e
jest \
--no-cache \
--watch \
+ --no-watchman \
./src/lib/
diff --git a/src/lib/DocSearch.js b/src/lib/DocSearch.js
index 1eaabefc..310d9ed3 100644
--- a/src/lib/DocSearch.js
+++ b/src/lib/DocSearch.js
@@ -64,7 +64,7 @@ class DocSearch {
this.apiKey = apiKey;
this.appId = appId;
this.indexName = indexName;
- this.inputs = DocSearch.getInputsFromSelector(inputSelector);
+ this.input = DocSearch.getInputFromSelector(inputSelector);
this.algoliaOptions = { hitsPerPage: 5, ...algoliaOptions };
const autocompleteOptionsDebug =
autocompleteOptions && autocompleteOptions.debug
@@ -79,48 +79,50 @@ class DocSearch {
this.autocompleteOptions.cssClasses.prefix || 'ds';
// eslint-disable-next-line no-param-reassign
+ handleSelected = handleSelected || this.handleSelected;
+
this.isSimpleLayout = layout === 'simple';
this.client = algoliasearch(this.appId, this.apiKey);
this.client.addAlgoliaAgent(`docsearch.js ${version}`);
- for (let i = 0; i < this.inputs.length; i++) {
- let uniqInput = this.inputs[i];
- if (uniqInput) {
- if (enhancedSearchInput) {
- uniqInput = DocSearch.injectSearchBox(uniqInput);
- }
- this.autocomplete = autocomplete(uniqInput, autocompleteOptions, [
- {
- source: this.getAutocompleteSource(transformData, queryHook),
- templates: {
- suggestion: DocSearch.getSuggestionTemplate(this.isSimpleLayout),
- footer: templates.footer,
- empty: DocSearch.getEmptyTemplate(),
- },
- },
- ]);
- if (handleSelected) {
- let handleSelectedFn = null;
- if (typeof handleSelected === 'function') {
- handleSelectedFn = handleSelected;
- } else {
- handleSelectedFn = this.handleSelected;
- }
- this.autocomplete.on(
- 'autocomplete:selected',
- handleSelectedFn.bind(null, this.autocomplete.autocomplete)
- );
- }
- this.autocomplete.on(
- 'autocomplete:shown',
- this.handleShown.bind(null, uniqInput)
- );
+ if (enhancedSearchInput) {
+ this.input = DocSearch.injectSearchBox(this.input);
+ }
- if (enhancedSearchInput) {
- DocSearch.bindSearchBoxEvent();
- }
- }
+ this.autocomplete = autocomplete(this.input, autocompleteOptions, [
+ {
+ source: this.getAutocompleteSource(transformData, queryHook),
+ templates: {
+ suggestion: DocSearch.getSuggestionTemplate(this.isSimpleLayout),
+ footer: templates.footer,
+ empty: DocSearch.getEmptyTemplate(),
+ },
+ },
+ ]);
+
+ // If user defined its own handleSelected, we prevent clicks on suggestions
+ // link to do anything
+ if (handleSelected) {
+ $('.algolia-autocomplete').on('click', '.ds-suggestions a', event => {
+ event.preventDefault();
+ });
+ }
+
+ // Click on suggestions will follow the link, but keyboard navigation still
+ // need the handleSelected
+ this.autocomplete.on(
+ 'autocomplete:selected',
+ handleSelected.bind(null, this.autocomplete.autocomplete)
+ );
+
+ this.autocomplete.on(
+ 'autocomplete:shown',
+ this.handleShown.bind(null, this.input)
+ );
+
+ if (enhancedSearchInput) {
+ DocSearch.bindSearchBoxEvent();
}
}
@@ -143,7 +145,7 @@ class DocSearch {
);
}
- if (!DocSearch.getInputsFromSelector(args.inputSelector)) {
+ if (!DocSearch.getInputFromSelector(args.inputSelector)) {
throw new Error(
`Error: No input element in the page matches ${args.inputSelector}`
);
@@ -179,26 +181,14 @@ class DocSearch {
/**
* Returns the matching input from a CSS selector, null if none matches
- * @function getInputsFromSelector
+ * @function getInputFromSelector
* @param {string} selector CSS selector that matches the search
* input of the page
* @returns {void}
*/
- static getInputsFromSelector(selector) {
- if (!selector.length) {
- return null;
- } else {
- const selectors = selector.split(',');
- if (selectors.length === 1) {
- const input = $(selector).filter('input');
- return input.length ? [$(input[0])] : null;
- } else {
- return selectors.map(s => {
- const input = $(s).filter('input');
- return input.length ? $(input[0]) : null;
- });
- }
- }
+ static getInputFromSelector(selector) {
+ const input = $(selector).filter('input');
+ return input.length ? $(input[0]) : null;
}
/**
diff --git a/src/lib/__tests__/DocSearch-test.js b/src/lib/__tests__/DocSearch-test.js
index e075e935..b435e278 100644
--- a/src/lib/__tests__/DocSearch-test.js
+++ b/src/lib/__tests__/DocSearch-test.js
@@ -46,7 +46,7 @@ describe('DocSearch', () => {
};
sinon.spy(DocSearch, 'checkArguments');
- sinon.stub(DocSearch, 'getInputsFromSelector').returns(true);
+ sinon.stub(DocSearch, 'getInputFromSelector').returns(true);
DocSearch.__Rewire__('algoliasearch', AlgoliaSearch);
DocSearch.__Rewire__('autocomplete', AutoComplete);
@@ -54,7 +54,7 @@ describe('DocSearch', () => {
afterEach(() => {
DocSearch.checkArguments.restore();
- DocSearch.getInputsFromSelector.restore();
+ DocSearch.getInputFromSelector.restore();
DocSearch.__ResetDependency__('algoliasearch');
DocSearch.__ResetDependency__('autocomplete');
});
@@ -136,13 +136,13 @@ describe('DocSearch', () => {
it('should pass the input element as an instance property', () => {
// Given
const options = defaultOptions;
- DocSearch.getInputsFromSelector.returns($('
foo'));
+ DocSearch.getInputFromSelector.returns($('
foo'));
// When
const actual = new DocSearch(options);
// Then
- const $inputs = actual.inputs;
+ const $inputs = actual.input;
expect($inputs.text()).toEqual('foo');
expect($inputs[0].tagName).toEqual('SPAN');
});
@@ -194,7 +194,7 @@ describe('DocSearch', () => {
autocompleteOptions: { anOption: '44' },
};
const $input = $('
');
- DocSearch.getInputsFromSelector.returns([$input]);
+ DocSearch.getInputFromSelector.returns($input);
// When
new DocSearch(options);
@@ -211,7 +211,7 @@ describe('DocSearch', () => {
});
it('should listen to the selected and shown event of autocomplete', () => {
// Given
- const options = { ...defaultOptions, handleSelected: true };
+ const options = { ...defaultOptions, handleSelected() {} };
// When
new DocSearch(options);
@@ -229,8 +229,8 @@ describe('DocSearch', () => {
});
afterEach(() => {
- if (DocSearch.getInputsFromSelector.restore) {
- DocSearch.getInputsFromSelector.restore();
+ if (DocSearch.getInputFromSelector.restore) {
+ DocSearch.getInputFromSelector.restore();
}
});
@@ -262,7 +262,7 @@ describe('DocSearch', () => {
apiKey: 'apiKey',
indexName: 'indexName',
};
- sinon.stub(DocSearch, 'getInputsFromSelector').returns(false);
+ sinon.stub(DocSearch, 'getInputFromSelector').returns(false);
// When
expect(() => {
@@ -271,10 +271,10 @@ describe('DocSearch', () => {
});
});
- describe('getInputsFromSelector', () => {
- let getInputsFromSelector;
+ describe('getInputFromSelector', () => {
+ let getInputFromSelector;
beforeEach(() => {
- getInputsFromSelector = DocSearch.getInputsFromSelector;
+ getInputFromSelector = DocSearch.getInputFromSelector;
});
it('should return null if no element matches the selector', () => {
@@ -282,7 +282,7 @@ describe('DocSearch', () => {
const selector = '.i-do-not-exist > at #all';
// When
- const actual = getInputsFromSelector(selector);
+ const actual = getInputFromSelector(selector);
// Then
expect(actual).toEqual(null);
@@ -292,7 +292,7 @@ describe('DocSearch', () => {
const selector = '.i-am-a-span';
// When
- const actual = getInputsFromSelector(selector);
+ const actual = getInputFromSelector(selector);
// Then
expect(actual).toEqual(null);
@@ -302,10 +302,10 @@ describe('DocSearch', () => {
const selector = '#input';
// When
- const actual = getInputsFromSelector(selector);
+ const actual = getInputFromSelector(selector);
// Then
- expect($.zepto.isZ(actual[0])).toBe(true);
+ expect($.zepto.isZ(actual)).toBe(true);
});
});
@@ -390,13 +390,12 @@ describe('DocSearch', () => {
});
describe('handleSelected', () => {
- it('should change the location', () => {
+ it('should change the location if no handleSelected specified', () => {
// Given
const options = {
apiKey: 'key',
indexName: 'foo',
inputSelector: '#input',
- handleSelected: true,
};
// When
@@ -412,6 +411,45 @@ describe('DocSearch', () => {
resolve();
});
});
+ it('should call the custom handleSelected if defined', () => {
+ // Given
+ const customHandleSelected = jest.fn();
+ const options = {
+ apiKey: 'key',
+ indexName: 'foo',
+ inputSelector: '#input',
+ handleSelected: customHandleSelected,
+ };
+ const expectedInput = expect.objectContaining({
+ open: expect.any(Function),
+ });
+ const expectedEvent = expect.objectContaining({
+ type: 'autocomplete:selected'
+ });
+ const expectedSuggestion = expect.objectContaining({
+ url: 'https://website.com/doc/page',
+ });
+
+ // When
+ const ds = new DocSearch(options);
+ ds.autocomplete.trigger('autocomplete:selected', {
+ url: 'https://website.com/doc/page',
+ });
+
+ return new Promise(resolve => {
+ expect(customHandleSelected).toHaveBeenCalledWith(
+ expectedInput,
+ expectedEvent,
+ expectedSuggestion,
+ );
+ resolve();
+ });
+ });
+ xit('should prevent all clicks on links if a custom handleSelected is specified', () => {
+ // TODO
+ // If handleSelected, we target one link, we manually trigger a click on
+ // it, and we check that preventDefault is called on the event
+ });
});
describe('handleShown', () => {
diff --git a/src/lib/templates.js b/src/lib/templates.js
index cbab5f2e..c2b60b48 100644
--- a/src/lib/templates.js
+++ b/src/lib/templates.js
@@ -10,7 +10,7 @@ const templates = {
{{#isCategoryHeader}}${suggestionPrefix}__main{{/isCategoryHeader}}
{{#isSubCategoryHeader}}${suggestionPrefix}__secondary{{/isSubCategoryHeader}}
"
- target="_blank" aria-label="Link to the result"
+ aria-label="Link to the result"
href="{{{url}}}"
>