1
0
Fork 0

Merge branch 'feat/v3' of github.com:algolia/docsearch into feat/v3

This commit is contained in:
s-pace 2018-11-05 11:01:35 +01:00
commit bbeb2f4cee
6 changed files with 139 additions and 120 deletions

View file

@ -1,61 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="utf-8" />
<title>DocSearch playground</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap/3.3.6/css/bootstrap.min.css" />
<!-- at the end of the HEAD -->
<link rel="stylesheet" href="http://127.0.0.1:8080/docsearch.min.css" />
</head>
<body>
</head>
<body>
<div class="container">
<div class="mt-1">
<h1> Search thanks to the assets served at http://127.0.0.1:8080/ </h1>
<p>Do not forget to hard refresh: cmd + maj + R</p>
</div>
<div class="col-md-12">
<div class="input-group">
<input type="search" class="form-control" id="q">
</div>
</div>
<div class="mt-1">
<p>2</p>
</div>
<div class="col-md-12">
<div class="input-group">
<input type="search" class="form-control" id="z">
</div>
<div class="mt-1">
<h1>Assets are served from http://127.0.0.1:8080/ </h1>
</div>
<div class="col-md-12">
<div class="input-group">
<input type="search" placeholder="DocSearch input" class="form-control" id="q">
</div>
</div>
</div>
<!-- at the end of the BODY -->
<script type="text/javascript" src="http://127.0.0.1:8080/docsearch.min.js"></script>
<script type="text/javascript"> docsearch({
apiKey: '25626fae796133dc1e734c6bcaaeac3c',
indexName: 'docsearch',
inputSelector: '#q,#z',
debug: true // Set debug to true if you want to inspect the dropdown
});
<script type="text/javascript" src="http://127.0.0.1:8080/docsearch.js"></script>
<script type="text/javascript">
docsearch({
apiKey: '25626fae796133dc1e734c6bcaaeac3c',
indexName: 'docsearch',
inputSelector: '#q',
handleSelected(input, event, suggestion) {
console.info(input);
console.info(event);
console.info(suggestion);
},
debug: true // Set debug to true if you want to inspect the dropdown
});
</script>
<style>
.container {
margin: 10%;
}
.input-group {
width: 60%;
margin: auto;
margin-top: 10%;
}
.container {
margin: 10%;
}
.input-group {
width: 60%;
margin: auto;
margin-top: 10%;
}
</style>
</body>
</body>
</html>

View file

@ -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"

View file

@ -3,5 +3,6 @@ set -e
jest \
--no-cache \
--watch \
--no-watchman \
./src/lib/

View file

@ -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;
}
/**

View file

@ -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($('<span>foo</span>'));
DocSearch.getInputFromSelector.returns($('<span>foo</span>'));
// 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 = $('<input name="foo" />');
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', () => {

View file

@ -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}}}"
>
<div class="${suggestionPrefix}--category-header">