UNPKG

@tdb/util

Version:
410 lines (362 loc) 12.4 kB
import { Route, RouteOptions } from '.'; import { expect } from '../../test'; describe('url.Route', () => { describe('constructor', () => { it('default values', () => { const route = Route.create(' /foo/:id '); expect(route.method).to.eql('GET'); expect(route.path).to.eql('/foo/:id'); expect(route.toString()).to.eql('/foo/:id'); expect(route.title).to.eql(undefined); expect(route.docs).to.eql(undefined); expect(route.description).to.eql(undefined); expect(route.meta).to.eql({}); }); it('given values', () => { const route = Route.create('/foo/:id', { title: 'My Title', docs: '/docs', description: 'Does...', meta: { foo: 123 }, }); expect(route.title).to.eql('My Title'); expect(route.docs).to.eql('/docs'); expect(route.description).to.eql('Does...'); expect(route.meta).to.eql({ foo: 123 }); }); it('sets REST method', () => { const route = Route.create('/foo/:id', { method: 'POST' }); expect(route.method).to.eql('POST'); }); it('symbols', () => { const route = Route.create('/foo/:id', { schema: { params: 'IFoo', query: 'IBam', data: 'IBoo' }, }); expect(route.schema.params).to.eql('IFoo'); expect(route.schema.query).to.eql('IBam'); expect(route.schema.data).to.eql('IBoo'); }); it('title/docs', () => { const route = Route.create('/foo/:id', { title: 'MyRoute', docs: '/docs/my-route', }); expect(route.title).to.eql('MyRoute'); expect(route.docs).to.eql('/docs/my-route'); }); }); describe('clone', () => { it('clones with same values', () => { const route = Route.create('/foo/:id'); const clone = route.clone(); expect(route).to.not.equal(clone); }); it('clones with new value', () => { const route = Route.create('/foo/:id', {}); const clone = route.clone({ path: '/bar' }); expect(route).to.not.equal(clone); expect(clone.path).to.eql('/bar'); }); }); describe('toObject', () => { it('with default values', () => { const route = Route.create('/'); const obj = route.toObject(); expect(obj.path).to.eql('/'); expect(obj.method).to.eql('GET'); expect(obj.tokens).to.eql(['/']); expect(obj.schema).to.eql({}); }); it('with specific values', () => { const route = Route.create('/foo/:id', { method: 'POST', schema: { params: 'IFoo' }, }); const obj = route.toObject(); expect(obj.path).to.eql('/foo/:id'); expect(obj.method).to.eql('POST'); expect(obj.tokens.length).to.eql(2); expect(obj.tokens[0]).to.eql('/foo'); expect(obj.tokens[1]).to.eql({ name: 'id', prefix: '/', delimiter: '/', optional: false, repeat: false, partial: false, }); expect(obj.schema).to.eql({ params: 'IFoo' }); }); }); describe('isMatch', () => { const test = ( url: string | undefined, result: boolean, options?: Partial<RouteOptions>, ) => { const route = Route.create('/foo/:id', { ...options }); expect(route.isMatch(url)).to.eql(result); }; it('is a match', () => { test('/foo/123', true); test('/foo/abc', true); test('/foo/abc?q=123', true); test('/foo/abc/', true, { strict: false }); test('/Foo/bar', true, { caseSensitive: false }); // Case sentitive (no match) }); it('is not a match', () => { test(undefined, false); test('', false); test('/', false); test('/foobar', false); test('/foo/abc/', false); // Strict (by default) test('/Foo/bar', false); // Case sentitive (no match) }); it('multi-part', () => { const route = Route.create('/o/:name/:path*'); const path = '/o/acme/foo/bar'; expect(route.isMatch(path)).to.eql(true); expect(route.params(path)).to.eql({ name: 'acme', path: 'foo/bar' }); }); }); describe('params (strongly typed)', () => { type IParams = { org: string; id: number | string; }; const route = Route.create<IParams>('/foo/:org/:id'); it('no url (undefined}', () => { const res = route.params(); expect(res).to.eql({}); }); it(':id as string', () => { const res = route.params('/foo/ibm/hello'); expect(res.org).to.eql('ibm'); expect(res.id).to.eql('hello'); }); it(':id as number', () => { const res = route.params('/foo/ibm/123'); expect(res.org).to.eql('ibm'); expect(res.id).to.eql(123); }); it(':id as boolean', () => { const res = route.params('/foo/ibm/TruE'); expect(res.org).to.eql('ibm'); expect(res.id).to.eql(true); }); it('has escaped colon (:) in route', () => { const route = Route.create<{ id: number }>('/foo::id'); const res = route.params('/foo:123'); expect(res).to.eql({ id: 123 }); }); }); describe('params', () => { it('wildcard (*)', () => { const route = Route.create<{ bar: string }>('/foo/:bar*'); const res = route.params('/foo/bar/bax'); expect(res.bar).to.eql('bar/bax'); }); it('removes query-string', () => { const route = Route.create<{ id: number }>('/foo/:id?q=:srch'); const res = route.params('/foo/123?q=search'); expect(res.id).to.eql(123); }); }); describe('query', () => { type IParams = { id: number | string }; type IQuery = { q: string; force?: boolean }; const route = Route.create<IParams, IQuery>('/foo/:id'); it('as string', () => { const query = route.query('/foo/123?q=abc'); expect(query.q).to.eql('abc'); }); it('as number', () => { const query = route.query('/foo/123?q=456'); expect(query.q).to.eql(456); }); it('as boolean', () => { const query = route.query('/foo/123?q=truE'); expect(query.q).to.eql(true); }); it('flag', () => { const test = (url: string, result?: boolean) => { const query = route.query(url); expect(query.force).to.eql(result); }; test('/foo?force', true); test('/foo?force=true', true); test('/foo?force=false', false); test('/foo', undefined); }); }); describe('url', () => { type IParams = { id: number | string }; type IQuery = { q: string; force?: boolean; f?: boolean }; const URL = '/foo/123?q=hello&force'; const route = Route.create<IParams, IQuery>('/foo/:id', {}); const url = route.url(URL); it('creates URL', () => { expect(url.route).to.eql(route); expect(url.path).to.eql(URL); expect(url.toString()).to.eql(URL); expect(url.params).to.eql({ id: 123 }); expect(url.query).to.eql({ q: 'hello', force: true }); }); it('url with origin (domain)', () => { const complete = `http://localhost:3000/foo/123?q=hello&force`; const res1 = url.toString({ origin: 'http://localhost:3000' }); const res2 = url.toString({ origin: 'http://localhost:3000/' }); const res3 = url.toString({ origin: 'http://localhost:3000///' }); expect(res1).to.eql(complete); expect(res2).to.eql(complete); expect(res3).to.eql(complete); }); it('hasFlag', () => { expect(url.hasFlag('force')).to.eql(true); expect(url.hasFlag('f')).to.eql(false); expect(url.hasFlag(['f', 'force'])).to.eql(true); expect(url.hasFlag()).to.eql(false); expect(url.hasFlag('bob')).to.eql(false); }); }); describe('toUrl', () => { type IQuery = { q: string; force?: boolean; f?: boolean }; type IParams = { org: string; id: number | string; }; const route = Route.create<IParams, IQuery>('/foo/:org/:id'); it('no params (error)', () => { const fn = () => { route.toUrl({ params: {} as any }); }; expect(fn).to.throw(/Expected "org"/); }); it('params only', () => { const url = route.toUrl({ params: { org: 'acme', id: 123 }, }).s; expect(url).to.eql('/foo/acme/123'); }); it('query as [:param]', () => { const route = Route.create('/manifest?path=:path&markdown'); const url = route.toUrl({ params: { path: '/my/path' } }); expect(url.toString()).to.eql('/manifest?path=%2Fmy%2Fpath&markdown'); expect(url.query).to.eql({ path: '/my/path', markdown: true }); expect(url.params).to.eql({ path: '%2Fmy%2Fpath' }); }); it('query as [:param] as well as {object}', () => { const route = Route.create('/manifest?path=:path&markdown'); const url = route.toUrl({ params: { path: '**' }, query: { s: 'find' }, }); expect(url.toString()).to.eql('/manifest?path=**&markdown&s=find'); expect(url.query).to.eql({ path: '**', markdown: true, s: 'find' }); }); it('encoded', () => { const org = 'café verona'; const url = route.toUrl({ params: { org, id: 123 }, }).s; expect(url).to.eql('/foo/caf%C3%A9%20verona/123'); expect(decodeURI(url)).to.eql('/foo/café verona/123'); }); it('params and query-string', () => { const url = route .toUrl({ params: { org: 'acme', id: 123 }, query: { q: 'search' }, }) .toString(); expect(url).to.eql('/foo/acme/123?q=search'); }); it('multi part query-string', () => { const url = route .toUrl({ params: { org: 'acme', id: 123 }, query: { q: 'hello', f: true }, }) .toString(); expect(url).to.eql('/foo/acme/123?q=hello&f=true'); }); it('query-string with no value (undeined)', () => { const url = route .toUrl({ params: { org: 'acme', id: 123 }, query: { q: 'hello', f: undefined }, }) .toString(); expect(url).to.eql('/foo/acme/123?q=hello'); }); it('encoded query-string', () => { const url = route .toUrl({ params: { org: 'acme', id: 123 }, query: { q: 'foo bar' }, }) .toString(); expect(url).to.eql('/foo/acme/123?q=foo%20bar'); }); it('has no origin by default', () => { const url = route.toUrl({ params: { org: 'acme', id: 123 } }); expect(url.origin).to.eql(undefined); expect(url.toString()).to.eql('/foo/acme/123'); }); it('sets a default origin', () => { const url = route.toUrl({ params: { org: 'acme', id: 123 }, origin: 'http://localhost:8080//', }); expect(url.origin).to.eql('http://localhost:8080'); expect(url.toString()).to.eql('http://localhost:8080/foo/acme/123'); // NB: Override with parameter. expect(url.toString({ origin: 'localhost:3000' })).to.eql( 'localhost:3000/foo/acme/123', ); }); }); describe('walk/map/find', () => { const TREE = { id: 123, foo: Route.create('/foo'), bar: { baz: Route.create('/foo/bar'), label: 'Hello', zoo: { child: Route.create('/deep'), }, }, }; it('walks a tree', () => { let routes: Route[] = []; Route.walk(TREE, route => (routes = [...routes, route])); expect(routes.length).to.eql(3); expect(routes[0].path).to.eql('/foo'); expect(routes[1].path).to.eql('/foo/bar'); expect(routes[2].path).to.eql('/deep'); }); it('walks a tree and stops before end', () => { let count = 0; Route.walk(TREE, (route, e) => { if (count >= 1) { e.stop(); } count++; }); expect(count).to.eql(2); }); it('maps a tree', () => { const res = Route.map(TREE, route => route.path); expect(res).to.eql(['/foo', '/foo/bar', '/deep']); }); it('finds a route', () => { const res1 = Route.find(TREE, r => r.path.startsWith('/foo')); const res2 = Route.find(TREE, r => r.path.startsWith('/deep')); const res3 = Route.find(TREE, r => r.path.startsWith('/__NOT_EXIST__')); expect(res1 && res1.path).to.eql('/foo'); expect(res2 && res2.path).to.eql('/deep'); expect(res3).to.eql(undefined); }); }); });