diff --git a/src/__tests__/router.test.js b/src/__tests__/router.test.js index 760bc2c..12c5c74 100644 --- a/src/__tests__/router.test.js +++ b/src/__tests__/router.test.js @@ -5,6 +5,7 @@ import CacheManager from '../cache-manager'; import Router from '../router'; let router; +let event; class StepPeople extends Steps { route = 'people'; @@ -28,10 +29,11 @@ class StepPeople extends Steps { class StepPlanet extends Steps { route = 'planet'; selector = '.step-planet'; + fallbackRoute = 'people'; canTheStepBeDisplayed () { return { canBeDisplayed: true, - fallbackRoute: null + fallbackRoute: 'people' }; } @@ -61,6 +63,10 @@ const getInstance = () => { }; beforeEach(() => { + event = { + target: true, + oldURL: 'http://localhost.com/#people' + }; window.sessionStorage.removeItem('stepManager'); document.body.innerHTML = '
'; router = getInstance(); @@ -72,7 +78,7 @@ afterEach(() => { window.location.hash = ''; }); -describe('Router function', () => { +describe('Router constructor', () => { it('Should initialize the constructor', () => { expect(router.options).toEqual({ defaultRoute: 'people', @@ -83,6 +89,11 @@ describe('Router function', () => { }, getDatasFromCache: expect.any(Function) }); + expect(router.reverseNavigation).toBe(false); + expect(router.stepCreated).toBe(false); + expect(router.applicationReady).toBe(false); + expect(router.stepRedirected).toEqual({}); + expect(router.hashChanged).toBe(router.hashChanged); }); it('Should initialize the constructor without options', () => { @@ -95,55 +106,402 @@ describe('Router function', () => { getDatasFromCache: expect.any(Function) }); }); +}); +describe('Router init', () => { it('Should call the init function', () => { router.addEvents = jest.fn(); - router.getRoute = jest.fn(); + router.getRoute = jest.fn().mockImplementation(() => ''); router.setRoute = jest.fn(); router.init(); expect(router.addEvents).toHaveBeenCalled(); expect(router.getRoute).toHaveBeenCalled(); - expect(router.setRoute).toHaveBeenCalled(); + expect(router.setRoute).toHaveBeenCalledWith('people'); }); - it('Should test route functions', () => { + it('Should call the init function with specific route', () => { + router.addEvents = jest.fn(); + router.getRoute = jest.fn().mockImplementation(() => 'planet'); + router.hashChanged = jest.fn(); + router.init(); - const previousRoute = router.getPreviousStepRoute('planet'); - const nextRoute = router.getNextStepRoute('people'); - const currentRoute = router.getRoute(); - const previousHash = router.getPreviousRoute(); - const nextRoute2 = router.getNextStepRoute('planet'); - expect(previousRoute).toBe('people'); - expect(nextRoute).toBe('planet'); - expect(currentRoute).toBe('people'); - expect(previousHash).toBe(null); - expect(nextRoute2).toBe('end'); + expect(router.addEvents).toHaveBeenCalled(); + expect(router.getRoute).toHaveBeenCalled(); + expect(router.hashChanged).toHaveBeenCalled(); + }); +}); + +describe('Router addEvents', () => { + it('Should call the addEvents function', () => { + window.addEventListener = jest.fn(); + + router.addEvents(); + + expect(window.addEventListener).toHaveBeenCalledWith( + 'hashchange', + router.hashChanged, + false + ); + }); +}); + +describe('Router hashChanged', () => { + it('Should call the hashChanged function', () => { + const route = 'planet'; + + router.getRoute = jest.fn().mockImplementation(() => route); + router.checkIfTheStepCanBeDisplay = jest.fn().mockImplementation(() => { + return { + canBeDisplayed: true, + fallbackRoute: 'people' + }; + }); + router.stepCanBeDisplayed = jest.fn(); + + router.hashChanged(event); + + expect(router.getRoute).toHaveBeenCalled(); + expect(router.checkIfTheStepCanBeDisplay).toHaveBeenCalledWith({ + route, + event + }); + expect(router.stepCanBeDisplayed).toHaveBeenCalledWith(event, 'people'); + }); + + it('Should call the hashChanged function with invalid step', () => { + const route = 'planet'; + + router.getRoute = jest.fn().mockImplementation(() => route); + router.checkIfTheStepCanBeDisplay = jest.fn().mockImplementation(() => { + return { + canBeDisplayed: false, + fallbackRoute: 'people' + }; + }); + router.stepCanBeDisplayed = jest.fn(); + + router.hashChanged(event); + + expect(router.getRoute).toHaveBeenCalled(); + expect(router.checkIfTheStepCanBeDisplay).toHaveBeenCalledWith({ + route, + event + }); + expect(router.stepCanBeDisplayed).toHaveBeenCalledWith(event, null); }); +}); + +describe('Router stepCanBeDisplayed', () => { + it('Should call the stepCanBeDisplayed function with event', () => { + router.getPreviousRoute = jest.fn().mockImplementation(() => 'people'); + router.destroyStep = jest.fn(); + router.createStep = jest.fn(); + + router.currentRoute = 'planet'; + router.stepCanBeDisplayed(event); + + expect(router.getPreviousRoute).toHaveBeenCalledWith(event); + expect(router.previousRoute).toBe('people'); + expect(router.destroyStep).toHaveBeenCalledWith('people'); + expect(router.createStep).toHaveBeenCalledWith({ + route: 'planet' + }); + }); + + it('Should call the stepCanBeDisplayed function without event', () => { + router.getPreviousRoute = jest.fn().mockImplementation(() => 'people'); + router.destroyStep = jest.fn(); + router.createStep = jest.fn(); + router.currentRoute = 'planet'; + + router.stepCanBeDisplayed(); - it('Should trigger the next route', () => { - router.getNextStepRoute = jest.fn(); + expect(router.getPreviousRoute).not.toHaveBeenCalled(); + expect(router.destroyStep).not.toHaveBeenCalledWith(); + expect(router.createStep).not.toHaveBeenCalledWith(); + }); + + it('Should call the stepCanBeDisplayed function with stepRedirected', () => { + router.getPreviousRoute = jest.fn(); + router.destroyStep = jest.fn(); + router.createStep = jest.fn(); + router.currentRoute = 'planet'; + + router.currentRoute = 'people'; + router.stepRedirected = { + redirect: true, + previousRoute: 'planet' + }; + router.stepCanBeDisplayed(event); + + expect(router.getPreviousRoute).not.toHaveBeenCalled(); + expect(router.previousRoute).toBe('planet'); + expect(router.destroyStep).toHaveBeenCalledWith('planet'); + expect(router.createStep).toHaveBeenCalledWith({ + route: 'people' + }); + expect(router.stepRedirected.redirect).toBe(false); + }); + + it('Should call the stepCanBeDisplayed function without previousRoute', () => { + router.getPreviousRoute = jest.fn().mockImplementation(() => null); + router.destroyStep = jest.fn(); + router.createStep = jest.fn(); + router.currentRoute = 'planet'; + + router.currentRoute = 'people'; + router.stepCanBeDisplayed(event); + + expect(router.getPreviousRoute).toHaveBeenCalledWith(event); + expect(router.previousRoute).toBe(null); + expect(router.destroyStep).not.toHaveBeenCalled(); + expect(router.createStep).not.toHaveBeenCalledWith(); + }); +}); + +describe('Router stepCantBeDisplayed', () => { + it('Should call the stepCantBeDisplayed function', () => { + const route = 'planet'; + + router.getPreviousRoute = jest.fn().mockImplementation(() => route); router.setRoute = jest.fn(); - router.init(); - const result = router.triggerNext(); + router.stepCantBeDisplayed(event, route); + + expect(router.stepRedirected).toEqual({ + redirect: true, + previousRoute: route + }); + expect(router.previousRoute).toBe(null); + expect(router.setRoute).toHaveBeenCalledWith(route); + }); - expect(router.getNextStepRoute).toHaveBeenCalled(); - expect(router.setRoute).toHaveBeenCalled(); + it('Should call the stepCantBeDisplayed function without fallbackRoute', () => { + router.getPreviousRoute = jest.fn(); + router.setRoute = jest.fn(); + + router.stepCantBeDisplayed(); + + expect(router.setRoute).toHaveBeenCalledWith('people'); + }); +}); + +describe('Router checkIfTheStepCanBeDisplay', () => { + it('Should call the checkIfTheStepCanBeDisplay function', () => { + const route = 'people'; + + router.options.steps[route].canTheStepBeDisplayed = jest + .fn() + .mockImplementation(() => true); + + const result = router.checkIfTheStepCanBeDisplay({ + route + }); + + expect(router.options.steps[route].canTheStepBeDisplayed).toHaveBeenCalled(); expect(result).toBe(true); }); - it('Should trigger the previous route', () => { - router.getPreviousStepRoute = jest.fn(); + it('Should call the checkIfTheStepCanBeDisplay function with invalid step and a fallback route', () => { + router.getPreviousRoute = jest.fn().mockImplementation(() => 'planet'); + + const result = router.checkIfTheStepCanBeDisplay({ + route: 'fakestep', + event + }); + + expect(router.getPreviousRoute).toHaveBeenCalledWith(event); + expect(result).toEqual({ + canBeDisplayed: false, + fallbackRoute: 'people' + }); + }); + + it('Should call the checkIfTheStepCanBeDisplay function with invalid step and no fallback route', () => { + router.getPreviousRoute = jest.fn().mockImplementation(() => 'people'); + + const result = router.checkIfTheStepCanBeDisplay({ + route: 'fakestep', + event + }); + + expect(router.getPreviousRoute).toHaveBeenCalledWith(event); + expect(result).toEqual({ + canBeDisplayed: false, + fallbackRoute: 'people' + }); + }); + + it('Should call the checkIfTheStepCanBeDisplay function with invalid step', () => { + router.getPreviousRoute = jest.fn().mockImplementation(() => null); + + const result = router.checkIfTheStepCanBeDisplay({ + route: 'fakestep', + event + }); + + expect(result).toEqual({ + canBeDisplayed: false, + fallbackRoute: 'people' + }); + }); +}); + +describe('Router createStep', () => { + it('Should call the createStep function', () => { + const route = 'people'; + + router.options.getDatasFromCache = jest.fn().mockImplementation(() => ({ + [route]: { + datas: true + } + })); + router.options.steps[route].render = jest.fn(); + + router.createStep({ route }); + + expect(router.options.getDatasFromCache).toHaveBeenCalledWith([route]); + expect(router.options.steps[route].render).toHaveBeenCalledWith({ + datas: true + }); + expect(router.applicationReady).toBe(true); + }); + + it('Should call the createStep function without datas from cache', () => { + const route = 'people'; + + router.options.getDatasFromCache = jest.fn().mockImplementation(() => null); + router.options.steps[route].render = jest.fn(); + + router.createStep({ route }); + + expect(router.options.getDatasFromCache).toHaveBeenCalledWith([route]); + expect(router.options.steps[route].render).toHaveBeenCalledWith({ + datas: null + }); + expect(router.applicationReady).toBe(true); + }); + + it('Should call the createStep function with application already ready', () => { + const route = 'people'; + + router.options.getDatasFromCache = jest.fn().mockImplementation(() => null); + router.options.steps[route].render = jest.fn(); + + router.applicationReady = true; + router.createStep({ route }); + + expect(router.options.getDatasFromCache).toHaveBeenCalledWith([route]); + expect(router.options.steps[route].render).toHaveBeenCalledWith({ + datas: null + }); + expect(router.applicationReady).toBe(true); + }); +}); + +describe('Router destroyStep', () => { + it('Should call the destroyStep function', () => { + const route = 'people'; + + router.options.steps[route].destroy = jest.fn(); + + router.destroyStep(route); + + expect(router.options.steps[route].destroy).toHaveBeenCalled(); + }); +}); + +describe('Router triggerNext', () => { + it('Should call the triggerNext function', () => { + const currentRoute = 'people'; + + router.getNextStepRoute = jest.fn().mockImplementation(() => 'planet'); router.setRoute = jest.fn(); - router.init(); + router.currentRoute = currentRoute; + router.triggerNext(); + + expect(router.getNextStepRoute).toHaveBeenCalledWith(currentRoute); + expect(router.setRoute).toHaveBeenCalledWith('planet'); + }); + + it('Should call the triggerNext function on the last step', () => { + router.getNextStepRoute = jest.fn().mockImplementation(() => 'end'); + + const result = router.triggerNext(); + + expect(result).toBe(false); + }); +}); + +describe('Router triggerPrevious', () => { + it('Should call the triggerPrevious function', () => { + router.getPreviousStepRoute = jest.fn().mockImplementation(() => 'people'); + router.setRoute = jest.fn(); + + router.currentRoute = 'planet'; router.triggerPrevious(); - expect(router.previousRoute).toBe('people'); - expect(router.getPreviousStepRoute).toHaveBeenCalled(); - expect(router.setRoute).toHaveBeenCalled(); + expect(router.previousRoute).toBe('planet'); + expect(router.getPreviousStepRoute).toHaveBeenCalledWith('planet'); + expect(router.setRoute).toHaveBeenCalledWith('people'); + }); +}); + +describe('Router getPreviousRoute', () => { + it('Should call the getPreviousRoute function', () => { + const result = router.getPreviousRoute(event); + + expect(result).toBe('people'); + }); + + it('Should call the getPreviousRoute function without oldURL', () => { + const result = router.getPreviousRoute({ + oldURL: '' + }); + + expect(result).toBe(null); + }); +}); + +describe('Router getPreviousStepRoute', () => { + it('Should call the getPreviousStepRoute function', () => { + router.getIndexFromRoute = jest.fn().mockImplementation(() => { + return '1'; + }); + + const route = 'planet'; + const result = router.getPreviousStepRoute(route); + + expect(router.getIndexFromRoute).toHaveBeenCalledWith(route); + expect(result).toBe('people'); + }); + + it('Should call the getPreviousStepRoute function on the first step', () => { + router.getIndexFromRoute = jest.fn().mockImplementation(() => { + return '0'; + }); + + const route = 'people'; + const result = router.getPreviousStepRoute(route); + + expect(result).toBe('people'); + }); +}); + +describe('Router getNextStepRoute', () => { + it('Should call the getNextStepRoute function', () => { + router.getIndexFromRoute = jest.fn().mockImplementation(() => { + return '0'; + }); + + const route = 'people'; + const result = router.getNextStepRoute(route); + + expect(router.getIndexFromRoute).toHaveBeenCalledWith(route); + expect(result).toBe('planet'); }); }); diff --git a/src/router.js b/src/router.js index 732af7a..ef6abb3 100644 --- a/src/router.js +++ b/src/router.js @@ -18,6 +18,8 @@ export default class Router { this.stepCreated = false; this.applicationReady = false; this.stepRedirected = {}; + + this.hashChanged = this.hashChanged.bind(this); } /** @@ -48,8 +50,7 @@ export default class Router { */ addEvents () { // Create the hash changed event of all the application - this.onHashChanged = this.hashChanged.bind(this); - window.addEventListener('hashchange', this.onHashChanged, false); + window.addEventListener('hashchange', this.hashChanged, false); } /** @@ -70,11 +71,7 @@ export default class Router { this.currentRoute = route; // The step can be dislayed - if (datas.canBeDisplayed) { - this.stepCanBeDisplayed(e); - } else { - this.stepCantBeDisplayed(e, datas.fallbackRoute); - } + this.stepCanBeDisplayed(e, datas.canBeDisplayed ? datas.fallbackRoute : null); } /** @@ -235,8 +232,8 @@ export default class Router { // Store the current route as the previous route because the route hasn't changed yet this.previousRoute = this.currentRoute; - const nextRoute = this.getPreviousStepRoute(this.previousRoute); - this.setRoute(nextRoute); + const previousRoute = this.getPreviousStepRoute(this.previousRoute); + this.setRoute(previousRoute); } /** @@ -251,18 +248,18 @@ export default class Router { } /** - * Get the next route from the step order array - * If there is no next step, the function return "end" + * Get the previous route from the step order array + * If there is no previous step, the function return "end" * * @param {String} route Current route * - * @returns {String} Next route or "end" + * @returns {String} Previous route or "end" */ getPreviousStepRoute (route) { - const nextStep = this.options.steps[ - this.options.stepsOrder[this.getIndexFromRoute(route) - 1] - ]; - return nextStep ? nextStep.route : 'end'; + const indexCurrentRoute = this.getIndexFromRoute(route); + const previousStep = this.options.steps[this.options.stepsOrder[indexCurrentRoute - 1]]; + + return previousStep ? previousStep.route : this.options.defaultRoute; } /** @@ -274,9 +271,9 @@ export default class Router { * @returns {String} Next route or "end" */ getNextStepRoute (route) { - const nextStep = this.options.steps[ - this.options.stepsOrder[this.getIndexFromRoute(route) + 1] - ]; + const indexCurrentRoute = this.getIndexFromRoute(route); + const nextStep = this.options.steps[this.options.stepsOrder[indexCurrentRoute + 1]]; + return nextStep ? nextStep.route : 'end'; }