diff --git a/dev/app/builtin/init-stories.js b/dev/app/builtin/init-stories.js index 0f69d4370a..c9f1c8e951 100644 --- a/dev/app/builtin/init-stories.js +++ b/dev/app/builtin/init-stories.js @@ -11,7 +11,6 @@ import initInstantSearchStories from './stories/instantsearch.stories'; import initMenuStories from './stories/menu.stories'; import initMenuSelectStories from './stories/menu-select.stories'; import initNumericRefinementListStories from './stories/numeric-refinement-list.stories'; -import initNumericSelectorStories from './stories/numeric-selector.stories'; import initPaginationStories from './stories/pagination.stories'; import initRangeInputStories from './stories/range-input.stories.js'; import initRangeSliderStories from './stories/range-slider.stories'; @@ -38,7 +37,6 @@ export default () => { initMenuStories(); initMenuSelectStories(); initNumericRefinementListStories(); - initNumericSelectorStories(); initPaginationStories(); initRangeInputStories(); initRangeSliderStories(); diff --git a/dev/app/builtin/stories/numeric-selector.stories.js b/dev/app/builtin/stories/numeric-selector.stories.js deleted file mode 100644 index ad9ad21f36..0000000000 --- a/dev/app/builtin/stories/numeric-selector.stories.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable import/default */ - -import { storiesOf } from 'dev-novel'; -import instantsearch from '../../../../index'; -import { wrapWithHits } from '../../utils/wrap-with-hits.js'; - -const stories = storiesOf('NumericSelector'); - -export default () => { - stories.add( - 'default', - wrapWithHits(container => { - window.search.addWidget( - instantsearch.widgets.numericSelector({ - container, - operator: '>=', - attributeName: 'popularity', - options: [ - { label: 'Default', value: 0 }, - { label: 'Top 10', value: 21459 }, - { label: 'Top 100', value: 21369 }, - { label: 'Top 500', value: 20969 }, - ], - }) - ); - }) - ); - stories.add( - 'with default value', - wrapWithHits(container => { - window.search.addWidget( - instantsearch.widgets.numericSelector({ - container, - operator: '=', - attributeName: 'rating', - options: [ - { label: 'No rating selected', value: undefined }, - { label: 'Rating: 5', value: 5 }, - { label: 'Rating: 4', value: 4 }, - { label: 'Rating: 3', value: 3 }, - { label: 'Rating: 2', value: 2 }, - { label: 'Rating: 1', value: 1 }, - ], - }) - ); - }) - ); - stories.add( - 'with transformed items', - wrapWithHits(container => { - window.search.addWidget( - instantsearch.widgets.numericSelector({ - container, - operator: '=', - attributeName: 'rating', - options: [ - { label: 'No rating selected', value: undefined }, - { label: 'Rating: 5', value: 5 }, - { label: 'Rating: 4', value: 4 }, - { label: 'Rating: 3', value: 3 }, - { label: 'Rating: 2', value: 2 }, - { label: 'Rating: 1', value: 1 }, - ], - transformItems: items => - items.map(item => ({ - ...item, - label: `${item.label} (transformed)`, - })), - }) - ); - }) - ); -}; diff --git a/dev/app/init-unmount-widgets.js b/dev/app/init-unmount-widgets.js index b13677fff0..e008ce5820 100644 --- a/dev/app/init-unmount-widgets.js +++ b/dev/app/init-unmount-widgets.js @@ -206,23 +206,6 @@ export default () => { ) ); - storiesOf('NumericSelector').add( - 'default', - wrapWithUnmount(container => - instantsearch.widgets.numericSelector({ - container, - operator: '>=', - attributeName: 'popularity', - options: [ - { label: 'Default', value: 0 }, - { label: 'Top 10', value: 9991 }, - { label: 'Top 100', value: 9901 }, - { label: 'Top 500', value: 9501 }, - ], - }) - ) - ); - storiesOf('Pagination').add( 'default', wrapWithUnmount(container => diff --git a/dev/app/jquery/init-stories.js b/dev/app/jquery/init-stories.js index dcd55943eb..72e7a5a3d8 100644 --- a/dev/app/jquery/init-stories.js +++ b/dev/app/jquery/init-stories.js @@ -6,7 +6,6 @@ import initHitsPerPageStories from './stories/hits-per-page.stories'; import initInfiniteHitsStories from './stories/infinite-hits.stories'; import initMenuStories from './stories/menu.stories'; import initNumericRefinementListStories from './stories/numeric-refinement-list.stories'; -import initNumericSelectorStories from './stories/numeric-selector.stories'; import initPaginationStories from './stories/pagination.stories'; import initRefinementListStories from './stories/refinement-list.stories'; import initSearchBoxStories from './stories/search-box.stories'; @@ -25,7 +24,6 @@ export default () => { initInfiniteHitsStories(); initMenuStories(); initNumericRefinementListStories(); - initNumericSelectorStories(); initPaginationStories(); initRefinementListStories(); initSearchBoxStories(); diff --git a/dev/app/jquery/stories/numeric-selector.stories.js b/dev/app/jquery/stories/numeric-selector.stories.js deleted file mode 100644 index 89145836f9..0000000000 --- a/dev/app/jquery/stories/numeric-selector.stories.js +++ /dev/null @@ -1,26 +0,0 @@ -import { storiesOf } from 'dev-novel'; -import { wrapWithHitsAndJquery } from '../../utils/wrap-with-hits.js'; -import * as widgets from '../widgets/index.js'; - -const stories = storiesOf('NumericSelector'); - -export default () => { - stories.add( - 'default', - wrapWithHitsAndJquery(containerNode => { - window.search.addWidget( - widgets.numericSelector({ - containerNode, - operator: '>=', - attributeName: 'popularity', - options: [ - { label: 'Default', value: 0 }, - { label: 'Top 10', value: 9991 }, - { label: 'Top 100', value: 9901 }, - { label: 'Top 500', value: 9501 }, - ], - }) - ); - }) - ); -}; diff --git a/dev/app/jquery/widgets/index.js b/dev/app/jquery/widgets/index.js index c6161aaa73..2b3b07ea6e 100644 --- a/dev/app/jquery/widgets/index.js +++ b/dev/app/jquery/widgets/index.js @@ -6,7 +6,6 @@ export { default as pagination } from './pagination'; export { default as hitsPerPage } from './hitsPerPage'; export { default as hits } from './hits'; export { default as refinementList } from './refinementList'; -export { default as numericSelector } from './numericSelector'; export { default as numericRefinementList } from './numericRefinementList'; export { default as searchBox } from './searchBox'; export { default as sortBy } from './sortBy'; diff --git a/dev/app/jquery/widgets/numericSelector.js b/dev/app/jquery/widgets/numericSelector.js deleted file mode 100644 index 6b0775e7bb..0000000000 --- a/dev/app/jquery/widgets/numericSelector.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable import/default */ -import instantsearch from '../../../../index.js'; - -const renderFn = ( - { currentRefinement, options, refine, widgetParams: { containerNode } }, - isFirstRendering -) => { - if (isFirstRendering) { - const markup = ''; - containerNode.append(markup); - } - - const optionsHTML = options.map( - ({ value, label }) => ` - - ` - ); - - containerNode.find('select').html(optionsHTML); - - containerNode - .find('select') - .off('change') - .on('change', e => { - refine(e.target.value); - }); -}; - -export default instantsearch.connectors.connectNumericSelector(renderFn); diff --git a/docgen/src/examples/tourism/search.js b/docgen/src/examples/tourism/search.js index ff8cf9d7bf..1ccedcc389 100644 --- a/docgen/src/examples/tourism/search.js +++ b/docgen/src/examples/tourism/search.js @@ -88,21 +88,5 @@ window.addEventListener('load', function() { }) ); - search.addWidget( - instantsearch.widgets.numericSelector({ - container: '#guests', - attributeName: 'person_capacity', - operator: '>=', - options: [ - { label: '1 guest', value: 1}, - { label: '2 guests', value: 2}, - { label: '3 guests', value: 3}, - { label: '4 guests', value: 4}, - { label: '5 guests', value: 5}, - { label: '6 guests', value: 6} - ] - }) - ); - search.start(); }); diff --git a/docgen/src/guides/routing.md b/docgen/src/guides/routing.md index d05d652e22..e8d0e52571 100644 --- a/docgen/src/guides/routing.md +++ b/docgen/src/guides/routing.md @@ -234,9 +234,6 @@ But the `uiState` object is created by InstantSearch.js internally and thus part numericRefinementList: { heightInCm: 40 }, - numericSelector: { - widthInCm: 30 - }, range: { ageInYears: '2-10' }, diff --git a/docgen/src/guides/v3-migration.md b/docgen/src/guides/v3-migration.md index 79d890e8b5..63d676e325 100644 --- a/docgen/src/guides/v3-migration.md +++ b/docgen/src/guides/v3-migration.md @@ -308,6 +308,10 @@ With the redo button: ``` +### NumericSelector + +Widget removed. + ### Pagination #### Options diff --git a/functional-tests/app/app.js b/functional-tests/app/app.js index eb12072744..d623dd35de 100644 --- a/functional-tests/app/app.js +++ b/functional-tests/app/app.js @@ -268,21 +268,4 @@ search.once('render', function() { document.querySelector('.search').className = 'row search search--visible'; }); -search.addWidget( - instantsearch.widgets.numericSelector({ - container: '#popularity-selector', - operator: '>=', - attributeName: 'popularity', - options: [ - { label: 'Default', value: 0 }, - { label: 'Top 10', value: 9991 }, - { label: 'Top 100', value: 9901 }, - { label: 'Top 500', value: 9501 }, - ], - cssClasses: { - select: 'form-control', - }, - }) -); - search.start(); diff --git a/src/connectors/index.js b/src/connectors/index.js index 6557ba4692..655e69ae65 100644 --- a/src/connectors/index.js +++ b/src/connectors/index.js @@ -18,9 +18,6 @@ export { default as connectMenu } from './menu/connectMenu.js'; export { default as connectNumericRefinementList, } from './numeric-refinement-list/connectNumericRefinementList.js'; -export { - default as connectNumericSelector, -} from './numeric-selector/connectNumericSelector.js'; export { default as connectPagination, } from './pagination/connectPagination.js'; diff --git a/src/connectors/numeric-selector/__tests__/__snapshots__/connectNumericSelector-test.js.snap b/src/connectors/numeric-selector/__tests__/__snapshots__/connectNumericSelector-test.js.snap deleted file mode 100644 index 1e269defb9..0000000000 --- a/src/connectors/numeric-selector/__tests__/__snapshots__/connectNumericSelector-test.js.snap +++ /dev/null @@ -1,144 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`connectNumericSelector routing getWidgetSearchParameters should add the refinements according to the UI state provided 1`] = ` -SearchParameters { - "advancedSyntax": undefined, - "allowTyposOnNumericTokens": undefined, - "analytics": undefined, - "analyticsTags": undefined, - "aroundLatLng": undefined, - "aroundLatLngViaIP": undefined, - "aroundPrecision": undefined, - "aroundRadius": undefined, - "attributesToHighlight": undefined, - "attributesToRetrieve": undefined, - "attributesToSnippet": undefined, - "disableExactOnAttributes": undefined, - "disjunctiveFacets": Array [], - "disjunctiveFacetsRefinements": Object {}, - "distinct": undefined, - "enableExactOnSingleWordQuery": undefined, - "facets": Array [], - "facetsExcludes": Object {}, - "facetsRefinements": Object {}, - "getRankingInfo": undefined, - "hierarchicalFacets": Array [], - "hierarchicalFacetsRefinements": Object {}, - "highlightPostTag": undefined, - "highlightPreTag": undefined, - "hitsPerPage": undefined, - "ignorePlurals": undefined, - "index": "", - "insideBoundingBox": undefined, - "insidePolygon": undefined, - "length": undefined, - "maxValuesPerFacet": undefined, - "minProximity": undefined, - "minWordSizefor1Typo": undefined, - "minWordSizefor2Typos": undefined, - "minimumAroundRadius": undefined, - "numericFilters": undefined, - "numericRefinements": Object { - "numerics": Object { - "=": Array [ - 20, - ], - }, - }, - "offset": undefined, - "optionalFacetFilters": undefined, - "optionalTagFilters": undefined, - "optionalWords": undefined, - "page": 0, - "query": "", - "queryType": undefined, - "removeWordsIfNoResults": undefined, - "replaceSynonymsInHighlight": undefined, - "restrictSearchableAttributes": undefined, - "snippetEllipsisText": undefined, - "synonyms": undefined, - "tagFilters": undefined, - "tagRefinements": Array [], - "typoTolerance": undefined, -} -`; - -exports[`connectNumericSelector routing getWidgetSearchParameters should enforce the default value if there are no refinements in the UI state 1`] = ` -SearchParameters { - "advancedSyntax": undefined, - "allowTyposOnNumericTokens": undefined, - "analytics": undefined, - "analyticsTags": undefined, - "aroundLatLng": undefined, - "aroundLatLngViaIP": undefined, - "aroundPrecision": undefined, - "aroundRadius": undefined, - "attributesToHighlight": undefined, - "attributesToRetrieve": undefined, - "attributesToSnippet": undefined, - "disableExactOnAttributes": undefined, - "disjunctiveFacets": Array [], - "disjunctiveFacetsRefinements": Object {}, - "distinct": undefined, - "enableExactOnSingleWordQuery": undefined, - "facets": Array [], - "facetsExcludes": Object {}, - "facetsRefinements": Object {}, - "getRankingInfo": undefined, - "hierarchicalFacets": Array [], - "hierarchicalFacetsRefinements": Object {}, - "highlightPostTag": undefined, - "highlightPreTag": undefined, - "hitsPerPage": undefined, - "ignorePlurals": undefined, - "index": "", - "insideBoundingBox": undefined, - "insidePolygon": undefined, - "length": undefined, - "maxValuesPerFacet": undefined, - "minProximity": undefined, - "minWordSizefor1Typo": undefined, - "minWordSizefor2Typos": undefined, - "minimumAroundRadius": undefined, - "numericFilters": undefined, - "numericRefinements": Object { - "numerics": Object { - "=": Array [ - 10, - ], - }, - }, - "offset": undefined, - "optionalFacetFilters": undefined, - "optionalTagFilters": undefined, - "optionalWords": undefined, - "page": 0, - "query": "", - "queryType": undefined, - "removeWordsIfNoResults": undefined, - "replaceSynonymsInHighlight": undefined, - "restrictSearchableAttributes": undefined, - "snippetEllipsisText": undefined, - "synonyms": undefined, - "tagFilters": undefined, - "tagRefinements": Array [], - "typoTolerance": undefined, -} -`; - -exports[`connectNumericSelector routing getWidgetState should add an entry equal to the refinement 1`] = ` -Object { - "numericSelector": Object { - "numerics": 20, - }, -} -`; - -exports[`connectNumericSelector routing getWidgetState should not override other values in the same namespace 1`] = ` -Object { - "numericSelector": Object { - "numerics": 20, - "numerics-2": "36", - }, -} -`; diff --git a/src/connectors/numeric-selector/__tests__/connectNumericSelector-test.js b/src/connectors/numeric-selector/__tests__/connectNumericSelector-test.js deleted file mode 100644 index beb3e9dbde..0000000000 --- a/src/connectors/numeric-selector/__tests__/connectNumericSelector-test.js +++ /dev/null @@ -1,481 +0,0 @@ -import jsHelper, { - SearchResults, - SearchParameters, -} from 'algoliasearch-helper'; - -import connectNumericSelector from '../connectNumericSelector.js'; - -describe('connectNumericSelector', () => { - it('Renders during init and render', () => { - // test that the dummyRendering is called with the isFirstRendering - // flag set accordingly - const rendering = jest.fn(); - const makeWidget = connectNumericSelector(rendering); - const listOptions = [ - { name: '10', value: 10 }, - { name: '20', value: 20 }, - { name: '30', value: 30 }, - ]; - const widget = makeWidget({ - attributeName: 'numerics', - options: listOptions, - }); - - const config = widget.getConfiguration({}, {}); - expect(config).toEqual({ - numericRefinements: { - numerics: { - '=': [listOptions[0].value], - }, - }, - }); - - // test if widget is not rendered yet at this point - expect(rendering).not.toHaveBeenCalled(); - - const helper = jsHelper({}, '', config); - helper.search = jest.fn(); - - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - onHistoryChange: () => {}, - }); - - // test that rendering has been called during init with isFirstRendering = true - expect(rendering).toHaveBeenCalledTimes(1); - // test if isFirstRendering is true during init - expect(rendering).toHaveBeenLastCalledWith(expect.any(Object), true); - - const firstRenderingOptions = rendering.mock.calls[0][0]; - expect(firstRenderingOptions.currentRefinement).toBe(listOptions[0].value); - expect(firstRenderingOptions.widgetParams).toEqual({ - attributeName: 'numerics', - options: listOptions, - }); - - widget.render({ - results: new SearchResults(helper.state, [{ nbHits: 0 }]), - state: helper.state, - helper, - createURL: () => '#', - }); - - // test that rendering has been called during init with isFirstRendering = false - expect(rendering).toHaveBeenCalledTimes(2); - expect(rendering).toHaveBeenLastCalledWith(expect.any(Object), false); - - const secondRenderingOptions = rendering.mock.calls[1][0]; - expect(secondRenderingOptions.currentRefinement).toBe(listOptions[0].value); - expect(secondRenderingOptions.widgetParams).toEqual({ - attributeName: 'numerics', - options: listOptions, - }); - }); - - it('Renders during init and render with transformed items', () => { - const rendering = jest.fn(); - const makeWidget = connectNumericSelector(rendering); - const listOptions = [ - { name: '10', value: 10 }, - { name: '20', value: 20 }, - { name: '30', value: 30 }, - ]; - const widget = makeWidget({ - attributeName: 'numerics', - options: listOptions, - transformItems: items => - items.map(item => ({ ...item, label: 'transformed' })), - }); - - const config = widget.getConfiguration({}, {}); - - const helper = jsHelper({}, '', config); - helper.search = jest.fn(); - - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - onHistoryChange: () => {}, - }); - - const firstRenderingOptions = rendering.mock.calls[0][0]; - expect(firstRenderingOptions.options).toEqual([ - { name: '10', value: 10, label: 'transformed' }, - { name: '20', value: 20, label: 'transformed' }, - { name: '30', value: 30, label: 'transformed' }, - ]); - - widget.render({ - results: new SearchResults(helper.state, [{ nbHits: 0 }]), - state: helper.state, - helper, - createURL: () => '#', - }); - - const secondRenderingOptions = rendering.mock.calls[1][0]; - expect(secondRenderingOptions.options).toEqual([ - { name: '10', value: 10, label: 'transformed' }, - { name: '20', value: 20, label: 'transformed' }, - { name: '30', value: 30, label: 'transformed' }, - ]); - }); - - it('Reads the default value from the URL if possible', () => { - // test that the dummyRendering is called with the isFirstRendering - // flag set accordingly - const rendering = jest.fn(); - const makeWidget = connectNumericSelector(rendering); - const listOptions = [ - { name: '10', value: 10 }, - { name: '20', value: 20 }, - { name: '30', value: 30 }, - ]; - const widget = makeWidget({ - attributeName: 'numerics', - options: listOptions, - }); - - expect(widget.getConfiguration({}, {})).toEqual({ - numericRefinements: { - numerics: { - '=': [listOptions[0].value], - }, - }, - }); - - expect( - widget.getConfiguration( - {}, - { - numericRefinements: { - numerics: { - '=': [30], - }, - }, - } - ) - ).toEqual({ - numericRefinements: { - numerics: { - '=': [30], - }, - }, - }); - }); - - it('Provide a function to update the refinements at each step', () => { - const rendering = jest.fn(); - const makeWidget = connectNumericSelector(rendering); - const listOptions = [ - { name: '10', value: 10 }, - { name: '20', value: 20 }, - { name: '30', value: 30 }, - ]; - const widget = makeWidget({ - attributeName: 'numerics', - options: listOptions, - }); - - const config = widget.getConfiguration({}, {}); - const helper = jsHelper({}, '', config); - helper.search = jest.fn(); - - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - onHistoryChange: () => {}, - }); - - const firstRenderingOptions = rendering.mock.calls[0][0]; - const { refine } = firstRenderingOptions; - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [10], - }); - refine(listOptions[1].name); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [20], - }); - refine(listOptions[2].name); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [30], - }); - refine(listOptions[0].name); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [10], - }); - - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); - - const secondRenderingOptions = rendering.mock.calls[1][0]; - const { refine: renderSetValue } = secondRenderingOptions; - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [10], - }); - renderSetValue(listOptions[1].name); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [20], - }); - renderSetValue(listOptions[2].name); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [30], - }); - renderSetValue(listOptions[0].name); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [10], - }); - }); - - it('provides isRefined for the currently selected value', () => { - const rendering = jest.fn(); - const makeWidget = connectNumericSelector(rendering); - const listOptions = [ - { name: '10', value: 10 }, - { name: '20', value: 20 }, - { name: '30', value: 30 }, - ]; - const widget = makeWidget({ - attributeName: 'numerics', - options: listOptions, - }); - - const config = widget.getConfiguration({}, {}); - const helper = jsHelper({}, '', config); - helper.search = jest.fn(); - - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - onHistoryChange: () => {}, - }); - - let refine = rendering.mock.calls[0][0].refine; - - listOptions.forEach((_, i) => { - // we loop with 1 increment because the first value is selected by default - const currentOption = listOptions[(i + 1) % listOptions.length]; - refine(currentOption.name); - - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); - - // The current option should be the one selected - // First we copy and set the default added values - const expectedResult = currentOption.value; - - const renderingParameters = rendering.mock.calls[1 + i][0]; - expect(renderingParameters.currentRefinement).toEqual(expectedResult); - - refine = renderingParameters.refine; - }); - }); - - it('The refine function can unselect with `undefined` and "undefined"', () => { - const rendering = jest.fn(); - const makeWidget = connectNumericSelector(rendering); - const listOptions = [ - { name: '' }, - { name: '10', value: 10 }, - { name: '20', value: 20 }, - { name: '30', value: 30 }, - ]; - const widget = makeWidget({ - attributeName: 'numerics', - options: listOptions, - }); - - const config = widget.getConfiguration({}, {}); - const helper = jsHelper({}, '', config); - helper.search = jest.fn(); - - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - onHistoryChange: () => {}, - }); - - const firstRenderingOptions = rendering.mock.calls[0][0]; - const { refine } = firstRenderingOptions; - expect(helper.state.getNumericRefinements('numerics')).toEqual({}); - refine(listOptions[1].value); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [10], - }); - refine(listOptions[0].value); - expect(helper.state.getNumericRefinements('numerics')).toEqual({}); - - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); - - const secondRenderingOptions = rendering.mock.calls[1][0]; - const { refine: refineBis } = secondRenderingOptions; - expect(helper.state.getNumericRefinements('numerics')).toEqual({}); - refineBis(listOptions[1].value); - expect(helper.state.getNumericRefinements('numerics')).toEqual({ - '=': [10], - }); - refineBis(listOptions[0].value); - expect(helper.state.getNumericRefinements('numerics')).toEqual({}); - }); - - describe('routing', () => { - const getInitializedWidget = () => { - const rendering = jest.fn(); - const makeWidget = connectNumericSelector(rendering); - const listOptions = [ - { name: '10', value: 10 }, - { name: '20', value: 20 }, - { name: '30', value: 30 }, - ]; - const widget = makeWidget({ - attributeName: 'numerics', - options: listOptions, - }); - - const config = widget.getConfiguration({}, {}); - const helper = jsHelper({}, '', config); - helper.search = jest.fn(); - - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - onHistoryChange: () => {}, - }); - - const { refine } = rendering.mock.calls[0][0]; - - return [widget, helper, refine]; - }; - - describe('getWidgetState', () => { - test('should give back the object unmodified if the default value is selected', () => { - const [widget, helper] = getInitializedWidget(); - const uiStateBefore = {}; - const uiStateAfter = widget.getWidgetState(uiStateBefore, { - searchParameters: helper.state, - helper, - }); - - expect(uiStateAfter).toBe(uiStateBefore); - }); - - test('should add an entry equal to the refinement', () => { - const [widget, helper, refine] = getInitializedWidget(); - refine(20); - const uiStateBefore = {}; - const uiStateAfter = widget.getWidgetState(uiStateBefore, { - searchParameters: helper.state, - helper, - }); - - expect(uiStateAfter).toMatchSnapshot(); - }); - - test('should not override other values in the same namespace', () => { - const [widget, helper, refine] = getInitializedWidget(); - const uiStateBefore = { - numericSelector: { - 'numerics-2': '36', - }, - }; - - refine(20); - - const uiStateAfter = widget.getWidgetState(uiStateBefore, { - searchParameters: helper.state, - helper, - }); - - expect(uiStateAfter).toMatchSnapshot(); - }); - - test('should give back the object unmodified if refinements are already set', () => { - const [widget, helper] = getInitializedWidget(); - const uiStateBefore = { - numericSelector: { - numerics: 20, - }, - }; - helper.addNumericRefinement('numerics', '=', 20); - const uiStateAfter = widget.getWidgetState(uiStateBefore, { - searchParameters: helper.state, - helper, - }); - - expect(uiStateAfter).toBe(uiStateBefore); - }); - }); - - describe('getWidgetSearchParameters', () => { - test('should enforce the default value if there are no refinements in the UI state', () => { - const [widget, helper] = getInitializedWidget(); - // User presses back (browser) and the URL contains nothing - const uiState = {}; - // The current search is empty - const searchParametersBefore = SearchParameters.make(helper.state); - const searchParametersAfter = widget.getWidgetSearchParameters( - searchParametersBefore, - { uiState } - ); - // The default parameters should be applied - expect(searchParametersAfter).toMatchSnapshot(); - }); - - test('should return the same SP if the value is the same in both UI State and SP', () => { - const [widget, helper, refine] = getInitializedWidget(); - // User presses back (browser) and the URL contains some refinements - const uiState = { - numericSelector: { - numerics: 30, - }, - }; - // The current state has the same parameters - refine(30); - const searchParametersBefore = SearchParameters.make(helper.state); - const searchParametersAfter = widget.getWidgetSearchParameters( - searchParametersBefore, - { uiState } - ); - // Applying the same parameters should not return a new object - expect(searchParametersAfter).toBe(searchParametersBefore); - }); - - test('should add the refinements according to the UI state provided', () => { - const [widget, helper] = getInitializedWidget(); - // User presses back (browser) and the URL contains some refinements - const uiState = { - numericSelector: { - numerics: 20, - }, - }; - // The current state is empty - const searchParametersBefore = SearchParameters.make(helper.state); - const searchParametersAfter = widget.getWidgetSearchParameters( - searchParametersBefore, - { uiState } - ); - // The new parameters should be applies - expect(searchParametersAfter).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/src/connectors/numeric-selector/connectNumericSelector.js b/src/connectors/numeric-selector/connectNumericSelector.js deleted file mode 100644 index 7e3f169402..0000000000 --- a/src/connectors/numeric-selector/connectNumericSelector.js +++ /dev/null @@ -1,231 +0,0 @@ -import { checkRendering } from '../../lib/utils.js'; - -const usage = `Usage: -var customNumericSelector = connectNumericSelector(function renderFn(params, isFirstRendering) { - // params = { - // currentRefinement, - // options, - // refine, - // hasNoResults, - // instantSearchInstance, - // widgetParams, - // } -}); -search.addWidget( - customNumericSelector({ - attributeName, - options, - [ operator = '=' ], - [ transformItems ] - }) -); -Full documentation available at https://community.algolia.com/instantsearch.js/v2/connectors/connectNumericSelector.html -`; - -/** - * @typedef {Object} NumericSelectorOption - * @property {number} value The numerical value to refine with. - * If the value is `undefined` or `"undefined"`, the option resets the filter. - * @property {string} label Label to display in the option. - */ - -/** - * @typedef {Object} CustomNumericSelectorWidgetOptions - * @property {string} attributeName Name of the attribute for faceting (eg. "free_shipping"). - * @property {NumericSelectorOption[]} options Array of objects defining the different values and labels. - * @property {string} [operator = '='] The operator to use to refine. Supports following operators: <, <=, =, >, >= and !=. - * @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates. - */ - -/** - * @typedef {Object} NumericSelectorRenderingOptions - * @property {string} currentRefinement The currently selected value. - * @property {NumericSelectorOption[]} options The different values and labels of the selector. - * @property {function(option.value)} refine Updates the results with the selected value. - * @property {boolean} hasNoResults `true` if the last search contains no result. - * @property {Object} widgetParams All original `CustomNumericSelectorWidgetOptions` forwarded to the `renderFn`. - */ - -/** - * **NumericSelector** connector provides the logic to build a custom widget that will let the - * user filter the results based on a list of numerical filters. - * - * It provides a `refine(value)` function to trigger a new search with selected option. - * @type {Connector} - * @param {function(NumericSelectorRenderingOptions, boolean)} renderFn Rendering function for the custom **NumericSelector** widget. - * @param {function} unmountFn Unmount function called when the widget is disposed. - * @return {function(CustomNumericSelectorWidgetOptions)} Re-usable widget factory for a custom **NumericSelector** widget. - * @example - * // custom `renderFn` to render the custom NumericSelector widget - * function renderFn(NumericSelectorRenderingOptions, isFirstRendering) { - * if (isFirstRendering) { - * NumericSelectorRenderingOptions.widgetParams.containerNode.html(''); - * NumericSelectorRenderingOptions.widgetParams.containerNode - * .find('select') - * .on('change', function(event) { - * NumericSelectorRenderingOptions.refine(event.target.value); - * }) - * } - * - * var optionsHTML = NumericSelectorRenderingOptions.options.map(function(option) { - * return ''; - * }); - * - * NumericSelectorRenderingOptions.widgetParams.containerNode - * .find('select') - * .html(optionsHTML); - * } - * - * // connect `renderFn` to NumericSelector logic - * var customNumericSelector = instantsearch.connectors.connectNumericSelector(renderFn); - * - * // mount widget on the page - * search.addWidget( - * customNumericSelector({ - * containerNode: $('#custom-numeric-selector-container'), - * operator: '>=', - * attributeName: 'popularity', - * options: [ - * {label: 'Default', value: 0}, - * {label: 'Top 10', value: 9991}, - * {label: 'Top 100', value: 9901}, - * {label: 'Top 500', value: 9501}, - * ], - * }) - * ); - */ -export default function connectNumericSelector(renderFn, unmountFn) { - checkRendering(renderFn, usage); - - return (widgetParams = {}) => { - const { - attributeName, - options, - operator = '=', - transformItems = items => items, - } = widgetParams; - - if (!attributeName || !options) { - throw new Error(usage); - } - - return { - getConfiguration(currentSearchParameters, searchParametersFromUrl) { - const value = this._getRefinedValue(searchParametersFromUrl); - if (value) { - return { - numericRefinements: { - [attributeName]: { - [operator]: [value], - }, - }, - }; - } - return {}; - }, - - init({ helper, instantSearchInstance }) { - this._refine = value => { - helper.clearRefinements(attributeName); - if (value !== undefined && value !== 'undefined') { - helper.addNumericRefinement(attributeName, operator, value); - } - helper.search(); - }; - - renderFn( - { - currentRefinement: this._getRefinedValue(helper.state), - options: transformItems(options), - refine: this._refine, - hasNoResults: true, - instantSearchInstance, - widgetParams, - }, - true - ); - }, - - render({ helper, results, instantSearchInstance }) { - renderFn( - { - currentRefinement: this._getRefinedValue(helper.state), - options: transformItems(options), - refine: this._refine, - hasNoResults: results.nbHits === 0, - instantSearchInstance, - widgetParams, - }, - false - ); - }, - - dispose({ state }) { - unmountFn(); - return state.removeNumericRefinement(attributeName); - }, - - getWidgetState(uiState, { searchParameters }) { - const currentRefinement = this._getRefinedValue(searchParameters); - if ( - // Does the current state contain the current refinement? - (uiState.numericSelector && - currentRefinement === uiState.numericSelector[attributeName]) || - // Is the current value the first option / default value? - currentRefinement === options[0].value - ) { - return uiState; - } - - if (currentRefinement || currentRefinement === 0) - return { - ...uiState, - numericSelector: { - ...uiState.numericSelector, - [attributeName]: currentRefinement, - }, - }; - return uiState; - }, - - getWidgetSearchParameters(searchParameters, { uiState }) { - const value = - uiState.numericSelector && uiState.numericSelector[attributeName]; - const currentlyRefinedValue = this._getRefinedValue(searchParameters); - - if (value) { - if (value === currentlyRefinedValue) return searchParameters; - return searchParameters - .clearRefinements(attributeName) - .addNumericRefinement(attributeName, operator, value); - } - - const firstItemValue = options[0] && options[0].value; - if (typeof firstItemValue === 'number') { - return searchParameters - .clearRefinements(attributeName) - .addNumericRefinement(attributeName, operator, options[0].value); - } - - return searchParameters; - }, - - _getRefinedValue(state) { - // This is reimplementing state.getNumericRefinement - // But searchParametersFromUrl is not an actual SearchParameters object - // It's only the object structure without the methods, because getStateFromQueryString - // is not sending a SearchParameters. There's no way given how we built the helper - // to initialize a true partial state where only the refinements are present - return state && - state.numericRefinements && - state.numericRefinements[attributeName] !== undefined && - state.numericRefinements[attributeName][operator] !== undefined && - state.numericRefinements[attributeName][operator][0] !== undefined // could be 0 - ? state.numericRefinements[attributeName][operator][0] - : options[0].value; - }, - }; - }; -} diff --git a/src/widgets/index.js b/src/widgets/index.js index ed62aae9e1..f88a27bb13 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -30,9 +30,6 @@ export { export { default as numericRefinementList, } from '../widgets/numeric-refinement-list/numeric-refinement-list.js'; -export { - default as numericSelector, -} from '../widgets/numeric-selector/numeric-selector.js'; export { default as pagination } from '../widgets/pagination/pagination.js'; export { default as rangeInput } from '../widgets/range-input/range-input.js'; export { default as searchBox } from '../widgets/search-box/search-box.js'; diff --git a/src/widgets/numeric-selector/__tests__/__snapshots__/numeric-selector-test.js.snap b/src/widgets/numeric-selector/__tests__/__snapshots__/numeric-selector-test.js.snap deleted file mode 100644 index 8405c36754..0000000000 --- a/src/widgets/numeric-selector/__tests__/__snapshots__/numeric-selector-test.js.snap +++ /dev/null @@ -1,82 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`numericSelector() calls twice ReactDOM.render(, container) 1`] = ` - -`; - -exports[`numericSelector() calls twice ReactDOM.render(, container) 2`] = ` - -`; - -exports[`numericSelector() computes refined values and pass them to 1`] = ` - -`; diff --git a/src/widgets/numeric-selector/__tests__/numeric-selector-test.js b/src/widgets/numeric-selector/__tests__/numeric-selector-test.js deleted file mode 100644 index ea63e059de..0000000000 --- a/src/widgets/numeric-selector/__tests__/numeric-selector-test.js +++ /dev/null @@ -1,136 +0,0 @@ -import expect from 'expect'; -import sinon from 'sinon'; -import numericSelector from '../numeric-selector'; - -describe('numericSelector()', () => { - let ReactDOM; - let container; - let options; - let cssClasses; - let widget; - let expectedProps; - let helper; - let results; - - beforeEach(() => { - ReactDOM = { render: sinon.spy() }; - - numericSelector.__Rewire__('render', ReactDOM.render); - - container = document.createElement('div'); - options = [{ value: 1, label: 'first' }, { value: 2, label: 'second' }]; - cssClasses = { - root: ['custom-root', 'cx'], - select: 'custom-select', - item: 'custom-item', - }; - widget = numericSelector({ - container, - options, - attributeName: 'aNumAttr', - cssClasses, - }); - expectedProps = { - shouldAutoHideContainer: false, - cssClasses: { - root: 'ais-numeric-selector custom-root cx', - select: 'ais-numeric-selector custom-select', - item: 'ais-numeric-selector--item custom-item', - }, - currentValue: 1, - options: [{ value: 1, label: 'first' }, { value: 2, label: 'second' }], - setValue: () => {}, - }; - helper = { - addNumericRefinement: sinon.spy(), - clearRefinements: sinon.spy(), - search: sinon.spy(), - }; - results = { - hits: [], - nbHits: 0, - }; - widget.init({ helper }); - helper.addNumericRefinement.resetHistory(); - }); - - it('configures the right numericRefinement', () => { - expect(widget.getConfiguration({}, {})).toEqual({ - numericRefinements: { - aNumAttr: { - '=': [1], - }, - }, - }); - }); - - it('configures the right numericRefinement when present in the url', () => { - const urlState = { - numericRefinements: { - aNumAttr: { - '=': [2], - }, - }, - }; - expect(widget.getConfiguration({}, urlState)).toEqual({ - numericRefinements: { - aNumAttr: { - '=': [2], - }, - }, - }); - }); - - it('calls twice ReactDOM.render(, container)', () => { - widget.render({ helper, results, state: helper.state }); - widget.render({ helper, results, state: helper.state }); - - expect(ReactDOM.render.calledTwice).toBe( - true, - 'ReactDOM.render called twice' - ); - expect(ReactDOM.render.firstCall.args[0]).toMatchSnapshot(); - expect(ReactDOM.render.firstCall.args[1]).toEqual(container); - expect(ReactDOM.render.secondCall.args[0]).toMatchSnapshot(); - expect(ReactDOM.render.secondCall.args[1]).toEqual(container); - }); - - it('computes refined values and pass them to ', () => { - helper.state = { - numericRefinements: { - aNumAttr: { - '=': [20], - }, - }, - }; - expectedProps.currentValue = 20; - widget.render({ helper, results, state: helper.state }); - expect(ReactDOM.render.firstCall.args[0]).toMatchSnapshot(); - }); - - it('sets the underlying numeric refinement', () => { - widget._refine(2); - expect(helper.addNumericRefinement.calledOnce).toBe( - true, - 'addNumericRefinement called once' - ); - expect(helper.search.calledOnce).toBe(true, 'search called once'); - }); - - it('cancels the underlying numeric refinement', () => { - widget._refine(undefined); - expect(helper.clearRefinements.calledOnce).toBe( - true, - 'clearRefinements called once' - ); - expect(helper.addNumericRefinement.called).toBe( - false, - 'addNumericRefinement never called' - ); - expect(helper.search.calledOnce).toBe(true, 'search called once'); - }); - - afterEach(() => { - numericSelector.__ResetDependency__('render'); - }); -}); diff --git a/src/widgets/numeric-selector/numeric-selector.js b/src/widgets/numeric-selector/numeric-selector.js deleted file mode 100644 index 7c7d676b72..0000000000 --- a/src/widgets/numeric-selector/numeric-selector.js +++ /dev/null @@ -1,136 +0,0 @@ -import React, { render, unmountComponentAtNode } from 'preact-compat'; -import cx from 'classnames'; - -import Selector from '../../components/Selector.js'; -import connectNumericSelector from '../../connectors/numeric-selector/connectNumericSelector.js'; - -import { bemHelper, getContainerNode } from '../../lib/utils.js'; - -const bem = bemHelper('ais-numeric-selector'); - -const renderer = ({ containerNode, autoHideContainer, cssClasses }) => ( - { currentRefinement, refine, hasNoResults, options }, - isFirstRendering -) => { - if (isFirstRendering) return; - - render( - , - containerNode - ); -}; - -const usage = `Usage: numericSelector({ - container, - attributeName, - options, - cssClasses.{root,select,item}, - autoHideContainer, - transformItems -})`; - -/** - * @typedef {Object} NumericOption - * @property {number} value The numerical value to refine with. - * If the value is `undefined` or `"undefined"`, the option resets the filter. - * @property {string} label Label to display in the option. - */ - -/** - * @typedef {Object} NumericSelectorCSSClasses - * @property {string|string[]} [root] CSS classes added to the outer `
`. - * @property {string|string[]} [select] CSS classes added to the parent `