UNPKG

react-url-query

Version:

A library for managing state through query parameters in the URL in React. Works well with or without Redux and React Router.

447 lines (351 loc) 14.6 kB
import React from 'react'; import { shallow } from 'enzyme'; import addUrlProps from '../addUrlProps'; import UrlQueryParamTypes from '../../UrlQueryParamTypes'; import UrlUpdateTypes from '../../UrlUpdateTypes'; import urlQueryConfig from '../../urlQueryConfig'; import configureUrlQuery from '../../configureUrlQuery'; const defaultUrlQueryConfig = { ...urlQueryConfig }; const MyComponent = () => <div />; beforeEach(() => { configureUrlQuery({ ...defaultUrlQueryConfig }); }); describe('url query params as props', () => { it('passes URL query parameters through', () => { const location = { query: { foo: '94', bar: 'baz' } }; const Wrapped = addUrlProps()(MyComponent); const wrapper = shallow( <Wrapped location={location} otherProp foo={1000} /> ); const props = wrapper.first().props(); // this only works when using `shallow` not `mount` expect(props.otherProp).toBe(true); expect(props.location).toBe(location); expect(props.foo).toBe('94'); expect(props.bar).toBe('baz'); }); it('decodes URL query params as props based on config', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, bar: { type: UrlQueryParamTypes.string }, }; const Wrapped = addUrlProps({ urlPropsQueryConfig })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.location).toBe(location); expect(props.foo).toBe(94); expect(props.bar).toBe('baz'); }); it('mapUrlToProps updates url query params as props', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; function mapUrlToProps(url) { return { foo: parseInt(url.fooInUrl, 10), bar: url.bar, }; } const Wrapped = addUrlProps({ mapUrlToProps })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.location).toBe(location); expect(props.foo).toBe(94); expect(props.bar).toBe('baz'); }); it('mapUrlToProps given decoded URL params if config also passed', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, bar: { type: UrlQueryParamTypes.string }, }; function mapUrlToProps(url) { return { foo: url.foo * 100, bar: url.bar, }; } const Wrapped = addUrlProps({ mapUrlToProps, urlPropsQueryConfig })( MyComponent ); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.location).toBe(location); expect(props.foo).toBe(9400); expect(props.bar).toBe('baz'); }); it('reads query params from urlQueryConfig.history.location', () => { const location = { query: { foo: '94', bar: 'baz' } }; const Wrapped = addUrlProps()(MyComponent); // set the history to have our location in it. Configure the history after // the Wrapped component is defined as that is likely how it will happen // in applications due to the way imports are resolved. configureUrlQuery({ history: { location } }); const wrapper = shallow(<Wrapped />); const props = wrapper.first().props(); expect(props.foo).toBe('94'); expect(props.bar).toBe('baz'); }); // TODO: would be nice to test for reading from window.location // but https://github.com/facebook/jest/issues/890 }); describe('adds router params', () => { it('url props includes props.params if addRouterParams is true', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const Wrapped = addUrlProps({ addRouterParams: true })(MyComponent); const wrapper = shallow( <Wrapped location={location} params={{ word: 'test' }} /> ); const props = wrapper.first().props(); expect(props.word).toBe('test'); }); it('url props does not include props.params if addRouterParams is false', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const Wrapped = addUrlProps({ addRouterParams: false })(MyComponent); const wrapper = shallow( <Wrapped location={location} params={{ word: 'test' }} /> ); const props = wrapper.first().props(); expect(props.word).not.toBeDefined(); }); it('reads addRouterParams from urlQueryConfig dynamically', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; // set to true before creating component configureUrlQuery({ addRouterParams: true }); const Wrapped = addUrlProps()(MyComponent); // update global config to be false after creating component, before rendering configureUrlQuery({ addRouterParams: false }); const wrapper = shallow( <Wrapped location={location} params={{ word: 'test' }} /> ); const props = wrapper.first().props(); expect(props.word).not.toBeDefined(); }); }); describe('url change callbacks', () => { it('generates change handlers based on config', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, bar: { type: UrlQueryParamTypes.string }, }; const Wrapped = addUrlProps({ urlPropsQueryConfig, addUrlChangeHandlers: true, })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.onChangeFoo).toBeDefined(); expect(props.onChangeBar).toBeDefined(); expect(props.onChangeUrlQueryParams).toBeDefined(); }); it('does not generate change handlers when addUrlChangeHandlers is false', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, bar: { type: UrlQueryParamTypes.string }, }; const Wrapped = addUrlProps({ urlPropsQueryConfig, addUrlChangeHandlers: false, })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.onChangeFoo).not.toBeDefined(); expect(props.onChangeBar).not.toBeDefined(); expect(props.onChangeUrlQueryParams).not.toBeDefined(); }); it('reads addUrlChangeHandlers from urlQueryConfig', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, bar: { type: UrlQueryParamTypes.string }, }; configureUrlQuery({ addUrlChangeHandlers: false }); const Wrapped = addUrlProps({ urlPropsQueryConfig })(MyComponent); configureUrlQuery({ addUrlChangeHandlers: true }); let wrapper = shallow(<Wrapped location={location} />); let props = wrapper.first().props(); expect(props.onChangeFoo).toBeDefined(); expect(props.onChangeBar).toBeDefined(); configureUrlQuery({ addUrlChangeHandlers: false }); wrapper = shallow(<Wrapped location={location} />); props = wrapper.first().props(); expect(props.onChangeFoo).not.toBeDefined(); expect(props.onChangeBar).not.toBeDefined(); }); it('generated change handlers have name configured by changeHandlerName', () => { configureUrlQuery({ addUrlChangeHandlers: true }); const location = { query: { fooInUrl: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, bar: { type: UrlQueryParamTypes.string }, }; const changeHandlerName = propName => `handle_${propName}`; const Wrapped = addUrlProps({ urlPropsQueryConfig, changeHandlerName })( MyComponent ); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.handle_foo).toBeDefined(); expect(props.handle_bar).toBeDefined(); }); it('generated change handlers have name configured by changeHandlerName in urlQueryConfig', () => { const location = { query: { fooInUrl: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, bar: { type: UrlQueryParamTypes.string }, }; const changeHandlerName = propName => `handle_${propName}`; const Wrapped = addUrlProps({ urlPropsQueryConfig })(MyComponent); configureUrlQuery({ addUrlChangeHandlers: true, changeHandlerName }); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.handle_foo).toBeDefined(); expect(props.handle_bar).toBeDefined(); }); it('mapUrlChangeHandlersToProps adds props', () => { const location = { query: { foo: '94', bar: 'baz' } }; function onChangeFoo(foo) { return foo; } function mapUrlChangeHandlersToProps() { return { onChangeFoo, }; } const Wrapped = addUrlProps({ mapUrlChangeHandlersToProps })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.onChangeFoo).toBe(onChangeFoo); expect(props.onChangeBar).not.toBeDefined(); props.onChangeFoo(123); }); it('mapUrlChangeHandlersToProps can access generated handlers', () => { const location = { query: { foo: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number }, bar: { type: UrlQueryParamTypes.string }, }; const onChangeFoo = foo => foo; function mapUrlChangeHandlersToProps(props, handlers) { return { onChangeFoo, onChangeBar: handlers.onChangeBar, }; } const Wrapped = addUrlProps({ urlPropsQueryConfig, mapUrlChangeHandlersToProps, addUrlChangeHandlers: true, })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); expect(props.onChangeFoo).toBe(onChangeFoo); expect(props.onChangeBar).toBeDefined(); }); it('generated change handlers are only generated once, not every render', () => { const location = { query: { foo: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number }, bar: { type: UrlQueryParamTypes.string }, }; const Wrapped = addUrlProps({ urlPropsQueryConfig, addUrlChangeHandlers: true, })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); const { onChangeFoo, onChangeBar } = props; // cause a re-render wrapper.setProps({ baz: 123 }); const newProps = wrapper.first().props(); expect(newProps.onChangeFoo).toBe(onChangeFoo); expect(newProps.onChangeBar).toBe(onChangeBar); }); it('generated change handlers encode values properly and interpret updateType', () => { const location = { query: { foo: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number }, bar: { type: UrlQueryParamTypes.string, updateType: UrlUpdateTypes.pushIn, }, }; // make the history just return the new location so we can test for logging const history = { replace: jest.fn().mockImplementation(d => d), push: jest.fn().mockImplementation(d => d), }; configureUrlQuery({ history }); const Wrapped = addUrlProps({ urlPropsQueryConfig, addUrlChangeHandlers: true, })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); const { onChangeFoo, onChangeBar } = props; const fooChangeResult = onChangeFoo(123); expect(history.replace).toBeCalled(); expect(history.push).not.toBeCalled(); expect(fooChangeResult).toEqual({ query: { foo: '123', bar: 'baz' } }); const barChangeResult = onChangeBar('new-bar'); expect(history.push).toBeCalled(); expect(barChangeResult).toEqual({ query: { foo: '94', bar: 'new-bar' } }); }); it('generated change handlers to read location dynamically from props', () => { const location = { query: { foo: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number }, bar: { type: UrlQueryParamTypes.string, updateType: UrlUpdateTypes.pushIn, }, }; // make the history just return the new location so we can test for logging const history = { replace: jest.fn().mockImplementation(d => d), push: jest.fn().mockImplementation(d => d), }; configureUrlQuery({ history }); const Wrapped = addUrlProps({ urlPropsQueryConfig, addUrlChangeHandlers: true, })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); const { onChangeFoo, onChangeBar } = props; // update the prop location const location2 = { query: { foo: '1000', bar: 'BAR' } }; wrapper.setProps({ location: location2 }); const fooChangeResult = onChangeFoo(123); expect(fooChangeResult).toEqual({ query: { foo: '123', bar: 'BAR' } }); const barChangeResult = onChangeBar('new-bar'); expect(barChangeResult).toEqual({ query: { foo: '1000', bar: 'new-bar' } }); }); it('generated change handlers do not update if the URL is the same', () => { const location = { query: { foo: '94', bar: 'baz' } }; const urlPropsQueryConfig = { foo: { type: UrlQueryParamTypes.number }, bar: { type: UrlQueryParamTypes.string, updateType: UrlUpdateTypes.pushIn, }, }; // make the history just return the new location so we can test for logging const history = { replace: jest.fn().mockImplementation(d => d), push: jest.fn().mockImplementation(d => d), }; configureUrlQuery({ history }); const Wrapped = addUrlProps({ urlPropsQueryConfig, addUrlChangeHandlers: true, })(MyComponent); const wrapper = shallow(<Wrapped location={location} />); const props = wrapper.first().props(); const { onChangeFoo, onChangeBar } = props; onChangeFoo(94); expect(history.replace).not.toBeCalled(); onChangeBar('baz'); expect(history.push).not.toBeCalled(); }); });