diff --git a/.eslintignore b/.eslintignore index af896782b3..bf77e667ca 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,3 +11,4 @@ vue-playground packages/vue .expo/ packages/reactivecore/coverage/ +__snapshots__ diff --git a/packages/playground b/packages/playground index 36ffa357df..d6c1d55f5e 160000 --- a/packages/playground +++ b/packages/playground @@ -1 +1 @@ -Subproject commit 36ffa357df5bc5daf374c982bffdc3ea8db80d9f +Subproject commit d6c1d55f5e2ffb2b8b5c98a9ead8433b85d12deb diff --git a/packages/vue/.gitignore b/packages/vue/.gitignore index 30bc249f77..01f3acb2b1 100644 --- a/packages/vue/.gitignore +++ b/packages/vue/.gitignore @@ -1,6 +1,7 @@ .DS_Store node_modules /dist +/coverage # local env files .env.local diff --git a/packages/vue/babel.config.js b/packages/vue/babel.config.js new file mode 100644 index 0000000000..9fa4dfe402 --- /dev/null +++ b/packages/vue/babel.config.js @@ -0,0 +1 @@ +module.exports = { presets: ['@babel/preset-env', '@vue/babel-preset-jsx'] }; diff --git a/packages/vue/jest.config.js b/packages/vue/jest.config.js new file mode 100644 index 0000000000..e6767d0c43 --- /dev/null +++ b/packages/vue/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + // preset: '@vue/cli-plugin-unit-jest', + // transform: { + // '^.+\\.(js|jsx)$': 'babel-jest', + // }, + verbose: true, + collectCoverage: false, + testURL: 'http://localhost/', + testEnvironment: 'jest-environment-jsdom-fifteen', + // snapshotSerializers: ['./serialize.js'], + // snapshotSerializers: ['dfs'], + // snapshotSerializers: [ + // '@emotion/jest/serializer' /* if needed other snapshotSerializers should go here */, + // ], + snapshotSerializers: ['jest-serializer-vue', '@emotion/jest/serializer'], + testMatch: ['**/*.test.[jt]s?(x)'], +}; diff --git a/packages/vue/package.json b/packages/vue/package.json index 02f69754f8..acb77d611b 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -24,6 +24,7 @@ "start": "nps", "pretest": "nps build", "test": "nps test", + "clear-jest-cache": "jest --clearCache", "precommit": "lint-staged", "prepare": "npm start validate", "version-upgrade": "nps upgrade-vue -c ../../package-scripts.js", @@ -61,11 +62,16 @@ "@babel/plugin-syntax-jsx": "^7.2.0", "@babel/preset-env": "^7.5.5", "@babel/preset-stage-2": "^7.0.0", + "@emotion/jest": "^11.9.0", "@vue/babel-preset-jsx": "^1.1.0", + "@vue/cli-plugin-unit-jest": "^4.5.15", + "@vue/test-utils": "^1.3.0", + "eslint": "^4.12.0", "eslint-config-airbnb-base": "^13.1.0", "eslint-config-prettier": "^3.1.0", "eslint-plugin-vue": "^4.7.1", - "eslint": "^4.12.0", + "jest-serializer-vue": "^2.0.2", + "jest-vue-emotion": "^1.0.0", "nps": "^5.9.3", "nps-utils": "^1.7.0", "rollup": "^1.20.3", diff --git a/packages/vue/src/components/basic/ComponentWrapper.jsx b/packages/vue/src/components/basic/ComponentWrapper.jsx index 3b7a191c8b..334f6c50d1 100644 --- a/packages/vue/src/components/basic/ComponentWrapper.jsx +++ b/packages/vue/src/components/basic/ComponentWrapper.jsx @@ -11,6 +11,7 @@ const { setQueryListener, setComponentProps, updateComponentProps, + mockDataForTesting, } = Actions; const { pushToAndClause, checkPropChange, checkSomePropChange } = helper; @@ -48,6 +49,13 @@ const ComponentWrapper = ( this.componentProps = parsedProps; this.componentId = this.componentProps.componentId; this.react = this.componentProps.react; + + if (this.componentProps.mockData) { + this.mockDataForTesting( + this.componentProps.componentId, + this.componentProps.mockData, + ); + } }, beforeMount() { let components = []; @@ -93,7 +101,7 @@ const ComponentWrapper = ( } }, mounted() { - if (this.internalComponent) { + if (this.internalComponent && this.componentProps.mode !== 'test') { // Watch component after rendering the component to avoid the un-necessary calls this.setReact(this.componentProps); } @@ -163,6 +171,7 @@ const mapDispatchToProps = { watchComponent, setComponentProps, updateComponentProps, + mockDataForTesting, }; export default (component, options = {}) => connect(mapStateToProps, mapDispatchToProps)(ComponentWrapper(component, options)); diff --git a/packages/vue/src/components/list/MultiDropdownList.jsx b/packages/vue/src/components/list/MultiDropdownList.jsx index 2dfda898fb..d7a15fa8e9 100644 --- a/packages/vue/src/components/list/MultiDropdownList.jsx +++ b/packages/vue/src/components/list/MultiDropdownList.jsx @@ -80,6 +80,7 @@ const MultiDropdownList = { nestedField: types.string, index: VueTypes.string, searchPlaceholder: VueTypes.string.def('Type here to search...'), + isOpen: VueTypes.bool.def(false), }, created() { if (!this.enableAppbase && this.$props.index) { @@ -88,8 +89,8 @@ const MultiDropdownList = { ); } const props = this.$props; - this.modifiedOptions = - this.options && this.options[props.dataField] + this.modifiedOptions + = this.options && this.options[props.dataField] ? this.options[props.dataField].buckets : []; // Set custom and default queries in store @@ -247,14 +248,15 @@ const MultiDropdownList = { searchPlaceholder={this.$props.searchPlaceholder} transformData={this.$props.transformData} footer={ - showLoadMore && - !isLastBucket && ( + showLoadMore + && !isLastBucket && (
) } customLabelRenderer={renderLabelCalc} + open={this.$props.isOpen} /> ); @@ -370,7 +372,6 @@ const MultiDropdownList = { const customQueryOptions = getOptionsForCustomQuery(customQueryCalc); this.setQueryOptions(props.componentId, customQueryOptions, false); } - this.updateQuery({ componentId: props.componentId, query, @@ -386,9 +387,9 @@ const MultiDropdownList = { const queryOptions = getQueryOptions(props); return props.showLoadMore ? getCompositeAggsQuery({ - query: queryOptions, - props, - after, + query: queryOptions, + props, + after, }) : getAggsQuery(queryOptions, props); }, @@ -531,9 +532,9 @@ MultiDropdownList.generateQueryOptions = (props, after) => { const queryOptions = getQueryOptions(props); return props.showLoadMore ? getCompositeAggsQuery({ - query: queryOptions, - props, - after, + query: queryOptions, + props, + after, }) : getAggsQuery(queryOptions, props); }; @@ -547,9 +548,9 @@ const mapStateToProps = (state, props) => ({ rawData: state.rawData[props.componentId], isLoading: state.isLoading[props.componentId], selectedValue: - (state.selectedValues[props.componentId] && - state.selectedValues[props.componentId].value) || - null, + (state.selectedValues[props.componentId] + && state.selectedValues[props.componentId].value) + || null, themePreset: state.config.themePreset, error: state.error[props.componentId], componentProps: state.props[props.componentId], @@ -563,7 +564,7 @@ const mapDispatchtoProps = { setDefaultQuery, }; -const ListConnected = ComponentWrapper( +export const ListConnected = ComponentWrapper( connect(mapStateToProps, mapDispatchtoProps)(MultiDropdownList), { componentType: componentTypes.multiDropdownList, diff --git a/packages/vue/src/components/list/MultiDropdownList.test.jsx b/packages/vue/src/components/list/MultiDropdownList.test.jsx new file mode 100644 index 0000000000..10c1a9d667 --- /dev/null +++ b/packages/vue/src/components/list/MultiDropdownList.test.jsx @@ -0,0 +1,219 @@ +import { mount } from '@vue/test-utils'; +import { ListConnected as MultiDropdownList } from './MultiDropdownList.jsx'; +import ReactiveBase from '../ReactiveBase/index.jsx'; + +const MOCK_AGGREGATIONS_DATA = { + 'authors.keyword': { + buckets: [ + { + key: 'J. K. Rowling', + doc_count: 10, + }, + { + key: 'Nora Roberts', + doc_count: 7, + }, + ], + }, +}; + +describe('MultiDropdownList', () => { + it('should render no results message', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + 'authors.keyword': { + buckets: [], + }, + }} + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render list of items', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render search/count when showSearch/showCount are true', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch + showCount + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should not render search/count when showSearch/showCount are set to false', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch={false} + showCount={false} + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use render prop to render the list', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + render={({ data, handleChange }) => ( +
+
    + {/* eslint-disable camelcase */} + {data.map(({ doc_count, key }) => ( +
  • handleChange(key)} + > + {key} --- {doc_count} +
  • + ))} +
+
+ )} + isOpen={true} + /> +
+ ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use renderItem to render the list item', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + renderItem={({ label, count }) => ( +
+ {label} + + {count} + +
+ )} + isOpen={true} + /> +
+ ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should select default value', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + defaultValue={['Nora Roberts']} + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/packages/vue/src/components/list/MultiList.jsx b/packages/vue/src/components/list/MultiList.jsx index b7e65ef139..2b7f37539e 100644 --- a/packages/vue/src/components/list/MultiList.jsx +++ b/packages/vue/src/components/list/MultiList.jsx @@ -83,8 +83,8 @@ const MultiList = { ); } const props = this.$props; - this.modifiedOptions = - this.options && this.options[props.dataField] + this.modifiedOptions + = this.options && this.options[props.dataField] ? this.options[props.dataField].buckets : []; // Set custom and default queries in store @@ -234,59 +234,59 @@ const MultiList = { ) : null} - {!this.hasCustomRenderer && - filteredItemsToRender.length === 0 && - !this.isLoading + {!this.hasCustomRenderer + && filteredItemsToRender.length === 0 + && !this.isLoading ? this.renderNoResult() : filteredItemsToRender.map((item) => ( -
  • + +
  • + {item.doc_count}) + + )} + + )} + + ))} )} @@ -300,9 +300,9 @@ const MultiList = { let { currentValue } = this.$data; let finalValues = null; if ( - selectAllLabel && - ((Array.isArray(value) && value.includes(selectAllLabel)) || - (typeof value === 'string' && value === selectAllLabel)) + selectAllLabel + && ((Array.isArray(value) && value.includes(selectAllLabel)) + || (typeof value === 'string' && value === selectAllLabel)) ) { if (currentValue[selectAllLabel]) { currentValue = {}; @@ -491,8 +491,8 @@ const MultiList = { }, renderNoResult() { - const renderNoResults = - this.$scopedSlots.renderNoResults || this.$props.renderNoResults; + const renderNoResults + = this.$scopedSlots.renderNoResults || this.$props.renderNoResults; return (

    {isFunction(renderNoResults) ? renderNoResults() : renderNoResults} @@ -603,9 +603,9 @@ const mapStateToProps = (state, props) => ({ rawData: state.rawData[props.componentId], isLoading: state.isLoading[props.componentId], selectedValue: - (state.selectedValues[props.componentId] && - state.selectedValues[props.componentId].value) || - null, + (state.selectedValues[props.componentId] + && state.selectedValues[props.componentId].value) + || null, themePreset: state.config.themePreset, error: state.error[props.componentId], componentProps: state.props[props.componentId], @@ -621,10 +621,13 @@ const mapDispatchtoProps = { MultiList.hasInternalComponent = () => true; -const ListConnected = ComponentWrapper(connect(mapStateToProps, mapDispatchtoProps)(MultiList), { - componentType: componentTypes.multiList, - internalComponent: MultiList.hasInternalComponent(), -}); +export const ListConnected = ComponentWrapper( + connect(mapStateToProps, mapDispatchtoProps)(MultiList), + { + componentType: componentTypes.multiList, + internalComponent: MultiList.hasInternalComponent(), + }, +); MultiList.install = function (Vue) { Vue.component(MultiList.name, ListConnected); diff --git a/packages/vue/src/components/list/MultiList.test.jsx b/packages/vue/src/components/list/MultiList.test.jsx new file mode 100644 index 0000000000..e98e50a13e --- /dev/null +++ b/packages/vue/src/components/list/MultiList.test.jsx @@ -0,0 +1,214 @@ +import { mount } from '@vue/test-utils'; +import { ListConnected as MultiList } from './MultiList.jsx'; +import ReactiveBase from '../ReactiveBase/index.jsx'; + +const MOCK_AGGREGATIONS_DATA = { + 'authors.keyword': { + buckets: [ + { + key: 'J. K. Rowling', + doc_count: 10, + }, + { + key: 'Nora Roberts', + doc_count: 7, + }, + ], + }, +}; + +describe('MultiList', () => { + it('should render no results message', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + 'authors.keyword': { + buckets: [], + }, + }} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render list of items', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render search/count/checkbox when showSearch/showCount/showCheckbox are true', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch + showCount + showCheckbox + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should not render search/count/checkbox when showSearch/showCount/showCheckbox are set to false', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch={false} + showCount={false} + showCheckbox={false} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use render prop to render the list', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + render={({ data, handleChange }) => ( +

    + +
    + )} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use renderItem to render the list item', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + renderItem={({ label, count }) => ( +
    + {label} + + {count} + +
    + )} + /> +
    + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should select default value', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + defaultValue={['Nora Roberts']} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/packages/vue/src/components/list/SingleDropdownList.jsx b/packages/vue/src/components/list/SingleDropdownList.jsx index 1b469756c6..5684c1123b 100644 --- a/packages/vue/src/components/list/SingleDropdownList.jsx +++ b/packages/vue/src/components/list/SingleDropdownList.jsx @@ -78,6 +78,7 @@ const SingleDropdownList = { nestedField: types.string, index: VueTypes.string, searchPlaceholder: VueTypes.string.def('Type here to search...'), + isOpen: VueTypes.bool.def(false), }, created() { if (!this.enableAppbase && this.$props.index) { @@ -86,8 +87,8 @@ const SingleDropdownList = { ); } const props = this.$props; - this.modifiedOptions = - this.options && this.options[props.dataField] + this.modifiedOptions + = this.options && this.options[props.dataField] ? this.options[props.dataField].buckets : []; // Set custom and default queries in store @@ -237,14 +238,15 @@ const SingleDropdownList = { searchPlaceholder={this.$props.searchPlaceholder} transformData={this.$props.transformData} footer={ - showLoadMore && - !isLastBucket && ( + showLoadMore + && !isLastBucket && (
    ) } customLabelRenderer={renderLabelCalc} + open={this.$props.isOpen} /> ); @@ -320,9 +322,9 @@ const SingleDropdownList = { const queryOptions = getQueryOptions(props); return props.showLoadMore ? getCompositeAggsQuery({ - query: queryOptions, - props, - after, + query: queryOptions, + props, + after, }) : getAggsQuery(queryOptions, props); }, @@ -419,9 +421,9 @@ SingleDropdownList.generateQueryOptions = (props, after) => { const queryOptions = getQueryOptions(props); return props.showLoadMore ? getCompositeAggsQuery({ - query: queryOptions, - props, - after, + query: queryOptions, + props, + after, }) : getAggsQuery(queryOptions, props); }; @@ -436,9 +438,9 @@ const mapStateToProps = (state, props) => ({ rawData: state.rawData[props.componentId], isLoading: state.isLoading[props.componentId], selectedValue: - (state.selectedValues[props.componentId] && - state.selectedValues[props.componentId].value) || - '', + (state.selectedValues[props.componentId] + && state.selectedValues[props.componentId].value) + || '', themePreset: state.config.themePreset, error: state.error[props.componentId], componentProps: state.props[props.componentId], @@ -452,7 +454,7 @@ const mapDispatchtoProps = { setDefaultQuery, }; -const ListConnected = ComponentWrapper( +export const ListConnected = ComponentWrapper( connect(mapStateToProps, mapDispatchtoProps)(SingleDropdownList), { componentType: componentTypes.singleDropdownList, diff --git a/packages/vue/src/components/list/SingleDropdownList.test.jsx b/packages/vue/src/components/list/SingleDropdownList.test.jsx new file mode 100644 index 0000000000..a1a7ee6629 --- /dev/null +++ b/packages/vue/src/components/list/SingleDropdownList.test.jsx @@ -0,0 +1,219 @@ +import { mount } from '@vue/test-utils'; +import { ListConnected as SingleDropdownList } from './SingleDropdownList.jsx'; +import ReactiveBase from '../ReactiveBase/index.jsx'; + +const MOCK_AGGREGATIONS_DATA = { + 'authors.keyword': { + buckets: [ + { + key: 'J. K. Rowling', + doc_count: 10, + }, + { + key: 'Nora Roberts', + doc_count: 7, + }, + ], + }, +}; + +describe('SingleDropdownList', () => { + it('should render no results message', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + 'authors.keyword': { + buckets: [], + }, + }} + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render list of items', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render search/count when showSearch/showCount are true', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch + showCount + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should not render search/count when showSearch/showCount are set to false', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch={false} + showCount={false} + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use render prop to render the list', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + render={({ data, handleChange }) => ( +
    +
      + {/* eslint-disable camelcase */} + {data.map(({ doc_count, key }) => ( +
    • handleChange(key)} + > + {key} --- {doc_count} +
    • + ))} +
    +
    + )} + isOpen={true} + /> +
    + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use renderItem to render the list item', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + renderItem={({ label, count }) => ( +
    + {label} + + {count} + +
    + )} + isOpen={true} + /> +
    + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should select default value', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + defaultValue='Nora Roberts' + isOpen={true} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/packages/vue/src/components/list/SingleList.jsx b/packages/vue/src/components/list/SingleList.jsx index dc64d6838e..66ba9563b0 100644 --- a/packages/vue/src/components/list/SingleList.jsx +++ b/packages/vue/src/components/list/SingleList.jsx @@ -81,8 +81,8 @@ const SingleList = { ); } const props = this.$props; - this.modifiedOptions = - this.options && this.options[props.dataField] + this.modifiedOptions + = this.options && this.options[props.dataField] ? this.options[props.dataField].buckets : []; // Set custom and default queries in store @@ -218,66 +218,66 @@ const SingleList = { ) : null} - {!this.hasCustomRenderer && - filteredItemsToRender.length === 0 && - !this.isLoading + {!this.hasCustomRenderer + && filteredItemsToRender.length === 0 + && !this.isLoading ? this.renderNoResult() : filteredItemsToRender.map((item) => ( -
  • + +
  • + {item.doc_count}) + + )} + + )} + + ))} )} @@ -430,8 +430,8 @@ const SingleList = { }, renderNoResult() { - const renderNoResults = - this.$scopedSlots.renderNoResults || this.$props.renderNoResults; + const renderNoResults + = this.$scopedSlots.renderNoResults || this.$props.renderNoResults; return (

    {isFunction(renderNoResults) ? renderNoResults() : renderNoResults} @@ -502,9 +502,9 @@ const mapStateToProps = (state, props) => ({ rawData: state.rawData[props.componentId], isLoading: state.isLoading[props.componentId], selectedValue: - (state.selectedValues[props.componentId] && - state.selectedValues[props.componentId].value) || - '', + (state.selectedValues[props.componentId] + && state.selectedValues[props.componentId].value) + || '', themePreset: state.config.themePreset, error: state.error[props.componentId], componentProps: state.props[props.componentId], @@ -518,10 +518,13 @@ const mapDispatchtoProps = { setDefaultQuery, }; -const ListConnected = ComponentWrapper(connect(mapStateToProps, mapDispatchtoProps)(SingleList), { - componentType: componentTypes.singleList, - internalComponent: SingleList.hasInternalComponent(), -}); +export const ListConnected = ComponentWrapper( + connect(mapStateToProps, mapDispatchtoProps)(SingleList), + { + componentType: componentTypes.singleList, + internalComponent: SingleList.hasInternalComponent(), + }, +); SingleList.install = function (Vue) { Vue.component(SingleList.name, ListConnected); diff --git a/packages/vue/src/components/list/SingleList.test.jsx b/packages/vue/src/components/list/SingleList.test.jsx new file mode 100644 index 0000000000..858c8b8121 --- /dev/null +++ b/packages/vue/src/components/list/SingleList.test.jsx @@ -0,0 +1,209 @@ +import { mount } from '@vue/test-utils'; +import { ListConnected as SingleList } from './SingleList.jsx'; +import ReactiveBase from '../ReactiveBase/index.jsx'; + +const MOCK_AGGREGATIONS_DATA = { + 'authors.keyword': { + buckets: [ + { + key: 'J. K. Rowling', + doc_count: 10, + }, + { + key: 'Nora Roberts', + doc_count: 7, + }, + ], + }, +}; + +describe('SingleList', () => { + it('should render no results message', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render list of items', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render search/count/radio when showSearch/showCount/showRadio are true', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch + showCount + showRadio + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should not render search/count/radio when showSearch/showCount/showRadio are set to false', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + showSearch={false} + showCount={false} + showRadio={false} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use render prop to render the list', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + render={({ data, handleChange }) => ( +

    + +
    + )} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should use renderItem to render the list item', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + renderItem={({ label, count }) => ( +
    + {label} + + {count} + +
    + )} + /> +
    + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should select default value', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + 'No authors found'} + mockData={{ + aggregations: MOCK_AGGREGATIONS_DATA, + }} + defaultValue="Nora Roberts" + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/packages/vue/src/components/list/__snapshots__/MultiDropdownList.test.jsx.snap b/packages/vue/src/components/list/__snapshots__/MultiDropdownList.test.jsx.snap new file mode 100644 index 0000000000..32c098ce53 --- /dev/null +++ b/packages/vue/src/components/list/__snapshots__/MultiDropdownList.test.jsx.snap @@ -0,0 +1,399 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MultiDropdownList should not render search/count when showSearch/showCount are set to false 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + + J. K. Rowling + +
      +
    • +
    • +
      + + Nora Roberts + +
      +
    • +
    +
    +
    +
    +
    +`; + +exports[`MultiDropdownList should render list of items 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + + J. K. Rowling + + +  (10) + +
      +
    • +
    • +
      + + Nora Roberts + + +  (7) + +
      +
    • +
    +
    +
    +
    +
    +`; + +exports[`MultiDropdownList should render no results message 1`] = ` +
    +
    + No authors found +
    +
    +`; + +exports[`MultiDropdownList should render search/count when showSearch/showCount are true 1`] = ` +
    +
    +
    +
    + +
      + +
    • +
      + + J. K. Rowling + + +  (10) + +
      +
    • +
    • +
      + + Nora Roberts + + +  (7) + +
      +
    • +
    +
    +
    +
    +
    +`; + +exports[`MultiDropdownList should select default value 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + + J. K. Rowling + + +  (10) + +
      +
    • +
    • +
      + + Nora Roberts + + +  (7) + +
      + +
    • +
    +
    +
    +
    +
    +`; + +exports[`MultiDropdownList should use render prop to render the list 1`] = ` +
    +
    +
    +
    + +
    +
      +
    • + J. K. Rowling --- 10 +
    • +
    • + Nora Roberts --- 7 +
    • +
    +
    +
    +
    +
    +
    +`; + +exports[`MultiDropdownList should use renderItem to render the list item 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + J. K. Rowling + + 10 + +
      +
    • +
    • +
      + Nora Roberts + + 7 + +
      +
    • +
    +
    +
    +
    +
    +`; diff --git a/packages/vue/src/components/list/__snapshots__/MultiList.test.jsx.snap b/packages/vue/src/components/list/__snapshots__/MultiList.test.jsx.snap new file mode 100644 index 0000000000..2e921e09a2 --- /dev/null +++ b/packages/vue/src/components/list/__snapshots__/MultiList.test.jsx.snap @@ -0,0 +1,409 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MultiList should not render search/count/checkbox when showSearch/showCount/showCheckbox are set to false 1`] = ` +
    +
    + +
    +
    +`; + +exports[`MultiList should render list of items 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`MultiList should render no results message 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`MultiList should render search/count/checkbox when showSearch/showCount/showCheckbox are true 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`MultiList should select default value 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`MultiList should use render prop to render the list 1`] = ` +
    +
    + +
    +
      +
    • + J. K. Rowling --- 10 +
    • +
    • + Nora Roberts --- 7 +
    • +
    +
    +
    +
    +`; + +exports[`MultiList should use renderItem to render the list item 1`] = ` +
    +
    + + +
    +
    +`; diff --git a/packages/vue/src/components/list/__snapshots__/SingleDropdownList.test.jsx.snap b/packages/vue/src/components/list/__snapshots__/SingleDropdownList.test.jsx.snap new file mode 100644 index 0000000000..fa90d0d3c6 --- /dev/null +++ b/packages/vue/src/components/list/__snapshots__/SingleDropdownList.test.jsx.snap @@ -0,0 +1,396 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SingleDropdownList should not render search/count when showSearch/showCount are set to false 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + + J. K. Rowling + +
      +
    • +
    • +
      + + Nora Roberts + +
      +
    • +
    +
    +
    +
    +
    +`; + +exports[`SingleDropdownList should render list of items 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + + J. K. Rowling + + +  (10) + +
      +
    • +
    • +
      + + Nora Roberts + + +  (7) + +
      +
    • +
    +
    +
    +
    +
    +`; + +exports[`SingleDropdownList should render no results message 1`] = ` +
    +
    + No authors found +
    +
    +`; + +exports[`SingleDropdownList should render search/count when showSearch/showCount are true 1`] = ` +
    +
    +
    +
    + +
      + +
    • +
      + + J. K. Rowling + + +  (10) + +
      +
    • +
    • +
      + + Nora Roberts + + +  (7) + +
      +
    • +
    +
    +
    +
    +
    +`; + +exports[`SingleDropdownList should select default value 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + + J. K. Rowling + + +  (10) + +
      +
    • +
    • +
      + + Nora Roberts + + +  (7) + +
      +
    • +
    +
    +
    +
    +
    +`; + +exports[`SingleDropdownList should use render prop to render the list 1`] = ` +
    +
    +
    +
    + +
    +
      +
    • + J. K. Rowling --- 10 +
    • +
    • + Nora Roberts --- 7 +
    • +
    +
    +
    +
    +
    +
    +`; + +exports[`SingleDropdownList should use renderItem to render the list item 1`] = ` +
    +
    +
    +
    + +
      +
    • +
      + J. K. Rowling + + 10 + +
      +
    • +
    • +
      + Nora Roberts + + 7 + +
      +
    • +
    +
    +
    +
    +
    +`; diff --git a/packages/vue/src/components/list/__snapshots__/SingleList.test.jsx.snap b/packages/vue/src/components/list/__snapshots__/SingleList.test.jsx.snap new file mode 100644 index 0000000000..c747d23a40 --- /dev/null +++ b/packages/vue/src/components/list/__snapshots__/SingleList.test.jsx.snap @@ -0,0 +1,394 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SingleList should not render search/count/radio when showSearch/showCount/showRadio are set to false 1`] = ` +
    +
    + +
    +
    +`; + +exports[`SingleList should render list of items 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`SingleList should render no results message 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`SingleList should render search/count/radio when showSearch/showCount/showRadio are true 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`SingleList should select default value 1`] = ` +
    +
    + + +
    +
    +`; + +exports[`SingleList should use render prop to render the list 1`] = ` +
    +
    + +
    +
      +
    • + J. K. Rowling --- 10 +
    • +
    • + Nora Roberts --- 7 +
    • +
    +
    +
    +
    +`; + +exports[`SingleList should use renderItem to render the list item 1`] = ` +
    +
    + + +
    +
    +`; diff --git a/packages/vue/src/components/range/DynamicRangeSlider.jsx b/packages/vue/src/components/range/DynamicRangeSlider.jsx index 896960abf9..341f4ff4d2 100644 --- a/packages/vue/src/components/range/DynamicRangeSlider.jsx +++ b/packages/vue/src/components/range/DynamicRangeSlider.jsx @@ -19,6 +19,7 @@ const { setComponentProps, setCustomQuery, updateComponentProps, + mockDataForTesting, } = Actions; const { @@ -55,6 +56,8 @@ const DynamicRangeSlider = { sliderOptions: VueTypes.object.def({}), nestedField: types.string, index: VueTypes.string, + mode: VueTypes.string, + mockData: VueTypes.object, value: types.range, }, @@ -89,7 +92,9 @@ const DynamicRangeSlider = { updateCustomQuery(this.componentId, this.setCustomQuery, this.$props, this.currentValue); }, mounted() { - this.setReact(); + if (this.$props.mode !== 'test') { + this.setReact(); + } }, beforeMount() { let components = []; @@ -107,9 +112,19 @@ const DynamicRangeSlider = { } else if (value) { this.handleChange(DynamicRangeSlider.parseValue(value, this.$props)); } - - // get range before executing other queries - this.updateRangeQueryOptions(); + if (this.$props.mockData) { + this.mockDataForTesting( + this.internalRangeComponent, + this.$props.mockData[this.internalRangeComponent], + ); + this.setDefaultValue({ + start: this.$props.mockData[this.internalRangeComponent].aggregations.min.value, + end: this.$props.mockData[this.internalRangeComponent].aggregations.max.value, + }); + } else { + // get range before executing other queries + this.updateRangeQueryOptions(); + } } }, @@ -251,6 +266,15 @@ const DynamicRangeSlider = { componentType: componentTypes.dynamicRangeSlider, }); }, + // the method is added to support snapshot testing + // component doesn't render the slider in test environment + // hence the change + renderSlider(sliderComponent) { + if (this.$props.mode === 'test') { + return sliderComponent(); + } + return {sliderComponent()}; + }, }, computed: { @@ -329,7 +353,7 @@ const DynamicRangeSlider = { {this.$props.title} )} - + {this.renderSlider(() => ( - + ))} ); }, @@ -420,26 +444,26 @@ const mapStateToProps = (state, props) => { let range = state.aggregations[`${props.componentId}__range__internal`]; if (props.nestedField) { - options = - options && - componentId[props.dataField][props.nestedField] && - componentId[props.dataField][props.nestedField].buckets + options + = options + && componentId[props.dataField][props.nestedField] + && componentId[props.dataField][props.nestedField].buckets ? componentId[props.dataField][props.nestedField].buckets : []; - range = - range && internalRange[props.nestedField].min + range + = range && internalRange[props.nestedField].min ? { - start: internalRange[props.nestedField].min.value, - end: internalRange[props.nestedField].max.value, + start: internalRange[props.nestedField].min.value, + end: internalRange[props.nestedField].max.value, } : null; } else { - options = - options && componentId[props.dataField].buckets + options + = options && componentId[props.dataField].buckets ? componentId[props.dataField].buckets : []; - range = - range && internalRange.min + range + = range && internalRange.min ? { start: internalRange.min.value, end: internalRange.max.value } : null; } @@ -465,9 +489,10 @@ const mapDispatchtoProps = { setComponentProps, setCustomQuery, updateComponentProps, + mockDataForTesting, }; -const RangeConnected = connect(mapStateToProps, mapDispatchtoProps)(DynamicRangeSlider); +export const RangeConnected = connect(mapStateToProps, mapDispatchtoProps)(DynamicRangeSlider); DynamicRangeSlider.install = function (Vue) { Vue.component(DynamicRangeSlider.name, RangeConnected); diff --git a/packages/vue/src/components/range/DynamicRangeSlider.test.jsx b/packages/vue/src/components/range/DynamicRangeSlider.test.jsx new file mode 100644 index 0000000000..abfc7bd2cc --- /dev/null +++ b/packages/vue/src/components/range/DynamicRangeSlider.test.jsx @@ -0,0 +1,161 @@ +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; +import { RangeConnected as DynamicRangeSlider } from './DynamicRangeSlider.jsx'; +import ReactiveBase from '../ReactiveBase/index.jsx'; + +Vue.component('vue-slider-component', require('vue-slider-component')); + +const MOCK_RANGE_DATA = { + min: { + value: 1, + }, + max: { + value: 3455, + }, +}; + +describe('DynamicRangeSlider', () => { + it('should render range with slider', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render title', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should render range labels', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + ({ + start, + end, + })} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should select default value', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + ({ + start: 30, + end: 2300, + })} + rangeLabels={() => ({ + start: 30, + end: 2300, + })} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('should not display tooltip when sliderOptions has tooltip set to false', () => { + const wrapper = mount({ + name: 'TestComponent', + render() { + return ( + + ({ + start: 30, + end: 2300, + })} + rangeLabels={() => ({ + start: 30, + end: 2300, + })} + sliderOptions={{ + tooltip: false, + }} + /> + + ); + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/packages/vue/src/components/range/MultiRange.jsx b/packages/vue/src/components/range/MultiRange.jsx index c312ccd70b..4c5d80f799 100644 --- a/packages/vue/src/components/range/MultiRange.jsx +++ b/packages/vue/src/components/range/MultiRange.jsx @@ -177,9 +177,9 @@ const MultiRange = { )}