Commit f9d4549c by Wee

chore: add build configuration

parent 5b82d5af
es
node_modules
umd
/*.js
!rollup.config.js
{
"presets": [ "../tools/babel-preset" ]
}
{
"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 "../Route";
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 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 "../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 "../Route";
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 "../Route";
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 "../Route";
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 "../Route";
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");
});
});
export LiveRoute 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;
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "react-router",
"version": "4.3.0-rc.3",
"description": "Declarative routing for React",
"repository": "ReactTraining/react-router",
"license": "MIT",
"authors": [
"Michael Jackson",
"Ryan Florence"
],
"files": [
"MemoryRouter.js",
"Prompt.js",
"Redirect.js",
"Route.js",
"Router.js",
"StaticRouter.js",
"Switch.js",
"es",
"index.js",
"generatePath.js",
"matchPath.js",
"withRouter.js",
"umd"
],
"main": "index.js",
"module": "es/index.js",
"sideEffects": false,
"scripts": {
"build": "node ./tools/build.js",
"watch": "babel ./modules -d . --ignore __tests__ --watch",
"prepublishOnly": "node ./tools/build.js",
"clean": "git clean -fdX .",
"lint": "eslint modules",
"test": "jest"
},
"peerDependencies": {
"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",
"warning": "^3.0.0"
},
"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-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",
"jest": "^22.4.3",
"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"
]
},
"jest": {
"setupFiles": [
"raf/polyfill"
]
},
"keywords": [
"react",
"router",
"route",
"routing",
"history",
"link"
]
}
import babel from "rollup-plugin-babel";
import uglify from "rollup-plugin-uglify";
import replace from "rollup-plugin-replace";
import commonjs from "rollup-plugin-commonjs";
import resolve from "rollup-plugin-node-resolve";
const config = {
input: "modules/index.js",
output: {
name: "ReactRouter",
globals: {
react: "React"
}
},
external: ["react"],
plugins: [
babel({
exclude: "node_modules/**",
plugins: ["external-helpers"]
}),
resolve(),
commonjs({
include: /node_modules/
}),
replace({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV)
})
]
};
if (process.env.NODE_ENV === "production") {
config.plugins.push(uglify());
}
export default config;
const BABEL_ENV = process.env.BABEL_ENV
const building = BABEL_ENV != undefined && BABEL_ENV !== 'cjs'
const plugins = []
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 fs = require('fs')
const execSync = require('child_process').execSync
const prettyBytes = require('pretty-bytes')
const gzipSize = require('gzip-size')
const exec = (command, extraEnv) =>
execSync(command, {
stdio: 'inherit',
env: Object.assign({}, process.env, extraEnv)
})
console.log('Building CommonJS modules ...')
exec('babel modules -d . --ignore __tests__', {
BABEL_ENV: 'cjs'
})
console.log('\nBuilding ES modules ...')
exec('babel modules -d es --ignore __tests__', {
BABEL_ENV: 'es'
})
console.log('\nBuilding react-router.js ...')
exec('rollup -c -f umd -o umd/react-router.js', {
BABEL_ENV: 'umd',
NODE_ENV: 'development'
})
console.log('\nBuilding react-router.min.js ...')
exec('rollup -c -f umd -o umd/react-router.min.js', {
BABEL_ENV: 'umd',
NODE_ENV: 'production'
})
const size = gzipSize.sync(
fs.readFileSync('umd/react-router.min.js')
)
console.log('\ngzipped, the UMD build is %s', prettyBytes(size))
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