diff --git a/package.json b/package.json index 9508d41..70ae0eb 100644 --- a/package.json +++ b/package.json @@ -73,9 +73,10 @@ "lodash": "^4.14.0", "mocha": "^2.5.3", "react": "^15.2.1", + "react-addons-test-utils": "^15.4.2", "react-bootstrap": "^0.30.0", "react-dom": "^15.2.1", - "react-router": "^2.6.0", + "react-router-dom": "^4.0.0", "release-script": "^1.0.2", "rimraf": "^2.5.4", "shelljs": "^0.7.2", diff --git a/src/IndexLinkContainer.js b/src/IndexLinkContainer.js index b46946f..33e5498 100644 --- a/src/IndexLinkContainer.js +++ b/src/IndexLinkContainer.js @@ -7,7 +7,7 @@ import LinkContainer from './LinkContainer'; export default class IndexLinkContainer extends React.Component { render() { return ( - + ); } } diff --git a/src/LinkContainer.js b/src/LinkContainer.js index 3225df0..1bb7c4c 100644 --- a/src/LinkContainer.js +++ b/src/LinkContainer.js @@ -1,61 +1,46 @@ -// This is largely taken from react-router/lib/Link. - -import React from 'react'; - -function isLeftClickEvent(event) { - return event.button === 0; -} - -function isModifiedEvent(event) { - return !!( - event.metaKey || - event.altKey || - event.ctrlKey || - event.shiftKey - ); -} - -function createLocationDescriptor(to, query, hash, state) { - if (query || hash || state) { - return { pathname: to, query, hash, state }; - } - - return to; -} - -const propTypes = { - onlyActiveOnIndex: React.PropTypes.bool.isRequired, - to: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.object, - ]).isRequired, - query: React.PropTypes.string, - hash: React.PropTypes.string, - state: React.PropTypes.object, - action: React.PropTypes.oneOf([ - 'push', - 'replace', - ]).isRequired, - onClick: React.PropTypes.func, - active: React.PropTypes.bool, - target: React.PropTypes.string, - children: React.PropTypes.node.isRequired, -}; +import React, { Component, PropTypes } from 'react'; +import { Route } from 'react-router-dom'; + +const isModifiedEvent = (event) => + !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); + +export default class LinkContainer extends Component { + static contextTypes = { + router: PropTypes.shape({ + history: PropTypes.shape({ + push: PropTypes.func.isRequired, + replace: PropTypes.func.isRequired, + createHref: PropTypes.func.isRequired, + }).isRequired, + }).isRequired, + }; -const contextTypes = { - router: React.PropTypes.object, -}; + static propTypes = { + children: PropTypes.element.isRequired, + onClick: PropTypes.func, + replace: PropTypes.bool, + to: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + ]).isRequired, + exact: PropTypes.bool, + strict: PropTypes.bool, + className: PropTypes.string, + activeClassName: PropTypes.string, + style: PropTypes.object, + activeStyle: PropTypes.object, + isActive: PropTypes.func, + }; -const defaultProps = { - onlyActiveOnIndex: false, - action: 'push', -}; + static defaultProps = { + replace: false, + exact: false, + strict: false, + activeClassName: 'active', + }; -class LinkContainer extends React.Component { - onClick = (event) => { - const { - to, query, hash, state, children, onClick, target, action, - } = this.props; + handleClick = (event) => { + const { children, onClick } = this.props; if (children.props.onClick) { children.props.onClick(event); @@ -66,42 +51,62 @@ class LinkContainer extends React.Component { } if ( - target || - event.defaultPrevented || - isModifiedEvent(event) || - !isLeftClickEvent(event) + !event.defaultPrevented && // onClick prevented default + event.button === 0 && // ignore right clicks + !isModifiedEvent(event) // ignore clicks with modifier keys ) { - return; - } + event.preventDefault(); - event.preventDefault(); + const { history } = this.context.router; + const { replace, to } = this.props; - this.context.router[action]( - createLocationDescriptor(to, query, hash, state) - ); - }; + if (replace) { + history.replace(to); + } else { + history.push(to); + } + } + } render() { - const { router } = this.context; - const { onlyActiveOnIndex, to, children, ...props } = this.props; - - props.onClick = this.onClick; - - // Ignore if rendered outside Router context; simplifies unit testing. - if (router) { - props.href = router.createHref(to); + const { + children, + replace, // eslint-disable-line no-unused-vars + to, + exact, + strict, + activeClassName, + className, + activeStyle, + style, + isActive: getIsActive, + ...props, + } = this.props; - if (props.active == null) { - props.active = router.isActive(to, onlyActiveOnIndex); - } - } + const href = this.context.router.history.createHref( + typeof to === 'string' ? { pathname: to } : to + ); - return React.cloneElement(React.Children.only(children), props); + return ( + { + const isActive = !!(getIsActive ? getIsActive(match, location) : match); + + return React.cloneElement( + React.Children.only(children), + { + ...props, + className: isActive ? [className, activeClassName].join(' ') : className, + style: isActive ? { ...style, ...activeStyle } : style, + href, + onClick: this.handleClick, + } + ); + }} + /> + ); } } - -LinkContainer.propTypes = propTypes; -LinkContainer.contextTypes = contextTypes; -LinkContainer.defaultProps = defaultProps; - -export default LinkContainer; diff --git a/test/IndexLinkContainer.spec.js b/test/IndexLinkContainer.spec.js index daacc13..f2e005a 100644 --- a/test/IndexLinkContainer.spec.js +++ b/test/IndexLinkContainer.spec.js @@ -1,8 +1,8 @@ import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; +import ReactTestUtils from 'react-addons-test-utils'; import * as ReactBootstrap from 'react-bootstrap'; -import ReactDOM from 'react-dom'; -import { createMemoryHistory, IndexRoute, Route, Router } from 'react-router'; +import { findDOMNode } from 'react-dom'; +import { Route, MemoryRouter as Router } from 'react-router-dom'; import IndexLinkContainer from '../src/IndexLinkContainer'; @@ -19,25 +19,24 @@ describe('IndexLinkContainer', () => { describe('active state', () => { function renderComponent(location) { const router = ReactTestUtils.renderIntoDocument( - - ( - - Root - - )} - > - - - + +
+ ( + + Root + + )} + /> +
); const component = ReactTestUtils.findRenderedComponentWithType( router, Component ); - return ReactDOM.findDOMNode(component); + return findDOMNode(component); } it('should be active on the index route', () => { diff --git a/test/LinkContainer.spec.js b/test/LinkContainer.spec.js index b4c2fd4..4e9ecab 100644 --- a/test/LinkContainer.spec.js +++ b/test/LinkContainer.spec.js @@ -1,8 +1,8 @@ import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; +import ReactTestUtils from 'react-addons-test-utils'; import * as ReactBootstrap from 'react-bootstrap'; -import ReactDOM from 'react-dom'; -import { createMemoryHistory, Route, Router } from 'react-router'; +import { findDOMNode } from 'react-dom'; +import { Route, MemoryRouter as Router } from 'react-router-dom'; import LinkContainer from '../src/LinkContainer'; @@ -18,14 +18,14 @@ describe('LinkContainer', () => { it('should make the correct href', () => { const router = ReactTestUtils.renderIntoDocument( - + ( + render={() => ( @@ -44,10 +44,10 @@ describe('LinkContainer', () => { it('should not add extra DOM nodes', () => { const router = ReactTestUtils.renderIntoDocument( - + ( + render={() => ( { router, Component ); - expect(ReactDOM.findDOMNode(container)) - .to.equal(ReactDOM.findDOMNode(component)); + expect(findDOMNode(container)) + .to.equal(findDOMNode(component)); }); describe('when clicked', () => { it('should transition to the correct route', () => { const router = ReactTestUtils.renderIntoDocument( - - ( - - Target - - )} - /> -
} - /> + +
+ ( + + Target + + )} + /> +
} + /> +
); @@ -107,19 +109,21 @@ describe('LinkContainer', () => { const childOnClick = sinon.spy(); const router = ReactTestUtils.renderIntoDocument( - - ( - - Foo - - )} - /> -
} - /> + +
+ ( + + Foo + + )} + /> +
} + /> +
); @@ -136,25 +140,22 @@ describe('LinkContainer', () => { describe('active state', () => { function renderComponent(location) { const router = ReactTestUtils.renderIntoDocument( - + ( + render={() => ( Foo )} - > - - - + /> ); const component = ReactTestUtils.findRenderedComponentWithType( router, Component ); - return ReactDOM.findDOMNode(component); + return findDOMNode(component); } it('should be active when on the target route', () => { @@ -167,25 +168,22 @@ describe('LinkContainer', () => { it('should respect explicit active prop on container', () => { const router = ReactTestUtils.renderIntoDocument( - + ( + render={() => ( Bar )} - > - - - + /> ); const component = ReactTestUtils.findRenderedComponentWithType( router, Component ); - expect(ReactDOM.findDOMNode(component).className) + expect(findDOMNode(component).className) .to.match(/\bactive\b/); }); }); @@ -195,19 +193,21 @@ describe('LinkContainer', () => { beforeEach(() => { router = ReactTestUtils.renderIntoDocument( - - ( - - Target - - )} - /> -
} - /> + +
+ ( + + Target + + )} + /> +
} + /> +
); }); @@ -218,7 +218,7 @@ describe('LinkContainer', () => { const component = ReactTestUtils.findRenderedComponentWithType( router, Component ); - ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(component), + ReactTestUtils.Simulate.click(findDOMNode(component), { button: 0 } ); @@ -233,7 +233,7 @@ describe('LinkContainer', () => { const component = ReactTestUtils.findRenderedComponentWithType( router, Component ); - expect(ReactDOM.findDOMNode(component).className) + expect(findDOMNode(component).className) .to.match(/\bdisabled\b/); }); }); diff --git a/test/visual/ButtonVisual.js b/test/visual/ButtonVisual.js index da54a81..f4c45eb 100644 --- a/test/visual/ButtonVisual.js +++ b/test/visual/ButtonVisual.js @@ -1,13 +1,13 @@ import React from 'react'; import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar'; import Button from 'react-bootstrap/lib/Button'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import LinkContainer from '../../src/LinkContainer'; export default () => (
- Back to Index + Back to Index

Button

Baseline

diff --git a/test/visual/Home.js b/test/visual/Home.js index f1e4e76..00af682 100644 --- a/test/visual/Home.js +++ b/test/visual/Home.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; export default () => (
diff --git a/test/visual/ListGroupItemVisual.js b/test/visual/ListGroupItemVisual.js index 75f2ff1..66d555c 100644 --- a/test/visual/ListGroupItemVisual.js +++ b/test/visual/ListGroupItemVisual.js @@ -1,7 +1,7 @@ import React from 'react'; import ListGroup from 'react-bootstrap/lib/ListGroup'; import ListGroupItem from 'react-bootstrap/lib/ListGroupItem'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import LinkContainer from '../../src/LinkContainer'; diff --git a/test/visual/MenuItemVisual.js b/test/visual/MenuItemVisual.js index 0359f40..1d9229c 100644 --- a/test/visual/MenuItemVisual.js +++ b/test/visual/MenuItemVisual.js @@ -2,7 +2,7 @@ import React from 'react'; import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar'; import MenuItem from 'react-bootstrap/lib/MenuItem'; import SplitButton from 'react-bootstrap/lib/SplitButton'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import LinkContainer from '../../src/LinkContainer'; diff --git a/test/visual/NavItemVisual.js b/test/visual/NavItemVisual.js index 3b957b7..6139762 100644 --- a/test/visual/NavItemVisual.js +++ b/test/visual/NavItemVisual.js @@ -1,7 +1,7 @@ import React from 'react'; import Nav from 'react-bootstrap/lib/Nav'; import NavItem from 'react-bootstrap/lib/NavItem'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import LinkContainer from '../../src/LinkContainer'; diff --git a/test/visual/index.js b/test/visual/index.js index 9c1a3c1..15b1ddd 100644 --- a/test/visual/index.js +++ b/test/visual/index.js @@ -1,7 +1,7 @@ import React from 'react'; import Grid from 'react-bootstrap/lib/Grid'; import ReactDOM from 'react-dom'; -import { hashHistory, IndexRedirect, Route, Router } from 'react-router'; +import { HashRouter as Router, Route, Redirect } from 'react-router-dom'; import ButtonVisual from './ButtonVisual'; import Home from './Home'; @@ -11,33 +11,22 @@ import NavItemVisual from './NavItemVisual'; import 'bootstrap/less/bootstrap.less'; -const propTypes = { - children: React.PropTypes.node.isRequired, -}; - -const App = ({ children }) => ( - -

React-Router-Bootstrap Module Visual Test

- {children} -
-); - -App.propTypes = propTypes; - const mountNode = document.createElement('div'); document.body.appendChild(mountNode); ReactDOM.render( - - - - - - - - - - + + +

React-Router-Bootstrap Module Visual Test

+ + } /> + + + + + + +
, mountNode );