mirror of
https://gitlab.silvrtree.co.uk/martind2000/frontexpress.git
synced 2025-02-10 17:29:16 +00:00
Added browser history management issue #1
This commit is contained in:
parent
9c7681e2b4
commit
390cd54a48
@ -26,7 +26,8 @@ export default class Application {
|
||||
|
||||
constructor() {
|
||||
this.routers = [];
|
||||
this.DOMLoading = false;
|
||||
this.isDOMLoaded = false;
|
||||
this.isDOMReady = false;
|
||||
this.settings = new Settings();
|
||||
}
|
||||
|
||||
@ -76,33 +77,40 @@ export default class Application {
|
||||
*/
|
||||
|
||||
listen(callback) {
|
||||
document.onreadystatechange = () => {
|
||||
const uri = window.location.pathname + window.location.search;
|
||||
const method = 'GET';
|
||||
const request = {method, uri};
|
||||
const response = {status: 200, statusText: 'OK'};
|
||||
window.onbeforeunload = () => {
|
||||
this._callMiddlewareExited();
|
||||
};
|
||||
|
||||
// gathers all routes impacted by the current browser location
|
||||
const currentRoutes = this._routes(uri, method);
|
||||
window.onpopstate = (event) => {
|
||||
if (event.state) {
|
||||
const {response, request} = event.state;
|
||||
const currentRoutes = this._routes(request.uri, request.method);
|
||||
|
||||
// listen dom events
|
||||
if (document.readyState === 'loading') {
|
||||
this.DOMLoading = true;
|
||||
this._callMiddlewareEntered(currentRoutes, request);
|
||||
} else if (document.readyState === 'interactive') {
|
||||
if (!this.DOMLoading) {
|
||||
this._callMiddlewareUpdated(currentRoutes, request, response);
|
||||
}
|
||||
};
|
||||
|
||||
document.onreadystatechange = () => {
|
||||
const request = {method: 'GET', uri: window.location.pathname + window.location.search};
|
||||
const response = {status: 200, statusText: 'OK'};
|
||||
const currentRoutes = this._routes();
|
||||
// DOM state
|
||||
if (document.readyState === 'loading' && !this.isDOMLoaded) {
|
||||
this.isDOMLoaded = true;
|
||||
this._callMiddlewareEntered(currentRoutes, request);
|
||||
} else if (document.readyState === 'interactive' && !this.isDOMReady) {
|
||||
if (!this.isDOMLoaded) {
|
||||
this.isDOMLoaded = true;
|
||||
this._callMiddlewareEntered(currentRoutes, request);
|
||||
}
|
||||
this.isDOMReady = true;
|
||||
this._callMiddlewareUpdated(currentRoutes, request, response);
|
||||
if (callback) {
|
||||
callback(request, response);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this._callMiddlewareExited();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -183,12 +191,13 @@ export default class Application {
|
||||
* @private
|
||||
*/
|
||||
|
||||
_routes(uri, method) {
|
||||
_routes(uri=window.location.pathname + window.location.search, method='GET') {
|
||||
const currentRoutes = [];
|
||||
for (const router of this.routers) {
|
||||
const routes = router.routes(uri, method);
|
||||
currentRoutes.push(...routes);
|
||||
}
|
||||
|
||||
return currentRoutes;
|
||||
}
|
||||
|
||||
@ -297,13 +306,12 @@ export default class Application {
|
||||
|
||||
|
||||
/**
|
||||
* Make an ajax request.
|
||||
* Make an ajax request. Manage History#pushState if history object set.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
_fetch({method, uri, headers, data}, resolve, reject) {
|
||||
|
||||
_fetch({method, uri, headers, data, history}, resolve, reject) {
|
||||
const httpMethodTransformer = this.get(`http ${method} transformer`);
|
||||
if (httpMethodTransformer) {
|
||||
uri = httpMethodTransformer.uri ? httpMethodTransformer.uri({uri, headers, data}) : uri;
|
||||
@ -323,6 +331,13 @@ export default class Application {
|
||||
// invokes http request
|
||||
this.settings.get('http requester').fetch({method, uri, headers, data},
|
||||
(request, response) => {
|
||||
if (history) {
|
||||
if (history.state) {
|
||||
response.historyState = history.state;
|
||||
}
|
||||
history.state = {request, response};
|
||||
window.history.pushState(history.state, history.title, history.uri);
|
||||
}
|
||||
this._callMiddlewareUpdated(currentRoutes, request, response);
|
||||
if (resolve) {
|
||||
resolve(request, response);
|
||||
@ -403,21 +418,24 @@ HTTP_METHODS.reduce((reqProto, method) => {
|
||||
*
|
||||
* // HTTP GET method
|
||||
* httpGet('/route1');
|
||||
*
|
||||
* // HTTP GET method
|
||||
* httpGet({uri: '/route1', data: {'p1': 'val1'});
|
||||
* // uri invoked => /route1?p1=val1
|
||||
*
|
||||
* // HTTP POST method
|
||||
* httpPost('/user');
|
||||
* ...
|
||||
* // HTTP GET method with browser history management
|
||||
* httpGet({uri: '/api/users', history: {state: {foo: "bar"}, title: 'users page', uri: '/view/users'});
|
||||
*
|
||||
* @param {String|Object} uri or object containing uri, http headers, data
|
||||
* Samples above can be applied on other HTTP methods.
|
||||
*
|
||||
* @param {String|Object} uri or object containing uri, http headers, data, history
|
||||
* @param {Function} success callback
|
||||
* @param {Function} failure callback
|
||||
* @public
|
||||
*/
|
||||
const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
||||
reqProto[httpMethodName] = function(request, resolve, reject) {
|
||||
let {uri, headers, data} = request;
|
||||
let {uri, headers, data, history} = request;
|
||||
if (!uri) {
|
||||
uri = request;
|
||||
}
|
||||
@ -425,7 +443,8 @@ HTTP_METHODS.reduce((reqProto, method) => {
|
||||
uri,
|
||||
method,
|
||||
headers,
|
||||
data
|
||||
data,
|
||||
history
|
||||
}, resolve, reject);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
/*eslint-env mocha*/
|
||||
import chai, {assert} from 'chai';
|
||||
import sinon from 'sinon';
|
||||
//import jsdom from 'jsdom';
|
||||
import frontexpress from '../lib/frontexpress';
|
||||
import Requester from '../lib/requester';
|
||||
|
||||
@ -25,18 +26,108 @@ describe('Application', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('listen method', () => {
|
||||
let eventFn = {};
|
||||
// // JSDOM cannot manage pushState/onpopstate/window.location
|
||||
// see https://github.com/tmpvar/jsdom/issues/1565
|
||||
//
|
||||
// describe('listen method with JSDOM', () => {
|
||||
// before(() => {
|
||||
// // Init DOM with a fake document
|
||||
// // <base> and uri (initial uri) allow to do pushState in jsdom
|
||||
// jsdom.env({
|
||||
// html:`
|
||||
// <html>
|
||||
// <head>
|
||||
// <base href="http://localhost:8080/"></base>
|
||||
// </head>
|
||||
// </html>
|
||||
// `,
|
||||
// url: 'http://localhost:8080/',
|
||||
// done(err, window) {
|
||||
// global.window = window;
|
||||
// global.document = window.document;
|
||||
// window.console = global.console;
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// let requester;
|
||||
// beforeEach(()=>{
|
||||
// requester = new Requester();
|
||||
// sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => {
|
||||
// resolve(
|
||||
// {uri, method, headers, data},
|
||||
// {status: 200, statusText: 'OK', responseText:''}
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('history management without state object', (done) => {
|
||||
// const spy_pushState = sinon.spy(window.history, 'pushState');
|
||||
|
||||
// const app = frontexpress();
|
||||
// const m = frontexpress.Middleware();
|
||||
// const spy_middleware = sinon.stub(m, 'updated');
|
||||
|
||||
// app.set('http requester', requester);
|
||||
// app.use('/api/route1', m);
|
||||
// app.listen();
|
||||
|
||||
// const spy_onpopstate = sinon.spy(window, 'onpopstate');
|
||||
|
||||
// app.httpGet({uri:'/api/route1', history: {
|
||||
// uri: '/route1',
|
||||
// title: 'route1'
|
||||
// }},
|
||||
// (req, res) => {
|
||||
// // On request succeeded
|
||||
// assert(spy_onpopstate.callCount === 0);
|
||||
// assert(spy_pushState.calledOnce);
|
||||
// assert(spy_middleware.calledOnce);
|
||||
|
||||
// spy_middleware.reset();
|
||||
// window.history.back();
|
||||
// window.history.forward();
|
||||
// assert(spy_onpopstate.calledOnce);
|
||||
// assert(spy_middleware.calledOnce);
|
||||
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('listen method', () => {
|
||||
// Here I cannot use jsdon to make these tests :(
|
||||
// JSDOM cannot simulate readyState changes
|
||||
beforeEach(() => {
|
||||
const browserHistory = [{uri: '/'}];
|
||||
let browserHistoryIndex = 0;
|
||||
|
||||
global.document = {};
|
||||
global.window = {
|
||||
addEventListener(eventType, callback) {
|
||||
eventFn[eventType] = callback;
|
||||
},
|
||||
location: {
|
||||
pathname: '/route1',
|
||||
search: '?a=b'
|
||||
},
|
||||
history: {
|
||||
pushState(state, title, pathname) {
|
||||
browserHistory.push({uri: pathname, state});
|
||||
browserHistoryIndex++;
|
||||
global.window.location.pathname = browserHistory[browserHistoryIndex].uri;
|
||||
},
|
||||
forward() {
|
||||
browserHistoryIndex++;
|
||||
global.window.location.pathname = browserHistory[browserHistoryIndex].uri;
|
||||
if (browserHistory[browserHistoryIndex].state) {
|
||||
window.onpopstate({state:browserHistory[browserHistoryIndex].state});
|
||||
}
|
||||
},
|
||||
back() {
|
||||
browserHistoryIndex--;
|
||||
global.window.location.pathname = browserHistory[browserHistoryIndex].uri;
|
||||
if (browserHistory[browserHistoryIndex].state) {
|
||||
window.onpopstate({state: browserHistory[browserHistoryIndex].state});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -93,13 +184,102 @@ describe('Application', () => {
|
||||
app.use('/route1', m);
|
||||
app.listen(() => {
|
||||
//simulate beforeunload
|
||||
eventFn['beforeunload']();
|
||||
window.onbeforeunload();
|
||||
});
|
||||
|
||||
//simulate readystatechange
|
||||
document.readyState = 'interactive';
|
||||
document.onreadystatechange();
|
||||
});
|
||||
|
||||
it('history management without state object', (done) => {
|
||||
const requester = new Requester();
|
||||
sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => {
|
||||
resolve(
|
||||
{uri, method, headers, data},
|
||||
{status: 200, statusText: 'OK', responseText:''}
|
||||
);
|
||||
});
|
||||
|
||||
const spy_pushState = sinon.spy(window.history, 'pushState');
|
||||
|
||||
const app = frontexpress();
|
||||
const m = frontexpress.Middleware();
|
||||
const spy_middleware = sinon.stub(m, 'updated');
|
||||
|
||||
app.set('http requester', requester);
|
||||
app.use('/api/route1', m);
|
||||
app.listen();
|
||||
|
||||
const spy_onpopstate = sinon.spy(window, 'onpopstate');
|
||||
|
||||
app.httpGet({uri:'/api/route1', history: {
|
||||
uri: '/route1',
|
||||
title: 'route1'
|
||||
}},
|
||||
(req, res) => {
|
||||
// On request succeeded
|
||||
assert(spy_onpopstate.callCount === 0);
|
||||
assert(spy_pushState.calledOnce);
|
||||
assert(spy_middleware.calledOnce);
|
||||
|
||||
spy_middleware.reset();
|
||||
window.history.back();
|
||||
window.history.forward();
|
||||
assert(spy_onpopstate.calledOnce);
|
||||
assert(spy_middleware.calledOnce);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('history management with state object', (done) => {
|
||||
let stateObj;
|
||||
const requester = new Requester();
|
||||
sinon.stub(requester, 'fetch', ({uri, method, headers, data}, resolve, reject) => {
|
||||
resolve(
|
||||
{uri, method, headers, data},
|
||||
{status: 200, statusText: 'OK', responseText:''}
|
||||
);
|
||||
});
|
||||
|
||||
const spy_pushState = sinon.spy(window.history, 'pushState');
|
||||
|
||||
const app = frontexpress();
|
||||
const m = frontexpress.Middleware();
|
||||
const spy_middleware = sinon.stub(m, 'updated', (req, res) => {
|
||||
stateObj = res.historyState;
|
||||
});
|
||||
|
||||
app.set('http requester', requester);
|
||||
app.use('/api/route1', m);
|
||||
app.listen();
|
||||
|
||||
const spy_onpopstate = sinon.spy(window, 'onpopstate');
|
||||
|
||||
app.httpGet({uri:'/api/route1', history: {
|
||||
uri: '/route1',
|
||||
title: 'route1',
|
||||
state: {a: 'b', c: 'd'}
|
||||
}},
|
||||
(req, res) => {
|
||||
// On request succeeded
|
||||
assert(spy_onpopstate.callCount === 0);
|
||||
assert(spy_pushState.calledOnce);
|
||||
assert(spy_middleware.calledOnce);
|
||||
|
||||
spy_middleware.reset();
|
||||
window.history.back();
|
||||
window.history.forward();
|
||||
assert(spy_onpopstate.calledOnce);
|
||||
assert(spy_middleware.calledOnce);
|
||||
assert(stateObj);
|
||||
assert(stateObj.a === 'b');
|
||||
assert(stateObj.c === 'd');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('set/get setting method', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user