84348047 |
import { applyMiddleware, createStore, compose } from "redux";
|
30db14b1 |
|
84348047 |
import installBrowserRouter from "./action-router";
import addChangeUrlEvent from "./change-url-event.js";
import addMissingHistoryEvents from "./history-events.js";
|
30db14b1 |
|
7b1d07fc |
const console_log = console.log;
console.log = () => {};
function with_console(cb) {
console.log = console_log;
try {
cb();
} catch (e) {
console.log = () => {};
throw e;
}
console.log = () => {};
}
|
30db14b1 |
function createLocation(path) {
return {
|
84348047 |
hash: "#hash",
host: "example.com",
hostname: "example",
origin: "",
href: "",
|
30db14b1 |
pathname: path,
port: 80,
|
84348047 |
protocol: "https:"
|
30db14b1 |
};
}
|
84348047 |
function createFakeWindow(path = "/path/to/thing") {
let locations = [createLocation("(root)")];
|
7b1d07fc |
function pushLocation(window, path) {
let newLoc = createLocation(path);
locations.push(newLoc);
window.location = newLoc;
return newLoc;
}
function popLocation(window) {
locations.pop();
|
84348047 |
let newLoc = locations[locations.length - 1];
|
7b1d07fc |
window.location = newLoc;
return newLoc;
}
|
30db14b1 |
const window = {
history: {
|
84348047 |
pushState: jest.fn((_, __, path) => {
window.location = pushLocation(window, path);
}),
replaceState: jest.fn()
|
30db14b1 |
}
};
|
7b1d07fc |
pushLocation(window, path);
|
30db14b1 |
const map = {};
window.addEventListener = jest.fn((event, cb) => {
map[event] = cb;
});
|
7b1d07fc |
function prepareEvent(window, evName) {
|
84348047 |
if (evName == "popstate") {
|
7b1d07fc |
window.location = popLocation(window);
}
}
|
30db14b1 |
window.dispatchEvent = jest.fn(ev => {
const evName = ev.type;
|
84348047 |
if (map[evName]) {
|
7b1d07fc |
prepareEvent(window, evName);
|
30db14b1 |
map[evName].handleEvent(ev);
}
});
return window;
}
|
84348047 |
function setupTest(routesConfig, path = "/path/to/thing") {
|
4b39d368 |
const window = createFakeWindow(path);
|
30db14b1 |
const mockPushState = window.history.pushState;
|
f7417070 |
addMissingHistoryEvents(window, window.history);
addChangeUrlEvent(window, window.history);
|
30db14b1 |
|
84348047 |
const { enhancer, init } = installBrowserRouter(routesConfig, window);
|
09761a45 |
const reduce = jest.fn();
|
f7417070 |
|
84348047 |
const store = createStore(reduce, enhancer);
|
30db14b1 |
|
09761a45 |
function urlChanges() {
return mockPushState.mock.calls.map(item => item[2]);
}
function actionsDispatched() {
return reduce.mock.calls.map(item => item[1]).slice(1);
}
function fireUrlChange(path) {
|
84348047 |
window.dispatchEvent(
new CustomEvent("urlchanged", { detail: createLocation(path) })
);
|
09761a45 |
}
|
30db14b1 |
|
84348047 |
return {
store,
reduce,
window,
urlChanges,
actionsDispatched,
fireUrlChange,
init
};
|
30db14b1 |
}
it("router handles exact match in preference to wildcard match", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
const action = { type: actionType, id: 1 };
|
30db14b1 |
const routesConfig = [
["/somewhere/:id", actionType, {}],
|
84348047 |
["/somewhere", actionType, { id: 1 }]
|
30db14b1 |
];
|
84348047 |
const { urlChanges, store } = setupTest(routesConfig);
|
30db14b1 |
// when
store.dispatch(action);
// then
|
84348047 |
expect(urlChanges()).toEqual(["/somewhere"]);
|
30db14b1 |
});
|
7e700dcc |
it("router doees not dispatch an action from url change that is caused by action dispatch", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
|
7e700dcc |
const id = "1";
const view = "home";
|
84348047 |
const action = { type: actionType, id, view };
|
7e700dcc |
const routesConfig = [
["/somewhere/:id/:view", actionType, {}],
|
84348047 |
["/somewhere/:id/default", actionType, { view: "home" }]
|
7e700dcc |
];
|
84348047 |
const { urlChanges, store, actionsDispatched, init } = setupTest(
routesConfig
);
|
7e700dcc |
// when
store.dispatch(action);
// then
expect(actionsDispatched()).toEqual([action]);
});
|
7b1d07fc |
it("popstate doesn't cause a pushstate", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
|
7b1d07fc |
const id = "1";
const view = "home";
|
84348047 |
const action = { type: actionType, id, view };
|
7b1d07fc |
const routesConfig = [
["/somewhere/:id/:view", actionType, {}],
|
84348047 |
["/somewhere/:id/default", actionType, { view: "home" }]
|
7b1d07fc |
];
const {
urlChanges,
store,
actionsDispatched,
fireUrlChange,
init,
window
|
84348047 |
} = setupTest(routesConfig, "/somewhere/foo/default");
|
7b1d07fc |
init();
|
84348047 |
window.history.pushState({}, "", "/somwhere/bar/default");
|
7b1d07fc |
// when
|
84348047 |
window.dispatchEvent(new CustomEvent("popstate", {}));
|
7b1d07fc |
// then
expect(urlChanges().length).toEqual(1);
});
|
30db14b1 |
it("router handles wildcard with extra args correctly", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
const action = { type: actionType, id: 1, view: "home" };
|
30db14b1 |
const routesConfig = [
["/somewhere/:id/:view", actionType, {}],
|
84348047 |
["/somewhere/:id/default", actionType, { view: "home" }]
|
30db14b1 |
];
|
84348047 |
const { urlChanges, store } = setupTest(routesConfig);
|
30db14b1 |
// when
store.dispatch(action);
// then
|
84348047 |
expect(urlChanges()).toEqual(["/somewhere/1/default"]);
|
30db14b1 |
});
it("router handles wildcard with extraArgs correctly with reverse order", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
const action = { type: actionType, id: 1, view: "home" };
|
30db14b1 |
const routesConfig = [
|
84348047 |
["/somewhere/:id/default", actionType, { view: "home" }],
["/somewhere/:id/:view", actionType, {}]
|
30db14b1 |
];
|
84348047 |
const { urlChanges, store } = setupTest(routesConfig);
|
30db14b1 |
// when
store.dispatch(action);
// then
|
84348047 |
expect(urlChanges()).toEqual(["/somewhere/1/default"]);
|
30db14b1 |
});
it("router handles wildcard without extraArgs correctly", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
const action = { type: actionType, id: 1 };
const routesConfig = [["/somewhere/:id/default", actionType, {}]];
const { urlChanges, store } = setupTest(routesConfig);
|
30db14b1 |
// when
store.dispatch(action);
// then
|
84348047 |
expect(urlChanges()).toEqual(["/somewhere/1/default"]);
|
30db14b1 |
});
it("router handles wildcard with no match correctly", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
const action = { type: actionType, foo: 1 };
const routesConfig = [["/somewhere/:id/default", actionType, {}]];
const { urlChanges, store } = setupTest(routesConfig);
|
30db14b1 |
// when
store.dispatch(action);
// then ( no url changes triggered)
expect(urlChanges()).toEqual([]);
});
it("router does not match when all args are not accounted for", () => {
//given
|
84348047 |
const actionType = "THE_ACTION";
const action = { type: actionType, id: 1, view: "home" };
const routesConfig = [["/somewhere/:id/default", actionType, {}]];
const { urlChanges, store } = setupTest(routesConfig);
|
30db14b1 |
// when
store.dispatch(action);
// then ( no url changes triggered)
expect(urlChanges()).toEqual([]);
});
it("router should match non-wildcard route in preference to wildcard route", () => {
// given
const routesConfig = [
|
84348047 |
["/somewhere/:id", "ACTION_NAME", {}],
["/somewhere/specific", "ACTION_NAME", { id: 1 }]
|
30db14b1 |
];
|
84348047 |
const { actionsDispatched, fireUrlChange } = setupTest(routesConfig);
|
30db14b1 |
// when
|
84348047 |
fireUrlChange("/somewhere/specific");
|
30db14b1 |
// then
|
84348047 |
expect(actionsDispatched()).toEqual([{ type: "ACTION_NAME", id: 1 }]);
|
30db14b1 |
});
|
10f5ef83 |
it("router should throw on duplicate paths", () => {
// given
const routesConfig = [
|
84348047 |
["/somewhere/:id", "ACTION_NAME", {}],
["/somewhere/:id", "ACTION_NAME", {}]
|
10f5ef83 |
];
|
84348047 |
expect(() => {
|
10f5ef83 |
setupTest(routesConfig);
}).toThrow();
});
it("router should throw on equally specific routes", () => {
// given
const routesConfig = [
|
84348047 |
["/somewhere/:id", "ACTION_NAME", {}],
["/somewhere/:specific", "ACTION_NAME", {}]
|
10f5ef83 |
];
|
84348047 |
expect(() => {
|
65acc8b5 |
setupTest(routesConfig);
|
10f5ef83 |
}).toThrow();
});
|
30db14b1 |
it("router should match less-wildcarded routes in preference to more wildcarded routes", () => {
//given
const routesConfig = [
|
10f5ef83 |
["/somewhere/:id/:view/:bar", "ACTION_NAME", {}],
|
84348047 |
["/somewhere/:foo/:id/:view/:baz", "ACTION_NAME", {}]
|
30db14b1 |
];
|
84348047 |
const { actionsDispatched, fireUrlChange } = setupTest(routesConfig);
|
30db14b1 |
// when
|
84348047 |
fireUrlChange("/somewhere/specific/etc/bar");
|
30db14b1 |
// then
|
84348047 |
expect(actionsDispatched()).toEqual([
{ type: "ACTION_NAME", id: "specific", view: "etc", bar: "bar" }
]);
|
d93719b8 |
});
it("router should propagate matches through non-matching cases", () => {
//given
const routesConfig = [
|
84348047 |
["/somewhere/specific/:view", "ACTION_NAME", { id: 1 }],
|
d93719b8 |
["/somewhere/:id/:view", "ACTION_NAME", {}],
["/not/a/match", "ACTION_NAME", {}]
];
|
84348047 |
const { actionsDispatched, fireUrlChange } = setupTest(routesConfig);
|
d93719b8 |
// when
|
84348047 |
fireUrlChange("/somewhere/specific/etc");
|
d93719b8 |
// then
|
84348047 |
expect(actionsDispatched()).toEqual([
{ type: "ACTION_NAME", id: 1, view: "etc" }
]);
|
30db14b1 |
});
it("router should give precedence to exact match first in equally-specific routes (/a/:b vs /:a/b)", () => {
// given
const routesConfig = [
["/something/:dynamic", "ACTION_NAME", {}],
|
84348047 |
["/:dyn/something", "ACTION_NAME", {}]
|
30db14b1 |
];
|
84348047 |
const { actionsDispatched, fireUrlChange } = setupTest(routesConfig);
|
30db14b1 |
// when
fireUrlChange("/something/something");
// then
|
84348047 |
expect(actionsDispatched()).toEqual([
{ type: "ACTION_NAME", dynamic: "something" }
]);
|
30db14b1 |
});
|
4b39d368 |
it("router handles the current location when initialized", () => {
// given
const routesConfig = [
["/something/:dynamic", "ACTION_NAME", {}],
|
84348047 |
["/:dyn/something", "ACTION_NAME", {}]
|
4b39d368 |
];
// when
/// We break the pattern because we're testing store construction.
|
84348047 |
const { actionsDispatched, init } = setupTest(
routesConfig,
"/something/something"
);
|
09761a45 |
init();
|
4b39d368 |
// then
|
84348047 |
expect(actionsDispatched()).toEqual([
{ type: "ACTION_NAME", dynamic: "something" }
]);
|
4b39d368 |
});
|
478c0afb |
it("pathForAction should render a route", () => {
// given
const routesConfig = [
["/something/:dynamic", "ACTION_NAME", {}],
|
84348047 |
["/:dyn/something", "ACTION_NAME", {}]
|
478c0afb |
];
|
84348047 |
const action = { type: "ACTION_NAME", dynamic: "hooray" };
const { store } = setupTest(routesConfig);
|
478c0afb |
// when
const actual = store.pathForAction(action);
// then
|
84348047 |
expect(actual).toEqual("/something/hooray");
|
478c0afb |
});
|
7b1d07fc |
console.log = console_log;
|