diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 3a252026103..de9b683c1c4 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2,7 +2,7 @@ "name": "instantsearch.js", "version": "1.4.1", "npm-shrinkwrap-version": "200.4.0", - "node-version": "v5.5.0", + "node-version": "v5.9.0", "dependencies": { "algoliasearch": { "version": "3.13.0", @@ -6372,6 +6372,100 @@ } } }, + "enzyme": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-2.2.0.tgz", + "dependencies": { + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz" + }, + "lodash": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.6.1.tgz" + }, + "object.assign": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.3.tgz", + "dependencies": { + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "dependencies": { + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz" + } + } + }, + "function-bind": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz" + }, + "object-keys": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.9.tgz" + } + } + }, + "object.values": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.3.tgz", + "dependencies": { + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "dependencies": { + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz" + }, + "object-keys": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.9.tgz" + } + } + }, + "es-abstract": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.5.0.tgz", + "dependencies": { + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "dependencies": { + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz" + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz" + } + } + }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz" + }, + "is-regex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.3.tgz" + } + } + }, + "function-bind": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz" + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz" + } + } + } + } + }, "eslint": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/eslint/-/eslint-1.10.3.tgz", diff --git a/package.json b/package.json index 706efcfb412..37c24360a23 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "conventional-changelog": "^0.5.3", "dmd": "^1.3.8", "doctoc": "^1.0.0", + "enzyme": "2.2.0", "eslint": "^1.10.3", "eslint-config-airbnb": "^0.1.0", "eslint-config-algolia": "^4.5.0", diff --git a/scripts/karma.conf.babel.js b/scripts/karma.conf.babel.js index 5ad8dd36e28..d7c0e5f30ea 100644 --- a/scripts/karma.conf.babel.js +++ b/scripts/karma.conf.babel.js @@ -11,6 +11,15 @@ let baseConfig = { loaders: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' }] + }, + // enzyme does not work well with webpack: + // https://github.com/airbnb/enzyme/issues/47#issuecomment-165430136 + externals: { + jsdom: 'window', + cheerio: 'window', + 'react/lib/ExecutionEnvironment': true, + 'react/lib/ReactContext': 'window', + 'text-encoding': 'window' } }, webpackMiddleware: {noInfo: true} diff --git a/src/components/RefinementList/__tests__/RefinementList-test.js b/src/components/RefinementList/__tests__/RefinementList-test.js index 46f76dbbae6..2179e04d5fd 100644 --- a/src/components/RefinementList/__tests__/RefinementList-test.js +++ b/src/components/RefinementList/__tests__/RefinementList-test.js @@ -1,8 +1,8 @@ /* eslint-env mocha */ import React from 'react'; +import {shallow} from 'enzyme'; import expect from 'expect'; -import TestUtils from 'react-addons-test-utils'; import RefinementList from '../RefinementList'; import RefinementListItem from '../RefinementListItem'; @@ -10,171 +10,318 @@ import expectJSX from 'expect-jsx'; expect.extend(expectJSX); describe('RefinementList', () => { - let renderer; - let parentListProps; - let itemProps; - - beforeEach(() => { - let {createRenderer} = TestUtils; - let cssClasses = { - list: 'list', - item: 'item', - active: 'active' - }; - let templateData = {cssClasses}; - let commonItemProps = { - handleClick: () => {}, - itemClassName: 'item', - subItems: undefined, - templateKey: 'item', - templateProps: {} + function shallowRender(extraProps: {}) { + let props = { + createURL: () => 'url', + facetValues: [], + ...extraProps }; + return shallow(React.createElement(RefinementList, props)); + } + + describe('cssClasses', () => { + it('should add the `list` class to the root element', () => { + // Given + let props = { + cssClasses: { + list: 'list' + } + }; + + // When + let actual = shallowRender(props); - parentListProps = {className: 'list'}; - itemProps = [{ - ...commonItemProps, - itemClassName: 'item active', - facetValueToRefine: 'facet1', - isRefined: true, - templateData: { - ...templateData, - name: 'facet1', - isRefined: true - } - }, { - ...commonItemProps, - facetValueToRefine: 'facet2', - isRefined: false, - templateData: { - ...templateData, - name: 'facet2', - isRefined: false - } - }]; - renderer = createRenderer(); + // Then + expect(actual.hasClass('list')).toEqual(true); + }); + + it('should set item classes to the refinements', () => { + // Given + let props = { + cssClasses: { + item: 'item' + }, + facetValues: [ + {name: 'foo', isRefined: true} + ] + }; + + // When + let actual = shallowRender(props).find(RefinementListItem); + + // Then + expect(actual.props().itemClassName).toContain('item'); + }); + + it('should set active classes to the active refinements', () => { + // Given + let props = { + cssClasses: { + active: 'active' + }, + facetValues: [ + {name: 'foo', isRefined: true}, + {name: 'bar', isRefined: false} + ] + }; + + // When + let activeItem = shallowRender(props).find({isRefined: true}); + let inactiveItem = shallowRender(props).find({isRefined: false}); + + // Then + expect(activeItem.props().itemClassName).toContain('active'); + expect(inactiveItem.props().itemClassName).toNotContain('active'); + }); }); + describe('items', () => { + it('should have the correct names', () => { + // Given + let props = { + facetValues: [ + {name: 'foo', isRefined: false}, + {name: 'bar', isRefined: false} + ] + }; + + // When + let items = shallowRender(props).find(RefinementListItem); + let firstItem = items.at(0); + let secondItem = items.at(1); + + // Then + expect(firstItem.props().facetValueToRefine).toEqual('foo'); + expect(secondItem.props().facetValueToRefine).toEqual('bar'); + }); + + it('should correctly set if refined or not', () => { + // Given + let props = { + facetValues: [ + {name: 'foo', isRefined: false}, + {name: 'bar', isRefined: true} + ] + }; + + // When + let items = shallowRender(props).find(RefinementListItem); + let firstItem = items.at(0); + let secondItem = items.at(1); - it('should render default list', () => { - let out = render(); - - expect(out).toEqualJSX( -
- - -
- ); - expect(out.props.children[0][0].key).toEqual('facet1/true'); - expect(out.props.children[0][1].key).toEqual('facet2/false'); + // Then + expect(firstItem.props().isRefined).toEqual(false); + expect(secondItem.props().isRefined).toEqual(true); + }); }); - it('should render default list highlighted', () => { - let out = render({facetValues: [{name: 'facet1', isRefined: true, count: 42}]}); - itemProps[0].templateData = { - ...itemProps[0].templateData, - count: 42, - isRefined: true - }; - expect(out).toEqualJSX( -
- -
- ); - expect(out.props.children[0][0].key).toEqual('facet1/true/42'); + describe('count', () => { + it('should pass the count to the templateData', () => { + // Given + let props = { + facetValues: [ + {name: 'foo', count: 42}, + {name: 'bar', count: 16} + ] + }; + + // When + let items = shallowRender(props).find(RefinementListItem); + let firstItem = items.at(0); + let secondItem = items.at(1); + + // Then + expect(firstItem.props().templateData.count).toEqual(42); + expect(secondItem.props().templateData.count).toEqual(16); + }); }); - context('showMore', () => { - it('should display the number accordingly to the state : closed', () => { - const out = render({ + describe('showMore', () => { + it('displays a number of items equal to the limit when showMore: false', () => { + // Given + let props = { facetValues: [ - {name: 'facet1', isRefined: false, count: 42}, - {name: 'facet2', isRefined: false, count: 42} + {name: 'foo'}, + {name: 'bar'}, + {name: 'baz'} + ], + showMore: false, + limitMin: 2 + }; + + // When + let actual = shallowRender(props).find(RefinementListItem); + + // Then + expect(actual.length).toEqual(2); + }); + + it('displays a number of items equal to the limit when showMore: true but not enabled', () => { + // Given + let props = { + facetValues: [ + {name: 'foo'}, + {name: 'bar'}, + {name: 'baz'} ], showMore: true, - limitMin: 1, - limitMax: 2 - }); - expect(out.props.children.length).toBe(2); + limitMin: 2, + limitMax: 3 + }; + + // When + let actual = shallowRender(props).find(RefinementListItem); + + // Then + expect(actual.length).toEqual(2); }); - it('should display the number accordingly to the state : open', () => { - // FIXME find a way to test this state... + it('displays a number of items equal to the showMore limit when showMore: true and enabled', () => { + // Given + let props = { + facetValues: [ + {name: 'foo'}, + {name: 'bar'}, + {name: 'baz'} + ], + limitMin: 2, + limitMax: 3, + showMore: true + }; + + // When + let root = shallowRender(props); + root.setState({isShowMoreOpen: true}); + let actual = root.find(RefinementListItem); + + // Then + expect(actual.length).toEqual(3); + }); + + it('adds a showMore link when the feature is enabled', () => { + // Given + let props = { + facetValues: [ + {name: 'foo'}, + {name: 'bar'}, + {name: 'baz'} + ], + showMore: true, + limitMin: 2, + limitMax: 3 + }; + + // When + let root = shallowRender(props); + let actual = root.find('Template').filter({templateKey: 'show-more-inactive'}); + + // Then + expect(actual.length).toEqual(1); + }); + + it('does not add a showMore link when the feature is disabled', () => { + // Given + let props = { + facetValues: [ + {name: 'foo'}, + {name: 'bar'}, + {name: 'baz'} + ], + showMore: false, + limitMin: 2, + limitMax: 3 + }; + + // When + let root = shallowRender(props); + let actual = root.find('Template').filter({templateKey: 'show-more-inactive'}); + + // Then + expect(actual.length).toEqual(0); + }); + + it('changing the state will toggle the number of items displayed', () => { + // Given + let props = { + facetValues: [ + {name: 'foo'}, + {name: 'bar'}, + {name: 'baz'} + ], + limitMin: 2, + limitMax: 3, + showMore: true + }; + + // When + let root = shallowRender(props); + + // Then: Not opened, initial number displayed + expect(root.find(RefinementListItem).length).toEqual(2); + + // Then: Toggling the state, display the limitMax + root.setState({isShowMoreOpen: true}); + expect(root.find(RefinementListItem).length).toEqual(3); + + // Then: Toggling the state again, back to the limitMin + root.setState({isShowMoreOpen: false}); + expect(root.find(RefinementListItem).length).toEqual(2); }); }); - context('sublist', () => { - it('works', () => { - let customProps = { + describe('sublist', () => { + it('should create a subList with the sub values', () => { + // Given + let props = { + facetValues: [ + { + name: 'foo', + data: [ + {name: 'bar'}, + {name: 'baz'} + ] + } + ] + }; + + // When + let root = shallowRender(props); + let mainItem = root.find(RefinementListItem).at(0); + let subList = shallow(mainItem.props().subItems); + let subItems = subList.find(RefinementListItem); + + // Then + expect(mainItem.props().facetValueToRefine).toEqual('foo'); + expect(subItems.at(0).props().facetValueToRefine).toEqual('bar'); + expect(subItems.at(1).props().facetValueToRefine).toEqual('baz'); + }); + + it('should add depth class for each depth', () => { + // Given + let props = { cssClasses: { - depth: 'depth', - item: 'item', - list: 'list', - active: 'active' + depth: 'depth-' }, facetValues: [ { - name: 'facet1', - isRefined: true, + name: 'foo', data: [ - {name: 'subfacet1'}, - {name: 'subfacet2'} + {name: 'bar'}, + {name: 'baz'} ] } ] }; - parentListProps = { - className: 'list depth0' - }; - itemProps[0].templateData.cssClasses = customProps.cssClasses; - itemProps[0].templateData = { - ...itemProps[0].templateData, - data: customProps.facetValues[0].data - }; - itemProps[0].subItems = ( - - ); - let out = render(customProps); - expect(out).toEqualJSX( -
- -
- ); - }); - }); - function render(extraProps = {}) { - let props = getProps(extraProps); - renderer.render(); - return renderer.getRenderOutput(); - } + // When + let root = shallowRender(props); + let mainItem = root.find(RefinementListItem).at(0); + let subList = shallow(mainItem.props().subItems); - function getProps(extraProps = {}) { - return { - cssClasses: { - list: 'list', - item: 'item', - active: 'active' - }, - facetValues: [ - {name: 'facet1', isRefined: true}, - {name: 'facet2', isRefined: false} - ], - ...extraProps - }; - } + // Then + expect(root.props().className).toContain('depth-0'); + expect(subList.props().className).toContain('depth-1'); + }); + }); });