Skip to content

Commit

Permalink
Rebase preactjs#100 with latest changes
Browse files Browse the repository at this point in the history
  • Loading branch information
silentsakky committed Sep 18, 2019
1 parent 3eb5b31 commit a8a3005
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 7 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ You can also make params optional by adding a `?` to it.
</Router>
```

### Nesting routers

Routers will append the parent Routers' URLs together to come up with the matching route for children.

```js
<Router>
<A path="/" /> //will route '/'
<Router path="/app/:bar*"> //will route '/app/*' (could also use default here)
<B path="/b"/> //will route '/app/b'
<C path="/c" /> //will route '/app/c'
</Router>
<D path="/d" /> //will route '/d'
<E default /> //will route anything not listed above
</Router>
```

### Lazy Loading

Expand Down
25 changes: 22 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cloneElement, createElement, Component, toChildArray } from 'preact';
import { exec, prepareVNodeForRanking, assign, pathRankSort } from './util';
import { exec, prepareVNodeForRanking, assign, pathRankSort, segmentize } from './util';

let customHistory = null;

Expand Down Expand Up @@ -142,19 +142,38 @@ function initEventListeners() {


class Router extends Component {
constructor(props) {
constructor(props, context) {
super(props);

this.baseUrl = props.base || '';
if (props.path) {
let segments = segmentize(props.path);
segments.forEach(segment => {
if (segment.indexOf(':') == -1) {
this.baseUrl = this.baseUrl + '/' + segment;
}
});
}

if (props.history) {
customHistory = props.history;
}

if (context && context['preact-router-base'] && !props.base) {
this.baseUrl = context['preact-router-base'] + this.baseUrl;
}

this.state = {
url: props.url || getCurrentUrl()
};

initEventListeners();
}

getChildContext() {
return {['preact-router-base']: this.baseUrl};
}

shouldComponentUpdate(props) {
if (props.static!==true) return true;
return props.url!==this.props.url || props.onChange!==this.props.onChange;
Expand Down Expand Up @@ -210,7 +229,7 @@ class Router extends Component {
.filter(prepareVNodeForRanking)
.sort(pathRankSort)
.map( vnode => {
let matches = exec(url, vnode.props.path, vnode.props);
let matches = exec(url, this.baseUrl + vnode.props.path, vnode.props);
if (matches) {
if (invoke !== false) {
let newProps = { url, matches };
Expand Down
33 changes: 29 additions & 4 deletions src/match.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
import { h, Component } from 'preact';
import { h, Component, cloneElement } from 'preact';
import { subscribers, getCurrentUrl, Link as StaticLink } from 'preact-router';
import { exec, segmentize } from './util';

export class Match extends Component {
constructor(props, context) {
super(props);

this.baseUrl = props.base || '';
if (props.path) {
let segments = segmentize(props.path);
segments.forEach(segment => {
if (segment.indexOf(':') == -1) {
this.baseUrl = this.baseUrl + '/' + segment;
}
});
}

if (context && context['preact-router-base']) {
this.baseUrl = context['preact-router-base'] + this.baseUrl;
}
}

update = url => {
this.nextUrl = url;
this.setState({});
Expand All @@ -12,15 +31,21 @@ export class Match extends Component {
componentWillUnmount() {
subscribers.splice(subscribers.indexOf(this.update)>>>0, 1);
}
getChildContext() {
return {['preact-router-base']: this.baseUrl};
}
render(props) {
let url = this.nextUrl || getCurrentUrl(),
path = url.replace(/\?.+$/,'');
this.nextUrl = null;
return props.children({

const newProps = {
url,
path,
matches: path===props.path
});
matches: path===props.path || exec(path, context['preact-router-base'] + props.path, {})
};

return typeof props.children === 'function' ? props.children(newProps) : cloneElement(props.children, newProps);
}
}

Expand Down
187 changes: 187 additions & 0 deletions test/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,193 @@ describe('dom', () => {
expect(A.prototype.componentWillUnmount).to.have.been.calledOnce;
});

it('should support nested routers with default', () => {
class X {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(X.prototype, 'componentWillMount');
sinon.spy(X.prototype, 'componentWillUnmount');
class Y {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(Y.prototype, 'componentWillMount');
sinon.spy(Y.prototype, 'componentWillUnmount');
mount(
<Router base="/app">
<X path="/x" />
<Router default>
<Y path="/y"/>
</Router>
</Router>
);
expect(X.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
route('/app/x');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
route('/app/y');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).to.have.been.calledOnce;
expect(Y.prototype.componentWillMount).to.have.been.calledOnce;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
});

it('should support nested routers with path', () => {
class X {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(X.prototype, 'componentWillMount');
sinon.spy(X.prototype, 'componentWillUnmount');
class Y {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(Y.prototype, 'componentWillMount');
sinon.spy(Y.prototype, 'componentWillUnmount');
mount(
<Router base='/baz'>
<X path="/j" />
<Router path="/box/:bar*">
<Y path="/k"/>
</Router>
</Router>
);
expect(X.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
route('/baz/j');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
route('/baz/box/k');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).to.have.been.calledOnce;
expect(Y.prototype.componentWillMount).to.have.been.calledOnce;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
});

it('should support deeply nested routers', () => {
class X {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(X.prototype, 'componentWillMount');
sinon.spy(X.prototype, 'componentWillUnmount');
class Y {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(Y.prototype, 'componentWillMount');
sinon.spy(Y.prototype, 'componentWillUnmount');
mount(
<Router base='/baz'>
<X path="/j" />
<z path="/box/:bar*">
<Router path="/box">
<Y path="/k"/>
</Router>
</z>
</Router>
);
expect(X.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
route('/baz/j');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
route('/baz/box/k');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).to.have.been.calledOnce;
expect(Y.prototype.componentWillMount).to.have.been.calledOnce;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
});

it('should support nested routers and Match(s)', () => {
class X {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(X.prototype, 'componentWillMount');
sinon.spy(X.prototype, 'componentWillUnmount');
class Y {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(Y.prototype, 'componentWillMount');
sinon.spy(Y.prototype, 'componentWillUnmount');
mount(
<Router base='/ccc'>
<X path="/jjj" />
<Match path="/xxx/:bar*">
<Y path="/kkk"/>
</Match>
</Router>
);
expect(X.prototype.componentWillMount, 'X1').not.to.have.been.called;
expect(Y.prototype.componentWillMount, 'Y1').not.to.have.been.called;
route('/ccc/jjj');
expect(X.prototype.componentWillMount, 'X2').to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount, 'X3').not.to.have.been.called;
expect(Y.prototype.componentWillMount, 'Y2').not.to.have.been.called;
expect(Y.prototype.componentWillUnmount, 'Y3').not.to.have.been.called;
route('/ccc/xxx/kkk');
expect(X.prototype.componentWillMount, 'X4').to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount, 'X5').to.have.been.calledOnce;
expect(Y.prototype.componentWillMount, 'Y4').to.have.been.calledOnce;
expect(Y.prototype.componentWillUnmount, 'Y5').not.to.have.been.called;
});

it('should support nested router reset via base attr', () => {
class X {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(X.prototype, 'componentWillMount');
sinon.spy(X.prototype, 'componentWillUnmount');
class Y {
componentWillMount() {}
componentWillUnmount() {}
render(){ return <div />; }
}
sinon.spy(Y.prototype, 'componentWillMount');
sinon.spy(Y.prototype, 'componentWillUnmount');
mount(
<Router base='/baz'>
<X path="/j" />
<Router path="/:bar*" base="/baz/foo">
<Y path="/k"/>
</Router>
</Router>
);
expect(X.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
route('/baz/j');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).not.to.have.been.called;
expect(Y.prototype.componentWillMount).not.to.have.been.called;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
route('/baz/foo/k');
expect(X.prototype.componentWillMount).to.have.been.calledOnce;
expect(X.prototype.componentWillUnmount).to.have.been.calledOnce;
expect(Y.prototype.componentWillMount).to.have.been.calledOnce;
expect(Y.prototype.componentWillUnmount).not.to.have.been.called;
});

it('should support re-routing', done => {
class A {
componentWillMount() {
Expand Down

0 comments on commit a8a3005

Please sign in to comment.