From 542a431015457c5f5aa6103941bb3b04c762b5f5 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Sun, 31 Jul 2016 14:53:15 +0100 Subject: [PATCH] walk: add --- README.md | 23 ++++++ package.json | 4 +- test.js | 187 ------------------------------------------------ test/router.js | 189 +++++++++++++++++++++++++++++++++++++++++++++++++ test/walk.js | 65 +++++++++++++++++ walk.js | 31 ++++++++ 6 files changed, 310 insertions(+), 189 deletions(-) delete mode 100644 test.js create mode 100644 test/router.js create mode 100644 test/walk.js create mode 100644 walk.js diff --git a/README.md b/README.md index 9d3364e..52f415d 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,29 @@ r1('/dada/child') // => 'subrouter trix!' ``` +## Walk +Sometimes it's necessary to walk the `trie` to apply transformations. +```js +const walk = require('wayfarer/walk') +const wayfarer = require('wayfarer') + +const router = wayfarer() +router.on('/multiply', (x, y) => x * y) +router.on('/divide', (x, y) => x / y) + +walk(router, (route, cb) => { + const y = 2 + return function (params, x) { + return cb(x, y) + } +}) + +router('/multiply', 4) +// => 8 +router('/divide', 8) +// => 4 +``` + ## API ### router = wayfarer(default) Initialize a router with a default route. Doesn't ignore querystrings and diff --git a/package.json b/package.json index d5c2842..6b2f54f 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Composable trie based router", "main": "index.js", "scripts": { - "test": "standard && NODE_ENV=test node test", - "test-cov": "NODE_ENV=test istanbul cover test.js" + "test": "standard && NODE_ENV=test tape test/*", + "test-cov": "NODE_ENV=test istanbul cover test/*" }, "repository": "yoshuawuyts/wayfarer", "keywords": [ diff --git a/test.js b/test.js deleted file mode 100644 index ff60c48..0000000 --- a/test.js +++ /dev/null @@ -1,187 +0,0 @@ -const noop = require('noop2') -const test = require('tape') -const wayfarer = require('./') - -test('should match a path', function (t) { - t.plan(1) - const r = wayfarer() - r.on('/', function () { - t.pass('called') - }) - r('/') -}) - -test('should match a nested path', function (t) { - t.plan(1) - const r = wayfarer() - r.on('/foo/bar', function () { - t.pass('called') - }) - r('/foo/bar') -}) - -test('should match a default path', function (t) { - t.plan(1) - const r = wayfarer('/404') - r.on('/404', function () { - t.pass('default') - }) - r('/nope') -}) - -test('should allow passing of extra values', function (t) { - t.plan(2) - const foo = {} - const bar = {} - const r = wayfarer() - r.on('/foo', function (params, arg1, arg2) { - t.equal(arg1, foo, 'arg1 was passed') - t.equal(arg2, bar, 'arg2 was passed') - }) - r('/foo', foo, bar) -}) - -test('.on() should catch type errors', function (t) { - t.plan(2) - const r = wayfarer() - t.throws(r.on.bind(r, 123), /string/, 'string') - t.throws(r.on.bind(r, '/hi', 123), /function/, 'function') -}) - -test('.emit() should match paths', function (t) { - t.plan(2) - const r = wayfarer() - r.on('/foo/bar', function (param) { - t.pass('bar called') - }) - r.on('/foo/baz', function (param) { - t.pass('baz called') - }) - r('/foo/bar') - r('/foo/baz') -}) - -test('.emit() should match partials', function (t) { - t.plan(1) - const r = wayfarer() - r.on('/:user', function (param) { - t.equal(param.user, 'tobi', 'param matched') - }) - r('/tobi') -}) - -test('.emit() should match paths before partials', function (t) { - t.plan(1) - const r = wayfarer() - r.on('/foo', function () { - t.pass('called') - }) - r.on('/:user', noop) - r('/foo') -}) - -test('.emit() should allow path overriding', function (t) { - t.plan(1) - const r = wayfarer() - r.on('/:user', noop) - r.on('/:user', function () { - t.pass('called') - }) - r('/foo') -}) - -test('.emit() should match nested partials', function (t) { - t.plan(2) - const r = wayfarer() - r.on('/:user/:name', function (param) { - t.equal(param.user, 'tobi', 'param matched') - t.equal(param.name, 'baz', 'param matched') - }) - r('/tobi/baz') -}) - -test('.emit() should throw if no matches are found', function (t) { - t.plan(1) - const r1 = wayfarer() - t.throws(r1.bind(r1, '/woops'), /route/, 'no matches found') -}) - -test('.emit() should return values', function (t) { - t.plan(1) - const r1 = wayfarer() - r1.on('/foo', function () { - return 'hello' - }) - t.equal(r1('foo'), 'hello', 'returns value') -}) - -test('.emit() mount subrouters', function (t) { - t.plan(5) - - const r4 = wayfarer() - const r3 = wayfarer() - r4.on('/kidlette', function () { t.pass('nested 2 levels') }) - r3.on('/mom', r4) - r3('/mom/kidlette') - - const r1 = wayfarer() - const r2 = wayfarer() - r2.on('/', function () { t.pass('nested 1 level') }) - r1.on('/home', r2) - r1('/home') - - const r5 = wayfarer() - const r6 = wayfarer() - r6.on('/child', function (param) { - t.equal(typeof param, 'object', 'param is passed') - t.equal(param.parent, 'hello', 'nested 2 levels with params') - }) - r5.on('/:parent', r6) - r5('/hello/child') - - const r7 = wayfarer() - const r8 = wayfarer() - const r9 = wayfarer() - r9.on('/bar', function (param) { t.pass('called', 'nested 3 levels') }) - r8.on('/bin', r9) - r7.on('/foo', r8) - r7('/foo/bin/bar') - - // const r10 = wayfarer() - // const r11 = wayfarer() - // const r12 = wayfarer() - // r12.on('/:grandchild', function (param) { - // t.equal(param.parent, 'bin', 'nested 3 levels with params') - // t.equal(param.child, 'bar', 'nested 3 levels with params') - // t.equal(param.grandchild, 'baz', 'nested 3 levels with parmas') - // }) - // r11.on('/:child', r12) - // r10.on('/foo/:parent', r11) - // r10('/foo/bin/bar/baz') -}) - -test('nested routes should call parent default route', function (t) { - t.plan(4) - const r1 = wayfarer('/404') - const r2 = wayfarer() - const r3 = wayfarer() - - r2.on('/bar', r3) - r1.on('foo', r2) - r1.on('/404', pass) - - r1('') - r1('foo') - r1('foo/bar') - r1('foo/beep/boop') - - function pass (params) { - t.pass('called') - } -}) - -test('aliases', function (t) { - t.plan(1) - const r = wayfarer() - t.equal(r, r.emit) -}) diff --git a/test/router.js b/test/router.js new file mode 100644 index 0000000..6ee18ff --- /dev/null +++ b/test/router.js @@ -0,0 +1,189 @@ +const wayfarer = require('../') +const noop = require('noop2') +const tape = require('tape') + +tape('trie', function (t) { + t.test('should match a path', function (t) { + t.plan(1) + const r = wayfarer() + r.on('/', function () { + t.pass('called') + }) + r('/') + }) + + t.test('should match a nested path', function (t) { + t.plan(1) + const r = wayfarer() + r.on('/foo/bar', function () { + t.pass('called') + }) + r('/foo/bar') + }) + + t.test('should match a default path', function (t) { + t.plan(1) + const r = wayfarer('/404') + r.on('/404', function () { + t.pass('default') + }) + r('/nope') + }) + + t.test('should allow passing of extra values', function (t) { + t.plan(2) + const foo = {} + const bar = {} + const r = wayfarer() + r.on('/foo', function (params, arg1, arg2) { + t.equal(arg1, foo, 'arg1 was passed') + t.equal(arg2, bar, 'arg2 was passed') + }) + r('/foo', foo, bar) + }) + + t.test('.on() should catch type errors', function (t) { + t.plan(2) + const r = wayfarer() + t.throws(r.on.bind(r, 123), /string/, 'string') + t.throws(r.on.bind(r, '/hi', 123), /function/, 'function') + }) + + t.test('.emit() should match paths', function (t) { + t.plan(2) + const r = wayfarer() + r.on('/foo/bar', function (param) { + t.pass('bar called') + }) + r.on('/foo/baz', function (param) { + t.pass('baz called') + }) + r('/foo/bar') + r('/foo/baz') + }) + + t.test('.emit() should match partials', function (t) { + t.plan(1) + const r = wayfarer() + r.on('/:user', function (param) { + t.equal(param.user, 'tobi', 'param matched') + }) + r('/tobi') + }) + + t.test('.emit() should match paths before partials', function (t) { + t.plan(1) + const r = wayfarer() + r.on('/foo', function () { + t.pass('called') + }) + r.on('/:user', noop) + r('/foo') + }) + + t.test('.emit() should allow path overriding', function (t) { + t.plan(1) + const r = wayfarer() + r.on('/:user', noop) + r.on('/:user', function () { + t.pass('called') + }) + r('/foo') + }) + + t.test('.emit() should match nested partials', function (t) { + t.plan(2) + const r = wayfarer() + r.on('/:user/:name', function (param) { + t.equal(param.user, 'tobi', 'param matched') + t.equal(param.name, 'baz', 'param matched') + }) + r('/tobi/baz') + }) + + t.test('.emit() should throw if no matches are found', function (t) { + t.plan(1) + const r1 = wayfarer() + t.throws(r1.bind(r1, '/woops'), /route/, 'no matches found') + }) + + t.test('.emit() should return values', function (t) { + t.plan(1) + const r1 = wayfarer() + r1.on('/foo', function () { + return 'hello' + }) + t.equal(r1('foo'), 'hello', 'returns value') + }) + + t.test('.emit() mount subrouters', function (t) { + t.plan(5) + + const r4 = wayfarer() + const r3 = wayfarer() + r4.on('/kidlette', function () { t.pass('nested 2 levels') }) + r3.on('/mom', r4) + r3('/mom/kidlette') + + const r1 = wayfarer() + const r2 = wayfarer() + r2.on('/', function () { t.pass('nested 1 level') }) + r1.on('/home', r2) + r1('/home') + + const r5 = wayfarer() + const r6 = wayfarer() + r6.on('/child', function (param) { + t.equal(typeof param, 'object', 'param is passed') + t.equal(param.parent, 'hello', 'nested 2 levels with params') + }) + r5.on('/:parent', r6) + r5('/hello/child') + + const r7 = wayfarer() + const r8 = wayfarer() + const r9 = wayfarer() + r9.on('/bar', function (param) { t.pass('called', 'nested 3 levels') }) + r8.on('/bin', r9) + r7.on('/foo', r8) + r7('/foo/bin/bar') + + // const r10 = wayfarer() + // const r11 = wayfarer() + // const r12 = wayfarer() + // r12.on('/:grandchild', function (param) { + // t.equal(param.parent, 'bin', 'nested 3 levels with params') + // t.equal(param.child, 'bar', 'nested 3 levels with params') + // t.equal(param.grandchild, 'baz', 'nested 3 levels with parmas') + // }) + // r11.on('/:child', r12) + // r10.on('/foo/:parent', r11) + // r10('/foo/bin/bar/baz') + }) + + t.test('nested routes should call parent default route', function (t) { + t.plan(4) + const r1 = wayfarer('/404') + const r2 = wayfarer() + const r3 = wayfarer() + + r2.on('/bar', r3) + r1.on('foo', r2) + r1.on('/404', pass) + + r1('') + r1('foo') + r1('foo/bar') + r1('foo/beep/boop') + + function pass (params) { + t.pass('called') + } + }) + + t.test('aliases', function (t) { + t.plan(1) + const r = wayfarer() + t.equal(r, r.emit) + }) +}) diff --git a/test/walk.js b/test/walk.js new file mode 100644 index 0000000..b17cd8e --- /dev/null +++ b/test/walk.js @@ -0,0 +1,65 @@ +const wayfarer = require('../') +const walk = require('../walk') +const noop = require('noop2') +const tape = require('tape') + +tape('walk', function (t) { + t.test('should assert input types', (t) => { + t.plan(3) + t.throws(walk.bind(null), /function/, 'assert first arg is function') + t.throws(walk.bind(null, noop), /function/, 'assert second arg is a function') + t.throws(walk.bind(null, noop, noop), /object/, 'assert trie exists') + }) + + t.test('should walk a trie', (t) => { + t.plan(2) + const router = wayfarer() + router.on('/foo', (x, y) => x * y) + router.on('/bar', (x, y) => x / y) + + walk(router, (route, cb) => { + const y = 2 + return function (params, x) { + return cb(x, y) + } + }) + + t.equal(router('/foo', 4), 8, 'multiply') + t.equal(router('/bar', 8), 4, 'divide') + }) + + t.test('should walk a nested trie', (t) => { + t.plan(3) + const router = wayfarer() + router.on('/foo/baz', (x, y) => x * y) + router.on('/bar/bin/barb', (x, y) => x / y) + router.on('/bar/bin/bla', (x, y) => x / y) + + walk(router, (route, cb) => { + const y = 2 + return function (params, x) { + return cb(x, y) + } + }) + + t.equal(router('/foo/baz', 4), 8, 'multiply') + t.equal(router('/bar/bin/barb', 8), 4, 'divide') + t.equal(router('/bar/bin/bla', 8), 4, 'divide') + }) + + t.test('should walk partials', (t) => { + t.plan(4) + const router = wayfarer() + router.on('/foo', (route) => route) + router.on('/:foo', (route) => route) + router.on('/:foo/bar', (route) => route) + router.on('/:foo/:bar', (route) => route) + + walk(router, (route, cb) => () => cb(route)) + + t.equal(router('/foo'), '/foo', 'no partials') + t.equal(router('/bleep'), '/:foo', 'one partial') + t.equal(router('/bleep/bar'), '/:foo/bar', 'partial and normal') + t.equal(router('/bleep/bloop'), '/:foo/:bar', 'two partials') + }) +}) diff --git a/walk.js b/walk.js new file mode 100644 index 0000000..621c3df --- /dev/null +++ b/walk.js @@ -0,0 +1,31 @@ +const assert = require('assert') + +module.exports = walk + +// walk a wayfarer trie +// (obj, fn) -> null +function walk (router, transform) { + assert.equal(typeof router, 'function', 'wayfarer.walk: router should be an function') + assert.equal(typeof transform, 'function', 'wayfarer.walk: transform should be a function') + + const trie = router._trie + assert.equal(typeof trie, 'object', 'wayfarer.walk: trie should be an object') + + // (str, obj) -> null + ;(function walk (route, trie) { + if (trie.cb) { + trie.cb = transform(route, trie.cb) + } + + if (trie.nodes) { + const nodes = trie.nodes + Object.keys(nodes).forEach(function (key) { + const node = nodes[key] + const newRoute = (key === '$$') + ? route + '/:' + trie.name + : route + '/' + key + walk(newRoute, node) + }) + } + })('', trie.trie) +}