var template, expect; if (typeof require !== 'undefined') { template = require('../lib/url-template.js'); expect = require("expect.js"); } else { template = window.urltemplate; expect = window.expect; } function createTestContext(c) { return function (t, r) { expect(template.parse(t).expand(c)).to.eql(r); }; } describe('uri-template', function () { describe('Level 1', function () { var assert = createTestContext({ 'var': 'value', 'some.value': 'some', 'some_value': 'value', 'Some%20Thing': 'hello', 'foo': 'bar', 'hello': 'Hello World!', 'bool': false, 'toString': 'string', 'number': 42, 'float': 3.14, 'undef': undefined, 'null': null, 'chars': 'šö䟜ñꀣ¥‡ÑÒÓÔÕÖ×ØÙÚàáâãäåæçÿü', 'surrogatepairs': '\uD834\uDF06' }); it('empty string', function () { assert('', ''); }); it('encodes non expressions correctly', function () { assert('hello/world', 'hello/world'); assert('Hello World!/{foo}', 'Hello%20World!/bar'); assert(':/?#[]@!$&()*+,;=\'', ':/?#[]@!$&()*+,;=\''); assert('%20', '%20'); assert('%xyz', '%25xyz'); assert('%', '%25'); }); it('expand plain ASCII strings', function () { assert('{var}', 'value'); }); it('expand non-ASCII strings', function () { assert('{chars}', '%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF%C3%BC'); }); it('expands and encodes surrogate pairs correctly', function () { assert('{surrogatepairs}', '%F0%9D%8C%86'); }); it('expand expressions with dot and underscore', function () { assert('{some.value}', 'some'); assert('{some_value}', 'value'); }); it('expand expressions with encoding', function () { assert('{Some%20Thing}', 'hello'); }); it('expand expressions with reserved JavaScript names', function () { assert('{toString}', 'string'); }); it('expand variables that are not strings', function () { assert('{number}', '42'); assert('{float}', '3.14'); assert('{bool}', 'false'); }); it('expand variables that are undefined or null', function () { assert('{undef}', ''); assert('{null}', ''); }); it('expand multiple values', function () { assert('{var}/{foo}', 'value/bar'); }); it('escape invalid characters correctly', function () { assert('{hello}', 'Hello%20World%21'); }); }); describe('Level 2', function () { var assert = createTestContext({ 'var': 'value', 'hello': 'Hello World!', 'path': '/foo/bar' }); it('reserved expansion of basic strings', function () { assert('{+var}', 'value'); assert('{+hello}', 'Hello%20World!'); }); it('preserves paths', function() { assert('{+path}/here', '/foo/bar/here'); assert('here?ref={+path}', 'here?ref=/foo/bar'); }); }); describe('Level 3', function () { var assert = createTestContext({ 'var' : 'value', 'hello' : 'Hello World!', 'empty' : '', 'path' : '/foo/bar', 'x' : '1024', 'y' : '768' }); it('variables without an operator', function () { assert('map?{x,y}', 'map?1024,768'); assert('{x,hello,y}', '1024,Hello%20World%21,768'); }); it('variables with the reserved expansion operator', function () { assert('{+x,hello,y}', '1024,Hello%20World!,768'); assert('{+path,x}/here', '/foo/bar,1024/here'); }); it('variables with the fragment expansion operator', function () { assert('{#x,hello,y}', '#1024,Hello%20World!,768'); assert('{#path,x}/here', '#/foo/bar,1024/here'); }); it('variables with the dot operator', function () { assert('X{.var}', 'X.value'); assert('X{.x,y}', 'X.1024.768'); }); it('variables with the path operator', function () { assert('{/var}', '/value'); assert('{/var,x}/here', '/value/1024/here'); }); it('variables with the parameter operator', function () { assert('{;x,y}', ';x=1024;y=768'); assert('{;x,y,empty}', ';x=1024;y=768;empty'); }); it('variables with the query operator', function () { assert('{?x,y}', '?x=1024&y=768'); assert('{?x,y,empty}', '?x=1024&y=768&empty='); }); it('variables with the query continuation operator', function () { assert('?fixed=yes{&x}', '?fixed=yes&x=1024'); assert('{&x,y,empty}', '&x=1024&y=768&empty='); }); }); describe('Level 4', function () { var assert = createTestContext({ 'var': 'value', 'hello': 'Hello World!', 'path': '/foo/bar', 'list': ['red', 'green', 'blue'], 'keys': { 'semi': ';', 'dot': '.', 'comma': ',' }, "chars": { 'ü': 'ü' }, 'number': 2133, 'emptystring': '', 'emptylist': [], 'emptyobject': {}, 'undefinedlistitem': [1,,2], 'undefinedobjectitem': { key: null, hello: 'world', 'empty': '', '': 'nothing' } }); it('variable empty list', function () { assert('{/emptylist}', ''); assert('{/emptylist*}', ''); assert('{?emptylist}', '?emptylist='); assert('{?emptylist*}', ''); }); it('variable empty object', function () { assert('{/emptyobject}', ''); assert('{/emptyobject*}', ''); assert('{?emptyobject}', '?emptyobject='); assert('{?emptyobject*}', ''); }); it('variable undefined list item', function () { assert('{undefinedlistitem}', '1,2'); assert('{undefinedlistitem*}', '1,2'); assert('{?undefinedlistitem*}', '?undefinedlistitem=1&undefinedlistitem=2'); }); it('variable undefined object item', function () { assert('{undefinedobjectitem}', 'hello,world,empty,,,nothing'); assert('{undefinedobjectitem*}', 'hello=world,empty=,nothing'); }); it('variable empty string', function () { assert('{emptystring}', ''); assert('{+emptystring}', ''); assert('{#emptystring}', '#'); assert('{.emptystring}', '.'); assert('{/emptystring}', '/'); assert('{;emptystring}', ';emptystring'); assert('{?emptystring}', '?emptystring='); assert('{&emptystring}', '&emptystring='); }); it('variable modifiers prefix', function () { assert('{var:3}', 'val'); assert('{var:30}', 'value'); assert('{+path:6}/here', '/foo/b/here'); assert('{#path:6}/here', '#/foo/b/here'); assert('X{.var:3}', 'X.val'); assert('{/var:1,var}', '/v/value'); assert('{;hello:5}', ';hello=Hello'); assert('{?var:3}', '?var=val'); assert('{&var:3}', '&var=val'); }); it('variable modifier prefix converted to string', function () { assert('{number:3}', '213'); }); it('variable list expansion', function () { assert('{list}', 'red,green,blue'); assert('{+list}', 'red,green,blue'); assert('{#list}', '#red,green,blue'); assert('{/list}', '/red,green,blue'); assert('{;list}', ';list=red,green,blue'); assert('{.list}', '.red,green,blue'); assert('{?list}', '?list=red,green,blue'); assert('{&list}', '&list=red,green,blue'); }); it('variable associative array expansion', function () { assert('{keys}', 'semi,%3B,dot,.,comma,%2C'); assert('{keys*}', 'semi=%3B,dot=.,comma=%2C'); assert('{+keys}', 'semi,;,dot,.,comma,,'); assert('{#keys}', '#semi,;,dot,.,comma,,'); assert('{.keys}', '.semi,%3B,dot,.,comma,%2C'); assert('{/keys}', '/semi,%3B,dot,.,comma,%2C'); assert('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'); assert('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'); assert('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'); }); it('variable list explode', function () { assert('{list*}', 'red,green,blue'); assert('{+list*}', 'red,green,blue'); assert('{#list*}', '#red,green,blue'); assert('{/list*}', '/red/green/blue'); assert('{;list*}', ';list=red;list=green;list=blue'); assert('{.list*}', '.red.green.blue'); assert('{?list*}', '?list=red&list=green&list=blue'); assert('{&list*}', '&list=red&list=green&list=blue'); assert('{/list*,path:4}', '/red/green/blue/%2Ffoo'); }); it('variable associative array explode', function () { assert('{+keys*}', 'semi=;,dot=.,comma=,'); assert('{#keys*}', '#semi=;,dot=.,comma=,'); assert('{/keys*}', '/semi=%3B/dot=./comma=%2C'); assert('{;keys*}', ';semi=%3B;dot=.;comma=%2C'); assert('{?keys*}', '?semi=%3B&dot=.&comma=%2C'); assert('{&keys*}', '&semi=%3B&dot=.&comma=%2C') }); it('encodes associative arrays correctly', function () { assert('{chars*}', '%C3%BC=%C3%BC'); }); }); describe('Encoding', function () { var assert = createTestContext({ restricted: ":/?#[]@!$&()*+,;='", percent: '%', encoded: '%25', 'pctencoded%20name': '', mapWithEncodedName: { 'encoded%20name': '' }, mapWithRestrictedName: { 'restricted=name': '' }, mapWidthUmlautName: { 'ümlaut': '' } }); it('passes through percent encoded values', function () { assert('{percent}', '%25'); assert('{+encoded}', '%25'); }); it('encodes restricted characters correctly', function () { assert('{restricted}', '%3A%2F%3F%23%5B%5D%40%21%24%26%28%29%2A%2B%2C%3B%3D%27'); assert('{+restricted}', ':/?#[]@!$&()*+,;=\''); assert('{#restricted}', '#:/?#[]@!$&()*+,;=\''); assert('{/restricted}', '/%3A%2F%3F%23%5B%5D%40%21%24%26%28%29%2A%2B%2C%3B%3D%27'); assert('{;restricted}', ';restricted=%3A%2F%3F%23%5B%5D%40%21%24%26%28%29%2A%2B%2C%3B%3D%27'); assert('{.restricted}', '.%3A%2F%3F%23%5B%5D%40%21%24%26%28%29%2A%2B%2C%3B%3D%27'); assert('{?restricted}', '?restricted=%3A%2F%3F%23%5B%5D%40%21%24%26%28%29%2A%2B%2C%3B%3D%27'); assert('{&restricted}', '&restricted=%3A%2F%3F%23%5B%5D%40%21%24%26%28%29%2A%2B%2C%3B%3D%27'); }); }); describe('Error handling (or the lack thereof)', function () { var assert = createTestContext({ foo: 'test', keys: { foo: 'bar' } }); it('does not expand invalid expressions', function () { assert('{test', '{test'); assert('test}', 'test}'); assert('{{test}}', '{}'); // TODO: Is this acceptable? }); it('does not expand with incorrect operators', function () { assert('{@foo}', ''); // TODO: This will try to match a variable called `@foo` which will fail because it is not in our context. We could catch this by ignoring reserved operators? assert('{$foo}', ''); // TODO: Same story, but $ is not a reserved operator. assert('{++foo}', ''); }); it('ignores incorrect prefixes', function () { assert('{foo:test}', 'test'); // TODO: Invalid prefixes are ignored. We could throw an error. assert('{foo:2test}', 'te'); // TODO: Best effort is OK? }); it('prefix applied to the wrong context', function () { assert('{keys:1}', 'foo,bar'); }); }); describe('Skipping undefined arguments', function () { var assert = createTestContext({ 'var': 'value', 'number': 2133, 'emptystring': '', 'emptylist': [], 'emptyobject': {}, 'undefinedlistitem': [1,,2], }); it('variable undefined list item', function () { assert('{undefinedlistitem}', '1,2'); assert('{undefinedlistitem*}', '1,2'); assert('{?undefinedlistitem*}', '?undefinedlistitem=1&undefinedlistitem=2'); }); it('query with empty/undefined arguments', function () { assert('{?var,number}', '?var=value&number=2133'); assert('{?undef}', ''); assert('{?emptystring}', '?emptystring='); assert('{?emptylist}', '?emptylist='); assert('{?emptyobject}', '?emptyobject='); assert('{?undef,var,emptystring}', '?var=value&emptystring='); }); }); });