-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Starting to refactor the router parsing.
The route parsing is meta-code, which means I've had to go with black-box input/output tests as opposed to a normal unit test. It wouldn't make sense to check the returned function, as this can change internally without changing its output, plus they'd be really brittle tests.
- Loading branch information
Showing
2 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
module.exports = (function() { | ||
var getParameters = function(pattern) { | ||
if (!pattern || pattern == '/' || !/\{(.*)\}/.test(pattern)) { | ||
return []; | ||
} | ||
|
||
if (pattern[pattern.length - 1] !== '/') { | ||
pattern += '/'; | ||
} | ||
|
||
var items = pattern.replace(/[^\{]*\{([^\}]*)\}[^\{]*/g, '$1;').split(';'); | ||
if (items.length && !items[items.length - 1]) { | ||
items.pop(); | ||
} | ||
|
||
return items; | ||
}; | ||
|
||
var generateParseFunction = function(pattern) { | ||
var outputFunction = ["var o = {};var v=true;with(str) {var t=str;var n=0;"]; | ||
|
||
var patternSegments = pattern.split(/\{|\}/g); | ||
|
||
var patternSegmentsCount = patternSegments.length; | ||
|
||
for (var index = 0; index < patternSegmentsCount; index++) { | ||
var variableName = patternSegments[index + 1]; | ||
if (!variableName) { | ||
continue; | ||
} | ||
|
||
var constant = patternSegments[index]; | ||
outputFunction.push("t=t.substring("); | ||
outputFunction.push(constant.length); | ||
outputFunction.push(");"); | ||
|
||
var nextDivider = patternSegments[index + 2]; | ||
if (!nextDivider) { | ||
outputFunction.push('n=t.length;') | ||
} else { | ||
outputFunction.push("n=t.indexOf('"); | ||
outputFunction.push(nextDivider); | ||
outputFunction.push("');"); | ||
} | ||
|
||
outputFunction.push("v=v&&!!~n;o['"); | ||
outputFunction.push(variableName); | ||
outputFunction.push("']=t.substring(0,n);t=t.substring(n);"); | ||
|
||
// We skip on two indexes as every second index is always a separator. | ||
index++; | ||
} | ||
|
||
outputFunction.push("};return v ? o : v;"); | ||
|
||
return new Function("str", outputFunction.join('')); | ||
}; | ||
|
||
var RouteParser = function RouteParser(pattern) { | ||
if (!pattern.url) { | ||
pattern = { | ||
url: pattern, | ||
data: {} | ||
} | ||
} | ||
|
||
pattern.func = parsePattern(pattern.url); | ||
pattern._parameters = getParameters(pattern.url); | ||
return pattern; | ||
}; | ||
|
||
// Expose methods for test purposes. | ||
RouteParser.getParameters = getParameters; | ||
RouteParser.generateParseFunction = generateParseFunction; | ||
|
||
return RouteParser; | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
describe('routeHandler', function() { | ||
var routeParser; | ||
beforeEach(function() { | ||
routeParser = require('../../../Routes/routeHandler/routeParser'); | ||
}); | ||
|
||
describe('#getParameters()', function() { | ||
it('should return no parameters from a blank route.', function() { | ||
var parameters = routeParser.getParameters('/'); | ||
|
||
parameters.should.eql([]); | ||
}); | ||
|
||
it('should return no parameters from no route.', function() { | ||
var parameters = routeParser.getParameters(''); | ||
|
||
parameters.should.eql([]); | ||
}); | ||
|
||
it('should return no parameters from a non-parameterised route.', function() { | ||
var parameters = routeParser.getParameters('/test'); | ||
|
||
parameters.should.eql([]); | ||
}); | ||
|
||
it('should split a single parameter from a simple route.', function() { | ||
var parameters = routeParser.getParameters('/{test}'); | ||
|
||
parameters.should.eql(['test']); | ||
}); | ||
|
||
it('should split a single parameter from a simple route that ends with a /.', function() { | ||
var parameters = routeParser.getParameters('/{test}/'); | ||
|
||
parameters.should.eql(['test']); | ||
}); | ||
|
||
it('should split a single parameter from a simple route with a constant.', function() { | ||
var parameters = routeParser.getParameters('/{test}-something'); | ||
|
||
parameters.should.eql(['test']); | ||
}); | ||
|
||
it('should split multiple parameters from a complex route.', function() { | ||
var parameters = routeParser.getParameters('/{test}/{test2}/'); | ||
|
||
parameters.should.eql(['test', 'test2']); | ||
}); | ||
|
||
it('should split multiple parameters from a complex route with a constant.', function() { | ||
var parameters = routeParser.getParameters('/{test}-test/test2-{test2}/test3'); | ||
|
||
parameters.should.eql(['test', 'test2']); | ||
}); | ||
|
||
it('should not grab parameters from a route without closed brackets.', function() { | ||
var parameters = routeParser.getParameters('/{test'); | ||
|
||
parameters.should.eql([]); | ||
}); | ||
|
||
it('should be greedy when parsing mis-matched brackets.', function() { | ||
var parameters = routeParser.getParameters('/{test/{test2}/'); | ||
|
||
parameters.should.eql(['test/{test2']); | ||
}); | ||
|
||
it('should allow multiple parameters to be named the same.', function() { | ||
// This case is useless, but not prevented. | ||
var parameters = routeParser.getParameters('/{test}/{test}/'); | ||
|
||
parameters.should.eql(['test', 'test']); | ||
}); | ||
}); | ||
|
||
describe('#generateParseFunction()', function() { | ||
var TestCase = function(pattern, url, expectedResult) { | ||
return { | ||
pattern: pattern, | ||
url: url, | ||
expectedResult: expectedResult | ||
}; | ||
}; | ||
|
||
[ | ||
// The gerated function doesn't currently allow for matches where a trailing / is missing. | ||
TestCase('/{test}/', '/test'), | ||
TestCase('/hello{test}/', '/blah/'), | ||
TestCase('/hello{test}/{test2}/', '/blah/test2/'), | ||
TestCase('/{test}/{test2}/', '/no-pe/'), | ||
].forEach(function(testCase) { | ||
it('should return false when a route doesn\'t match the URL. (' + testCase.pattern + ', ' + testCase.url + ')', function() { | ||
var parseFunction = routeParser.generateParseFunction(testCase.pattern); | ||
var parameters = parseFunction(testCase.url); | ||
|
||
parameters.should.eql(false); | ||
}); | ||
}); | ||
|
||
[ | ||
TestCase('/', '/', {}), | ||
TestCase('/', '/test', {}), | ||
TestCase('/{test}/', '/test/', { | ||
test: 'test' | ||
}), | ||
// It's not recommended to use greedy patterns, but you can. | ||
TestCase('/{test}/{test2}', '/testOne/testTwo', { | ||
test: 'testOne', | ||
test2: 'testTwo' | ||
}), | ||
TestCase('/{test}/{test2}/', '/testOne/testTwo/', { | ||
test: 'testOne', | ||
test2: 'testTwo' | ||
}) | ||
].forEach(function(testCase) { | ||
it('should return a function that resolves the expected parameters. (' + testCase.pattern + ', ' + testCase.url + ')', function() { | ||
var parseFunction = routeParser.generateParseFunction(testCase.pattern); | ||
var parameters = parseFunction(testCase.url); | ||
|
||
parameters.should.eql(testCase.expectedResult); | ||
}); | ||
}); | ||
}); | ||
}); |