Blob Blame History Raw
/* global describe, it */

var util = require('util')
var expect = require('chai').expect
var pathToRegexp = require('./')

/**
 * An array of test cases with expected inputs and outputs.
 *
 * @type {Array}
 */
var TESTS = [
  /**
   * Simple paths.
   */
  [
    '/',
    null,
    [
      '/'
    ],
    [
      ['/', ['/']],
      ['/route', null]
    ],
    [
      [null, '/'],
      [{}, '/'],
      [{ id: 123 }, '/']
    ]
  ],
  [
    '/test',
    null,
    [
      '/test'
    ],
    [
      ['/test', ['/test']],
      ['/route', null],
      ['/test/route', null],
      ['/test/', ['/test/']]
    ],
    [
      [null, '/test'],
      [{}, '/test']
    ]
  ],
  [
    '/test/',
    null,
    [
      '/test/'
    ],
    [
      ['/test', ['/test']],
      ['/test/', ['/test/']],
      ['/test//', null]
    ],
    [
      [null, '/test/']
    ]
  ],

  /**
   * Case-sensitive paths.
   */
  [
    '/test',
    {
      sensitive: true
    },
    [
      '/test'
    ],
    [
      ['/test', ['/test']],
      ['/TEST', null]
    ],
    [
      [null, '/test']
    ]
  ],
  [
    '/TEST',
    {
      sensitive: true
    },
    [
      '/TEST'
    ],
    [
      ['/test', null],
      ['/TEST', ['/TEST']]
    ],
    [
      [null, '/TEST']
    ]
  ],

  /**
   * Strict mode.
   */
  [
    '/test',
    {
      strict: true
    },
    [
      '/test'
    ],
    [
      ['/test', ['/test']],
      ['/test/', null],
      ['/TEST', ['/TEST']]
    ],
    [
      [null, '/test']
    ]
  ],
  [
    '/test/',
    {
      strict: true
    },
    [
      '/test/'
    ],
    [
      ['/test', null],
      ['/test/', ['/test/']],
      ['/test//', null]
    ],
    [
      [null, '/test/']
    ]
  ],

  /**
   * Non-ending mode.
   */
  [
    '/test',
    {
      end: false
    },
    [
      '/test'
    ],
    [
      ['/test', ['/test']],
      ['/test/', ['/test/']],
      ['/test/route', ['/test']],
      ['/route', null]
    ],
    [
      [null, '/test']
    ]
  ],
  [
    '/test/',
    {
      end: false
    },
    [
      '/test/'
    ],
    [
      ['/test/route', ['/test']],
      ['/test//', ['/test']],
      ['/test//route', ['/test']]
    ],
    [
      [null, '/test/']
    ]
  ],
  [
    '/:test',
    {
      end: false
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/route', ['/route', 'route']]
    ],
    [
      [{}, null],
      [{ test: 'abc' }, '/abc']
    ]
  ],
  [
    '/:test/',
    {
      end: false
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      '/'
    ],
    [
      ['/route', ['/route', 'route']]
    ],
    [
      [{ test: 'abc' }, '/abc/']
    ]
  ],

  /**
   * Combine modes.
   */
  [
    '/test',
    {
      end: false,
      strict: true
    },
    [
      '/test'
    ],
    [
      ['/test', ['/test']],
      ['/test/', ['/test']],
      ['/test/route', ['/test']]
    ],
    [
      [null, '/test']
    ]
  ],
  [
    '/test/',
    {
      end: false,
      strict: true
    },
    [
      '/test/'
    ],
    [
      ['/test', null],
      ['/test/', ['/test/']],
      ['/test//', ['/test/']],
      ['/test/route', ['/test/']]
    ],
    [
      [null, '/test/']
    ]
  ],
  [
    '/test.json',
    {
      end: false,
      strict: true
    },
    [
      '/test.json'
    ],
    [
      ['/test.json', ['/test.json']],
      ['/test.json.hbs', null],
      ['/test.json/route', ['/test.json']]
    ],
    [
      [null, '/test.json']
    ]
  ],
  [
    '/:test',
    {
      end: false,
      strict: true
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/route', ['/route', 'route']],
      ['/route/', ['/route', 'route']]
    ],
    [
      [{}, null],
      [{ test: 'abc' }, '/abc']
    ]
  ],
  [
    '/:test/',
    {
      end: false,
      strict: true
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      '/'
    ],
    [
      ['/route', null],
      ['/route/', ['/route/', 'route']]
    ],
    [
      [{ test: 'foobar' }, '/foobar/']
    ]
  ],

  /**
   * Arrays of simple paths.
   */
  [
    ['/one', '/two'],
    null,
    [],
    [
      ['/one', ['/one']],
      ['/two', ['/two']],
      ['/three', null],
      ['/one/two', null]
    ]
  ],

  /**
   * Non-ending simple path.
   */
  [
    '/test',
    {
      end: false
    },
    [
      '/test'
    ],
    [
      ['/test/route', ['/test']]
    ],
    [
      [null, '/test']
    ]
  ],

  /**
   * Single named parameter.
   */
  [
    '/:test',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/route', ['/route', 'route']],
      ['/another', ['/another', 'another']],
      ['/something/else', null],
      ['/route.json', ['/route.json', 'route.json']],
      ['/something%2Felse', ['/something%2Felse', 'something%2Felse']]
    ],
    [
      [{ test: 'route' }, '/route'],
      [{ test: 'something/else' }, '/something%2Felse']
    ]
  ],
  [
    '/:test',
    {
      strict: true
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/route', ['/route', 'route']],
      ['/route/', null]
    ],
    [
      [{ test: 'route' }, '/route']
    ]
  ],
  [
    '/:test/',
    {
      strict: true
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      '/'
    ],
    [
      ['/route/', ['/route/', 'route']],
      ['/route//', null]
    ],
    [
      [{ test: 'route' }, '/route/']
    ]
  ],
  [
    '/:test',
    {
      end: false
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/route.json', ['/route.json', 'route.json']],
      ['/route//', ['/route', 'route']]
    ],
    [
      [{ test: 'route' }, '/route']
    ]
  ],

  /**
   * Optional named parameter.
   */
  [
    '/:test?',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: true,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/route', ['/route', 'route']],
      ['/route/nested', null],
      ['/', ['/', undefined]],
      ['//', null]
    ],
    [
      [null, ''],
      [{ test: 'foobar' }, '/foobar']
    ]
  ],
  [
    '/:test?',
    {
      strict: true
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: true,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/route', ['/route', 'route']],
      ['/', null], // Questionable behaviour.
      ['//', null]
    ],
    [
      [null, ''],
      [{ test: 'foobar' }, '/foobar']
    ]
  ],
  [
    '/:test?/',
    {
      strict: true
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: true,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      '/'
    ],
    [
      ['/route', null],
      ['/route/', ['/route/', 'route']],
      ['/', ['/', undefined]],
      ['//', null]
    ],
    [
      [null, '/'],
      [{ test: 'foobar' }, '/foobar/']
    ]
  ],

  /**
   * Repeated one or more times parameters.
   */
  [
    '/:test+',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: true,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/', null],
      ['/route', ['/route', 'route']],
      ['/some/basic/route', ['/some/basic/route', 'some/basic/route']],
      ['//', null]
    ],
    [
      [{}, null],
      [{ test: 'foobar' }, '/foobar'],
      [{ test: ['a', 'b', 'c'] }, '/a/b/c']
    ]
  ],
  [
    '/:test(\\d+)+',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: true,
        pattern: '\\d+'
      }
    ],
    [
      ['/abc/456/789', null],
      ['/123/456/789', ['/123/456/789', '123/456/789']]
    ],
    [
      [{ test: 'abc' }, null],
      [{ test: 123 }, '/123'],
      [{ test: [1, 2, 3] }, '/1/2/3']
    ]
  ],
  [
    '/route.:ext(json|xml)+',
    null,
    [
      '/route',
      {
        name: 'ext',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: true,
        pattern: 'json|xml'
      }
    ],
    [
      ['/route', null],
      ['/route.json', ['/route.json', 'json']],
      ['/route.xml.json', ['/route.xml.json', 'xml.json']],
      ['/route.html', null]
    ],
    [
      [{ ext: 'foobar' }, null],
      [{ ext: 'xml' }, '/route.xml'],
      [{ ext: ['xml', 'json'] }, '/route.xml.json']
    ]
  ],

  /**
   * Repeated zero or more times parameters.
   */
  [
    '/:test*',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: true,
        repeat: true,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/', ['/', undefined]],
      ['//', null],
      ['/route', ['/route', 'route']],
      ['/some/basic/route', ['/some/basic/route', 'some/basic/route']]
    ],
    [
      [{}, ''],
      [{ test: 'foobar' }, '/foobar'],
      [{ test: ['foo', 'bar'] }, '/foo/bar']
    ]
  ],
  [
    '/route.:ext([a-z]+)*',
    null,
    [
      '/route',
      {
        name: 'ext',
        prefix: '.',
        delimiter: '.',
        optional: true,
        repeat: true,
        pattern: '[a-z]+'
      }
    ],
    [
      ['/route', ['/route', undefined]],
      ['/route.json', ['/route.json', 'json']],
      ['/route.json.xml', ['/route.json.xml', 'json.xml']],
      ['/route.123', null]
    ],
    [
      [{}, '/route'],
      [{ ext: [] }, '/route'],
      [{ ext: '123' }, null],
      [{ ext: 'foobar' }, '/route.foobar'],
      [{ ext: ['foo', 'bar'] }, '/route.foo.bar']
    ]
  ],

  /**
   * Custom named parameters.
   */
  [
    '/:test(\\d+)',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '\\d+'
      }
    ],
    [
      ['/123', ['/123', '123']],
      ['/abc', null],
      ['/123/abc', null]
    ],
    [
      [{ test: 'abc' }, null],
      [{ test: '123' }, '/123']
    ]
  ],
  [
    '/:test(\\d+)',
    {
      end: false
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '\\d+'
      }
    ],
    [
      ['/123', ['/123', '123']],
      ['/abc', null],
      ['/123/abc', ['/123', '123']]
    ],
    [
      [{ test: '123' }, '/123']
    ]
  ],
  [
    '/:test(.*)',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '.*'
      }
    ],
    [
      ['/anything/goes/here', ['/anything/goes/here', 'anything/goes/here']]
    ],
    [
      [{ test: '' }, '/'],
      [{ test: 'abc' }, '/abc'],
      [{ test: 'abc/123' }, '/abc%2F123']
    ]
  ],
  [
    '/:route([a-z]+)',
    null,
    [
      {
        name: 'route',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[a-z]+'
      }
    ],
    [
      ['/abcde', ['/abcde', 'abcde']],
      ['/12345', null]
    ],
    [
      [{ route: '' }, null],
      [{ route: '123' }, null],
      [{ route: 'abc' }, '/abc']
    ]
  ],
  [
    '/:route(this|that)',
    null,
    [
      {
        name: 'route',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: 'this|that'
      }
    ],
    [
      ['/this', ['/this', 'this']],
      ['/that', ['/that', 'that']],
      ['/foo', null]
    ],
    [
      [{ route: 'this' }, '/this'],
      [{ route: 'foo' }, null],
      [{ route: 'that' }, '/that']
    ]
  ],

  /**
   * Prefixed slashes could be omitted.
   */
  [
    'test',
    null,
    [
      'test'
    ],
    [
      ['test', ['test']],
      ['/test', null]
    ],
    [
      [null, 'test']
    ]
  ],
  [
    ':test',
    null,
    [
      {
        name: 'test',
        prefix: '',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['route', ['route', 'route']],
      ['/route', null],
      ['route/', ['route/', 'route']]
    ],
    [
      [{ test: '' }, null],
      [{}, null],
      [{ test: null }, null],
      [{ test: 'route' }, 'route']
    ]
  ],
  [
    ':test',
    {
      strict: true
    },
    [
      {
        name: 'test',
        prefix: '',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['route', ['route', 'route']],
      ['/route', null],
      ['route/', null]
    ],
    [
      [{ test: 'route' }, 'route']
    ]
  ],
  [
    ':test',
    {
      end: false
    },
    [
      {
        name: 'test',
        prefix: '',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['route', ['route', 'route']],
      ['/route', null],
      ['route/', ['route/', 'route']],
      ['route/foobar', ['route', 'route']]
    ],
    [
      [{ test: 'route' }, 'route']
    ]
  ],
  [
    ':test?',
    null,
    [
      {
        name: 'test',
        prefix: '',
        delimiter: '/',
        optional: true,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['route', ['route', 'route']],
      ['/route', null],
      ['', ['', undefined]],
      ['route/foobar', null]
    ],
    [
      [{}, ''],
      [{ test: '' }, null],
      [{ test: 'route' }, 'route']
    ]
  ],

  /**
   * Formats.
   */
  [
    '/test.json',
    null,
    [
      '/test.json'
    ],
    [
      ['/test.json', ['/test.json']],
      ['/route.json', null]
    ],
    [
      [{}, '/test.json']
    ]
  ],
  [
    '/:test.json',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      '.json'
    ],
    [
      ['/test.json', ['/test.json', 'test']],
      ['/route.json', ['/route.json', 'route']],
      ['/route.json.json', ['/route.json.json', 'route.json']]
    ],
    [
      [{ test: 'foo' }, '/foo.json']
    ]
  ],

  /**
   * Format params.
   */
  [
    '/test.:format',
    null,
    [
      '/test',
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: false,
        pattern: '[^.]+?'
      }
    ],
    [
      ['/test.html', ['/test.html', 'html']],
      ['/test.hbs.html', null]
    ],
    [
      [{}, null],
      [{ format: '' }, null],
      [{ format: 'foo' }, '/test.foo']
    ]
  ],
  [
    '/test.:format.:format',
    null,
    [
      '/test',
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: false,
        pattern: '[^.]+?'
      },
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: false,
        pattern: '[^.]+?'
      }
    ],
    [
      ['/test.html', null],
      ['/test.hbs.html', ['/test.hbs.html', 'hbs', 'html']]
    ],
    [
      [{ format: 'foo.bar' }, null],
      [{ format: 'foo' }, '/test.foo.foo']
    ]
  ],
  [
    '/test.:format+',
    null,
    [
      '/test',
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: true,
        pattern: '[^.]+?'
      }
    ],
    [
      ['/test.html', ['/test.html', 'html']],
      ['/test.hbs.html', ['/test.hbs.html', 'hbs.html']]
    ],
    [
      [{ format: [] }, null],
      [{ format: 'foo' }, '/test.foo'],
      [{ format: ['foo', 'bar'] }, '/test.foo.bar']
    ]
  ],
  [
    '/test.:format',
    {
      end: false
    },
    [
      '/test',
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: false,
        pattern: '[^.]+?'
      }
    ],
    [
      ['/test.html', ['/test.html', 'html']],
      ['/test.hbs.html', null]
    ],
    [
      [{ format: 'foo' }, '/test.foo']
    ]
  ],
  [
    '/test.:format.',
    null,
    [
      '/test',
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: false,
        pattern: '[^.]+?'
      },
      '.'
    ],
    [
      ['/test.html.', ['/test.html.', 'html']],
      ['/test.hbs.html', null]
    ],
    [
      [{ format: '' }, null],
      [{ format: 'foo' }, '/test.foo.']
    ]
  ],

  /**
   * Format and path params.
   */
  [
    '/:test.:format',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: false,
        pattern: '[^.]+?'
      }
    ],
    [
      ['/route.html', ['/route.html', 'route', 'html']],
      ['/route', null],
      ['/route.html.json', ['/route.html.json', 'route.html', 'json']]
    ],
    [
      [{}, null],
      [{ test: 'route', format: 'foo' }, '/route.foo']
    ]
  ],
  [
    '/:test.:format?',
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: true,
        repeat: false,
        pattern: '[^.]+?'
      }
    ],
    [
      ['/route', ['/route', 'route', undefined]],
      ['/route.json', ['/route.json', 'route', 'json']],
      ['/route.json.html', ['/route.json.html', 'route.json', 'html']]
    ],
    [
      [{ test: 'route' }, '/route'],
      [{ test: 'route', format: '' }, null],
      [{ test: 'route', format: 'foo' }, '/route.foo']
    ]
  ],
  [
    '/:test.:format?',
    {
      end: false
    },
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: true,
        repeat: false,
        pattern: '[^.]+?'
      }
    ],
    [
      ['/route', ['/route', 'route', undefined]],
      ['/route.json', ['/route.json', 'route', 'json']],
      ['/route.json.html', ['/route.json.html', 'route.json', 'html']]
    ],
    [
      [{ test: 'route' }, '/route'],
      [{ test: 'route', format: undefined }, '/route'],
      [{ test: 'route', format: '' }, null],
      [{ test: 'route', format: 'foo' }, '/route.foo']
    ]
  ],
  [
    '/test.:format(.*)z',
    {
      end: false
    },
    [
      '/test',
      {
        name: 'format',
        prefix: '.',
        delimiter: '.',
        optional: false,
        repeat: false,
        pattern: '.*'
      },
      'z'
    ],
    [
      ['/test.abc', null],
      ['/test.z', ['/test.z', '']],
      ['/test.abcz', ['/test.abcz', 'abc']]
    ],
    [
      [{}, null],
      [{ format: '' }, '/test.z'],
      [{ format: 'foo' }, '/test.fooz']
    ]
  ],

  /**
   * Unnamed params.
   */
  [
    '/(\\d+)',
    null,
    [
      {
        name: 0,
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '\\d+'
      }
    ],
    [
      ['/123', ['/123', '123']],
      ['/abc', null],
      ['/123/abc', null]
    ],
    [
      [{}, null],
      [{ '0': '123' }, '/123']
    ]
  ],
  [
    '/(\\d+)',
    {
      end: false
    },
    [
      {
        name: 0,
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '\\d+'
      }
    ],
    [
      ['/123', ['/123', '123']],
      ['/abc', null],
      ['/123/abc', ['/123', '123']],
      ['/123/', ['/123/', '123']]
    ],
    [
      [{ '0': '123' }, '/123']
    ]
  ],
  [
    '/(\\d+)?',
    null,
    [
      {
        name: 0,
        prefix: '/',
        delimiter: '/',
        optional: true,
        repeat: false,
        pattern: '\\d+'
      }
    ],
    [
      ['/', ['/', undefined]],
      ['/123', ['/123', '123']]
    ],
    [
      [{}, ''],
      [{ '0': '123' }, '/123']
    ]
  ],
  [
    '/(.*)',
    null,
    [
      {
        name: 0,
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '.*'
      }
    ],
    [
      ['/', ['/', '']],
      ['/route', ['/route', 'route']],
      ['/route/nested', ['/route/nested', 'route/nested']]
    ],
    [
      [{ '0': '' }, '/'],
      [{ '0': '123' }, '/123']
    ]
  ],

  /**
   * Regexps.
   */
  [
    /.*/,
    null,
    [],
    [
      ['/match/anything', ['/match/anything']]
    ]
  ],
  [
    /(.*)/,
    null,
    [
      {
        name: 0,
        prefix: null,
        delimiter: null,
        optional: false,
        repeat: false,
        pattern: null
      }
    ],
    [
      ['/match/anything', ['/match/anything', '/match/anything']]
    ]
  ],
  [
    /\/(\d+)/,
    null,
    [
      {
        name: 0,
        prefix: null,
        delimiter: null,
        optional: false,
        repeat: false,
        pattern: null
      }
    ],
    [
      ['/abc', null],
      ['/123', ['/123', '123']]
    ]
  ],

  /**
   * Mixed arrays.
   */
  [
    ['/test', /\/(\d+)/],
    null,
    [
      {
        name: 0,
        prefix: null,
        delimiter: null,
        optional: false,
        repeat: false,
        pattern: null
      }
    ],
    [
      ['/test', ['/test', undefined]]
    ]
  ],
  [
    ['/:test(\\d+)', /(.*)/],
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '\\d+'
      },
      {
        name: 0,
        prefix: null,
        delimiter: null,
        optional: false,
        repeat: false,
        pattern: null
      }
    ],
    [
      ['/123', ['/123', '123', undefined]],
      ['/abc', ['/abc', undefined, '/abc']]
    ]
  ],

  /**
   * Correct names and indexes.
   */
  [
    ['/:test', '/route/:test'],
    null,
    [
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      {
        name: 'test',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/test', ['/test', 'test', undefined]],
      ['/route/test', ['/route/test', undefined, 'test']]
    ]
  ],
  [
    [/^\/([^\/]+)$/, /^\/route\/([^\/]+)$/],
    null,
    [
      {
        name: 0,
        prefix: null,
        delimiter: null,
        optional: false,
        repeat: false,
        pattern: null
      },
      {
        name: 0,
        prefix: null,
        delimiter: null,
        optional: false,
        repeat: false,
        pattern: null
      }
    ],
    [
      ['/test', ['/test', 'test', undefined]],
      ['/route/test', ['/route/test', undefined, 'test']]
    ]
  ],

  /**
   * Ignore non-matching groups in regexps.
   */
  [
    /(?:.*)/,
    null,
    [],
    [
      ['/anything/you/want', ['/anything/you/want']]
    ]
  ],

  /**
   * Respect escaped characters.
   */
  [
    '/\\(testing\\)',
    null,
    [
      '/(testing)'
    ],
    [
      ['/testing', null],
      ['/(testing)', ['/(testing)']]
    ],
    [
      [null, '/(testing)']
    ]
  ],
  [
    '/.+\\*?=^!:${}[]|',
    null,
    [
      '/.+*?=^!:${}[]|'
    ],
    [
      ['/.+*?=^!:${}[]|', ['/.+*?=^!:${}[]|']]
    ],
    [
      [null, '/.+*?=^!:${}[]|']
    ]
  ],

  /**
   * Asterisk functionality.
   */
  [
    '/*',
    null,
    [
      {
        name: 0,
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '.*'
      }
    ],
    [
      ['', null],
      ['/', ['/', '']],
      ['/foo/bar', ['/foo/bar', 'foo/bar']]
    ],
    [
      [null, null],
      [{ '0': '' }, '/'],
      [{ '0': 'foobar' }, '/foobar']
    ]
  ],
  [
    '/foo/*',
    null,
    [
      '/foo',
      {
        name: 0,
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '.*'
      }
    ],
    [
      ['', null],
      ['/test', null],
      ['/foo', null],
      ['/foo/', ['/foo/', '']],
      ['/foo/bar', ['/foo/bar', 'bar']]
    ],
    [
      [{ '0': 'bar' }, '/foo/bar']
    ]
  ],
  [
    '/:foo/*',
    null,
    [
      {
        name: 'foo',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      {
        name: 0,
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '.*'
      }
    ],
    [
      ['', null],
      ['/test', null],
      ['/foo', null],
      ['/foo/', ['/foo/', 'foo', '']],
      ['/foo/bar', ['/foo/bar', 'foo', 'bar']]
    ],
    [
      [{ foo: 'foo' }, null],
      [{ '0': 'bar' }, null],
      [{ foo: 'foo', '0': 'bar' }, '/foo/bar']
    ]
  ],

  /**
   * Random examples.
   */
  [
    '/:foo/:bar',
    null,
    [
      {
        name: 'foo',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      {
        name: 'bar',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/match/route', ['/match/route', 'match', 'route']]
    ],
    [
      [{ foo: 'a', bar: 'b' }, '/a/b']
    ]
  ],
  [
    '/:remote([\\w-.]+)/:user([\\w-]+)',
    null,
    [
      {
        name: 'remote',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[\\w-.]+'
      },
      {
        name: 'user',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[\\w-]+'
      }
    ],
    [
      ['/endpoint/user', ['/endpoint/user', 'endpoint', 'user']],
      ['/endpoint/user-name', ['/endpoint/user-name', 'endpoint', 'user-name']],
      ['/foo.bar/user-name', ['/foo.bar/user-name', 'foo.bar', 'user-name']]
    ],
    [
      [{ remote: 'foo', user: 'bar' }, '/foo/bar'],
      [{ remote: 'foo.bar', user: 'uno' }, '/foo.bar/uno']
    ]
  ],
  [
    '/:foo\\?',
    null,
    [
      {
        name: 'foo',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      '?'
    ],
    [
      ['/route?', ['/route?', 'route']]
    ],
    [
      [{ foo: 'bar' }, '/bar?']
    ]
  ],
  [
    '/:foo\\(:bar?\\)',
    null,
    [
      {
        name: 'foo',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      '(',
      {
        name: 'bar',
        prefix: '',
        delimiter: '/',
        optional: true,
        repeat: false,
        pattern: '[^\\/]+?'
      },
      ')'
    ],
    [
      ['/hello(world)', ['/hello(world)', 'hello', 'world']],
      ['/hello()', ['/hello()', 'hello', undefined]]
    ],
    [
      [{ foo: 'hello', bar: 'world' }, '/hello(world)'],
      [{ foo: 'hello' }, '/hello()']
    ]
  ],
  [
    '/:postType(video|audio|text)(\\+.+)?',
    null,
    [
      {
        name: 'postType',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: 'video|audio|text'
      },
      {
        name: 0,
        prefix: '',
        delimiter: '/',
        optional: true,
        repeat: false,
        pattern: '\\+.+'
      }
    ],
    [
      ['/video', ['/video', 'video', undefined]],
      ['/video+test', ['/video+test', 'video', '+test']],
      ['/video+', null]
    ],
    [
      [{ postType: 'video' }, '/video'],
      [{ postType: 'random' }, null]
    ]
  ],

  /**
   * Unicode characters.
   */
  [
    '/:foo',
    null,
    [
      {
        name: 'foo',
        prefix: '/',
        delimiter: '/',
        optional: false,
        repeat: false,
        pattern: '[^\\/]+?'
      }
    ],
    [
      ['/café', ['/café', 'café']]
    ],
    [
      [{ foo: 'café' }, '/caf%C3%A9']
    ]
  ]
]

/**
 * Dynamically generate the entire test suite.
 */
describe('path-to-regexp', function () {
  var TEST_PATH = '/user/:id'

  var TEST_PARAM = {
    name: 'id',
    prefix: '/',
    delimiter: '/',
    optional: false,
    repeat: false,
    pattern: '[^\\/]+?'
  }

  describe('arguments', function () {
    it('should accept an array of keys as the second argument', function () {
      var keys = []
      var re = pathToRegexp(TEST_PATH, keys, { end: false })

      expect(re.keys).to.equal(keys)
      expect(keys).to.deep.equal([TEST_PARAM])
      expect(exec(re, '/user/123/show')).to.deep.equal(['/user/123', '123'])
    })

    it('should work with keys as null', function () {
      var re = pathToRegexp(TEST_PATH, null, { end: false })

      expect(re.keys).to.deep.equal([TEST_PARAM])
      expect(exec(re, '/user/123/show')).to.deep.equal(['/user/123', '123'])
    })
  })

  describe('tokens', function () {
    var tokens = pathToRegexp.parse(TEST_PATH)

    it('should expose method to compile tokens to regexp', function () {
      var re = pathToRegexp.tokensToRegExp(tokens)

      expect(exec(re, '/user/123')).to.deep.equal(['/user/123', '123'])
    })

    it('should expose method to compile tokens to a path function', function () {
      var fn = pathToRegexp.tokensToFunction(tokens)

      expect(fn({ id: 123 })).to.equal('/user/123')
    })
  })

  describe('rules', function () {
    TESTS.forEach(function (test) {
      var path = test[0]
      var opts = test[1]
      var tokens = test[2]
      var matchCases = test[3]
      var compileCases = test[4]

      var keys = tokens.filter(function (token) {
        return typeof token !== 'string'
      })

      describe(util.inspect(path), function () {
        // Parsing and compiling is only supported with string input.
        if (typeof path === 'string') {
          it('should parse', function () {
            expect(pathToRegexp.parse(path)).to.deep.equal(tokens)
          })

          describe('compile', function () {
            var toPath = pathToRegexp.compile(path)

            compileCases.forEach(function (io) {
              var input = io[0]
              var output = io[1]

              if (output != null) {
                it('should compile using ' + util.inspect(input), function () {
                  expect(toPath(input)).to.equal(output)
                })
              } else {
                it('should not compile using ' + util.inspect(input), function () {
                  expect(function () {
                    toPath(input)
                  }).to.throw(TypeError)
                })
              }
            })
          })
        }

        describe('match' + (opts ? ' using ' + util.inspect(opts) : ''), function () {
          matchCases.forEach(function (io) {
            var input = io[0]
            var output = io[1]
            var message = 'should' + (output ? ' ' : ' not ') + 'match ' + util.inspect(input)

            it(message, function () {
              var re = pathToRegexp(path, opts)

              expect(re.keys).to.deep.equal(keys)
              expect(exec(re, input)).to.deep.equal(output)
            })
          })
        })
      })
    })
  })

  describe('compile errors', function () {
    it('should throw when a required param is undefined', function () {
      var toPath = pathToRegexp.compile('/a/:b/c')

      expect(function () {
        toPath()
      }).to.throw(TypeError, 'Expected "b" to be defined')
    })

    it('should throw when it does not match the pattern', function () {
      var toPath = pathToRegexp.compile('/:foo(\\d+)')

      expect(function () {
        toPath({ foo: 'abc' })
      }).to.throw(TypeError, 'Expected "foo" to match "\\d+"')
    })

    it('should throw when expecting a repeated value', function () {
      var toPath = pathToRegexp.compile('/:foo+')

      expect(function () {
        toPath({ foo: [] })
      }).to.throw(TypeError, 'Expected "foo" to not be empty')
    })

    it('should throw when not expecting a repeated value', function () {
      var toPath = pathToRegexp.compile('/:foo')

      expect(function () {
        toPath({ foo: [] })
      }).to.throw(TypeError, 'Expected "foo" to not repeat')
    })

    it('should throw when repeated value does not match', function () {
      var toPath = pathToRegexp.compile('/:foo(\\d+)+')

      expect(function () {
        toPath({ foo: [1, 2, 3, 'a'] })
      }).to.throw(TypeError, 'Expected all "foo" to match "\\d+"')
    })
  })
})

/**
 * Execute a regular expression and return a flat array for comparison.
 *
 * @param  {RegExp} re
 * @param  {String} str
 * @return {Array}
 */
function exec (re, str) {
  var match = re.exec(str)

  return match && Array.prototype.slice.call(match)
}