From ac500c8b242935f986876c69079e9a006624313a Mon Sep 17 00:00:00 2001
From: Jimmy Jia <tesrin@gmail.com>
Date: Tue, 15 Sep 2015 11:27:59 -0400
Subject: [PATCH] [changed] Replace everything with LinkContainer

---
 .eslintignore                       |   4 -
 .eslintrc                           |  38 +-----
 CONTRIBUTING.md                     |   8 +-
 README.md                           | 120 +++----------------
 assets/thumbnail.png                | Bin 3936 -> 0 bytes
 karma.conf.js                       |  20 ++--
 package.json                        |  80 ++++++-------
 scripts/bower-prepare.js            |  70 -----------
 src/ButtonLink.js                   |  20 ----
 src/IndexLinkContainer.js           |  11 ++
 src/LinkContainer.js                |  53 +++++++++
 src/LinkMixin.js                    |  83 --------------
 src/ListGroupItemLink.js            |  20 ----
 src/MenuItemLink.js                 |  23 ----
 src/NavItemLink.js                  |  20 ----
 src/PageItemLink.js                 |  20 ----
 src/RouterOverlayTrigger.js         |   7 --
 src/ThumbnailLink.js                |  20 ----
 src/index.js                        |   9 +-
 tests/.eslintrc                     |  10 +-
 tests/ButtonLink.spec.js            | 166 ---------------------------
 tests/IndexLinkContainer.spec.js    |  56 +++++++++
 tests/LinkContainer.spec.js         | 223 ++++++++++++++++++++++++++++++++++++
 tests/ListGroupItemLink.spec.js     | 164 --------------------------
 tests/MenuItemLink.spec.js          | 168 ---------------------------
 tests/NavItemLink.spec.js           | 190 ------------------------------
 tests/PageItemLink.spec.js          | 103 -----------------
 tests/RouterOverlayTrigger.spec.js  |  13 ---
 tests/TestHandlers.js               |  31 -----
 tests/ThumbnailLink.spec.js         | 103 -----------------
 tests/bower-imports-module.spec.js  |  21 ----
 tests/index.js                      |  21 +---
 tests/phantom-shims.js              |  31 -----
 tests/visual.js                     |   2 -
 tests/visual/ButtonVisual.js        |  71 +++++++-----
 tests/visual/ListGroupItemVisual.js | 108 +++++++++++------
 tests/visual/MenuItemVisual.js      |  42 -------
 tests/visual/NavItemVisual.js       |  56 ++++-----
 tests/visual/PageItemVisual.js      |  28 -----
 tests/visual/ThumbnailVisual.js     |  21 ----
 tests/visual/app.js                 |  29 -----
 tests/visual/home.js                |  29 ++---
 tests/visual/index.js               |  35 ++++++
 webpack.config.babel.js             |  50 ++------
 webpack.test.config.babel.js        |  23 +---
 webpack.visual.config.babel.js      |  17 +++
 webpack/bower-imports-loader.js     |   7 --
 47 files changed, 639 insertions(+), 1805 deletions(-)
 delete mode 100644 .eslintignore
 delete mode 100644 assets/thumbnail.png
 delete mode 100644 scripts/bower-prepare.js
 delete mode 100644 src/ButtonLink.js
 create mode 100644 src/IndexLinkContainer.js
 create mode 100644 src/LinkContainer.js
 delete mode 100644 src/LinkMixin.js
 delete mode 100644 src/ListGroupItemLink.js
 delete mode 100644 src/MenuItemLink.js
 delete mode 100644 src/NavItemLink.js
 delete mode 100644 src/PageItemLink.js
 delete mode 100644 src/RouterOverlayTrigger.js
 delete mode 100644 src/ThumbnailLink.js
 delete mode 100644 tests/ButtonLink.spec.js
 create mode 100644 tests/IndexLinkContainer.spec.js
 create mode 100644 tests/LinkContainer.spec.js
 delete mode 100644 tests/ListGroupItemLink.spec.js
 delete mode 100644 tests/MenuItemLink.spec.js
 delete mode 100644 tests/NavItemLink.spec.js
 delete mode 100644 tests/PageItemLink.spec.js
 delete mode 100644 tests/RouterOverlayTrigger.spec.js
 delete mode 100644 tests/TestHandlers.js
 delete mode 100644 tests/ThumbnailLink.spec.js
 delete mode 100644 tests/bower-imports-module.spec.js
 delete mode 100644 tests/phantom-shims.js
 delete mode 100644 tests/visual.js
 delete mode 100644 tests/visual/MenuItemVisual.js
 delete mode 100644 tests/visual/PageItemVisual.js
 delete mode 100644 tests/visual/ThumbnailVisual.js
 delete mode 100644 tests/visual/app.js
 create mode 100644 tests/visual/index.js
 create mode 100644 webpack.visual.config.babel.js
 delete mode 100644 webpack/bower-imports-loader.js

diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index cb9e0c2..0000000
--- a/.eslintignore
+++ /dev/null
@@ -1,4 +0,0 @@
-node_modules/
-lib/
-amd/
-karma.conf.js
diff --git a/.eslintrc b/.eslintrc
index c048662..15139dc 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,41 +1,7 @@
 {
-  "extends": ["eslint-config-airbnb"],
-  "env": {
-    "browser": true,
-    "node": true
-  },
-  "ecmaFeatures": {
-    "jsx": true
-  },
-  "parser": "babel-eslint",
-  "plugins": [
-    "react",
-    "babel"
-  ],
+  "extends": "airbnb",
   "rules": {
     "comma-dangle": 0,
-    "comma-spacing": 1,
-    "key-spacing": 0,
-    "no-eq-null": 0,
-    "no-param-reassign": 0,
-    "no-underscore-dangle": 0,
-    "no-undef": 2,
-    "no-unused-vars": [2, { "vars": "all", "args": "none" }],
-    "no-var": 2,
-    "babel/object-shorthand": 2,
-    "quotes": [1, "single", "avoid-escape"],
-    "react/display-name": 0,
-    "react/jsx-no-undef": 2,
-    "react/jsx-quotes": 0,
-    "react/jsx-uses-react": 2,
-    "react/no-did-mount-set-state": 2,
-    "react/no-did-update-set-state": 2,
-    "react/no-multi-comp": 2,
-    "react/prop-types": [1, { "ignore": ["children", "className"] }],
-    "react/react-in-jsx-scope": 2,
-    "react/self-closing-comp": 1,
-    "react/wrap-multilines": 2,
-    "react/jsx-uses-vars": 1,
-    "strict": 0
+    "no-eq-null": 0
   }
 }
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1dfea92..8cbd1c3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,7 +1,7 @@
 # Contributing
 
-As part of the react-bootstrap organization all contributing guidelines can be
-found at:
-https://github.com/react-bootstrap/react-bootstrap/blob/master/CONTRIBUTING.md
+As part of the react-bootstrap organization all contributing guidelines can be found at: https://github.com/react-bootstrap/react-bootstrap/blob/master/CONTRIBUTING.md.
 
-Note that automated changelog generation has not been setup on this repo yet.
+Use `npm run visual-test` to check the appearance of components in your browser. The page will load at [http://localhost:8080/](http://localhost:8080/).
+
+Note that automated changelog generation has not been set up on this repo yet.
diff --git a/README.md b/README.md
index 933a84a..fd7633b 100644
--- a/README.md
+++ b/README.md
@@ -1,127 +1,33 @@
 # react-router-bootstrap
+Integration between [React Router](https://github.com/rackt/react-router) and [React-Bootstrap](https://github.com/react-bootstrap/react-bootstrap).
 
 [![Build Status](https://travis-ci.org/react-bootstrap/react-router-bootstrap.svg?branch=master)](https://travis-ci.org/react-bootstrap/react-router-bootstrap)
+[![npm version](https://badge.fury.io/js/react-router-bootstrap.svg)](http://badge.fury.io/js/react-router-bootstrap)
 
-Intregation between [react-router](https://github.com/rackt/react-router) and [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap)
-
-This package gives you react-router compatible substitutes for:
-
-- `NavItem` -> `NavItemLink`
-- `Button` -> `ButtonLink`
-- `MenuItem` -> `MenuItemLink`
-- `ListGroupItem` -> `ListGroupItemLink`
-- `PageItem` -> `PageItemLink`
-- `Thumbnail` -> `ThumbnailLink`
-
-Turning this:
+## Usage
 
-```jsx
-React.createClass({
-  mixins: [State, Navigation],
+Wrap your React-Bootstrap element in a `LinkContainer` to make it behave like a React Router `Link`:
 
-  render: function() {
-    var href = this.makeHref('destination', {some: 'params'}, {some: 'query param'});
-    var isActive = this.isActive('destination', {some: 'params'}, {some: 'query param'});
-    return <Button href={href} active={isActive}>;
-  }
-});
+```js
+<LinkContainer to="/foo" query={{bar: "baz"}}>
+  <Button>Foo</Button>
+</LinkContainer>
 ```
 
-Into this
-
-```jsx
-React.createClass({
-  render: function() {
-    return <ButtonLink to="destination" params={{ some: 'params' }} query={{some: 'query param'}}>;
-  }
-});
-```
+To disable the element and the link, set the `disabled` prop on the `LinkContainer`. For the equivalent of `IndexLink`, use `IndexLinkContainer`.
 
 ## Installation
 
 ```
-npm install --save react-router-bootstrap
+npm install react-router-bootstrap
 ```
 
-You will also (if you haven't already) want to install `react-router` and `react-bootstrap`
+You will also want to have React Router and React-Bootstrap.
 
 ```
-npm install --save react-router react-bootstrap
-```
-
-## Usage
-
-A simple example
-
-```jsx
-var Router = require('react-router')
-  , RouteHandler = Router.RouteHandler
-  , Route = Router.Route;
-
-var ReactBootstrap = require('react-bootstrap')
-  , Nav = ReactBootstrap.Nav
-  , ListGroup = ReactBootstrap.ListGroup;
-
-var ReactRouterBootstrap = require('react-router-bootstrap')
-  , NavItemLink = ReactRouterBootstrap.NavItemLink
-  , ButtonLink = ReactRouterBootstrap.ButtonLink
-  , ListGroupItemLink = ReactRouterBootstrap.ListGroupItemLink;
-
-var App = React.createClass({
-  render: function() {
-    return (
-      <div>
-        NavItemLink<br />
-        <Nav>
-          <NavItemLink
-            to="destination"
-            params={{ someparam: 'hello' }}>
-            Linky!
-          </NavItemLink>
-        </Nav>
-        <br />
-        ButtonLink<br />
-        <ButtonLink
-          to="destination"
-          params={{ someparam: 'hello' }}>
-          Linky!
-        </ButtonLink>
-        <br />
-        <ListGroup>
-          <ListGroupItemLink
-            to="destination"
-            params={{ someparam: 'hello' }}>
-            Linky!
-          </ListGroupItemLink>
-        </ListGroup>
-        <RouteHandler />
-      </div>
-    );
-  }
-});
-
-var Destination = React.createClass({
-  render: function() {
-    return <div>You made it!</div>;
-  }
-});
-
-var routes = (
-  <Route handler={App} path="/">
-    <Route name="destination" path="destination/:someparam" handler={Destination} />
-  </Route>
-);
-
-Router.run(routes, function (Handler) {
-  React.render(<Handler/>, document.body);
-});
-
+npm install react-router react-bootstrap
 ```
 
 ## Contributing
 
-See [CONTRIBUTING](CONTRIBUTING.md)
-
-Use `npm run visual-test` command to check components appearance in browser. It will open browser with a blank page. Then after `webpack-server` finishes its bundling, the browser automatically will refresh the page.
-
-URL for it: http://localhost:8080/public/visual#/
+See [CONTRIBUTING](CONTRIBUTING.md).
diff --git a/assets/thumbnail.png b/assets/thumbnail.png
deleted file mode 100644
index 6c05a892d40e6b9c0a433b17029341e33c0231a3..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/karma.conf.js b/karma.conf.js
index ade7535..a2d839a 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -4,26 +4,28 @@ delete webpackConfig.entry;
 
 module.exports = function (config) {
   config.set({
-
     basePath: '',
 
-    frameworks: [ 'mocha' ],
+    frameworks: [
+      'mocha',
+      'sinon-chai'
+    ],
 
     files: [
       './tests/index.js'
     ],
 
-    exclude: [],
-
     preprocessors: {
-      './tests/index.js': [ 'webpack' ]
+      './tests/index.js': ['webpack', 'sourcemap']
     },
 
-    webpack: [ webpackConfig ],
+    webpack: webpackConfig,
 
-    webpackMiddleware: { },
+    webpackMiddleware: {
+      noInfo: true
+    },
 
-    reporters: [ 'mocha' ],
+    reporters: ['mocha'],
 
     port: 9876,
 
@@ -33,7 +35,7 @@ module.exports = function (config) {
 
     autoWatch: true,
 
-    browsers: [ 'PhantomJS' ],
+    browsers: ['PhantomJS'],
 
     captureTimeout: 60000,
     browserDisconnectTimeout: 7000,
diff --git a/package.json b/package.json
index 2e9f964..343de98 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,16 @@
 {
   "name": "react-router-bootstrap",
   "version": "0.18.1",
-  "description": "react-router and react-bootstrap compatible components",
+  "description": "Integration between React Router and React-Bootstrap",
   "main": "./lib/index.js",
   "scripts": {
     "prepublish": "npm run build",
-    "build": "babel src --out-dir=lib && webpack && COMPRESS=1 webpack && npm run bower-prepare",
+    "build": "babel src --out-dir=lib && webpack && webpack -p",
     "test": "npm run lint && karma start --single-run",
     "tdd": "karma start",
-    "visual-test": "open http://localhost:8080/public/visual#/ && webpack-dev-server --config webpack.test.config.babel.js",
-    "lint": "eslint ./",
-    "bower-prepare": "babel-node scripts/bower-prepare.js",
-    "patch": "release patch",
-    "minor": "release minor",
-    "major": "release major"
+    "visual-test": "open http://localhost:8080/ && webpack-dev-server --config webpack.visual.config.babel.js",
+    "lint": "eslint *.babel.js src tests",
+    "release": "release"
   },
   "repository": {
     "type": "git",
@@ -34,48 +31,53 @@
   },
   "homepage": "https://github.com/react-bootstrap/react-router-bootstrap",
   "peerDependencies": {
-    "react-bootstrap": ">=0.22.4",
-    "react-router": ">=0.13.1"
+    "react": ">=0.13.0 || >=0.14.0-rc1",
+    "react-router": ">=1.0.0-rc1"
   },
   "devDependencies": {
-    "babel": "^5.5.6",
-    "babel-core": "^5.5.6",
-    "babel-eslint": "^4.0.5",
-    "babel-loader": "^5.1.4",
-    "bootstrap": "^3.3.1",
-    "chai": "^3.0.0",
-    "colors": "^1.1.2",
-    "css-loader": "^0.15.3",
-    "eslint": "^1.0.0",
-    "eslint-config-airbnb": "0.0.7",
-    "eslint-plugin-babel": "^1.0.0",
-    "eslint-plugin-mocha": "^0.4.0",
-    "eslint-plugin-react": "^3.1.0",
-    "karma": "^0.13.3",
+    "babel": "^5.8.23",
+    "babel-core": "^5.8.24",
+    "babel-eslint": "^4.1.2",
+    "babel-loader": "^5.3.2",
+    "bootstrap": "^3.3.5",
+    "css-loader": "^0.18.0",
+    "es5-shim": "^4.1.13",
+    "eslint": "1.3.x",
+    "eslint-config-airbnb": "0.0.8",
+    "eslint-plugin-babel": "^2.1.1",
+    "eslint-plugin-mocha": "^0.5.1",
+    "eslint-plugin-react": "^3.3.2",
+    "file-loader": "^0.8.4",
+    "history": "^1.9.1",
+    "html-webpack-plugin": "^1.6.1",
+    "karma": "^0.13.9",
     "karma-cli": "^0.1.0",
     "karma-mocha": "^0.2.0",
-    "karma-mocha-reporter": "^1.0.2",
-    "karma-phantomjs-launcher": "^0.2.0",
-    "karma-webpack": "^1.5.0",
+    "karma-mocha-reporter": "^1.1.1",
+    "karma-phantomjs-launcher": "^0.2.1",
+    "karma-sinon-chai": "^1.1.0",
+    "karma-sourcemap-loader": "^0.3.5",
+    "karma-webpack": "^1.7.0",
     "less": "^2.5.1",
     "less-loader": "^2.2.0",
-    "lodash": "^3.10.0",
-    "mocha": "^2.1.0",
-    "mt-changelog": "^0.6.1",
+    "mocha": "^2.3.2",
+    "mt-changelog": "^0.6.2",
     "node-libs-browser": "^0.5.2",
-    "phantomjs": "^1.9.13",
-    "react": ">0.10.0",
-    "react-bootstrap": ">=0.22.4",
-    "react-router": ">=0.13.1",
-    "release-script": "^0.2.1",
-    "shelljs": "^0.5.1",
+    "phantomjs": "^1.9.18",
+    "react": "^0.14.0-rc1",
+    "react-bootstrap": "^0.25.100-react-pre.1",
+    "react-dom": "^0.14.0-rc1",
+    "react-router": "^1.0.0-rc1",
+    "release-script": "^0.2.7",
     "style-loader": "^0.12.3",
     "url-loader": "^0.5.6",
-    "webpack": "^1.4.15",
-    "webpack-dev-server": "^1.7.0",
-    "yargs": "^3.15.0"
+    "webpack": "^1.12.1",
+    "webpack-dev-server": "^1.10.1",
+    "yargs": "^3.25.0"
   },
   "files": [
+    "README",
+    "CHANGELOG.md",
     "lib"
   ],
   "release-script": {
diff --git a/scripts/bower-prepare.js b/scripts/bower-prepare.js
deleted file mode 100644
index e98318e..0000000
--- a/scripts/bower-prepare.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* globals cat, config, cp, mkdir, rm, test */
-/* eslint curly: 0, no-console: 0 */
-import 'colors';
-import 'shelljs/global';
-import path from 'path';
-import _ from 'lodash';
-import yargs from 'yargs';
-
-// do not die on errors
-config.fatal = false;
-
-//------------------------------------------------------------------------------
-// constants
-const repoRoot = path.resolve(__dirname, '../');
-const libFolder = path.join(repoRoot, 'lib');
-const bowerRoot = path.join(repoRoot, 'amd');
-const bowerTemplate = path.join(repoRoot, 'bower.template.json');
-const license = path.join(repoRoot, 'LICENSE');
-
-
-//------------------------------------------------------------------------------
-// command line options
-const argv = yargs
-  .usage('Usage: $0 [--verbose]')
-  .example('$0', 'Prepare bower package for releasing')
-  .option('verbose', {
-    demand: false,
-    default: false,
-    describe: 'Increased debug output'
-  })
-  .argv;
-
-if (argv.dryRun) console.log('DRY RUN'.magenta);
-
-config.silent = !argv.verbose;
-
-
-//------------------------------------------------------------------------------
-// functions
-function bower() {
-  console.log('Creating: '.cyan + 'bower package'.green);
-
-  rm('-rf', bowerRoot);
-  mkdir('-p', bowerRoot);
-
-  // generate bower.json from template
-  const pkg = JSON.parse(cat(path.join(repoRoot, 'package.json')));
-  const template = _.template(cat(bowerTemplate));
-  const bowerConfigObject = template({ pkg });
-  const json = JSON.stringify(JSON.parse(bowerConfigObject), null, 2); // proper formatting hack
-  json.to(path.join(bowerRoot, 'bower.json'));
-
-  // copy readme and license
-  const readmeBower = path.join(repoRoot, 'README.bower.md');
-  const readme = path.join(repoRoot, 'README.md');
-  if (test('-e', readmeBower)) {
-    cp(readmeBower, path.join(bowerRoot, 'README.md'));
-  } else {
-    cp(readme, bowerRoot);
-  }
-  if (test('-e', license)) cp(license, bowerRoot);
-
-  // copy distr files
-  cp('-r', libFolder, bowerRoot);
-
-  console.log('Created: '.cyan + 'bower package'.green);
-}
-
-//------------------------------------------------------------------------------
-bower();
diff --git a/src/ButtonLink.js b/src/ButtonLink.js
deleted file mode 100644
index c0dfe26..0000000
--- a/src/ButtonLink.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import Button from 'react-bootstrap/lib/Button';
-import LinkMixin from './LinkMixin';
-
-const ButtonLink = React.createClass({
-  mixins: [
-    LinkMixin
-  ],
-
-  render() {
-    return (
-      <Button {...this.getLinkProps()} ref='button'>
-        {this.props.children}
-      </Button>
-    );
-  }
-});
-
-export default ButtonLink;
diff --git a/src/IndexLinkContainer.js b/src/IndexLinkContainer.js
new file mode 100644
index 0000000..7b6b999
--- /dev/null
+++ b/src/IndexLinkContainer.js
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import LinkContainer from './LinkContainer';
+
+export default class IndexLinkContainer extends React.Component {
+  render() {
+    return (
+      <LinkContainer {...this.props} onlyActiveOnIndex />
+    );
+  }
+}
diff --git a/src/LinkContainer.js b/src/LinkContainer.js
new file mode 100644
index 0000000..9d4df62
--- /dev/null
+++ b/src/LinkContainer.js
@@ -0,0 +1,53 @@
+// This is largely taken from react-router/lib/Link.
+
+import React from 'react';
+import {Link} from 'react-router';
+
+export default class LinkContainer extends React.Component {
+  constructor(props, context) {
+    super(props, context);
+
+    this.onClick = this.onClick.bind(this);
+  }
+
+  onClick(event) {
+    if (this.props.disabled) {
+      event.preventDefault();
+      return;
+    }
+
+    Link.prototype.handleClick.call(this, event);
+  }
+
+  render() {
+    const {history} = this.context;
+    const {onlyActiveOnIndex, to, query, children, ...props} = this.props;
+
+    delete props.state;
+    delete props.onClick;
+    props.onClick = this.onClick;
+    props.href = history.createHref(to, query);
+    props.active = history.isActive(to, query, onlyActiveOnIndex);
+
+    return React.cloneElement(React.Children.only(children), props);
+  }
+}
+
+LinkContainer.propTypes = {
+  onlyActiveOnIndex: React.PropTypes.bool.isRequired,
+  to: React.PropTypes.string.isRequired,
+  query: React.PropTypes.object,
+  state: React.PropTypes.object,
+  onClick: React.PropTypes.func,
+  disabled: React.PropTypes.bool.isRequired,
+  children: React.PropTypes.node.isRequired
+};
+
+LinkContainer.contextTypes = {
+  history: React.PropTypes.object.isRequired
+};
+
+LinkContainer.defaultProps = {
+  onlyActiveOnIndex: false,
+  disabled: false
+};
diff --git a/src/LinkMixin.js b/src/LinkMixin.js
deleted file mode 100644
index e1cccbc..0000000
--- a/src/LinkMixin.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import React from 'react';
-
-function isLeftClickEvent(event) {
-  return event.button === 0;
-}
-
-function isModifiedEvent(event) {
-  return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
-}
-
-export default {
-  propTypes: {
-    active: React.PropTypes.bool,
-    activeClassName: React.PropTypes.string.isRequired,
-    disabled: React.PropTypes.bool,
-    to: React.PropTypes.string.isRequired,
-    params: React.PropTypes.object,
-    query: React.PropTypes.object,
-    onClick: React.PropTypes.func
-  },
-  contextTypes: {
-    router: React.PropTypes.func.isRequired
-  },
-
-  getDefaultProps() {
-    return {
-      activeClassName: 'active'
-    };
-  },
-
-  /**
-   * Returns props except those used by this Mixin
-   * Gets "active" from router if needed.
-   * Gets the value of the "href" attribute to use on the DOM element.
-   * Sets "onClick" to "handleRouteTo".
-   */
-  getLinkProps() {
-    const {
-      to,
-      params,
-      query,
-      ...props
-    } = this.props;
-
-    if (this.props.active === undefined) {
-      props.active = this.context.router.isActive(to, params, query);
-    }
-
-    props.href = this.context.router.makeHref(to, params, query);
-
-    props.onClick = this.handleRouteTo;
-
-    return props;
-  },
-
-  handleRouteTo(event) {
-    let allowTransition = true;
-    let clickResult;
-
-    if (this.props.disabled) {
-      event.preventDefault();
-      return;
-    }
-
-    if (this.props.onClick) {
-      clickResult = this.props.onClick(event);
-    }
-
-    if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
-      return;
-    }
-
-    if (clickResult === false || event.defaultPrevented === true) {
-      allowTransition = false;
-    }
-
-    event.preventDefault();
-
-    if (allowTransition) {
-      this.context.router.transitionTo(this.props.to, this.props.params, this.props.query);
-    }
-  }
-};
diff --git a/src/ListGroupItemLink.js b/src/ListGroupItemLink.js
deleted file mode 100644
index 5de16ed..0000000
--- a/src/ListGroupItemLink.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import ListGroupItem from 'react-bootstrap/lib/ListGroupItem';
-import LinkMixin from './LinkMixin';
-
-const LinkGroupItemLink = React.createClass({
-  mixins: [
-    LinkMixin
-  ],
-
-  render() {
-    return (
-      <ListGroupItem {...this.getLinkProps()} ref='listGroupItem'>
-        {this.props.children}
-      </ListGroupItem>
-    );
-  }
-});
-
-export default LinkGroupItemLink;
diff --git a/src/MenuItemLink.js b/src/MenuItemLink.js
deleted file mode 100644
index 2cd4af5..0000000
--- a/src/MenuItemLink.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-
-import MenuItem from 'react-bootstrap/lib/MenuItem';
-import LinkMixin from './LinkMixin';
-
-const MenuItemLink = React.createClass({
-  mixins: [
-    LinkMixin
-  ],
-
-  render() {
-    const props = this.getLinkProps();
-    delete props.onSelect; // this is done on the copy of this.props
-
-    return (
-      <MenuItem {...props} ref="menuItem">
-        {this.props.children}
-      </MenuItem>
-    );
-  }
-});
-
-export default MenuItemLink;
diff --git a/src/NavItemLink.js b/src/NavItemLink.js
deleted file mode 100644
index 27a32a9..0000000
--- a/src/NavItemLink.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import NavItem from 'react-bootstrap/lib/NavItem';
-import LinkMixin from './LinkMixin';
-
-const NavItemLink = React.createClass({
-  mixins: [
-    LinkMixin
-  ],
-
-  render() {
-    return (
-      <NavItem {...this.getLinkProps()} ref="navItem">
-        {this.props.children}
-      </NavItem>
-    );
-  }
-});
-
-export default NavItemLink;
diff --git a/src/PageItemLink.js b/src/PageItemLink.js
deleted file mode 100644
index 56b2ba9..0000000
--- a/src/PageItemLink.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import PageItem from 'react-bootstrap/lib/PageItem';
-import LinkMixin from './LinkMixin';
-
-const PageItemLink = React.createClass({
-  mixins: [
-    LinkMixin
-  ],
-
-  render() {
-    return (
-      <PageItem {...this.getLinkProps()} ref='pageItem'>
-        {this.props.children}
-      </PageItem>
-    );
-  }
-});
-
-export default PageItemLink;
diff --git a/src/RouterOverlayTrigger.js b/src/RouterOverlayTrigger.js
deleted file mode 100644
index 1ba7224..0000000
--- a/src/RouterOverlayTrigger.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from 'react';
-
-import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
-
-export default OverlayTrigger.withContext({
-  router: React.PropTypes.func
-});
diff --git a/src/ThumbnailLink.js b/src/ThumbnailLink.js
deleted file mode 100644
index 979e8f6..0000000
--- a/src/ThumbnailLink.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import Thumbnail from 'react-bootstrap/lib/Thumbnail';
-import LinkMixin from './LinkMixin';
-
-const ThumbnailLink = React.createClass({
-  mixins: [
-    LinkMixin
-  ],
-
-  render() {
-    return (
-      <Thumbnail {...this.getLinkProps()} ref='thumbnail'>
-        {this.props.children}
-      </Thumbnail>
-    );
-  }
-});
-
-export default ThumbnailLink;
diff --git a/src/index.js b/src/index.js
index 1d8d34f..5fd05e4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,2 @@
-export ButtonLink from './ButtonLink';
-export ListGroupItemLink from './ListGroupItemLink';
-export MenuItemLink from './MenuItemLink';
-export NavItemLink from './NavItemLink';
-export PageItemLink from './PageItemLink';
-export RouterOverlayTrigger from './RouterOverlayTrigger';
-export ThumbnailLink from './ThumbnailLink';
+export IndexLinkContainer from './IndexLinkContainer';
+export LinkContainer from './LinkContainer';
diff --git a/tests/.eslintrc b/tests/.eslintrc
index b1618b1..c382f66 100644
--- a/tests/.eslintrc
+++ b/tests/.eslintrc
@@ -11,13 +11,7 @@
     "mocha"
   ],
   "rules": {
-    "func-names": 0,
-    "no-alert": 0,
-    "no-script-url": 0,
-    "no-unused-expressions": 0,
-    "react/no-multi-comp": 0,
-    "react/prop-types": 0,
-    "react/sort-comp": 0,
-    "mocha/no-exclusive-tests": 2
+    "mocha/no-exclusive-tests": 2,
+    "react/no-multi-comp": 0
   }
 }
diff --git a/tests/ButtonLink.spec.js b/tests/ButtonLink.spec.js
deleted file mode 100644
index 729778b..0000000
--- a/tests/ButtonLink.spec.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/* globals describe, it, assert, expect */
-
-import React from 'react/addons';
-import ButtonLink from '../src/ButtonLink';
-import Router, { Route, RouteHandler } from 'react-router';
-import { Foo, Bar } from './TestHandlers';
-import TestLocation from 'react-router/lib/locations/TestLocation';
-const { click } = React.addons.TestUtils.Simulate;
-
-describe('A ButtonLink', function() {
-  describe('with params and a query', function() {
-    it('knows how to make its href', function() {
-      const ButtonLinkHandler = React.createClass({
-        render() {
-          return <ButtonLink to="foo" params={{bar: 'baz'}} query={{qux: 'quux'}}>ButtonLink</ButtonLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" path="foo/:bar" handler={Foo} />,
-        <Route name="link" handler={ButtonLinkHandler} />
-      ];
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          const a = div.querySelector('a');
-          expect(a.getAttribute('href')).to.equal('/foo/baz?qux=quux');
-        });
-      });
-    });
-  });
-
-  describe('when its route is active', function() {
-    it('has an active class name', function(done) {
-      const ButtonLinkHandler = React.createClass({
-        render() {
-          return (
-            <div>
-              <ButtonLink
-                to='foo'
-                className='dontKillMe'
-                bsStyle='primary'
-                bsSize='small'
-              >ButtonLink</ButtonLink>
-              <RouteHandler/>
-            </div>
-          );
-        }
-      });
-
-      const routes = (
-        <Route path="/" handler={ButtonLinkHandler}>
-          <Route name="foo" handler={Foo} />
-          <Route name="bar" handler={Bar} />
-        </Route>
-      );
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/foo'];
-      const steps = [];
-
-      function assertActive() {
-        const a = div.querySelector('a');
-        expect(a.className.split(' ').sort().join(' ')).to.equal('active btn btn-primary btn-sm dontKillMe');
-      }
-
-      function assertInactive() {
-        const a = div.querySelector('a');
-        expect(a.className.split(' ').sort().join(' ')).to.equal('btn btn-primary btn-sm dontKillMe');
-      }
-
-      steps.push(() => {
-        assertActive();
-        testLocation.push('/bar');
-      });
-
-      steps.push(() => {
-        assertInactive();
-        testLocation.push('/foo');
-      });
-
-      steps.push(() => {
-        assertActive();
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, () => {
-          steps.shift()();
-        });
-      });
-    });
-  });
-
-  describe('when clicked', function() {
-    it('calls a user defined click handler', function(done) {
-      const ButtonLinkHandler = React.createClass({
-        handleClick(event) {
-          assert.ok(true);
-          done();
-        },
-
-        render() {
-          return <ButtonLink to="foo" onClick={this.handleClick}>ButtonLink</ButtonLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={ButtonLinkHandler} />
-      ];
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          click(div.querySelector('a'));
-        });
-      });
-    });
-
-    it('transitions to the correct route', function(done) {
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      const ButtonLinkHandler = React.createClass({
-        handleClick() {
-          // just here to make sure click handlers don't prevent it from happening
-        },
-
-        render() {
-          return <ButtonLink to="foo" onClick={this.handleClick}>ButtonLink</ButtonLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={ButtonLinkHandler} />
-      ];
-
-      const steps = [];
-
-      steps.push(function() {
-        click(div.querySelector('a'), {button: 0});
-      });
-
-      steps.push(function() {
-        expect(div.innerHTML).to.match(/Foo/);
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          steps.shift()();
-        });
-      });
-    });
-  });
-});
diff --git a/tests/IndexLinkContainer.spec.js b/tests/IndexLinkContainer.spec.js
new file mode 100644
index 0000000..c1c6e84
--- /dev/null
+++ b/tests/IndexLinkContainer.spec.js
@@ -0,0 +1,56 @@
+import createMemoryHistory from 'history/lib/createMemoryHistory';
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import * as ReactBootstrap from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import {IndexRoute, Route, Router} from 'react-router';
+
+import IndexLinkContainer from '../src/IndexLinkContainer';
+
+describe('IndexLinkContainer', () => {
+  [
+    'Button',
+    'NavItem',
+    'ListGroupItem'
+  ].forEach(name => {
+    describe(name, () => {
+      const Component = ReactBootstrap[name];
+
+      describe('active state', () => {
+        function renderComponent(location) {
+          class LinkWrapper extends React.Component {
+            render() {
+              return (
+                <IndexLinkContainer to="/">
+                  <Component>Root</Component>
+                </IndexLinkContainer>
+              );
+            }
+          }
+
+          const router = ReactTestUtils.renderIntoDocument(
+            <Router history={createMemoryHistory(location)}>
+              <Route path="/" component={LinkWrapper}>
+                <IndexRoute />
+                <Route path="foo" />
+              </Route>
+            </Router>
+          );
+
+          const component = ReactTestUtils.findRenderedComponentWithType(
+            router, Component
+          );
+          return ReactDOM.findDOMNode(component);
+        }
+
+        it('should be active on the index route', () => {
+          expect(renderComponent('/').className).to.match(/\bactive\b/);
+        });
+
+        it('should not be active on a child route', () => {
+          expect(renderComponent('/foo').className).to.not.match(/\bactive\b/);
+        });
+      });
+    });
+  });
+});
diff --git a/tests/LinkContainer.spec.js b/tests/LinkContainer.spec.js
new file mode 100644
index 0000000..8db2355
--- /dev/null
+++ b/tests/LinkContainer.spec.js
@@ -0,0 +1,223 @@
+import createMemoryHistory from 'history/lib/createMemoryHistory';
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import * as ReactBootstrap from 'react-bootstrap';
+import ReactDOM from 'react-dom';
+import {Route, Router} from 'react-router';
+
+import LinkContainer from '../src/LinkContainer';
+
+describe('LinkContainer', () => {
+  ['Button',
+    'NavItem',
+    'ListGroupItem'
+  ].forEach(name => {
+    describe(name, () => {
+      const Component = ReactBootstrap[name];
+
+      it('should make the correct href', () => {
+        class LinkWrapper extends React.Component {
+          render() {
+            return (
+              <LinkContainer to="/foo" query={{bar: 'baz'}}>
+                <Component>Foo</Component>
+              </LinkContainer>
+            );
+          }
+        }
+
+        const router = ReactTestUtils.renderIntoDocument(
+          <Router history={createMemoryHistory('/')}>
+            <Route path="/" component={LinkWrapper} />
+          </Router>
+        );
+
+        const anchor = ReactTestUtils.findRenderedDOMComponentWithTag(
+          router, 'A'
+        );
+        expect(anchor.getAttribute('href')).to.equal('/foo?bar=baz');
+      });
+
+      it('should not add extra DOM nodes', () => {
+        class LinkWrapper extends React.Component {
+          render() {
+            return (
+              <LinkContainer to="/foo" query={{bar: 'baz'}}>
+                <Component>Foo</Component>
+              </LinkContainer>
+            );
+          }
+        }
+
+        const router = ReactTestUtils.renderIntoDocument(
+          <Router history={createMemoryHistory('/')}>
+            <Route path="/" component={LinkWrapper} />
+          </Router>
+        );
+
+        const container = ReactTestUtils.findRenderedComponentWithType(
+          router, LinkContainer
+        );
+        const component = ReactTestUtils.findRenderedComponentWithType(
+          router, Component
+        );
+
+        expect(ReactDOM.findDOMNode(container))
+          .to.equal(ReactDOM.findDOMNode(component));
+      });
+
+      describe('when clicked', () => {
+        it('should transition to the correct route', () => {
+          class LinkWrapper extends React.Component {
+            render() {
+              return (
+                <LinkContainer to="/target">
+                  <Component>Target</Component>
+                </LinkContainer>
+              );
+            }
+          }
+
+          class Target extends React.Component {
+            render() {
+              return <div className="target" />;
+            }
+          }
+
+          const router = ReactTestUtils.renderIntoDocument(
+            <Router history={createMemoryHistory('/')}>
+              <Route path="/" component={LinkWrapper} />
+              <Route path="/target" component={Target} />
+            </Router>
+          );
+
+          const component = ReactTestUtils.findRenderedComponentWithType(
+            router, Component
+          );
+          ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(component),
+            {button: 0}
+          );
+
+          const target = ReactTestUtils.findRenderedDOMComponentWithClass(
+            router, 'target'
+          );
+          expect(target).to.exist;
+        });
+
+        it('should call a user defined click handler', () => {
+          const onClick = sinon.spy();
+
+          class LinkWrapper extends React.Component {
+            render() {
+              return (
+                <LinkContainer to="/foo" onClick={onClick}>
+                  <Component>Foo</Component>
+                </LinkContainer>
+              );
+            }
+          }
+
+          const router = ReactTestUtils.renderIntoDocument(
+            <Router history={createMemoryHistory('/')}>
+              <Route path="/" component={LinkWrapper} />
+            </Router>
+          );
+
+          const component = ReactTestUtils.findRenderedComponentWithType(
+            router, Component
+          );
+          ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(component));
+
+          expect(onClick).to.have.been.called;
+        });
+      });
+
+      describe('active state', () => {
+        function renderComponent(location) {
+          class LinkWrapper extends React.Component {
+            render() {
+              return (
+                <LinkContainer to="/foo">
+                  <Component>Foo</Component>
+                </LinkContainer>
+              );
+            }
+          }
+
+          const router = ReactTestUtils.renderIntoDocument(
+            <Router history={createMemoryHistory(location)}>
+              <Route component={LinkWrapper}>
+                <Route path="/foo" />
+                <Route path="/bar" />
+              </Route>
+            </Router>
+          );
+
+          const component = ReactTestUtils.findRenderedComponentWithType(
+            router, Component
+          );
+          return ReactDOM.findDOMNode(component);
+        }
+
+        it('should be active when on the target route', () => {
+          expect(renderComponent('/foo').className).to.match(/\bactive\b/);
+        });
+
+        it('should not be active when on a different route', () => {
+          expect(renderComponent('/bar').className).to.not.match(/\bactive\b/);
+        });
+      });
+
+      describe('disabled state', () => {
+        let router;
+
+        beforeEach(() => {
+          class LinkWrapper extends React.Component {
+            render() {
+              return (
+                <LinkContainer to="/target" disabled>
+                  <Component>Target</Component>
+                </LinkContainer>
+              );
+            }
+          }
+
+          class Target extends React.Component {
+            render() {
+              return <div className="target" />;
+            }
+          }
+
+          router = ReactTestUtils.renderIntoDocument(
+            <Router history={createMemoryHistory('/')}>
+              <Route path="/" component={LinkWrapper} />
+              <Route path="/target" component={Target} />
+            </Router>
+          );
+        });
+
+        it('should not transition on click', () => {
+          const component = ReactTestUtils.findRenderedComponentWithType(
+            router, Component
+          );
+          ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(component),
+            {button: 0}
+          );
+
+          const target = ReactTestUtils.scryRenderedDOMComponentsWithClass(
+            router, 'target'
+          );
+          expect(target).to.be.empty;
+        });
+
+        it('should render with disabled class', () => {
+          const component = ReactTestUtils.findRenderedComponentWithType(
+            router, Component
+          );
+          expect(ReactDOM.findDOMNode(component).className)
+            .to.match(/\bdisabled\b/);
+        });
+      });
+    });
+  });
+});
diff --git a/tests/ListGroupItemLink.spec.js b/tests/ListGroupItemLink.spec.js
deleted file mode 100644
index dfea564..0000000
--- a/tests/ListGroupItemLink.spec.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/* globals describe, it, assert, expect */
-
-import React from 'react/addons';
-import ListGroupItemLink from '../src/ListGroupItemLink';
-import Router, { Route, RouteHandler } from 'react-router';
-import { Foo, Bar } from './TestHandlers';
-import TestLocation from 'react-router/lib/locations/TestLocation';
-const { click } = React.addons.TestUtils.Simulate;
-
-describe('A ListGroupItemLink', function() {
-  describe('with params and a query', function() {
-    it('knows how to make its href', function() {
-      const ListGroupItemLinkHandler = React.createClass({
-        render() {
-          return <ListGroupItemLink to="foo" params={{bar: 'baz'}} query={{qux: 'quux'}}>ListGroupItemLink</ListGroupItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" path="foo/:bar" handler={Foo} />,
-        <Route name="link" handler={ListGroupItemLinkHandler} />
-      ];
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          const a = div.querySelector('a');
-          expect(a.getAttribute('href')).to.equal('/foo/baz?qux=quux');
-        });
-      });
-    });
-  });
-
-  describe('when its route is active', function() {
-    it('has an active class name', function(done) {
-      const ListGroupItemLinkHandler = React.createClass({
-        render() {
-          return (
-            <div>
-              <ListGroupItemLink
-                to="foo"
-                className="dontKillMe"
-              >ListGroupItemLink</ListGroupItemLink>
-              <RouteHandler/>
-            </div>
-          );
-        }
-      });
-
-      const routes = (
-        <Route path="/" handler={ListGroupItemLinkHandler}>
-          <Route name="foo" handler={Foo} />
-          <Route name="bar" handler={Bar} />
-        </Route>
-      );
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/foo'];
-      const steps = [];
-
-      function assertActive() {
-        const a = div.querySelector('a');
-        expect(a.className.split(' ').sort().join(' ')).to.equal('active dontKillMe list-group-item');
-      }
-
-      function assertInactive() {
-        const a = div.querySelector('a');
-        expect(a.className).to.equal('dontKillMe list-group-item');
-      }
-
-      steps.push(() => {
-        assertActive();
-        testLocation.push('/bar');
-      });
-
-      steps.push(() => {
-        assertInactive();
-        testLocation.push('/foo');
-      });
-
-      steps.push(() => {
-        assertActive();
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, () => {
-          steps.shift()();
-        });
-      });
-    });
-  });
-
-  describe('when clicked', function() {
-    it('calls a user defined click handler', function(done) {
-      const ListGroupItemLinkHandler = React.createClass({
-        handleClick(event) {
-          assert.ok(true);
-          done();
-        },
-
-        render() {
-          return <ListGroupItemLink to="foo" onClick={this.handleClick}>ListGroupItemLink</ListGroupItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={ListGroupItemLinkHandler} />
-      ];
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          click(div.querySelector('a'));
-        });
-      });
-    });
-
-    it('transitions to the correct route', function(done) {
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      const ListGroupItemLinkHandler = React.createClass({
-        handleClick() {
-          // just here to make sure click handlers don't prevent it from happening
-        },
-
-        render() {
-          return <ListGroupItemLink to="foo" onClick={this.handleClick}>ListGroupItemLink</ListGroupItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={ListGroupItemLinkHandler} />
-      ];
-
-      const steps = [];
-
-      steps.push(function() {
-        click(div.querySelector('a'), {button: 0});
-      });
-
-      steps.push(function() {
-        expect(div.innerHTML).to.match(/Foo/);
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          steps.shift()();
-        });
-      });
-    });
-  });
-});
diff --git a/tests/MenuItemLink.spec.js b/tests/MenuItemLink.spec.js
deleted file mode 100644
index ebae3a2..0000000
--- a/tests/MenuItemLink.spec.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/* globals describe, it, assert, expect */
-
-import React from 'react/addons';
-import MenuItemLink from '../src/MenuItemLink';
-import Router, { Route, RouteHandler } from 'react-router';
-import { Foo, Bar } from './TestHandlers';
-import TestLocation from 'react-router/lib/locations/TestLocation';
-const { click } = React.addons.TestUtils.Simulate;
-
-describe('A MenuItemLink', function() {
-  describe('with params and a query', function() {
-    it('knows how to make its href', function() {
-      const MenuItemLinkHandler = React.createClass({
-        render() {
-          return <MenuItemLink to="foo" params={{bar: 'baz'}} query={{qux: 'quux'}}>MenuItemLink</MenuItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" path="foo/:bar" handler={Foo} />,
-        <Route name="link" handler={MenuItemLinkHandler} />
-      ];
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          const a = div.querySelector('a');
-          expect(a.getAttribute('href')).to.equal('/foo/baz?qux=quux');
-        });
-      });
-    });
-  });
-
-  describe('when its route is active', function() {
-    it('has an active class name', function(done) {
-      const MenuItemLinkHandler = React.createClass({
-        render() {
-          return (
-            <div>
-              <MenuItemLink
-                to="foo"
-                className="dontKillMe"
-              >MenuItemLink</MenuItemLink>
-              <RouteHandler/>
-            </div>
-          );
-        }
-      });
-
-      const routes = (
-        <Route path="/" handler={MenuItemLinkHandler}>
-          <Route name="foo" handler={Foo} />
-          <Route name="bar" handler={Bar} />
-        </Route>
-      );
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/foo'];
-      const steps = [];
-
-      function assertActive() {
-        const li = div.querySelector('li');
-        expect(li.className.split(' ').sort().join(' ')).to.equal('active dontKillMe');
-        const a = div.querySelector('a');
-        expect(a.className.split(' ').sort().join(' ')).to.equal('');
-      }
-
-      function assertInactive() {
-        const li = div.querySelector('li');
-        expect(li.className.split(' ').sort().join(' ')).to.equal('dontKillMe');
-        const a = div.querySelector('a');
-        expect(a.className.split(' ').sort().join(' ')).to.equal('');
-      }
-
-      steps.push(() => {
-        assertActive();
-        testLocation.push('/bar');
-      });
-
-      steps.push(() => {
-        assertInactive();
-        testLocation.push('/foo');
-      });
-
-      steps.push(() => {
-        assertActive();
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, () => {
-          steps.shift()();
-        });
-      });
-    });
-  });
-
-  describe('when clicked', function() {
-    it('calls a user defined click handler', function(done) {
-      const MenuItemLinkHandler = React.createClass({
-        handleClick(event) {
-          assert.ok(true);
-          done();
-        },
-
-        render() {
-          return <MenuItemLink to="foo" onClick={this.handleClick}>MenuItemLink</MenuItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={MenuItemLinkHandler} />
-      ];
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          click(div.querySelector('a'));
-        });
-      });
-    });
-
-    it('transitions to the correct route', function(done) {
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      const MenuItemLinkHandler = React.createClass({
-        handleClick() {
-          // just here to make sure click handlers don't prevent it from happening
-        },
-
-        render() {
-          return <MenuItemLink to="foo" onClick={this.handleClick}>MenuItemLink</MenuItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={MenuItemLinkHandler} />
-      ];
-
-      const steps = [];
-
-      steps.push(function() {
-        click(div.querySelector('a'), {button: 0});
-      });
-
-      steps.push(function() {
-        expect(div.innerHTML).to.match(/Foo/);
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          steps.shift()();
-        });
-      });
-    });
-  });
-});
diff --git a/tests/NavItemLink.spec.js b/tests/NavItemLink.spec.js
deleted file mode 100644
index b72f754..0000000
--- a/tests/NavItemLink.spec.js
+++ /dev/null
@@ -1,190 +0,0 @@
-/* globals describe, it, assert, expect */
-
-import React from 'react/addons';
-import NavItemLink from '../src/NavItemLink';
-import Router, { Route, RouteHandler } from 'react-router';
-import { Foo, Bar } from './TestHandlers';
-import TestLocation from 'react-router/lib/locations/TestLocation';
-const { click } = React.addons.TestUtils.Simulate;
-
-describe('A NavItemLink', function() {
-  describe('with params and a query', function() {
-    it('knows how to make its href', function() {
-      const NavItemLinkHandler = React.createClass({
-        render() {
-          return <NavItemLink to="foo" params={{bar: 'baz'}} query={{qux: 'quux'}}>NavItemLink</NavItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" path="foo/:bar" handler={Foo} />,
-        <Route name="link" handler={NavItemLinkHandler} />
-      ];
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          const a = div.querySelector('a');
-          expect(a.getAttribute('href')).to.equal('/foo/baz?qux=quux');
-        });
-      });
-    });
-  });
-
-  describe('when its route is active', function() {
-    it('has an active class name', function(done) {
-      const NavItemLinkHandler = React.createClass({
-        render() {
-          return (
-            <div>
-              <NavItemLink
-                to="foo"
-                className="dontKillMe"
-              >NavItemLink</NavItemLink>
-              <RouteHandler/>
-            </div>
-          );
-        }
-      });
-
-      const routes = (
-        <Route path="/" handler={NavItemLinkHandler}>
-          <Route name="foo" handler={Foo} />
-          <Route name="bar" handler={Bar} />
-        </Route>
-      );
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/foo'];
-      const steps = [];
-
-      function assertActive() {
-        const a = div.querySelector('li');
-        expect(a.className.split(' ').sort().join(' ')).to.equal('active dontKillMe');
-      }
-
-      function assertInactive() {
-        const a = div.querySelector('li');
-        expect(a.className).to.equal('dontKillMe');
-      }
-
-      steps.push(() => {
-        assertActive();
-        testLocation.push('/bar');
-      });
-
-      steps.push(() => {
-        assertInactive();
-        testLocation.push('/foo');
-      });
-
-      steps.push(() => {
-        assertActive();
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, () => {
-          steps.shift()();
-        });
-      });
-    });
-  });
-
-  describe('when clicked', function() {
-    it('calls a user defined click handler', function(done) {
-      const NavItemLinkHandler = React.createClass({
-        handleClick(event) {
-          assert.ok(true);
-          done();
-        },
-
-        render() {
-          return <NavItemLink to="foo" onClick={this.handleClick}>NavItemLink</NavItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={NavItemLinkHandler} />
-      ];
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          click(div.querySelector('a'));
-        });
-      });
-    });
-
-    it('when disabled does not proceed', function() {
-      const NavItemLinkHandler = React.createClass({
-        handleClick(event) {
-          throw new Error('click should not be called');
-        },
-
-        render() {
-          return <NavItemLink to="foo" disabled onClick={this.handleClick}>NavItemLink</NavItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={NavItemLinkHandler} />
-      ];
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          click(div.querySelector('a'));
-        });
-      });
-    });
-
-    it('transitions to the correct route', function(done) {
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      const NavItemLinkHandler = React.createClass({
-        handleClick() {
-          // just here to make sure click handlers don't prevent it from happening
-        },
-
-        render() {
-          return <NavItemLink to="foo" onClick={this.handleClick}>NavItemLink</NavItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={NavItemLinkHandler} />
-      ];
-
-      const steps = [];
-
-      steps.push(function() {
-        click(div.querySelector('a'), {button: 0});
-      });
-
-      steps.push(function() {
-        expect(div.innerHTML).to.match(/Foo/);
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          steps.shift()();
-        });
-      });
-    });
-  });
-});
diff --git a/tests/PageItemLink.spec.js b/tests/PageItemLink.spec.js
deleted file mode 100644
index 534853c..0000000
--- a/tests/PageItemLink.spec.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/* globals describe, it, assert, expect */
-
-import React from 'react/addons';
-import PageItemLink from '../src/PageItemLink';
-import Router, { Route } from 'react-router';
-import { Foo } from './TestHandlers';
-import TestLocation from 'react-router/lib/locations/TestLocation';
-const { click } = React.addons.TestUtils.Simulate;
-
-describe('A PageItemLink', function() {
-  describe('with params and a query', function() {
-    it('knows how to make its href', function() {
-      const PageItemLinkHandler = React.createClass({
-        render() {
-          return <PageItemLink to="foo" params={{bar: 'baz'}} query={{qux: 'quux'}}>PageItemLink</PageItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" path="foo/:bar" handler={Foo} />,
-        <Route name="link" handler={PageItemLinkHandler} />
-      ];
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          const a = div.querySelector('a');
-          expect(a.getAttribute('href')).to.equal('/foo/baz?qux=quux');
-        });
-      });
-    });
-  });
-
-  describe('when clicked', function() {
-    it('calls a user defined click handler', function(done) {
-      const PageItemLinkHandler = React.createClass({
-        handleClick(event) {
-          assert.ok(true);
-          done();
-        },
-
-        render() {
-          return <PageItemLink to="foo" onClick={this.handleClick}>PageItemLink</PageItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={PageItemLinkHandler} />
-      ];
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          click(div.querySelector('a'));
-        });
-      });
-    });
-
-    it('transitions to the correct route', function(done) {
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      const PageItemLinkHandler = React.createClass({
-        handleClick() {
-          // just here to make sure click handlers don't prevent it from happening
-        },
-
-        render() {
-          return <PageItemLink to="foo" onClick={this.handleClick}>PageItemLink</PageItemLink>;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={PageItemLinkHandler} />
-      ];
-
-      const steps = [];
-
-      steps.push(function() {
-        click(div.querySelector('a'), {button: 0});
-      });
-
-      steps.push(function() {
-        expect(div.innerHTML).to.match(/Foo/);
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          steps.shift()();
-        });
-      });
-    });
-  });
-});
diff --git a/tests/RouterOverlayTrigger.spec.js b/tests/RouterOverlayTrigger.spec.js
deleted file mode 100644
index 8cbf51b..0000000
--- a/tests/RouterOverlayTrigger.spec.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/* globals describe, it, expect */
-
-import React from 'react';
-
-import RouterOverlayTrigger from '../src/RouterOverlayTrigger';
-
-describe('A RouterOverlayTrigger', function() {
-  it('has the right contextTypes', function() {
-    expect(RouterOverlayTrigger.contextTypes).to.eql({
-      router: React.PropTypes.func
-    });
-  });
-});
diff --git a/tests/TestHandlers.js b/tests/TestHandlers.js
deleted file mode 100644
index f01e033..0000000
--- a/tests/TestHandlers.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { RouteHandler } from 'react-router';
-
-exports.Nested = React.createClass({
-  render() {
-    return (
-      <div>
-        <h1 className="Nested">Nested</h1>
-        <RouteHandler />
-      </div>
-    );
-  }
-});
-
-exports.Foo = React.createClass({
-  render() {
-    return <div className="Foo">Foo</div>;
-  }
-});
-
-exports.Bar = React.createClass({
-  render() {
-    return <div className="Bar">Bar</div>;
-  }
-});
-
-exports.Baz = React.createClass({
-  render() {
-    return <div className="Baz">Baz</div>;
-  }
-});
diff --git a/tests/ThumbnailLink.spec.js b/tests/ThumbnailLink.spec.js
deleted file mode 100644
index a66a5d6..0000000
--- a/tests/ThumbnailLink.spec.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/* globals describe, it, assert, expect */
-
-import React from 'react/addons';
-import ThumbnailLink from '../src/ThumbnailLink';
-import Router, { Route } from 'react-router';
-import { Foo } from './TestHandlers';
-import TestLocation from 'react-router/lib/locations/TestLocation';
-const { click } = React.addons.TestUtils.Simulate;
-
-describe('A ThumbnailLink', function() {
-  describe('with params and a query', function() {
-    it('knows how to make its href', function() {
-      const ThumbnailLinkHandler = React.createClass({
-        render() {
-          return <ThumbnailLink to="foo" params={{bar: 'baz'}} query={{qux: 'quux'}} />;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" path="foo/:bar" handler={Foo} />,
-        <Route name="link" handler={ThumbnailLinkHandler} />
-      ];
-
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          const a = div.querySelector('a');
-          expect(a.getAttribute('href')).to.equal('/foo/baz?qux=quux');
-        });
-      });
-    });
-  });
-
-  describe('when clicked', function() {
-    it('calls a user defined click handler', function(done) {
-      const ThumbnailLinkHandler = React.createClass({
-        handleClick(event) {
-          assert.ok(true);
-          done();
-        },
-
-        render() {
-          return <ThumbnailLink to="foo" onClick={this.handleClick} />;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={ThumbnailLinkHandler} />
-      ];
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          click(div.querySelector('a'));
-        });
-      });
-    });
-
-    it('transitions to the correct route', function(done) {
-      const div = document.createElement('div');
-      const testLocation = new TestLocation();
-      testLocation.history = ['/link'];
-
-      const ThumbnailLinkHandler = React.createClass({
-        handleClick() {
-          // just here to make sure click handlers don't prevent it from happening
-        },
-
-        render() {
-          return <ThumbnailLink to="foo" onClick={this.handleClick} />;
-        }
-      });
-
-      const routes = [
-        <Route name="foo" handler={Foo} />,
-        <Route name="link" handler={ThumbnailLinkHandler} />
-      ];
-
-      const steps = [];
-
-      steps.push(function() {
-        click(div.querySelector('a'), {button: 0});
-      });
-
-      steps.push(function() {
-        expect(div.innerHTML).to.match(/Foo/);
-        done();
-      });
-
-      Router.run(routes, testLocation, function(Handler) {
-        React.render(<Handler/>, div, function() {
-          steps.shift()();
-        });
-      });
-    });
-  });
-});
diff --git a/tests/bower-imports-module.spec.js b/tests/bower-imports-module.spec.js
deleted file mode 100644
index d61a9bd..0000000
--- a/tests/bower-imports-module.spec.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import loader from '../webpack/bower-imports-loader';
-
-describe('bower-imports-loader', function() {
-  it('replaces react-bootstrap/lib/* imports to work with bower dependency', function() {
-    const input = `import React from 'react';
-
-import Button from 'react-bootstrap/lib/Button';
-import LinkMixin from './LinkMixin';
-
-const ButtonLink = React.createClass({`;
-
-    const expected = `import React from 'react';
-
-import {Button} from 'react-bootstrap';
-import LinkMixin from './LinkMixin';
-
-const ButtonLink = React.createClass({`;
-
-    loader(input).should.equal(expected);
-  });
-});
diff --git a/tests/index.js b/tests/index.js
index 1c5bc19..3e704d9 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -1,19 +1,4 @@
-import './phantom-shims';
-import 'mocha';
+import 'es5-shim';
 
-const chai = require('chai');
-chai.should();
-
-global.expect = chai.expect;
-global.assert = chai.assert;
-
-global.TestUtils = require('react/addons').addons.TestUtils;
-
-import './ButtonLink.spec.js';
-import './ListGroupItemLink.spec.js';
-import './MenuItemLink.spec.js';
-import './NavItemLink.spec.js';
-import './PageItemLink.spec.js';
-import './RouterOverlayTrigger.spec.js';
-import './ThumbnailLink.spec.js';
-import './bower-imports-module.spec.js';
+const testsContext = require.context('.', true, /\.spec\.js$/);
+testsContext.keys().forEach(testsContext);
diff --git a/tests/phantom-shims.js b/tests/phantom-shims.js
deleted file mode 100644
index 3d0e2aa..0000000
--- a/tests/phantom-shims.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const Ap = Array.prototype;
-const slice = Ap.slice;
-const Fp = Function.prototype;
-
-if (!Fp.bind) {
-  // PhantomJS doesn't support Function.prototype.bind natively, so
-  // polyfill it whenever this module is required.
-  Fp.bind = function(context) {
-    const func = this;
-    const args = slice.call(arguments, 1);
-
-    function bound() {
-      const invokedAsConstructor = func.prototype && (this instanceof func);
-      return func.apply(
-        // Ignore the context parameter when invoking the bound function
-        // as a constructor. Note that this includes not only constructor
-        // invocations using the new keyword but also calls to base class
-        // constructors such as BaseClass.call(this, ...) or super(...).
-        !invokedAsConstructor && context || this,
-        args.concat(slice.call(arguments))
-      );
-    }
-
-    // The bound function must share the .prototype of the unbound
-    // function so that any object created by one constructor will count
-    // as an instance of both constructors.
-    bound.prototype = func.prototype;
-
-    return bound;
-  };
-}
diff --git a/tests/visual.js b/tests/visual.js
deleted file mode 100644
index 12c0354..0000000
--- a/tests/visual.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import 'bootstrap/less/bootstrap.less';
-import './visual/app';
diff --git a/tests/visual/ButtonVisual.js b/tests/visual/ButtonVisual.js
index a67dde6..715ed92 100644
--- a/tests/visual/ButtonVisual.js
+++ b/tests/visual/ButtonVisual.js
@@ -1,36 +1,45 @@
 import React from 'react';
-import {Link} from 'react-router';
 import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar';
 import Button from 'react-bootstrap/lib/Button';
-import ButtonLink from '../../src/ButtonLink';
+import {Link} from 'react-router';
+
+import LinkContainer from '../../src/LinkContainer';
+
+export default () => (
+  <div>
+    <Link to="home">Back to Index</Link>
+    <h2>Button</h2>
 
-const ButtonVisual = React.createClass({
-  render() {
-    return (
-      <div>
-        <Link to='home'>&lt;-- Back to Index</Link>
-        <h2>ButtonLink</h2>
-        <h3>Baseline (Raw React-Bootstrap)</h3>
-        <ButtonToolbar>
-          <Button>Default</Button>
-          <Button bsStyle="success">Success</Button>
-          <Button bsStyle="info">Info</Button>
-          <Button bsStyle="warning">Warning</Button>
-          <Button bsStyle="danger">Danger</Button>
-          <Button bsStyle="link">Link</Button>
-        </ButtonToolbar>
-        <h3>ButtonLink</h3>
-        <ButtonToolbar>
-          <ButtonLink to='home'>Default</ButtonLink>
-          <ButtonLink to='home' bsStyle="success">Success</ButtonLink>
-          <ButtonLink to='home' bsStyle="info">Info</ButtonLink>
-          <ButtonLink to='home' bsStyle="warning">Warning</ButtonLink>
-          <ButtonLink to='home' bsStyle="danger">Danger</ButtonLink>
-          <ButtonLink to='home' bsStyle="link">Link</ButtonLink>
-        </ButtonToolbar>
-      </div>
-    );
-  }
-});
+    <h3>Baseline</h3>
+    <ButtonToolbar>
+      <Button>Default</Button>
+      <Button bsStyle="success">Success</Button>
+      <Button bsStyle="info">Info</Button>
+      <Button bsStyle="warning">Warning</Button>
+      <Button bsStyle="danger">Danger</Button>
+      <Button bsStyle="link">Link</Button>
+    </ButtonToolbar>
 
-export default ButtonVisual;
+    <h3>LinkContainer</h3>
+    <ButtonToolbar>
+      <LinkContainer to="/home">
+        <Button>Default</Button>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <Button bsStyle="success">Success</Button>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <Button bsStyle="info">Info</Button>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <Button bsStyle="warning">Warning</Button>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <Button bsStyle="danger">Danger</Button>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <Button bsStyle="link">Link</Button>
+      </LinkContainer>
+    </ButtonToolbar>
+  </div>
+);
diff --git a/tests/visual/ListGroupItemVisual.js b/tests/visual/ListGroupItemVisual.js
index 9701304..8ebc95c 100644
--- a/tests/visual/ListGroupItemVisual.js
+++ b/tests/visual/ListGroupItemVisual.js
@@ -1,41 +1,77 @@
 import React from 'react';
-import {Link} from 'react-router';
 import ListGroup from 'react-bootstrap/lib/ListGroup';
 import ListGroupItem from 'react-bootstrap/lib/ListGroupItem';
-import ListGroupItemLink from '../../src/ListGroupItemLink';
+import {Link} from 'react-router';
+
+import LinkContainer from '../../src/LinkContainer';
+
+export default () => (
+  <div>
+    <Link to="/home">Back to Index</Link>
+    <h2>ListGroupItem</h2>
 
-const NavItemVisual = React.createClass({
-  handleSelect(selectedKey) {
-    window.alert('selected ' + selectedKey);
-  },
-  render() {
-    return (
-      <div>
-        <Link to='home'>&lt;-- Back to Index</Link>
-        <h2>ListGroupItemLink</h2>
-        <h3>Baseline (Raw React-Bootstrap)</h3>
-        <ListGroup>
-          <ListGroupItem eventKey={1} href="/home" header="ListGroupItem 1 Heading">ListGroupItem 1 content</ListGroupItem>
-          <ListGroupItem eventKey={2} header="ListGroupItem 2 Heading">ListGroupItem 2 content</ListGroupItem>
-          <ListGroupItem eventKey={3} disabled={true}>ListGroupItem 3 content disabled</ListGroupItem>
-          <ListGroupItem eventKey={4} bsStyle="success">ListGroupItem 4 content success</ListGroupItem>
-          <ListGroupItem eventKey={5} bsStyle="info">ListGroupItem 5 content info</ListGroupItem>
-          <ListGroupItem eventKey={6} bsStyle="warning">ListGroupItem 6 content warning</ListGroupItem>
-          <ListGroupItem eventKey={7} bsStyle="danger">ListGroupItem 7 content danger</ListGroupItem>
-        </ListGroup>
-        <h3>ListGroupItemLink</h3>
-        <ListGroup>
-          <ListGroupItemLink to="list-group-item" header="ListGroupItemLink 1 Heading">ListGroupItemLink 1 content</ListGroupItemLink>
-          <ListGroupItemLink to="home" header="ListGroupItemLink 2 Heading">ListGroupItemLink 2 content</ListGroupItemLink>
-          <ListGroupItemLink to="home" disabled={true}>ListGroupItemLink 3 content disabled</ListGroupItemLink>
-          <ListGroupItemLink to="home" bsStyle="success">ListGroupItemLink 4 content success</ListGroupItemLink>
-          <ListGroupItemLink to="home" bsStyle="info">ListGroupItemLink 5 content info</ListGroupItemLink>
-          <ListGroupItemLink to="home" bsStyle="warning">ListGroupItemLink 6 content warning</ListGroupItemLink>
-          <ListGroupItemLink to="home" bsStyle="danger">ListGroupItemLink 7 content danger</ListGroupItemLink>
-        </ListGroup>
-      </div>
-    );
-  }
-});
+    <h3>Baseline</h3>
+    <ListGroup>
+      <ListGroupItem href="#/home" active header="ListGroupItem 1 Heading">
+        ListGroupItem 1 content
+      </ListGroupItem>
+      <ListGroupItem header="ListGroupItem 2 Heading">
+        ListGroupItem 2 content
+      </ListGroupItem>
+      <ListGroupItem disabled>
+        ListGroupItem 3 content disabled
+      </ListGroupItem>
+      <ListGroupItem bsStyle="success">
+        ListGroupItem 4 content success
+      </ListGroupItem>
+      <ListGroupItem bsStyle="info">
+        ListGroupItem 5 content info
+      </ListGroupItem>
+      <ListGroupItem bsStyle="warning">
+        ListGroupItem 6 content warning
+      </ListGroupItem>
+      <ListGroupItem bsStyle="danger">
+        ListGroupItem 7 content danger
+      </ListGroupItem>
+    </ListGroup>
 
-export default NavItemVisual;
+    <h3>LinkContainer</h3>
+    <ListGroup>
+      <LinkContainer to="/list-group-item">
+        <ListGroupItem header="ListGroupItem 1 Heading">
+          ListGroupItem 1 content
+        </ListGroupItem>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <ListGroupItem header="ListGroupItem 2 Heading">
+          ListGroupItem 2 content
+        </ListGroupItem>
+      </LinkContainer>
+      <LinkContainer to="/home" disabled>
+        <ListGroupItem>
+          ListGroupItem 3 content disabled
+        </ListGroupItem>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <ListGroupItem bsStyle="success">
+          ListGroupItem 4 content success
+        </ListGroupItem>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <ListGroupItem bsStyle="info">
+          ListGroupItem 5 content info
+        </ListGroupItem>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <ListGroupItem bsStyle="warning">
+          ListGroupItem 6 content warning
+        </ListGroupItem>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <ListGroupItem bsStyle="danger">
+          ListGroupItem 7 content danger
+        </ListGroupItem>
+      </LinkContainer>
+    </ListGroup>
+  </div>
+);
diff --git a/tests/visual/MenuItemVisual.js b/tests/visual/MenuItemVisual.js
deleted file mode 100644
index 517d460..0000000
--- a/tests/visual/MenuItemVisual.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import {Link} from 'react-router';
-import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar';
-import SplitButton from 'react-bootstrap/lib/SplitButton';
-import MenuItem from 'react-bootstrap/lib/MenuItem';
-import MenuItemLink from '../../src/MenuItemLink';
-
-const MenuItemVisual = React.createClass({
-  handleSelect(selectedKey) {
-    window.alert('selected ' + selectedKey);
-  },
-  render() {
-    return (
-      <div>
-        <Link to='home'>&lt;-- Back to Index</Link>
-        <h2>MenuItemLink</h2>
-        <h3>Baseline (Raw React-Bootstrap)</h3>
-        <ButtonToolbar>
-          <SplitButton title="Dropdown">
-            <MenuItem eventKey="1">Action</MenuItem>
-            <MenuItem eventKey="2">Another action</MenuItem>
-            <MenuItem eventKey="3">Something else here</MenuItem>
-            <MenuItem divider />
-            <MenuItem eventKey="4">Separated link</MenuItem>
-          </SplitButton>
-        </ButtonToolbar>
-        <h3>MenuItemLink</h3>
-        <ButtonToolbar>
-          <SplitButton title="Dropdown">
-            <MenuItemLink to='menu-item'>Action</MenuItemLink>
-            <MenuItemLink to='home'>Another action</MenuItemLink>
-            <MenuItemLink to='home'>Something else here</MenuItemLink>
-            <MenuItem divider />
-            <MenuItemLink to='home'>Separated link</MenuItemLink>
-          </SplitButton>
-        </ButtonToolbar>
-      </div>
-    );
-  }
-});
-
-export default MenuItemVisual;
diff --git a/tests/visual/NavItemVisual.js b/tests/visual/NavItemVisual.js
index 01c7ce8..aabf278 100644
--- a/tests/visual/NavItemVisual.js
+++ b/tests/visual/NavItemVisual.js
@@ -1,33 +1,33 @@
 import React from 'react';
-import {Link} from 'react-router';
 import Nav from 'react-bootstrap/lib/Nav';
 import NavItem from 'react-bootstrap/lib/NavItem';
-import NavItemLink from '../../src/NavItemLink';
+import {Link} from 'react-router';
+
+import LinkContainer from '../../src/LinkContainer';
+
+export default () => (
+  <div>
+    <Link to="/home">Back to Index</Link>
+    <h2>NavItem</h2>
 
-const NavItemVisual = React.createClass({
-  handleSelect(selectedKey) {
-    window.alert('selected ' + selectedKey);
-  },
-  render() {
-    return (
-      <div>
-        <Link to='home'>&lt;-- Back to Index</Link>
-        <h2>NavItemLink</h2>
-        <h3>Baseline (Raw React-Bootstrap)</h3>
-        <Nav bsStyle="pills" activeKey={1} onSelect={this.handleSelect}>
-          <NavItem eventKey={1} href="/home">NavItem 1 content</NavItem>
-          <NavItem eventKey={2} title="Item">NavItem 2 content</NavItem>
-          <NavItem eventKey={3} disabled={true}>NavItem 3 content</NavItem>
-        </Nav>
-        <h3>NavItemLink</h3>
-        <Nav bsStyle="pills">
-          <NavItemLink to='nav-item'>NavItemLink 1 content</NavItemLink>
-          <NavItemLink to='home'>NavItemLink 2 content</NavItemLink>
-          <NavItemLink to='home' disabled={true}>NavItemLink 3 content</NavItemLink>
-        </Nav>
-      </div>
-    );
-  }
-});
+    <h3>Baseline</h3>
+    <Nav bsStyle="pills" activeKey={1}>
+      <NavItem eventKey={1}>NavItem 1 content</NavItem>
+      <NavItem eventKey={2}>NavItem 2 content</NavItem>
+      <NavItem eventKey={3} disabled>NavItem 3 content</NavItem>
+    </Nav>
 
-export default NavItemVisual;
+    <h3>LinkContainer</h3>
+    <Nav bsStyle="pills">
+      <LinkContainer to="/nav-item">
+        <NavItem>NavItem 1 content</NavItem>
+      </LinkContainer>
+      <LinkContainer to="/home">
+        <NavItem>NavItem 2 content</NavItem>
+      </LinkContainer>
+      <LinkContainer to="/home" disabled>
+        <NavItem>NavItem 3 content</NavItem>
+      </LinkContainer>
+    </Nav>
+  </div>
+);
diff --git a/tests/visual/PageItemVisual.js b/tests/visual/PageItemVisual.js
deleted file mode 100644
index 5fa116f..0000000
--- a/tests/visual/PageItemVisual.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react';
-import {Link} from 'react-router';
-import Pager from 'react-bootstrap/lib/Pager';
-import PageItem from 'react-bootstrap/lib/PageItem';
-import PageItemLink from '../../src/PageItemLink';
-
-const PageItemVisual = React.createClass({
-  render() {
-    return (
-      <div>
-        <Link to='home'>&lt;-- Back to Index</Link>
-        <h2>PageItemLink</h2>
-        <h3>Baseline (Raw React-Bootstrap)</h3>
-        <Pager>
-          <PageItem previous>&larr; Previous Page</PageItem>
-          <PageItem next>Next Page &rarr;</PageItem>
-        </Pager>
-        <h3>PageItemLink</h3>
-        <Pager>
-          <PageItemLink previous to='home'>&larr; Previous Page</PageItemLink>
-          <PageItemLink next to='home'>Next Page &rarr;</PageItemLink>
-        </Pager>
-      </div>
-    );
-  }
-});
-
-export default PageItemVisual;
diff --git a/tests/visual/ThumbnailVisual.js b/tests/visual/ThumbnailVisual.js
deleted file mode 100644
index 16c75d6..0000000
--- a/tests/visual/ThumbnailVisual.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import {Link} from 'react-router';
-import Thumbnail from 'react-bootstrap/lib/Thumbnail';
-import ThumbnailLink from '../../src/ThumbnailLink';
-
-const ThumbnailVisual = React.createClass({
-  render() {
-    return (
-      <div>
-        <Link to='home'>&lt;-- Back to Index</Link>
-        <h2>Thumbnailink</h2>
-        <h3>Baseline (Raw React-Bootstrap)</h3>
-        <Thumbnail alt='171x180' src='/assets/thumbnail.png' />
-        <h3>ThumbnailLink</h3>
-        <ThumbnailLink to='home' alt='171x180' src='/assets/thumbnail.png' />
-      </div>
-    );
-  }
-});
-
-export default ThumbnailVisual;
diff --git a/tests/visual/app.js b/tests/visual/app.js
deleted file mode 100644
index f3035a2..0000000
--- a/tests/visual/app.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import Router, { Route, RouteHandler } from 'react-router';
-
-const App = React.createClass({
-  render() {
-    return (
-      <div>
-        <h1>React-Router-Bootstrap Module Visual Test</h1>
-        <RouteHandler />
-      </div>
-    );
-  }
-});
-
-const routes = (
-  <Route handler={App} path="/">
-    <Route name='home' path='/' handler={require('./home')} />
-    <Route name='button' handler={require('./ButtonVisual')} />
-    <Route name='nav-item' handler={require('./NavItemVisual')} />
-    <Route name='menu-item' handler={require('./MenuItemVisual')} />
-    <Route name='list-group-item' handler={require('./ListGroupItemVisual')} />
-    <Route name='page-item' handler={require('./PageItemVisual')} />
-    <Route name='thumbnail' handler={require('./ThumbnailVisual')} />
-  </Route>
-);
-
-Router.run(routes, function(Handler) {
-  React.render(<Handler/>, document.body);
-});
diff --git a/tests/visual/home.js b/tests/visual/home.js
index a83c80b..834776e 100644
--- a/tests/visual/home.js
+++ b/tests/visual/home.js
@@ -1,22 +1,13 @@
 import React from 'react';
 import {Link} from 'react-router';
 
-const Home = React.createClass({
-  render() {
-    return (
-      <div>
-        <h2>Index</h2>
-        <ul>
-          <li><Link to='button'>ButtonLink</Link></li>
-          <li><Link to='nav-item'>NavItemLink</Link></li>
-          <li><Link to='menu-item'>MenuItemLink</Link></li>
-          <li><Link to='list-group-item'>ListGroupItemLink</Link></li>
-          <li><Link to='page-item'>PageItemLink</Link></li>
-          <li><Link to='thumbnail'>ThumbnailLink</Link></li>
-        </ul>
-      </div>
-    );
-  }
-});
-
-export default Home;
+export default () => (
+  <div>
+    <h2>Index</h2>
+    <ul>
+      <li><Link to="/button">Button</Link></li>
+      <li><Link to="/nav-item">NavItem</Link></li>
+      <li><Link to="/list-group-item">ListGroupItem</Link></li>
+    </ul>
+  </div>
+);
diff --git a/tests/visual/index.js b/tests/visual/index.js
new file mode 100644
index 0000000..72a67a1
--- /dev/null
+++ b/tests/visual/index.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import Grid from 'react-bootstrap/lib/Grid';
+import ReactDOM from 'react-dom';
+import {IndexRoute, Route, Router} from 'react-router';
+
+import ButtonVisual from './ButtonVisual';
+import Home from './Home';
+import ListGroupItemVisual from './ListGroupItemVisual';
+import NavItemVisual from './NavItemVisual';
+
+import 'bootstrap/less/bootstrap.less';
+
+const App = ({children}) => (
+  <Grid>
+    <h1>React-Router-Bootstrap Module Visual Test</h1>
+    {children}
+  </Grid>
+);
+
+const mountNode = document.createElement('div');
+document.body.appendChild(mountNode);
+
+ReactDOM.render(
+  <Router>
+    <Route path="/" component={App}>
+      <IndexRoute onEnter={(_, replaceWith) => replaceWith(null, '/home')} />
+      <Route path="home" component={Home} />
+
+      <Route path="button" component={ButtonVisual} />
+      <Route path="nav-item" component={NavItemVisual} />
+      <Route path="list-group-item" component={ListGroupItemVisual} />
+    </Route>
+  </Router>,
+  mountNode
+);
diff --git a/webpack.config.babel.js b/webpack.config.babel.js
index 22cc107..a1fd0d6 100644
--- a/webpack.config.babel.js
+++ b/webpack.config.babel.js
@@ -1,44 +1,24 @@
-import path from 'path';
 import webpack from 'webpack';
+import yargs from 'yargs';
 
-const plugins = [];
-
-if (process.env.COMPRESS) {
-  plugins.push(
-    new webpack.optimize.UglifyJsPlugin({
-      compressor: {
-        warnings: false
-      }
-    })
-  );
-}
+const {optimizeMinimize} = yargs.alias('p', 'optimize-minimize').argv;
+const nodeEnv = optimizeMinimize ? 'production' : 'development';
 
 export default {
   entry: {
     'ReactRouterBootstrap': './src/index.js'
   },
-
   output: {
     path: './lib',
-    filename: process.env.COMPRESS ? '[name].min.js' : '[name].js',
+    filename: optimizeMinimize ? '[name].min.js' : '[name].js',
     library: 'ReactRouterBootstrap',
     libraryTarget: 'umd'
   },
-
   module: {
     loaders: [
-      {
-        test: /\.js/,
-        loaders: [
-          'babel',
-          path.join(__dirname, 'webpack/bower-imports-loader.js')
-        ],
-        exclude: /node_modules/ }
+      {test: /\.js$/, loader: 'babel', exclude: /node_modules/}
     ]
   },
-
-  devtool: process.env.COMPRESS && 'source-map',
-
   externals: [
     {
       'react': {
@@ -55,20 +35,12 @@ export default {
         commonjs: 'react-router',
         amd: 'react-router'
       }
-    },
-    {
-      'react-bootstrap': {
-        root: 'ReactBootstrap',
-        commonjs2: 'react-bootstrap',
-        commonjs: 'react-bootstrap',
-        amd: 'react-bootstrap'
-      }
     }
   ],
-
-  node: {
-    buffer: false
-  },
-
-  plugins
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': {'NODE_ENV': JSON.stringify(nodeEnv)}
+    })
+  ],
+  devtool: optimizeMinimize ? 'source-map' : null
 };
diff --git a/webpack.test.config.babel.js b/webpack.test.config.babel.js
index 3c1a7a8..2e18c18 100644
--- a/webpack.test.config.babel.js
+++ b/webpack.test.config.babel.js
@@ -1,24 +1,11 @@
-import path from 'path';
-
-module.exports = {
-  entry: {
-    visual: ['./tests/visual.js']
-  },
-
-  devtool: 'inline-source-map',
-
+export default {
   output: {
-    path: path.join(__dirname, 'tests-served/'),
-    publicPath: '/public/',
-    filename: '[name].js'
+    pathinfo: true
   },
-
   module: {
     loaders: [
-      {test: /\.js/, loader: 'babel', exclude: /node_modules/},
-      {test: /\.less$/, loader: 'style!css!less'},
-      {test:/\.woff|\.woff2$/, loader: 'url?prefix=font/&limit=5000'},
-      {test:/\.eot$|\.ttf$|\.svg$/, loader: 'file?prefix=font/'}
+      {test: /\.js/, loader: 'babel', exclude: /node_modules/}
     ]
-  }
+  },
+  devtool: 'inline-source-map'
 };
diff --git a/webpack.visual.config.babel.js b/webpack.visual.config.babel.js
new file mode 100644
index 0000000..4da1a57
--- /dev/null
+++ b/webpack.visual.config.babel.js
@@ -0,0 +1,17 @@
+import HtmlWebpackPlugin from 'html-webpack-plugin';
+
+export default {
+  entry: './tests/visual',
+  module: {
+    loaders: [
+      {test: /\.js/, loader: 'babel', exclude: /node_modules/},
+      {test: /\.less$/, loader: 'style!css!less'},
+      {test: /\.woff|\.woff2$/, loader: 'url?prefix=font/&limit=5000'},
+      {test: /\.eot$|\.ttf$|\.svg$/, loader: 'file?prefix=font/'}
+    ]
+  },
+  plugins: [
+    new HtmlWebpackPlugin()
+  ],
+  devtool: 'eval-source-map'
+};
diff --git a/webpack/bower-imports-loader.js b/webpack/bower-imports-loader.js
deleted file mode 100644
index 32e4d90..0000000
--- a/webpack/bower-imports-loader.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function bowerImportsLoader(source) {
-  const rgx = /^import (.*) from 'react-bootstrap\/lib\/.*';/;
-
-  return source.split('\n')
-    .map(line => line.replace(rgx, "import {$1} from 'react-bootstrap';"))
-    .join('\n');
-}
