Skip to content
This repository has been archived by the owner on Apr 6, 2022. It is now read-only.

Commit

Permalink
#1 Add router integration.
Browse files Browse the repository at this point in the history
  • Loading branch information
enbock committed Oct 14, 2017
1 parent 263b411 commit 6be2049
Show file tree
Hide file tree
Showing 10 changed files with 405 additions and 40 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
* Create tests for the current components.
* Activate auto deployment to gh-pages.
* [#4] Add this CHANGELOG.md file.
* [#1] Option page.
* Add main menu drawer.
* Add router (History API) integration.

### Changed
* [#1] Replace MDL with MDC.

## [0.1.0] - 2017-10-06
### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
],
"coverageThreshold": {
"global": {
"branches": 80
"branches": 90
}
}
},
Expand Down
12 changes: 7 additions & 5 deletions public/Template/Application.html.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
/>

<main>
<br />
<div style={{textAlign:"right"}}>
Page: {this.state.history.page}<br />
Component: {this.state.currentComponent}
</div>
<br/>
<pre style={{textAlign:"right"}}>
Root: {this.state.history.root}<br/>
Page: {this.state.history.page}<br/>
Path: {this.state.pathname}<br/>
</pre>
{this.state.currentComponent}
</main>
</div>
</div>
96 changes: 91 additions & 5 deletions src/Application/Application.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import Component from '../Shared/LiveJSX';
import Menu from '../Menu';
import Router from './Router';
import Router from '../Shared/Router';

/**
* Root Application.
Expand All @@ -16,6 +16,18 @@ class Application extends Component {
return '/Template/Application.html.tpl';
}

/**
* Setup routes of the application.
*
* @returns {Object}
*/
static get routes() {
return {
index: '/',
settings: '/settings/' // option page
};
}

/**
* Constructor.
*
Expand All @@ -35,8 +47,10 @@ class Application extends Component {
{
currentComponent: <div/>,
history: {
page: 'none'
}
page: 'none',
root: ''
},
pathname: ''
}
);

Expand All @@ -46,6 +60,8 @@ class Application extends Component {
};

this.registeredButtonHandler = [];

this.boundPathChange = this.onPathChange.bind(this);
}

onMainButtonClick() {
Expand All @@ -57,7 +73,23 @@ class Application extends Component {
*/
onMenuChange(menu) {
// TODO: router stuff here?
this.setState({history: {page: menu}});
const history = this.state.history;
history.page = menu;
this.setState({history: history});
}

/**
* Received router change.
*
* @param event
*/
onPathChange(event) {
this.setState(
{
pathname: event.pathname,
history: event.state
}
);
}

/**
Expand All @@ -82,6 +114,56 @@ class Application extends Component {
this.registeredButtonHandler.splice(index, 1);
}

/**
* Decide the page
* @param pathname
*/
restorePageByPathName(pathname) {
const pages = Application.routes;

// fallback
let history = {
page: 'index',
root: ''
};

let found = false;
// create history by pathname detection.
for(let page in pages) {
let index = pathname.indexOf(pages[page]);
/**
* Check if in path and goes to end.
* TODO: When data attached to path, is it needed to change this logic.
*/
if(index !== -1 && pages[page].length + index === pathname.length) {
history = {
page: page
};

break;
}
}

// actualize root pathname
history.root = pathname.substr(0, pathname.lastIndexOf(pages[history.page]));

return history;
}

/**
* Decider routing
*
* @param {Object} nextProps
* @param {Object} nextState
*/
componentWillUpdate(nextProps, nextState) {
if (nextState.history === null) {
nextState.history = this.restorePageByPathName(nextState.pathname);
}
// Go to route
nextState.pathname = nextState.history.root + Application.routes[nextState.history.page];
}

/**
* Adding singleton components to application.
*
Expand All @@ -90,7 +172,11 @@ class Application extends Component {
render() {
return (
<div>
<Router/>
<Router
onChange={this.boundPathChange}
state={this.state.history}
pathname={this.state.pathname}
/>
{super.render()}
</div>
);
Expand Down
90 changes: 80 additions & 10 deletions src/Application/Application.test.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
/* global jest, Babel */

import {mockAxiosAction} from 'axios';
import React from 'react';
import {shallow} from 'enzyme';
import Application from './Application';

jest.mock('../Menu', () => 'Menu');
jest.mock('../Shared/Router', () => 'Router');

/**
* Test Application Container.
*/
describe('Application', function testApplication() {
let bound = null;
let success = false;
let promise;

/**
* Reset global mocks.
*/
beforeEach(function beforeEach() {
Babel.transform.mockClear();
});

/**
* Test if correct layout loaded.
*/
it('Loads the correct layout', function testLoadLayout() {
let bound = null;
const promise = {
promise = {
then: function onThen(callback) {
bound = callback;
return promise;
Expand All @@ -31,8 +32,6 @@ describe('Application', function testApplication() {
}
};

let success = false;

mockAxiosAction(
'get',
function onRequest(url) {
Expand All @@ -47,7 +46,25 @@ describe('Application', function testApplication() {
code: 'React.createElement(\'button\', { onClick: this.onMainButtonClick.bind(this) })'
}
);
});

/**
* Test if correct layout loaded.
*/
it('Loads the correct layout', function testLoadLayout() {
const wrapper = shallow(<Application/>);
const instance = wrapper.instance();
bound({data: 'TEMPLATE'});
wrapper.update();

// Template loaded?
expect(success).toBe(true);
});

/**
* Test if menu interaction works.
*/
it('Test menu interaction', function testLoadLayout() {
const wrapper = shallow(<Application/>);
const instance = wrapper.instance();
const buttonClick = jest.fn();
Expand All @@ -58,10 +75,63 @@ describe('Application', function testApplication() {

wrapper.find('button').simulate('click');
expect(buttonClick).toHaveBeenCalled();
expect(success).toBe(true);

instance.onMenuChange('newMenu');
instance.menuAdapter.deregisterMenuToggleHandler(buttonClick);
instance.menuAdapter.deregisterMenuToggleHandler(buttonClick); // check that double remove don't break
});

/**
* Test kinds of redirection.
*/
it('Redirect from router to page', function testRouterRedirect() {
const wrapper = shallow(<Application/>);
const instance = wrapper.instance();
bound({data: 'TEMPLATE'});
wrapper.update();

// start on sub page in a sub directory of hosting page
instance.onPathChange(
{
pathname: '/MainPath/settings/',
state: null
}
);
expect(instance.state.pathname).toBe('/MainPath/settings/');
expect(instance.state.history.page).toBe('settings');
expect(instance.state.history.root).toBe('/MainPath');

// start on index page in a sub directory of hosting page
instance.onPathChange(
{
pathname: '/MainPath/',
state: null
}
);
expect(instance.state.pathname).toBe('/MainPath/');
expect(instance.state.history.page).toBe('index');
expect(instance.state.history.root).toBe('/MainPath');

// start on sub page in a root directory of hosting page
instance.onPathChange(
{
pathname: '/',
state: null
}
);
expect(instance.state.pathname).toBe('/');
expect(instance.state.history.page).toBe('index');
expect(instance.state.history.root).toBe('');

// reload page with using state data
instance.onPathChange(
{
pathname: '/settings/', // <-- should be ignored if state present
state: {page:"index", root:"/MainPath"}
}
);
expect(instance.state.pathname).toBe('/MainPath/');
expect(instance.state.history.page).toBe('index');
expect(instance.state.history.root).toBe('/MainPath');
});
});
13 changes: 0 additions & 13 deletions src/Application/Router/Router.js

This file was deleted.

6 changes: 0 additions & 6 deletions src/Application/Router/index.js

This file was deleted.

Loading

0 comments on commit 6be2049

Please sign in to comment.