30db14b1 |
import {applyMiddleware, createStore, compose} from 'redux';
import installBrowserRouter from './action-router';
|
f7417070 |
import addChangeUrlEvent from './change-url-event.js';
import addMissingHistoryEvents from './history-events.js';
|
30db14b1 |
function createLocation(path) {
return {
hash: '#hash',
host: 'example.com',
hostname: 'example',
origin: '',
href: '',
pathname: path,
port: 80,
protocol: 'https:'
};
}
|
4b39d368 |
function createFakeWindow(path='/path/to/thing') {
|
30db14b1 |
const window = {
|
4b39d368 |
location: createLocation(path),
|
30db14b1 |
history: {
pushState: jest.fn(),
replaceState: jest.fn()
}
};
const map = {};
window.addEventListener = jest.fn((event, cb) => {
map[event] = cb;
});
window.dispatchEvent = jest.fn(ev => {
const evName = ev.type;
if(map[evName]) {
map[evName].handleEvent(ev);
}
});
return window;
}
|
4b39d368 |
function setupTest(routesConfig, path='/path/to/thing') {
const window = createFakeWindow(path);
|
30db14b1 |
const mockPushState = window.history.pushState;
|
f7417070 |
addMissingHistoryEvents(window, window.history);
addChangeUrlEvent(window, window.history);
|
30db14b1 |
|
09761a45 |
const {middleware, enhancer, init} = installBrowserRouter(routesConfig, window);
const reduce = jest.fn();
|
f7417070 |
|
09761a45 |
const store = createStore(
reduce,
compose(
enhancer,
applyMiddleware(
middleware)));
|
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) {
window.dispatchEvent(new CustomEvent('urlchanged', {detail: createLocation(path)}));
}
|
30db14b1 |
|
09761a45 |
return {store, reduce, window, urlChanges, actionsDispatched, fireUrlChange, init};
|
30db14b1 |
}
it("router handles exact match in preference to wildcard match", () => {
//given
const actionType = 'THE_ACTION';
const action = {type: actionType, id: 1};
const routesConfig = [
["/somewhere/:id", actionType, {}],
["/somewhere", actionType, {id: 1}],
];
const {urlChanges, store} = setupTest(routesConfig);
// when
store.dispatch(action);
// then
expect(urlChanges()).toEqual(['/somewhere']);
});
it("router handles wildcard with extra args correctly", () => {
//given
const actionType = 'THE_ACTION';
const action = {type: actionType, id: 1, view: "home"};
const routesConfig = [
["/somewhere/:id/:view", actionType, {}],
["/somewhere/:id/default", actionType, {view: "home"}],
];
const {urlChanges, store} = setupTest(routesConfig);
// when
store.dispatch(action);
// then
expect(urlChanges()).toEqual(['/somewhere/1/default']);
});
it("router handles wildcard with extraArgs correctly with reverse order", () => {
//given
const actionType = 'THE_ACTION';
const action = {type: actionType, id: 1, view: "home"};
const routesConfig = [
["/somewhere/:id/default", actionType, {view: "home"}],
["/somewhere/:id/:view", actionType, {}],
];
const {urlChanges, store} = setupTest(routesConfig);
// when
store.dispatch(action);
// then
expect(urlChanges()).toEqual(['/somewhere/1/default']);
});
it("router handles wildcard without extraArgs correctly", () => {
//given
const actionType = 'THE_ACTION';
const action = {type: actionType, id: 1};
const routesConfig = [
["/somewhere/:id/default", actionType, {}],
];
const {urlChanges, store} = setupTest(routesConfig);
// when
store.dispatch(action);
// then
expect(urlChanges()).toEqual(['/somewhere/1/default']);
});
it("router handles wildcard with no match correctly", () => {
//given
const actionType = 'THE_ACTION';
const action = {type: actionType, foo: 1};
const routesConfig = [
["/somewhere/:id/default", actionType, {}],
];
const {urlChanges, store} = setupTest(routesConfig);
// 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
const actionType = 'THE_ACTION';
const action = {type: actionType, id: 1, view: "home"};
const routesConfig = [
["/somewhere/:id/default", actionType, {}],
];
const {urlChanges, store} = setupTest(routesConfig);
// 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 = [
['/somewhere/:id', 'ACTION_NAME', {}],
["/somewhere/specific", 'ACTION_NAME', {id: 1}],
];
const {actionsDispatched, fireUrlChange} = setupTest(routesConfig);
// when
fireUrlChange('/somewhere/specific');
// then
expect(actionsDispatched()).toEqual([{type: 'ACTION_NAME', id: 1}]);
});
|
10f5ef83 |
it("router should throw on duplicate paths", () => {
// given
const routesConfig = [
['/somewhere/:id', 'ACTION_NAME', {}],
["/somewhere/:id", 'ACTION_NAME', {}],
];
expect( () => {
setupTest(routesConfig);
}).toThrow();
});
it("router should throw on equally specific routes", () => {
// given
const routesConfig = [
['/somewhere/:id', 'ACTION_NAME', {}],
["/somewhere/:specific", 'ACTION_NAME', {}],
];
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", {}],
["/somewhere/:foo/:id/:view/:baz", "ACTION_NAME", {}],
|
30db14b1 |
];
const {actionsDispatched, fireUrlChange} = setupTest(routesConfig);
// when
|
10f5ef83 |
fireUrlChange('/somewhere/specific/etc/bar');
|
30db14b1 |
// then
|
10f5ef83 |
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 = [
["/somewhere/specific/:view", "ACTION_NAME", {id: 1}],
["/somewhere/:id/:view", "ACTION_NAME", {}],
["/not/a/match", "ACTION_NAME", {}]
];
const {actionsDispatched, fireUrlChange} = setupTest(routesConfig);
// when
fireUrlChange('/somewhere/specific/etc');
// then
|
30db14b1 |
expect(actionsDispatched()).toEqual([{type:'ACTION_NAME', id: 1, view: "etc"}]);
});
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", {}],
["/:dyn/something", "ACTION_NAME", {}],
];
const {actionsDispatched, fireUrlChange} = setupTest(routesConfig);
// when
fireUrlChange("/something/something");
// then
expect(actionsDispatched()).toEqual([{type: 'ACTION_NAME', dynamic: 'something'}]);
});
|
4b39d368 |
it("router handles the current location when initialized", () => {
// given
const routesConfig = [
["/something/:dynamic", "ACTION_NAME", {}],
["/:dyn/something", "ACTION_NAME", {}],
];
// when
/// We break the pattern because we're testing store construction.
|
09761a45 |
const {actionsDispatched, init} = setupTest(routesConfig, '/something/something');
init();
|
4b39d368 |
// then
expect(actionsDispatched()).toEqual([{type: 'ACTION_NAME', dynamic: 'something'}]);
});
|