Commit 68375a1c by Wee

refactor: migrate to TypeScript

parent 9161afbf
File added
modules/react-router
tools
es
node_modules
/*.js
\ No newline at end of file
node_modules
\ No newline at end of file
MIT License
Copyright (c) 2018 Wee
Copyright (c) 2018 fi3ework
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import warning from 'warning';
import invariant from 'invariant';
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import { matchPath } from 'react-router';
import { isValidElementType } from 'react-is';
var isEmptyChildren = function (children) { return React.Children.count(children) === 0; };
var NORMAL_RENDER_MATCHED = 'normal matched render';
var NORMAL_RENDER_UNMATCHED = 'normal unmatched render (unmount)';
var NORMAL_RENDER_ON_INIT = 'normal render (matched or unmatched)';
var HIDE_RENDER = 'hide route when livePath matched';
/**
* The public API for matching a single path and rendering.
*/
var LiveRoute = /** @class */ (function (_super) {
__extends(LiveRoute, _super);
function LiveRoute() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.routeDom = null;
_this.state = {
match: _this.computeMatch(_this.props, _this.context.router)
};
_this.liveState = NORMAL_RENDER_ON_INIT;
_this.scrollPosBackup = null;
_this.previousDisplayStyle = null;
return _this;
}
LiveRoute.prototype.getChildContext = function () {
return {
router: __assign({}, this.context.router, { route: {
location: this.props.location || this.context.router.route.location,
match: this.state.match
} })
};
};
LiveRoute.prototype.componentWillMount = function () {
warning(!(this.props.component && this.props.render), 'You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored');
warning(!(this.props.component && this.props.children && !isEmptyChildren(this.props.children)), 'You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored');
warning(!(this.props.render && this.props.children && !isEmptyChildren(this.props.children)), 'You should not use <Route render> and <Route children> in the same route; <Route children> will be ignored');
};
LiveRoute.prototype.componentDidMount = function () {
// backup router and get DOM when mounting
if (this.doesRouteEnableLive() && this.state.match) {
this._latestMatchedRouter = this.context.router;
this.getRouteDom();
}
};
LiveRoute.prototype.componentWillReceiveProps = function (nextProps, nextContext) {
warning(!(nextProps.location && !this.props.location), '<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.');
warning(!(!nextProps.location && this.props.location), '<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.');
var match = this.computeMatch(nextProps, nextContext.router);
var computedMatch = match;
// recompute match if enable live
if (this.doesRouteEnableLive()) {
computedMatch = this.computeMatchWithLive(this.props, nextProps, nextContext, match);
}
this.setState({
match: computedMatch
});
};
// 获取 Route 对应的 DOM
LiveRoute.prototype.componentDidUpdate = function (prevProps, prevState) {
if (!this.doesRouteEnableLive()) {
return;
}
// restore display when matched normally
console.log(this.liveState);
if (this.liveState === NORMAL_RENDER_MATCHED) {
this.showRoute();
this.restoreScrollPosition();
this.clearScroll();
}
// get DOM if match and render
if (this.state.match) {
this.getRouteDom();
}
};
// clear on unmounting
LiveRoute.prototype.componentWillUnmount = function () {
this.clearScroll();
};
LiveRoute.prototype.doesRouteEnableLive = function () {
return this.props.livePath || this.props.alwaysLive;
};
/**
* @param {*} props: this.props
* @param {*} nextProps: nextProps
* @param {*} nextContext: nextContext
* @param {*} match: computed `match` of current path
* @returns
* the returned object will be computed by following orders:
* If path matched (no matter livePath matched or not), return normal computed `match`
* If livePath matched, return latest normal render `match`
* If livePath unmatched, return normal computed `match`
* @memberof Route
* Back up current router every time it is rendered normally, backing up to the next livePath rendering
*/
LiveRoute.prototype.computeMatchWithLive = function (props, nextProps, nextContext, match) {
// console.log(`>>> ` + this.props.name + ` <<<`)
// compute if livePath match
var livePath = nextProps.livePath;
var nextPropsWithLivePath = __assign({}, nextProps, { paths: livePath });
var prevMatch = this.computeMatch(props, this.context.router);
var livePathMatch = this.computePathsMatch(nextPropsWithLivePath, nextContext.router);
if (match) {
// normal matched render
console.log('--- NORMAL MATCH FLAG ---');
this.liveState = NORMAL_RENDER_MATCHED;
return match;
}
else if ((livePathMatch || props.alwaysLive) && this.routeDom) {
// backup router when from normal match render to hide render
if (prevMatch) {
this._latestMatchedRouter = this.context.router;
}
// hide render
console.log('--- HIDE FLAG ---');
this.liveState = HIDE_RENDER;
this.saveScrollPosition();
this.hideRoute();
return prevMatch;
}
else {
// normal unmatched unmount
console.log('--- NORMAL UNMATCH FLAG ---');
this.liveState = NORMAL_RENDER_UNMATCHED;
this.clearScroll();
this.clearDomData();
}
};
LiveRoute.prototype.computePathsMatch = function (_a, router) {
var computedMatch = _a.computedMatch, location = _a.location, paths = _a.paths, strict = _a.strict, exact = _a.exact, sensitive = _a.sensitive;
invariant(router, 'You should not use <Route> or withRouter() outside a <Router>');
var route = router.route;
var pathname = (location || route.location).pathname;
// livePath could accept a string or an array of string
if (Array.isArray(paths)) {
for (var _i = 0, paths_1 = paths; _i < paths_1.length; _i++) {
var path = paths_1[_i];
if (typeof path !== 'string') {
continue;
}
var currPath = matchPath(pathname, { path: path, strict: strict, exact: exact, sensitive: sensitive }, router.match);
// return if one of the livePaths is matched
if (currPath) {
return currPath;
}
}
return null;
}
else {
return matchPath(pathname, { path: paths, strict: strict, exact: exact, sensitive: sensitive }, router.match);
}
};
LiveRoute.prototype.computeMatch = function (_a, router) {
var computedMatch = _a.computedMatch, location = _a.location, path = _a.path, strict = _a.strict, exact = _a.exact, sensitive = _a.sensitive;
// DO NOT use the computedMatch from Switch!
// react-live-route: ignore match from <Switch>, actually LiveRoute should not be wrapped by <Switch>.
// if (computedMatch) return computedMatch // <Switch> already computed the match for us
invariant(router, 'You should not use <Route> or withRouter() outside a <Router>');
var route = router.route;
var pathname = (location || route.location).pathname;
return matchPath(pathname, { path: path, strict: strict, exact: exact, sensitive: sensitive }, route.match);
};
// get DOM of Route
LiveRoute.prototype.getRouteDom = function () {
var routeDom = ReactDOM.findDOMNode(this);
this.routeDom = routeDom;
};
// backup scroll and hide DOM
LiveRoute.prototype.hideRoute = function () {
if (this.routeDom && this.routeDom.style.display !== 'none') {
console.log('--- hide route ---');
this.previousDisplayStyle = this.routeDom.style.display;
this.routeDom.style.display = 'none';
}
};
// reveal DOM display
LiveRoute.prototype.showRoute = function () {
if (this.routeDom && this.previousDisplayStyle !== null) {
this.routeDom.style.display = this.previousDisplayStyle;
}
};
// save scroll position before hide DOM
LiveRoute.prototype.saveScrollPosition = function () {
if (this.routeDom && this.scrollPosBackup === null) {
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
console.log("saved top = " + scrollTop + ", left = " + scrollLeft);
this.scrollPosBackup = { top: scrollTop, left: scrollLeft };
}
};
// restore the scroll position before hide
LiveRoute.prototype.restoreScrollPosition = function () {
var scroll = this.scrollPosBackup;
console.log(scroll);
if (scroll && this.routeDom) {
window.scrollTo(scroll.left, scroll.top);
}
};
// clear scroll position
LiveRoute.prototype.clearDomData = function () {
if (this.doesRouteEnableLive()) {
this.routeDom = null;
this.previousDisplayStyle = null;
}
};
// clear scroll position
LiveRoute.prototype.clearScroll = function () {
if (this.doesRouteEnableLive()) {
this.scrollPosBackup = null;
}
};
// normally render or unmount Route
LiveRoute.prototype.renderRoute = function (component, render, props, match) {
console.log(match);
if (component)
return match ? React.createElement(component, props) : null;
if (render)
return match ? render(props) : null;
};
LiveRoute.prototype.render = function () {
var match = this.state.match;
var _a = this.props, children = _a.children, component = _a.component, render = _a.render, livePath = _a.livePath, alwaysLive = _a.alwaysLive;
var _b = this.context.router, history = _b.history, route = _b.route, staticContext = _b.staticContext;
var location = this.props.location || route.location;
var props = { match: match, location: location, history: history, staticContext: staticContext };
if ((livePath || alwaysLive) && (component || render)) {
console.log('=== RENDER FLAG: ' + this.liveState + ' ===');
if (this.liveState === NORMAL_RENDER_MATCHED ||
this.liveState === NORMAL_RENDER_UNMATCHED ||
this.liveState === NORMAL_RENDER_ON_INIT) {
// normal render
return this.renderRoute(component, render, props, match);
}
else if (this.liveState === HIDE_RENDER) {
// hide render
var prevRouter = this._latestMatchedRouter;
// load properties from prevRouter and fake props of latest normal render
var history_1 = prevRouter.history, route_1 = prevRouter.route, staticContext_1 = prevRouter.staticContext;
var location_1 = this.props.location || route_1.location;
var liveProps = { match: match, location: location_1, history: history_1, staticContext: staticContext_1 };
return this.renderRoute(component, render, liveProps, true);
}
}
// the following is the same as Route of react-router, just render it normally
if (component)
return match ? React.createElement(component, props) : null;
if (render)
return match ? render(props) : null;
if (typeof children === 'function')
return children(props);
if (children && !isEmptyChildren(children))
return React.Children.only(children);
return null;
};
LiveRoute.propTypes = {
computedMatch: PropTypes.object,
path: PropTypes.string,
exact: PropTypes.bool,
strict: PropTypes.bool,
sensitive: PropTypes.bool,
component: function (props, propName) {
if (props[propName] && !isValidElementType(props[propName])) {
return new Error("Invalid prop 'component' supplied to 'Route': the prop is not a valid React component");
}
},
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
location: PropTypes.object,
livePath: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
alwaysLive: PropTypes.bool,
name: PropTypes.string // for LiveRoute debug
};
LiveRoute.defaultProps = {
alwaysLive: false
};
LiveRoute.contextTypes = {
router: PropTypes.shape({
history: PropTypes.object.isRequired,
route: PropTypes.object.isRequired,
staticContext: PropTypes.object
})
};
LiveRoute.childContextTypes = {
router: PropTypes.object.isRequired
};
return LiveRoute;
}(React.Component));
export { LiveRoute };
//# sourceMappingURL=LiveRoute.js.map
\ No newline at end of file
{"version":3,"file":"LiveRoute.js","sourceRoot":"","sources":["../src/LiveRoute.tsx"],"names":[],"mappings":"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"}
\ No newline at end of file
export { LiveRoute } from './LiveRoute';
//# sourceMappingURL=index.js.map
\ No newline at end of file
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA"}
\ No newline at end of file
console.log(233);
var a = 3;
export default a;
//# sourceMappingURL=test.js.map
\ No newline at end of file
{"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.tsx"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAEhB,IAAM,CAAC,GAAG,CAAC,CAAA;AAEX,eAAe,CAAC,CAAA"}
\ No newline at end of file
{
"name": "react-live-route-demo-1",
"version": "1.0.0",
"description": "",
"keywords": [],
"main": "src/index.js",
"dependencies": {
"react": "16.4.1",
"react-dom": "16.4.1",
"react-live-route": "1.2.4",
"react-router-dom": "4.3.1",
"react-scripts": "1.1.4"
},
"devDependencies": {},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
\ No newline at end of file
import React from "react";
class ListPage extends React.Component {
state = { count: 0 };
componentDidMount() {
window.scrollTo(0, 0);
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 200);
}
render() {
const timer = <h2 className="count">count: {this.state.count}</h2>;
const desc = (
<div className="desc">
<p>
This page of route is using <code>LiveRoute</code> with{" "}
<code>alwaysLive</code>.
</p>
<p>
It will not unmount after mounted and that means it will only mount
once.
</p>
</div>
);
const placeholder = Array(60)
.fill("")
.map((item, index) => {
return (
<p className="placeholder-text" key={index}>
{index} - You can scroll the screen to test if react-live-route can
restore scroll position.
</p>
);
});
return (
<div className="about">
{timer}
{desc}
{placeholder}
</div>
);
}
}
export default ListPage;
import React from "react";
import { Link } from "react-router-dom";
import "./styles.css";
const Bar = props => {
return (
<div className="bar">
<Link to="/">Home</Link>
<Link to="/items">Items</Link>
<Link to="/about">About</Link>
</div>
);
};
export default Bar;
import React from "react";
import { Link } from "react-router-dom";
const List = props => {
return (
<div>
<Link to="/items">
<div>&gt;&gt; back to List</div>
</Link>
<div className="detailContent">{`hello, I'm item - ${
props.match.params.id
}`}</div>
<Link
className="pagination"
to={`/item/${Number.parseInt(props.match.params.id) - 1}`}
>
Prev item
</Link>
<Link
className="pagination"
to={`/item/${Number.parseInt(props.match.params.id) + 1}`}
>
Next item
</Link>
</div>
);
};
export default List;
import React from "react";
import { Link } from "react-router-dom";
const Home = props => {
return (
<div>
<a
className="detailContent"
href="https://github.com/fi3ework/react-live-route"
target="_blank"
rel="noopener noreferrer"
>
<h1>react-live-route</h1>
</a>
<Link to="/items">
<div className="entry">into items</div>
</Link>
</div>
);
};
export default Home;
import React from "react";
import ReactDOM from "react-dom";
import { Route, BrowserRouter } from "react-router-dom";
import LiveRoute from "react-live-route";
import List from "./list";
import Detail from "./detail";
import Bar from "./bar";
import About from "./about";
import Home from "./home";
import "./styles.css";
function App() {
return (
<div className="App">
<Route exact path="/" component={Home} />
<LiveRoute
path="/items"
component={List}
livePath="/item/:id"
name="items"
/>
<Route path="/item/:id" component={Detail} />
<LiveRoute
path="/about"
alwaysLive={true}
component={About}
name="about"
/>
<Bar />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
rootElement
);
if ("scrollRestoration" in window.history) {
// 默认值为'auto'
// window.history.scrollRestoration = 'manual'
}
document.addEventListener("scrollTo", () => {
console.log("233");
});
import React from "react";
import { Link } from "react-router-dom";
class ListPage extends React.Component {
state = { count: 0 };
componentWillUnmount() {
console.log("##### items will unmount #####");
}
componentDidMount() {
console.log("##### items did mount #####");
window.scrollTo(0, 0);
setInterval(() => {
this.setState({
count: this.state.count + 1
});
}, 200);
}
render() {
const list = Array(200)
.fill("")
.map((item, index) => {
return (
<Link className="item" to={`/item/${index}`} key={index}>
<div>Item - {index}</div>
</Link>
);
});
const timer = <h2 className="count">count: {this.state.count}</h2>;
const desc = (
<div className="desc">
<p>
This page of route is using <code>LiveRoute</code> with{" "}
<code>livePath</code>.
</p>
<p>
In this page, the list page will not be unmounted on item detail page
and will be unmounted when enter into other pages such as home page.
</p>
<p>
The count number above is a sign of component live state. It will be
reset to 0 when the component of Route unmounted. You can scroll the
page and it will be restored when backing from item detail page.
</p>
<p>Feel free to try it.</p>
</div>
);
return (
<div className="list">
{timer}
{desc}
{list}
</div>
);
}
}
export default ListPage;
.App {
font-family: sans-serif;
text-align: center;
}
a {
text-decoration: none;
color: #666;
}
.bar {
position: fixed;
display: flex;
width: 100%;
justify-content: space-around;
padding: 10px 0;
left: 0;
bottom: 0;
background-color: #aaa;
border-top: 2px solid #888;
}
.bar a {
color: #fff;
}
.item {
display: block;
color: #666;
width: 100px;
padding: 5px 0;
border-radius: 4px;
margin: 5px auto;
background-color: #f1f1f1;
}
.count {
color: #333;
}
.desc {
color: #666;
margin: 0 auto;
text-align: left;
}
.detailContent {
margin: 50px 0;
}
.pagination {
display: inline-block;
margin: 0 20px;
padding: 0 10px;
color: #fff;
background-color: #777;
border-radius: 4px;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 2px;
color: darkcyan;
}
.about,
.list {
width: 80%;
margin: 0 auto;
}
.entry {
padding: 20px 0;
width: 80%;
margin: 0 auto;
transition: all 0.2s ease-in;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.entry:hover {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
}
.placeholder-text {
color: #999;
}
{
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true
},
"plugins": ["import", "react"],
"extends": ["eslint:recommended", "plugin:import/errors", "plugin:react/recommended"],
"rules": {
"prefer-arrow-callback": 2,
"react/display-name": 0,
"react/no-children-prop": 0,
"react/prop-types": [2, { "ignore": ["history"] }],
"react/no-find-dom-node": 0,
"no-console": 0
}
}
export default from './LiveRoute'
import pathToRegexp from "path-to-regexp";
// ES6 的 import 会共享,所以可以理解为一个全局缓存
// 缓存的结构是 option 对象下的 pattern
const patternCache = {};
const cacheLimit = 10000;
let cacheCount = 0;
const compilePath = (pattern, options) => {
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});
if (cache[pattern]) return cache[pattern];
const keys = [];
const re = pathToRegexp(pattern, keys, options);
const compiledPattern = { re, keys };
// 这是 path-to-regex 的返回值
// var keys = []
// var re = pathToRegexp('/foo/:bar', keys)
// re = /^\/foo\/([^\/]+?)\/?$/i
// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
if (cacheCount < cacheLimit) {
cache[pattern] = compiledPattern;
cacheCount++;
}
return compiledPattern;
};
/**
* Public API for matching a URL pathname to a path pattern.
*/
const matchPath = (pathname, options = {}, parent) => {
if (typeof options === "string") options = { path: options };
const { path, exact = false, strict = false, sensitive = false } = options;
if (path == null) return parent;
// path-to-regex 的 end 相当于就是 router 的 exact,相当于是否使用全局匹配 /g
const { re, keys } = compilePath(path, { end: exact, strict, sensitive });
// 使用生成的正则表达式去匹配
const match = re.exec(pathname);
// 不匹配直接返回
if (!match) return null;
const [url, ...values] = match;
const isExact = pathname === url;
// 如果在要求 exact 匹配时为非 exact 的匹配,则直接返回
if (exact && !isExact) return null;
return {
path, // the path pattern used to match
url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params: keys.reduce((memo, key, index) => {
memo[key.name] = values[index];
return memo;
}, {})
};
};
export default matchPath;
{
"presets": [ "../../tools/babel-preset" ]
}
{
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true
},
"plugins": ["import", "react"],
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:react/recommended"
],
"rules": {
"no-unused-vars": [2, { "ignoreRestSiblings": true }],
"prefer-arrow-callback": 2,
"react/display-name": 0,
"react/no-children-prop": 0,
"react/prop-types": [2, { "ignore": ["history"] }]
}
}
import warning from "warning";
import React from "react";
import PropTypes from "prop-types";
import { createMemoryHistory as createHistory } from "history";
import Router from "./Router";
/**
* The public API for a <Router> that stores location in memory.
*/
class MemoryRouter extends React.Component {
static propTypes = {
initialEntries: PropTypes.array,
initialIndex: PropTypes.number,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number,
children: PropTypes.node
};
history = createHistory(this.props);
componentWillMount() {
warning(
!this.props.history,
"<MemoryRouter> ignores the history prop. To use a custom history, " +
"use `import { Router }` instead of `import { MemoryRouter as Router }`."
);
}
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
export default MemoryRouter;
import React from "react";
import PropTypes from "prop-types";
import invariant from "invariant";
/**
* The public API for prompting the user before navigating away
* from a screen with a component.
*/
class Prompt extends React.Component {
static propTypes = {
when: PropTypes.bool,
message: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired
};
static defaultProps = {
when: true
};
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
block: PropTypes.func.isRequired
}).isRequired
}).isRequired
};
enable(message) {
if (this.unblock) this.unblock();
this.unblock = this.context.router.history.block(message);
}
disable() {
if (this.unblock) {
this.unblock();
this.unblock = null;
}
}
componentWillMount() {
invariant(
this.context.router,
"You should not use <Prompt> outside a <Router>"
);
if (this.props.when) this.enable(this.props.message);
}
componentWillReceiveProps(nextProps) {
if (nextProps.when) {
if (!this.props.when || this.props.message !== nextProps.message)
this.enable(nextProps.message);
} else {
this.disable();
}
}
componentWillUnmount() {
this.disable();
}
render() {
return null;
}
}
export default Prompt;
import React from "react";
import PropTypes from "prop-types";
import warning from "warning";
import invariant from "invariant";
import { createLocation, locationsAreEqual } from "history";
import generatePath from "./generatePath";
/**
* The public API for updating the location programmatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
computedMatch: PropTypes.object, // private, from <Switch>
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
};
static defaultProps = {
push: false
};
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
};
isStatic() {
return this.context.router && this.context.router.staticContext;
}
componentWillMount() {
invariant(
this.context.router,
"You should not use <Redirect> outside a <Router>"
);
if (this.isStatic()) this.perform();
}
componentDidMount() {
if (!this.isStatic()) this.perform();
}
componentDidUpdate(prevProps) {
const prevTo = createLocation(prevProps.to);
const nextTo = createLocation(this.props.to);
if (locationsAreEqual(prevTo, nextTo)) {
warning(
false,
`You tried to redirect to the same route you're currently on: ` +
`"${nextTo.pathname}${nextTo.search}"`
);
return;
}
this.perform();
}
computeTo({ computedMatch, to }) {
if (computedMatch) {
if (typeof to === "string") {
return generatePath(to, computedMatch.params);
} else {
return {
...to,
pathname: generatePath(to.pathname, computedMatch.params)
};
}
}
return to;
}
perform() {
const { history } = this.context.router;
const { push } = this.props;
const to = this.computeTo(this.props);
if (push) {
history.push(to);
} else {
history.replace(to);
}
}
render() {
return null;
}
}
export default Redirect;
import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";
import matchPath from "./matchPath";
const isEmptyChildren = children => React.Children.count(children) === 0;
/**
* The public API for matching a single path and rendering.
*/
class Route extends React.Component {
static propTypes = {
computedMatch: PropTypes.object, // private, from <Switch>
path: PropTypes.string,
exact: PropTypes.bool,
strict: PropTypes.bool,
sensitive: PropTypes.bool,
component: PropTypes.func,
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
location: PropTypes.object
};
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.object.isRequired,
route: PropTypes.object.isRequired,
staticContext: PropTypes.object
})
};
static childContextTypes = {
router: PropTypes.object.isRequired
};
getChildContext() {
return {
router: {
...this.context.router,
route: {
location: this.props.location || this.context.router.route.location,
match: this.state.match
}
}
};
}
state = {
match: this.computeMatch(this.props, this.context.router)
};
computeMatch(
{ computedMatch, location, path, strict, exact, sensitive },
router
) {
if (computedMatch) return computedMatch; // <Switch> already computed the match for us
invariant(
router,
"You should not use <Route> or withRouter() outside a <Router>"
);
const { route } = router;
const pathname = (location || route.location).pathname;
return matchPath(pathname, { path, strict, exact, sensitive }, route.match);
}
componentWillMount() {
warning(
!(this.props.component && this.props.render),
"You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored"
);
warning(
!(
this.props.component &&
this.props.children &&
!isEmptyChildren(this.props.children)
),
"You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored"
);
warning(
!(
this.props.render &&
this.props.children &&
!isEmptyChildren(this.props.children)
),
"You should not use <Route render> and <Route children> in the same route; <Route children> will be ignored"
);
}
componentWillReceiveProps(nextProps, nextContext) {
warning(
!(nextProps.location && !this.props.location),
'<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
);
warning(
!(!nextProps.location && this.props.location),
'<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
);
this.setState({
match: this.computeMatch(nextProps, nextContext.router)
});
}
render() {
const { match } = this.state;
const { children, component, render } = this.props;
const { history, route, staticContext } = this.context.router;
const location = this.props.location || route.location;
const props = { match, location, history, staticContext };
if (component) return match ? React.createElement(component, props) : null;
if (render) return match ? render(props) : null;
if (typeof children === "function") return children(props);
if (children && !isEmptyChildren(children))
return React.Children.only(children);
return null;
}
}
export default Route;
import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";
/**
* The public API for putting history on context.
*/
class Router extends React.Component {
static propTypes = {
history: PropTypes.object.isRequired,
children: PropTypes.node
};
static contextTypes = {
router: PropTypes.object
};
static childContextTypes = {
router: PropTypes.object.isRequired
};
getChildContext() {
return {
router: {
...this.context.router,
history: this.props.history,
route: {
location: this.props.history.location,
match: this.state.match
}
}
};
}
state = {
match: this.computeMatch(this.props.history.location.pathname)
};
computeMatch(pathname) {
return {
path: "/",
url: "/",
params: {},
isExact: pathname === "/"
};
}
componentWillMount() {
const { children, history } = this.props;
invariant(
children == null || React.Children.count(children) === 1,
"A <Router> may have only one child element"
);
// Do this here so we can setState when a <Redirect> changes the
// location in componentWillMount. This happens e.g. when doing
// server rendering using a <StaticRouter>.
this.unlisten = history.listen(() => {
this.setState({
match: this.computeMatch(history.location.pathname)
});
});
}
componentWillReceiveProps(nextProps) {
warning(
this.props.history === nextProps.history,
"You cannot change <Router history>"
);
}
componentWillUnmount() {
this.unlisten();
}
render() {
const { children } = this.props;
return children ? React.Children.only(children) : null;
}
}
export default Router;
import warning from "warning";
import invariant from "invariant";
import React from "react";
import PropTypes from "prop-types";
import { createLocation, createPath } from "history";
import Router from "./Router";
const addLeadingSlash = path => {
return path.charAt(0) === "/" ? path : "/" + path;
};
const addBasename = (basename, location) => {
if (!basename) return location;
return {
...location,
pathname: addLeadingSlash(basename) + location.pathname
};
};
const stripBasename = (basename, location) => {
if (!basename) return location;
const base = addLeadingSlash(basename);
if (location.pathname.indexOf(base) !== 0) return location;
return {
...location,
pathname: location.pathname.substr(base.length)
};
};
const createURL = location =>
typeof location === "string" ? location : createPath(location);
const staticHandler = methodName => () => {
invariant(false, "You cannot %s with <StaticRouter>", methodName);
};
const noop = () => {};
/**
* The public top-level API for a "static" <Router>, so-called because it
* can't actually change the current location. Instead, it just records
* location changes in a context object. Useful mainly in testing and
* server-rendering scenarios.
*/
class StaticRouter extends React.Component {
static propTypes = {
basename: PropTypes.string,
context: PropTypes.object.isRequired,
location: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
};
static defaultProps = {
basename: "",
location: "/"
};
static childContextTypes = {
router: PropTypes.object.isRequired
};
getChildContext() {
return {
router: {
staticContext: this.props.context
}
};
}
createHref = path => addLeadingSlash(this.props.basename + createURL(path));
handlePush = location => {
const { basename, context } = this.props;
context.action = "PUSH";
context.location = addBasename(basename, createLocation(location));
context.url = createURL(context.location);
};
handleReplace = location => {
const { basename, context } = this.props;
context.action = "REPLACE";
context.location = addBasename(basename, createLocation(location));
context.url = createURL(context.location);
};
handleListen = () => noop;
handleBlock = () => noop;
componentWillMount() {
warning(
!this.props.history,
"<StaticRouter> ignores the history prop. To use a custom history, " +
"use `import { Router }` instead of `import { StaticRouter as Router }`."
);
}
render() {
const { basename, context, location, ...props } = this.props;
const history = {
createHref: this.createHref,
action: "POP",
location: stripBasename(basename, createLocation(location)),
push: this.handlePush,
replace: this.handleReplace,
go: staticHandler("go"),
goBack: staticHandler("goBack"),
goForward: staticHandler("goForward"),
listen: this.handleListen,
block: this.handleBlock
};
return <Router {...props} history={history} />;
}
}
export default StaticRouter;
import React from "react";
import PropTypes from "prop-types";
import warning from "warning";
import invariant from "invariant";
import matchPath from "./matchPath";
/**
* The public API for rendering the first <Route> that matches.
*/
class Switch extends React.Component {
static contextTypes = {
router: PropTypes.shape({
route: PropTypes.object.isRequired
}).isRequired
};
static propTypes = {
children: PropTypes.node,
location: PropTypes.object
};
componentWillMount() {
invariant(
this.context.router,
"You should not use <Switch> outside a <Router>"
);
}
componentWillReceiveProps(nextProps) {
warning(
!(nextProps.location && !this.props.location),
'<Switch> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
);
warning(
!(!nextProps.location && this.props.location),
'<Switch> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
);
}
render() {
const { route } = this.context.router;
const { children } = this.props;
const location = this.props.location || route.location;
let match, child;
React.Children.forEach(children, element => {
if (match == null && React.isValidElement(element)) {
const {
path: pathProp,
exact,
strict,
sensitive,
from
} = element.props;
const path = pathProp || from;
child = element;
match = matchPath(
location.pathname,
{ path, exact, strict, sensitive },
route.match
);
}
});
return match
? React.cloneElement(child, { location, computedMatch: match })
: null;
}
}
export default Switch;
{
"env": {
"jest": true
},
"globals": {
"spyOn": true
},
"rules": {
"no-console": 0,
"react/display-name": 0,
"react/prop-types": 0
}
}
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import MemoryRouter from "../MemoryRouter";
describe("A <MemoryRouter>", () => {
it("puts history on context.router", () => {
let history;
const ContextChecker = (props, context) => {
history = context.router.history;
return null;
};
ContextChecker.contextTypes = {
router: PropTypes.object.isRequired
};
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter>
<ContextChecker />
</MemoryRouter>,
node
);
expect(typeof history).toBe("object");
});
it("warns when passed a history prop", () => {
const history = {};
const node = document.createElement("div");
spyOn(console, "error");
ReactDOM.render(<MemoryRouter history={history} />, node);
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining("<MemoryRouter> ignores the history prop")
);
});
});
import React from "react";
import ReactDOM from "react-dom";
import MemoryRouter from "../MemoryRouter";
import Redirect from "../Redirect";
import Route from "../../LiveRoute";
import Switch from "../Switch";
describe("A <Redirect>", () => {
describe("inside a <Switch>", () => {
it("automatically interpolates params", () => {
const node = document.createElement("div");
let params;
ReactDOM.render(
<MemoryRouter initialEntries={["/users/mjackson/messages/123"]}>
<Switch>
<Redirect
from="/users/:username/messages/:messageId"
to="/:username/messages/:messageId"
/>
<Route
path="/:username/messages/:messageId"
render={({ match }) => {
params = match.params;
return null;
}}
/>
</Switch>
</MemoryRouter>,
node
);
expect(params).toMatchObject({
username: "mjackson",
messageId: "123"
});
});
});
});
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import { createMemoryHistory } from "history";
import MemoryRouter from "../MemoryRouter";
import Router from "../Router";
import Route from "../../LiveRoute";
describe("A <Route>", () => {
it("renders at the root", () => {
const TEXT = "Mrs. Kato";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/"]}>
<Route path="/" render={() => <h1>{TEXT}</h1>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("does not render when it does not match", () => {
const TEXT = "bubblegum";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/bunnies"]}>
<Route path="/flowers" render={() => <h1>{TEXT}</h1>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain(TEXT);
});
it("can use a `location` prop instead of `context.router.route.location`", () => {
const TEXT = "tamarind chutney";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/mint"]}>
<Route
location={{ pathname: "/tamarind" }}
path="/tamarind"
render={() => <h1>{TEXT}</h1>}
/>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("supports preact by nulling out children prop when empty array is passed", () => {
const TEXT = "Mrs. Kato";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/"]}>
<Route path="/" render={() => <h1>{TEXT}</h1>}>
{[]}
</Route>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("matches using nextContext when updating", () => {
const node = document.createElement("div");
let push;
ReactDOM.render(
<MemoryRouter initialEntries={["/sushi/california"]}>
<Route
path="/sushi/:roll"
render={({ history, match }) => {
push = history.push;
return <div>{match.url}</div>;
}}
/>
</MemoryRouter>,
node
);
push("/sushi/spicy-tuna");
expect(node.innerHTML).toContain("/sushi/spicy-tuna");
});
it("throws with no <Router>", () => {
const node = document.createElement("div");
spyOn(console, "error");
expect(() => {
ReactDOM.render(<Route path="/" render={() => null} />, node);
}).toThrow(
/You should not use <Route> or withRouter\(\) outside a <Router>/
);
});
});
describe("A <Route> with dynamic segments in the path", () => {
it("decodes them", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/a%20dynamic%20segment"]}>
<Route
path="/:id"
render={({ match }) => <div>{match.params.id}</div>}
/>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain("a dynamic segment");
});
});
describe("A unicode <Route>", () => {
it("is able to match", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/パス名"]}>
<Route path="/パス名" render={({ match }) => <div>{match.url}</div>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain("/パス名");
});
});
describe("<Route render>", () => {
const history = createMemoryHistory();
const node = document.createElement("div");
afterEach(() => {
ReactDOM.unmountComponentAtNode(node);
});
it("renders its return value", () => {
const TEXT = "Mrs. Kato";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/"]}>
<Route path="/" render={() => <div>{TEXT}</div>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("receives { match, location, history } props", () => {
let actual = null;
ReactDOM.render(
<Router history={history}>
<Route path="/" render={props => (actual = props) && null} />
</Router>,
node
);
expect(actual.history).toBe(history);
expect(typeof actual.match).toBe("object");
expect(typeof actual.location).toBe("object");
});
});
describe("<Route component>", () => {
const history = createMemoryHistory();
const node = document.createElement("div");
afterEach(() => {
ReactDOM.unmountComponentAtNode(node);
});
it("renders the component", () => {
const TEXT = "Mrs. Kato";
const node = document.createElement("div");
const Home = () => <div>{TEXT}</div>;
ReactDOM.render(
<MemoryRouter initialEntries={["/"]}>
<Route path="/" component={Home} />
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("receives { match, location, history } props", () => {
let actual = null;
const Component = props => (actual = props) && null;
ReactDOM.render(
<Router history={history}>
<Route path="/" component={Component} />
</Router>,
node
);
expect(actual.history).toBe(history);
expect(typeof actual.match).toBe("object");
expect(typeof actual.location).toBe("object");
});
});
describe("<Route children>", () => {
const history = createMemoryHistory();
const node = document.createElement("div");
afterEach(() => {
ReactDOM.unmountComponentAtNode(node);
});
it("renders a function", () => {
const TEXT = "Mrs. Kato";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/"]}>
<Route path="/" children={() => <div>{TEXT}</div>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("renders a child element", () => {
const TEXT = "Mrs. Kato";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/"]}>
<Route path="/">
<div>{TEXT}</div>
</Route>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("receives { match, location, history } props", () => {
let actual = null;
ReactDOM.render(
<Router history={history}>
<Route path="/" children={props => (actual = props) && null} />
</Router>,
node
);
expect(actual.history).toBe(history);
expect(typeof actual.match).toBe("object");
expect(typeof actual.location).toBe("object");
});
});
describe("A <Route exact>", () => {
it("renders when the URL does not have a trailing slash", () => {
const TEXT = "bubblegum";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/somepath/"]}>
<Route exact path="/somepath" render={() => <h1>{TEXT}</h1>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("renders when the URL has trailing slash", () => {
const TEXT = "bubblegum";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/somepath"]}>
<Route exact path="/somepath/" render={() => <h1>{TEXT}</h1>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
});
describe("A <Route exact strict>", () => {
it("does not render when the URL has a trailing slash", () => {
const TEXT = "bubblegum";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/somepath/"]}>
<Route exact strict path="/somepath" render={() => <h1>{TEXT}</h1>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain(TEXT);
});
it("does not render when the URL does not have a trailing slash", () => {
const TEXT = "bubblegum";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/somepath"]}>
<Route exact strict path="/somepath/" render={() => <h1>{TEXT}</h1>} />
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain(TEXT);
});
});
describe("A <Route location>", () => {
it("can use a `location` prop instead of `router.location`", () => {
const TEXT = "tamarind chutney";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/mint"]}>
<Route
location={{ pathname: "/tamarind" }}
path="/tamarind"
render={() => <h1>{TEXT}</h1>}
/>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
describe("children", () => {
it("uses parent's prop location", () => {
const TEXT = "cheddar pretzel";
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/popcorn"]}>
<Route
location={{ pathname: "/pretzels/cheddar" }}
path="/pretzels"
render={() => (
<Route path="/pretzels/cheddar" render={() => <h1>{TEXT}</h1>} />
)}
/>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
});
it("continues to use parent's prop location after navigation", () => {
const TEXT = "cheddar pretzel";
const node = document.createElement("div");
let push;
ReactDOM.render(
<MemoryRouter initialEntries={["/popcorn"]}>
<Route
location={{ pathname: "/pretzels/cheddar" }}
path="/pretzels"
render={({ history }) => {
push = history.push;
return (
<Route
path="/pretzels/cheddar"
render={() => <h1>{TEXT}</h1>}
/>
);
}}
/>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT);
push("/chips");
expect(node.innerHTML).toContain(TEXT);
});
});
});
describe("A pathless <Route>", () => {
let rootContext;
const ContextChecker = (props, context) => {
rootContext = context;
return null;
};
ContextChecker.contextTypes = {
router: PropTypes.object
};
afterEach(() => {
rootContext = undefined;
});
it("inherits its parent match", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/somepath"]}>
<Route component={ContextChecker} />
</MemoryRouter>,
node
);
const { match } = rootContext.router.route;
expect(match.path).toBe("/");
expect(match.url).toBe("/");
expect(match.isExact).toBe(false);
expect(match.params).toEqual({});
});
it("does not render when parent match is null", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/somepath"]}>
<Route
path="/no-match"
children={() => <Route component={ContextChecker} />}
/>
</MemoryRouter>,
node
);
expect(rootContext).toBe(undefined);
});
});
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import Router from "../Router";
import { createMemoryHistory as createHistory } from "history";
describe("A <Router>", () => {
const node = document.createElement("div");
afterEach(() => {
ReactDOM.unmountComponentAtNode(node);
});
describe("when it has more than one child", () => {
it("throws an error explaining a Router may have only one child", () => {
spyOn(console, "error");
expect(() => {
ReactDOM.render(
<Router history={createHistory()}>
<p>Foo</p>
<p>Bar</p>
</Router>,
node
);
}).toThrow(/A <Router> may have only one child element/);
});
});
describe("with exactly one child", () => {
it("does not throw an error", () => {
expect(() => {
ReactDOM.render(
<Router history={createHistory()}>
<p>Bar</p>
</Router>,
node
);
}).not.toThrow();
});
});
describe("with no children", () => {
it("does not throw an error", () => {
expect(() => {
ReactDOM.render(<Router history={createHistory()} />, node);
}).not.toThrow();
});
});
describe("context", () => {
let rootContext;
const ContextChecker = (props, context) => {
rootContext = context;
return null;
};
ContextChecker.contextTypes = {
router: PropTypes.shape({
history: PropTypes.object,
route: PropTypes.object
})
};
afterEach(() => {
rootContext = undefined;
});
it("puts history on context.history", () => {
const history = createHistory();
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
);
expect(rootContext.router.history).toBe(history);
});
it("sets context.router.route at the root", () => {
const history = createHistory({
initialEntries: ["/"]
});
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
);
expect(rootContext.router.route.match.path).toEqual("/");
expect(rootContext.router.route.match.url).toEqual("/");
expect(rootContext.router.route.match.params).toEqual({});
expect(rootContext.router.route.match.isExact).toEqual(true);
expect(rootContext.router.route.location).toEqual(history.location);
});
it("updates context.router.route upon navigation", () => {
const history = createHistory({
initialEntries: ["/"]
});
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
);
expect(rootContext.router.route.match.isExact).toBe(true);
const newLocation = { pathname: "/new" };
history.push(newLocation);
expect(rootContext.router.route.match.isExact).toBe(false);
});
it("does not contain context.router.staticContext by default", () => {
const history = createHistory({
initialEntries: ["/"]
});
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
);
expect(rootContext.router.staticContext).toBe(undefined);
});
});
});
import React from "react";
import ReactDOM from "react-dom";
import ReactDOMServer from "react-dom/server";
import PropTypes from "prop-types";
import StaticRouter from "../StaticRouter";
import Redirect from "../Redirect";
import Route from "../../LiveRoute";
// import Route from "../Route";
import Prompt from "../Prompt";
describe("A <StaticRouter>", () => {
it("provides context.router.staticContext in props.staticContext", () => {
const ContextChecker = (props, reactContext) => {
expect(typeof reactContext.router).toBe("object");
expect(reactContext.router.staticContext).toBe(props.staticContext);
return null;
};
ContextChecker.contextTypes = {
router: PropTypes.object.isRequired
};
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context}>
<Route component={ContextChecker} />
</StaticRouter>
);
});
it("context.router.staticContext persists inside of a <Route>", () => {
const ContextChecker = (props, reactContext) => {
expect(typeof reactContext.router).toBe("object");
expect(reactContext.router.staticContext).toBe(context);
return null;
};
ContextChecker.contextTypes = {
router: PropTypes.object.isRequired
};
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context}>
<Route component={ContextChecker} />
</StaticRouter>
);
});
it("provides context.router.history", () => {
const ContextChecker = (props, reactContext) => {
expect(typeof reactContext.router.history).toBe("object");
return null;
};
ContextChecker.contextTypes = {
router: PropTypes.object.isRequired
};
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context}>
<ContextChecker />
</StaticRouter>
);
});
it("warns when passed a history prop", () => {
const context = {};
const history = {};
const node = document.createElement("div");
spyOn(console, "error");
ReactDOM.render(<StaticRouter context={context} history={history} />, node);
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining("<StaticRouter> ignores the history prop")
);
});
it("reports PUSH actions on the context object", () => {
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context}>
<Redirect push to="/somewhere-else" />
</StaticRouter>
);
expect(context.action).toBe("PUSH");
expect(context.url).toBe("/somewhere-else");
});
it("reports REPLACE actions on the context object", () => {
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context}>
<Redirect to="/somewhere-else" />
</StaticRouter>
);
expect(context.action).toBe("REPLACE");
expect(context.url).toBe("/somewhere-else");
});
describe("location", () => {
it("knows how to parse raw URL string into an object", () => {
const LocationChecker = props => {
expect(props.location).toMatchObject({
pathname: "/the/path",
search: "?the=query",
hash: "#the-hash"
});
return null;
};
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context} location="/the/path?the=query#the-hash">
<Route component={LocationChecker} />
</StaticRouter>
);
});
it("adds missing properties to location object", () => {
const LocationChecker = props => {
expect(props.location).toMatchObject({
pathname: "/test",
search: "",
hash: ""
});
return null;
};
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context} location={{ pathname: "/test" }}>
<Route component={LocationChecker} />
</StaticRouter>
);
});
it("decodes an encoded pathname", () => {
const LocationChecker = props => {
expect(props.location).toMatchObject({
pathname: "/estático",
search: "",
hash: ""
});
expect(props.match.params.type).toBe("estático");
return null;
};
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter
context={context}
location={{ pathname: "/est%C3%A1tico" }}
>
<Route path="/:type" component={LocationChecker} />
</StaticRouter>
);
});
it("knows how to serialize location objects", () => {
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context}>
<Redirect to={{ pathname: "/somewhere-else" }} />
</StaticRouter>
);
expect(context.action).toBe("REPLACE");
expect(context.location.pathname).toBe("/somewhere-else");
expect(context.location.search).toBe("");
expect(context.location.hash).toBe("");
expect(context.url).toBe("/somewhere-else");
});
describe("with a basename", () => {
it("strips the basename from location pathnames", () => {
const LocationChecker = props => {
expect(props.location).toMatchObject({
pathname: "/path"
});
return null;
};
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter
context={context}
basename="/the-base"
location="/the-base/path"
>
<Route component={LocationChecker} />
</StaticRouter>
);
});
it("reports PUSH actions on the context object", () => {
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context} basename="/the-base">
<Redirect push to="/somewhere-else" />
</StaticRouter>
);
expect(context.action).toBe("PUSH");
expect(context.url).toBe("/the-base/somewhere-else");
});
it("reports REPLACE actions on the context object", () => {
const context = {};
ReactDOMServer.renderToStaticMarkup(
<StaticRouter context={context} basename="/the-base">
<Redirect to="/somewhere-else" />
</StaticRouter>
);
expect(context.action).toBe("REPLACE");
expect(context.url).toBe("/the-base/somewhere-else");
});
});
describe("no basename", () => {
it("createHref does not append extra leading slash", () => {
const context = {};
const node = document.createElement("div");
const pathname = "/test-path-please-ignore";
const Link = ({ to, children }) => (
<Route
children={({ history: { createHref } }) => (
<a href={createHref(to)}>{children}</a>
)}
/>
);
ReactDOM.render(
<StaticRouter context={context}>
<Link to={pathname} />
</StaticRouter>,
node
);
const a = node.getElementsByTagName("a")[0];
expect(a.getAttribute("href")).toEqual(pathname);
});
});
});
describe("render a <Prompt>", () => {
it("does nothing", () => {
const context = {};
const node = document.createElement("div");
expect(() => {
ReactDOM.render(
<StaticRouter context={context}>
<Prompt message="this is only a test" />
</StaticRouter>,
node
);
}).not.toThrow();
});
});
});
import React from "react";
import ReactDOM from "react-dom";
import MemoryRouter from "../MemoryRouter";
import Switch from "../Switch";
import Route from "../../LiveRoute";
import Redirect from "../Redirect";
describe("A <Switch>", () => {
it("renders the first <Route> that matches the URL", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Route path="/one" render={() => <h1>one</h1>} />
<Route path="/two" render={() => <h1>two</h1>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).toMatch(/one/);
});
it("renders the first <Redirect from> that matches the URL", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/three"]}>
<Switch>
<Route path="/one" render={() => <h1>one</h1>} />
<Redirect from="/four" to="/one" />
<Redirect from="/three" to="/two" />
<Route path="/two" render={() => <h1>two</h1>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).toMatch(/two/);
});
it("does not render a second <Route> or <Redirect> that also matches the URL", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Route path="/one" render={() => <h1>one</h1>} />
<Redirect from="/one" to="/two" />
<Route path="/one" render={() => <h1>two</h1>} />
<Route path="/two" render={() => <h1>two</h1>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toMatch(/two/);
});
it("renders pathless Routes", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/cupcakes"]}>
<Switch>
<Route path="/bubblegum" render={() => <div>one</div>} />
<Route render={() => <div>two</div>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain("one");
expect(node.innerHTML).toContain("two");
});
it("handles from-less Redirects", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/cupcakes"]}>
<Switch>
<Route path="/bubblegum" render={() => <div>bub</div>} />
<Redirect to="/bubblegum" />
<Route path="/cupcakes" render={() => <div>cup</div>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain("cup");
expect(node.innerHTML).toContain("bub");
});
it("handles subsequent redirects", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Redirect exact from="/one" to="/two" />
<Redirect exact from="/two" to="/three" />
<Route path="/three" render={() => <div>three</div>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain("three");
});
it("warns when redirecting to same route, both strings", () => {
const node = document.createElement("div");
let redirected = false;
let done = false;
spyOn(console, "error");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Route
path="/one"
render={() => {
if (done) return <h1>done</h1>;
if (!redirected) {
return <Redirect to="/one" />;
}
done = true;
return <Redirect to="/one" />;
}}
/>
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain("done");
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toMatch(/Warning:.*"\/one"/);
});
it("warns when redirecting to same route, mixed types", () => {
const node = document.createElement("div");
let redirected = false;
let done = false;
spyOn(console, "error");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Route
path="/one"
render={() => {
if (done) return <h1>done</h1>;
if (!redirected) {
redirected = true;
return <Redirect to="/one" />;
}
done = true;
return <Redirect to={{ pathname: "/one" }} />;
}}
/>
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain("done");
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toMatch(/Warning:.*"\/one"/);
});
it("warns when redirecting to same route, mixed types, string with query", () => {
const node = document.createElement("div");
let redirected = false;
let done = false;
spyOn(console, "error");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Route
path="/one"
render={() => {
if (done) return <h1>done</h1>;
if (!redirected) {
redirected = true;
return <Redirect to="/one?utm=1" />;
}
done = true;
return <Redirect to={{ pathname: "/one", search: "?utm=1" }} />;
}}
/>
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain("done");
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toMatch(
/Warning:.*"\/one\?utm=1"/
);
});
it("does NOT warn when redirecting to same route with different `search`", () => {
const node = document.createElement("div");
let redirected = false;
let done = false;
spyOn(console, "error");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Route
path="/one"
render={() => {
if (done) return <h1>done</h1>;
if (!redirected) {
redirected = true;
return <Redirect to={{ pathname: "/one", search: "?utm=1" }} />;
}
done = true;
return <Redirect to={{ pathname: "/one", search: "?utm=2" }} />;
}}
/>
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain("done");
expect(console.error.calls.count()).toBe(0);
});
it("handles comments", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/cupcakes"]}>
<Switch>
<Route path="/bubblegum" render={() => <div>bub</div>} />
{/* this is a comment */}
<Route path="/cupcakes" render={() => <div>cup</div>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).not.toContain("bub");
expect(node.innerHTML).toContain("cup");
});
it("renders with non-element children", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch>
<Route path="/one" render={() => <h1>one</h1>} />
{false}
{undefined}
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).toMatch(/one/);
});
it("throws with no <Router>", () => {
const node = document.createElement("div");
spyOn(console, "error");
expect(() => {
ReactDOM.render(
<Switch>
<Route path="/one" render={() => <h1>one</h1>} />
<Route path="/two" render={() => <h1>two</h1>} />
</Switch>,
node
);
}).toThrow(/You should not use <Switch> outside a <Router>/);
expect(console.error.calls.count()).toBe(3);
expect(console.error.calls.argsFor(0)[0]).toContain(
"The context `router` is marked as required in `Switch`"
);
});
});
describe("A <Switch location>", () => {
it("can use a `location` prop instead of `router.location`", () => {
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch location={{ pathname: "/two" }}>
<Route path="/one" render={() => <h1>one</h1>} />
<Route path="/two" render={() => <h1>two</h1>} />
</Switch>
</MemoryRouter>,
node
);
expect(node.innerHTML).toMatch(/two/);
});
describe("children", () => {
it("passes location prop to matched <Route>", () => {
const node = document.createElement("div");
let propLocation;
const RouteHoneytrap = props => {
propLocation = props.location;
return <Route {...props} />;
};
const switchLocation = { pathname: "/two" };
ReactDOM.render(
<MemoryRouter initialEntries={["/one"]}>
<Switch location={switchLocation}>
<Route path="/one" render={() => <h1>one</h1>} />
<RouteHoneytrap path="/two" render={() => <h1>two</h1>} />
</Switch>
</MemoryRouter>,
node
);
expect(propLocation).toEqual(switchLocation);
});
});
});
import React from "react";
import ReactDOM from "react-dom";
import { createMemoryHistory as createHistory } from "history";
import Router from "../Router";
import Switch from "../Switch";
import Route from "../../LiveRoute";
describe("A <Switch>", () => {
it("does not remount a <Route>", () => {
const node = document.createElement("div");
let mountCount = 0;
class App extends React.Component {
componentWillMount() {
mountCount++;
}
render() {
return <div />;
}
}
const history = createHistory({
initialEntries: ["/one"]
});
ReactDOM.render(
<Router history={history}>
<Switch>
<Route path="/one" component={App} />
<Route path="/two" component={App} />
</Switch>
</Router>,
node
);
expect(mountCount).toBe(1);
history.push("/two");
expect(mountCount).toBe(1);
history.push("/one");
expect(mountCount).toBe(1);
});
});
import generatePath from "../generatePath";
describe("generatePath", () => {
describe('with pattern="/"', () => {
it("returns correct url with no params", () => {
const pattern = "/";
const generated = generatePath(pattern);
expect(generated).toBe("/");
});
it("returns correct url with params", () => {
const pattern = "/";
const params = { foo: "tobi", bar: 123 };
const generated = generatePath(pattern, params);
expect(generated).toBe("/");
});
});
describe('with pattern="/:foo/somewhere/:bar"', () => {
it("throws with no params", () => {
const pattern = "/:foo/somewhere/:bar";
expect(() => {
generatePath(pattern);
}).toThrow();
});
it("throws with some params", () => {
const pattern = "/:foo/somewhere/:bar";
const params = { foo: "tobi", quux: 999 };
expect(() => {
generatePath(pattern, params);
}).toThrow();
});
it("returns correct url with params", () => {
const pattern = "/:foo/somewhere/:bar";
const params = { foo: "tobi", bar: 123 };
const generated = generatePath(pattern, params);
expect(generated).toBe("/tobi/somewhere/123");
});
it("returns correct url with additional params", () => {
const pattern = "/:foo/somewhere/:bar";
const params = { foo: "tobi", bar: 123, quux: 999 };
const generated = generatePath(pattern, params);
expect(generated).toBe("/tobi/somewhere/123");
});
});
describe("with no path", () => {
it("matches the root URL", () => {
const generated = generatePath();
expect(generated).toBe("/");
});
});
});
import React from "react";
import ReactDOM from "react-dom";
import MemoryRouter from "../MemoryRouter";
import Route from "../../LiveRoute";
describe("Integration Tests", () => {
it("renders nested matches", () => {
const node = document.createElement("div");
const TEXT1 = "Ms. Tripp";
const TEXT2 = "Mrs. Schiffman";
ReactDOM.render(
<MemoryRouter initialEntries={["/nested"]}>
<Route
path="/"
render={() => (
<div>
<h1>{TEXT1}</h1>
<Route path="/nested" render={() => <h2>{TEXT2}</h2>} />
</div>
)}
/>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT1);
expect(node.innerHTML).toContain(TEXT2);
});
it("renders only as deep as the matching Route", () => {
const node = document.createElement("div");
const TEXT1 = "Ms. Tripp";
const TEXT2 = "Mrs. Schiffman";
ReactDOM.render(
<MemoryRouter initialEntries={["/"]}>
<Route
path="/"
render={() => (
<div>
<h1>{TEXT1}</h1>
<Route path="/nested" render={() => <h2>{TEXT2}</h2>} />
</div>
)}
/>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT1);
expect(node.innerHTML).not.toContain(TEXT2);
});
it("renders multiple matching routes", () => {
const node = document.createElement("div");
const TEXT1 = "Mrs. Schiffman";
const TEXT2 = "Mrs. Burton";
ReactDOM.render(
<MemoryRouter initialEntries={["/double"]}>
<div>
<aside>
<Route path="/double" render={() => <h1>{TEXT1}</h1>} />
</aside>
<main>
<Route path="/double" render={() => <h1>{TEXT2}</h1>} />
</main>
</div>
</MemoryRouter>,
node
);
expect(node.innerHTML).toContain(TEXT1);
expect(node.innerHTML).toContain(TEXT2);
});
});
import matchPath from "../matchPath";
describe("matchPath", () => {
describe('with path="/"', () => {
it('returns correct url at "/"', () => {
const path = "/";
const pathname = "/";
const match = matchPath(pathname, path);
expect(match.url).toBe("/");
});
it('returns correct url at "/somewhere/else"', () => {
const path = "/";
const pathname = "/somewhere/else";
const match = matchPath(pathname, path);
expect(match.url).toBe("/");
});
});
describe('with path="/somewhere"', () => {
it('returns correct url at "/somewhere"', () => {
const path = "/somewhere";
const pathname = "/somewhere";
const match = matchPath(pathname, path);
expect(match.url).toBe("/somewhere");
});
it('returns correct url at "/somewhere/else"', () => {
const path = "/somewhere";
const pathname = "/somewhere/else";
const match = matchPath(pathname, path);
expect(match.url).toBe("/somewhere");
});
});
describe("with sensitive path", () => {
it("returns non-sensitive url", () => {
const options = {
path: "/SomeWhere"
};
const pathname = "/somewhere";
const match = matchPath(pathname, options);
expect(match.url).toBe("/somewhere");
});
it("returns sensitive url", () => {
const options = {
path: "/SomeWhere",
sensitive: true
};
const pathname = "/somewhere";
const match = matchPath(pathname, options);
expect(match).toBe(null);
});
});
describe("with no path", () => {
it("returns parent match", () => {
const parentMatch = {
url: "/test-location/7",
path: "/test-location/:number",
params: { number: 7 },
isExact: true
};
const match = matchPath("/test-location/7", {}, parentMatch);
expect(match).toBe(parentMatch);
});
it("returns null when parent match is null", () => {
const pathname = "/some/path";
const match = matchPath(pathname, {}, null);
expect(match).toBe(null);
});
});
describe("cache", () => {
it("creates a cache entry for each exact/strict pair", () => {
// true/false and false/true will collide when adding booleans
const trueFalse = matchPath("/one/two", {
path: "/one/two/",
exact: true,
strict: false
});
const falseTrue = matchPath("/one/two", {
path: "/one/two/",
exact: false,
strict: true
});
expect(!!trueFalse).toBe(true);
expect(!!falseTrue).toBe(false);
});
});
});
import React from "react";
import ReactDOM from "react-dom";
import MemoryRouter from "../MemoryRouter";
import StaticRouter from "../StaticRouter";
import Route from "../../LiveRoute";
import withRouter from "../withRouter";
describe("withRouter", () => {
const node = document.createElement("div");
afterEach(() => {
ReactDOM.unmountComponentAtNode(node);
});
it("provides { match, location, history } props", () => {
const PropsChecker = withRouter(props => {
expect(typeof props.match).toBe("object");
expect(typeof props.location).toBe("object");
expect(typeof props.history).toBe("object");
return null;
});
ReactDOM.render(
<MemoryRouter initialEntries={["/bubblegum"]}>
<Route path="/bubblegum" render={() => <PropsChecker />} />
</MemoryRouter>,
node
);
});
it("provides the parent match as a prop to the wrapped component", () => {
let parentMatch;
const PropsChecker = withRouter(props => {
expect(props.match).toEqual(parentMatch);
return null;
});
ReactDOM.render(
<MemoryRouter initialEntries={["/bubblegum"]}>
<Route
path="/:flavor"
render={({ match }) => {
parentMatch = match;
return <PropsChecker />;
}}
/>
</MemoryRouter>,
node
);
});
it("works when parent match is null", () => {
let injectedProps;
let parentMatch;
const PropChecker = props => {
injectedProps = props;
return null;
};
const WrappedPropChecker = withRouter(PropChecker);
const node = document.createElement("div");
ReactDOM.render(
<MemoryRouter initialEntries={["/somepath"]}>
<Route
path="/no-match"
children={({ match }) => {
parentMatch = match;
return <WrappedPropChecker />;
}}
/>
</MemoryRouter>,
node
);
expect(parentMatch).toBe(null);
expect(injectedProps.match).toBe(null);
});
describe("inside a <StaticRouter>", () => {
it("provides the staticContext prop", () => {
const PropsChecker = withRouter(props => {
expect(typeof props.staticContext).toBe("object");
expect(props.staticContext).toBe(context);
return null;
});
const context = {};
ReactDOM.render(
<StaticRouter context={context}>
<Route component={PropsChecker} />
</StaticRouter>,
node
);
});
});
it("exposes the wrapped component as WrappedComponent", () => {
const Component = () => <div />;
const decorated = withRouter(Component);
expect(decorated.WrappedComponent).toBe(Component);
});
it("exposes the instance of the wrapped component via wrappedComponentRef", () => {
class WrappedComponent extends React.Component {
render() {
return null;
}
}
const Component = withRouter(WrappedComponent);
let ref;
ReactDOM.render(
<MemoryRouter initialEntries={["/bubblegum"]}>
<Route
path="/bubblegum"
render={() => <Component wrappedComponentRef={r => (ref = r)} />}
/>
</MemoryRouter>,
node
);
expect(ref instanceof WrappedComponent).toBe(true);
});
it("hoists non-react statics from the wrapped component", () => {
class Component extends React.Component {
static foo() {
return "bar";
}
render() {
return null;
}
}
Component.hello = "world";
const decorated = withRouter(Component);
expect(decorated.hello).toBe("world");
expect(typeof decorated.foo).toBe("function");
expect(decorated.foo()).toBe("bar");
});
});
import pathToRegexp from "path-to-regexp";
const patternCache = {};
const cacheLimit = 10000;
let cacheCount = 0;
const compileGenerator = pattern => {
const cacheKey = pattern;
const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});
if (cache[pattern]) return cache[pattern];
const compiledGenerator = pathToRegexp.compile(pattern);
if (cacheCount < cacheLimit) {
cache[pattern] = compiledGenerator;
cacheCount++;
}
return compiledGenerator;
};
/**
* Public API for generating a URL pathname from a pattern and parameters.
*/
const generatePath = (pattern = "/", params = {}) => {
if (pattern === "/") {
return pattern;
}
const generator = compileGenerator(pattern);
return generator(params, { pretty: true });
};
export default generatePath;
export MemoryRouter from "./MemoryRouter";
export Prompt from "./Prompt";
export Redirect from "./Redirect";
export Route from "./Route";
export Router from "./Router";
export StaticRouter from "./StaticRouter";
export Switch from "./Switch";
export generatePath from "./generatePath";
export matchPath from "./matchPath";
export withRouter from "./withRouter";
import pathToRegexp from "path-to-regexp";
const patternCache = {};
const cacheLimit = 10000;
let cacheCount = 0;
const compilePath = (pattern, options) => {
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
const cache = patternCache[cacheKey] || (patternCache[cacheKey] = {});
if (cache[pattern]) return cache[pattern];
const keys = [];
const re = pathToRegexp(pattern, keys, options);
const compiledPattern = { re, keys };
if (cacheCount < cacheLimit) {
cache[pattern] = compiledPattern;
cacheCount++;
}
return compiledPattern;
};
/**
* Public API for matching a URL pathname to a path pattern.
*/
const matchPath = (pathname, options = {}, parent) => {
if (typeof options === "string") options = { path: options };
const { path, exact = false, strict = false, sensitive = false } = options;
if (path == null) return parent;
const { re, keys } = compilePath(path, { end: exact, strict, sensitive });
const match = re.exec(pathname);
if (!match) return null;
const [url, ...values] = match;
const isExact = pathname === url;
if (exact && !isExact) return null;
return {
path, // the path pattern used to match
url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params: keys.reduce((memo, key, index) => {
memo[key.name] = values[index];
return memo;
}, {})
};
};
export default matchPath;
import React from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
import Route from "./Route";
/**
* A public higher-order component to access the imperative API
*/
const withRouter = Component => {
const C = props => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<Route
children={routeComponentProps => (
<Component
{...remainingProps}
{...routeComponentProps}
ref={wrappedComponentRef}
/>
)}
/>
);
};
C.displayName = `withRouter(${Component.displayName || Component.name})`;
C.WrappedComponent = Component;
C.propTypes = {
wrappedComponentRef: PropTypes.func
};
return hoistStatics(C, Component);
};
export default withRouter;
{
"name": "react-live-route",
"version": "1.1.7",
"version": "1.2.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -197,6 +197,31 @@
"integrity": "sha512-kctoM36XiNZT86a7tPsUje+Q/yl+dqELjtYApi0T5eOQ90Elhu0MI10rmYk44yEP4v1jdDvtjQ9DFtpRtHf2Bw==",
"dev": true
},
"@types/prop-types": {
"version": "15.5.8",
"resolved": "http://registry.npm.taobao.org/@types/prop-types/download/@types/prop-types-15.5.8.tgz",
"integrity": "sha1-iuTg6iBf6Vw5AaWh339mSV46Vs4=",
"dev": true
},
"@types/react": {
"version": "16.7.18",
"resolved": "http://registry.npm.taobao.org/@types/react/download/@types/react-16.7.18.tgz",
"integrity": "sha1-9M4NU5qJPdYeNs0RrjpeVPWkgzc=",
"dev": true,
"requires": {
"@types/prop-types": "15.5.8",
"csstype": "2.6.0"
}
},
"@types/react-dom": {
"version": "16.0.11",
"resolved": "http://registry.npm.taobao.org/@types/react-dom/download/@types/react-dom-16.0.11.tgz",
"integrity": "sha1-vRDMsNkmA0P0uaSdT3qDMKXB8IE=",
"dev": true,
"requires": {
"@types/react": "16.7.18"
}
},
"abab": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
......@@ -2270,6 +2295,12 @@
"cssom": "0.3.2"
}
},
"csstype": {
"version": "2.6.0",
"resolved": "http://registry.npm.taobao.org/csstype/download/csstype-2.6.0.tgz",
"integrity": "sha1-bPey+n/DKqs9dGgCwkTU7acTcaI=",
"dev": true
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
......@@ -4283,7 +4314,18 @@
"invariant": "2.2.4",
"loose-envify": "1.3.1",
"resolve-pathname": "2.2.0",
"value-equal": "0.4.0"
"value-equal": "0.4.0",
"warning": "3.0.0"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "http://registry.npm.taobao.org/warning/download/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "1.3.1"
}
}
}
},
"hoek": {
......@@ -6882,6 +6924,38 @@
"prop-types": "15.6.1"
}
},
"react-is": {
"version": "16.7.0",
"resolved": "http://registry.npm.taobao.org/react-is/download/react-is-16.7.0.tgz",
"integrity": "sha1-wb0hxk8fE2TG9waV7ALWk5L0G/o="
},
"react-router": {
"version": "4.3.1",
"resolved": "http://registry.npm.taobao.org/react-router/download/react-router-4.3.1.tgz",
"integrity": "sha1-qtpK7xTICcsuaGsFzuR0IjRQbE4=",
"requires": {
"history": "4.7.2",
"hoist-non-react-statics": "2.5.0",
"invariant": "2.2.4",
"loose-envify": "1.3.1",
"path-to-regexp": "1.7.0",
"prop-types": "15.6.1",
"warning": "4.0.1"
}
},
"react-router-dom": {
"version": "4.3.1",
"resolved": "http://registry.npm.taobao.org/react-router-dom/download/react-router-dom-4.3.1.tgz",
"integrity": "sha1-TCYZ/CTE+ofJ/Rj0+0pD/mP71cY=",
"requires": {
"history": "4.7.2",
"invariant": "2.2.4",
"loose-envify": "1.3.1",
"prop-types": "15.6.1",
"react-router": "4.3.1",
"warning": "4.0.1"
}
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
......
......@@ -7,17 +7,15 @@
"authors": "fi3ework",
"files": [
"index.js",
"matchPath.js",
"LiveRoute.js"
],
"main": "index.js",
"main": "dist/index.js",
"module": "es/index.js",
"sideEffects": false,
"scripts": {
"build": "node ./tools/build.js",
"watch": "babel ./modules -d . --ignore react-router --watch",
"prepublishOnly": "node ./tools/build.js",
"clean": "git clean -fdX .",
"build": "rm -fr dist && tsc",
"watch": "tsc -b -w --pretty",
"prepare": "npm run build",
"lint": "eslint modules",
"test": "jest"
},
......@@ -25,46 +23,21 @@
"react": ">=15"
},
"dependencies": {
"history": "^4.7.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.1",
"react-is": "^16.7.0",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"warning": "^4.0.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^8.2.3",
"babel-jest": "^22.4.3",
"babel-plugin-dev-expression": "^0.2.1",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
"babel-plugin-transform-remove-console": "^6.9.4",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-1": "^6.5.0",
"eslint": "^4.19.1",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-react": "^7.7.0",
"gzip-size": "^4.1.0",
"@types/react": "^16.7.18",
"@types/react-dom": "^16.0.11",
"jest": "^23.1.0",
"pretty-bytes": "^4.0.2",
"raf": "^3.4.0",
"react": "^16.3.2",
"react-addons-test-utils": "^15.6.2",
"react-dom": "^16.3.2",
"rollup": "^0.58.2",
"rollup-plugin-babel": "^3.0.4",
"rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-plugin-uglify": "^3.0.0"
},
"browserify": {
"transform": [
"loose-envify"
]
"tslint-config-prettier": "^1.17.0"
},
"jest": {
"setupFiles": [
......
import warning from 'warning'
import invariant from 'invariant'
import React from 'react'
import PropTypes from 'prop-types'
import matchPath from './matchPath'
import React, { ReactNode } from 'react'
import PropTypes, { ReactComponentLike } from 'prop-types'
import ReactDOM from 'react-dom'
import { matchPath } from 'react-router'
import { isValidElementType } from 'react-is'
const isEmptyChildren = children => React.Children.count(children) === 0
const NORMAL_RENDER_MATCHED = 'normal matched render'
const NORMAL_RENDER_UNMATCHED = 'normal unmatched render (unmount)'
const NORMAL_RENDER_ON_INIT = 'normal render (matched or unmatched)'
const HIDE_RENDER = 'hide route when livePath matched'
type CacheDom = HTMLElement | null
interface IProps {
computedMatch: any // private, from <Switch>
path: string
exact?: boolean
strict?: boolean
sensitive?: boolean
component?: ReactComponentLike
render?: React.StatelessComponent
location: string
livePath?: string
alwaysLive: boolean
name?: string
}
/**
* The public API for matching a single path and rendering.
*/
class Route extends React.Component {
class LiveRoute extends React.Component<IProps, any> {
static propTypes = {
computedMatch: PropTypes.object, // private, from <Switch>
path: PropTypes.string,
exact: PropTypes.bool,
strict: PropTypes.bool,
sensitive: PropTypes.bool,
component: PropTypes.func,
component: (props, propName): any => {
if (props[propName] && !isValidElementType(props[propName])) {
return new Error(`Invalid prop 'component' supplied to 'Route': the prop is not a valid React component`)
}
},
render: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
location: PropTypes.object,
......@@ -29,6 +51,10 @@ class Route extends React.Component {
name: PropTypes.string // for LiveRoute debug
}
static defaultProps = {
alwaysLive: false
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.object.isRequired,
......@@ -41,6 +67,9 @@ class Route extends React.Component {
router: PropTypes.object.isRequired
}
_latestMatchedRouter: any
routeDom: CacheDom = null
getChildContext() {
return {
router: {
......@@ -54,12 +83,12 @@ class Route extends React.Component {
}
state = {
match: this.computeMatch(this.props, this.context.router)
match: this.computeMatch(this.props as any, this.context.router)
}
liveState = NORMAL_RENDER_ON_INIT
scrollPosBackup = null
previousDisplayStyle = null
scrollPosBackup: { left: number; top: number } | null = null
previousDisplayStyle: string | null = null
componentWillMount() {
warning(
......@@ -153,7 +182,7 @@ class Route extends React.Component {
* Back up current router every time it is rendered normally, backing up to the next livePath rendering
*/
computeMatchWithLive(props, nextProps, nextContext, match) {
console.log(`>>> ` + this.props.name + ` <<<`)
// console.log(`>>> ` + this.props.name + ` <<<`)
// compute if livePath match
const livePath = nextProps.livePath
const nextPropsWithLivePath = { ...nextProps, paths: livePath }
......@@ -222,7 +251,7 @@ class Route extends React.Component {
// get DOM of Route
getRouteDom() {
let routeDom = ReactDOM.findDOMNode(this)
this.routeDom = routeDom
this.routeDom = routeDom as CacheDom
}
// backup scroll and hide DOM
......@@ -311,7 +340,7 @@ class Route extends React.Component {
// the following is the same as Route of react-router, just render it normally
if (component) return match ? React.createElement(component, props) : null
if (render) return match ? render(props) : null
if (render) return match ? render(props as any) : null
if (typeof children === 'function') return children(props)
......@@ -321,4 +350,4 @@ class Route extends React.Component {
}
}
export default Route
export { LiveRoute }
export { LiveRoute } from './LiveRoute'
const BABEL_ENV = process.env.BABEL_ENV
const building = BABEL_ENV != undefined && BABEL_ENV !== 'cjs'
const plugins = ['transform-remove-console']
if (process.env.NODE_ENV === 'production') {
plugins.push(
'dev-expression',
'transform-react-remove-prop-types'
)
}
module.exports = {
presets: [
[ 'es2015', {
loose: true,
modules: building ? false : 'commonjs'
} ],
'stage-1',
'react'
],
plugins: plugins
}
const execSync = require("child_process").execSync;
const exec = (command, extraEnv) =>
execSync(command, {
stdio: "inherit",
env: Object.assign({}, process.env, extraEnv)
});
console.log("Building CommonJS modules ...");
exec("babel modules -d . --ignore react-router,", {
BABEL_ENV: "cjs"
});
console.log("\nBuilding ES modules ...");
exec("babel modules -d es --ignore react-router", {
BABEL_ENV: "es"
});
{
"compilerOptions": {
"strict": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/index/*"]
},
"outDir": "dist/",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": false,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": false
},
"include": ["src"]
}
{
"presets": [ "../tools/babel-preset" ]
}
"extends": "./tsconfig.json"
}
\ No newline at end of file
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}
\ No newline at end of file
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"linterOptions": {
"exclude": ["node_modules/**/*.ts"]
},
"rules": {
"no-var-requires": false,
"max-classes-per-file": false,
// "prettier": true,
"arrow-parens": false,
"arrow-return-shorthand": [false],
"comment-format": [true, "check-space"],
"import-blacklist": [true, "rxjs"],
"interface-over-type-literal": false,
"interface-name": false,
"member-access": false,
"member-ordering": [true, { "order": "fields-first" }],
// "newline-before-return": false,]
"no-any": false,
"no-empty-interface": false,
"no-import-side-effect": false,
"no-inferrable-types": [true, "ignore-params", "ignore-properties"],
"no-invalid-this": [true, "check-function-in-method"],
"no-null-keyword": false,
"no-require-imports": false,
"no-this-assignment": [true, { "allow-destructuring": true }],
"no-trailing-whitespace": true,
"no-unused-variable": [false, "react"],
"object-literal-sort-keys": false,
"object-literal-shorthand": false,
"one-variable-per-declaration": [false],
"only-arrow-functions": [true, "allow-declarations"],
"ordered-imports": [false],
"no-console": [false],
"prefer-method-signature": false,
"prefer-template": [true, "allow-single-concat"],
"quotemark": [true, "single", "jsx-double"],
"triple-equals": [true, "allow-null-check"],
// "type-literal-delimiter": true,
"typedef": {
"severity": "off",
"options": ["parameter", "property-declaration"]
},
"variable-name": [true, "ban-keywords", "check-format", "allow-pascal-case", "allow-leading-underscore"],
// tslint-react
"jsx-no-lambda": false
},
"jsRules": {
"object-literal-sort-keys": false // Disable for javascript
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment