From a0f79760cf91ed225827b2b89378d0a10a19f736 Mon Sep 17 00:00:00 2001 From: Yoeri Nijs Date: Wed, 26 Jul 2023 09:49:57 +0200 Subject: [PATCH] feat(vinternalrouter): implement route wildcard --- README.md | 31 ++++ .../__tests__/v-internal-router.spec.ts | 161 ++++++++++++++++++ src/core/router/v-internal-router.ts | 9 +- 3 files changed, 199 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e658db8..26044a6 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Check out the [demo application](https://github.com/YoeriNijs/vienna-demo-app). - [Json](#json) - [Routes](#routes) - [Nested routes](#nested-routes) + - [Route wildcards](#route-wildcards) - [Route data](#route-data) - [Route params](#route-params) - [Query params](#query-params) @@ -597,6 +598,36 @@ export class Application {} ``` +### Route wildcards + +The Vienna router supports wildcards. Just pass the `*`-sign to add a wildcard. Note that the first route wins. This means +that if you have two routes that match, the router picks the first one. It does not matter whether one of them is a wildcard. + +``` + +@VApplication({ + declarations: [ + AboutComponent, + PageNotFoundComponent + ], + routes: [ + { + path: '/about', + component: AboutComponent + }, + { + path: '*', + component: PageNotFoundComponent + } + ] +}) +export class Application {} + +``` + +Please note: the example above is just to demonstrate how you can use the wildcard. Of course, if you to render a component +for a page that is not found, you can also implement the `routeNotFoundStrategy`. + ### Route data Optional key-value based map to specify some custom values for a specific route. diff --git a/src/core/router/__tests__/v-internal-router.spec.ts b/src/core/router/__tests__/v-internal-router.spec.ts index f792570..d5118e3 100644 --- a/src/core/router/__tests__/v-internal-router.spec.ts +++ b/src/core/router/__tests__/v-internal-router.spec.ts @@ -423,5 +423,166 @@ describe('VInternalRouter', () => { ] }).catch(e => expect(e).toEqual(new VInvalidRouteStrategyException('Invalid route strategy: \'none\''))); }); + + it('should navigate with route param', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('/:id'); + done(); + }); + setup('/blog/:id', { + routes: [ + { + path: '/blog', + component: jest.fn(), + guards: [], + children: [ + { + path: '/:id', + component: jest.fn(), + guards: [], + children: [] + } + ] + } + ] + }); + }); + + it('should navigate with query param', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('/page'); + done(); + }); + setup('/page?message=Hello%20there', { + routes: [ + { + path: '/page', + component: jest.fn(), + guards: [], + children: [] + } + ] + }); + }); + + it('should navigate with query param and wildcard', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('/'); + done(); + }); + setup('/*?message=Hello%20there', { + routes: [ + { + path: '/', + component: jest.fn(), + guards: [], + children: [] + } + ] + }); + }); + + it('should navigate with query param and wildcard', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('/'); + done(); + }); + setup('*?message=Hello%20there', { + routes: [ + { + path: '/', + component: jest.fn(), + guards: [], + children: [] + } + ] + }); + }); + + it('should navigate with wildcard and children', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('/relevant'); + done(); + }); + setup('/*/relevant', { + routes: [ + { + path: '/*', + component: jest.fn(), + guards: [], + children: [ + { + path: '/relevant', + component: jest.fn(), + guards: [], + children: [] + } + ] + } + ] + }); + }); + + it('should navigate with wildcard and children without slash', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('/relevant'); + done(); + }); + setup('/*/relevant', { + routes: [ + { + path: '*', + component: jest.fn(), + guards: [], + children: [ + { + path: '/relevant', + component: jest.fn(), + guards: [], + children: [] + } + ] + } + ] + }); + }); + + it('should navigate to first route before wildcard', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('/about'); + done(); + }); + setup('/about', { + routes: [ + { + path: '/about', + component: jest.fn() + }, + { + path: '*', + component: jest.fn() + } + ] + }); + }); + + it('should navigate to wildcard first', done => { + eventBus.subscribe(VInternalEventName.NAVIGATED, (r: VRoute) => { + expect(r.path).toEqual('*'); + done(); + }); + setup('/about', { + routes: [ + { + path: '*', + component: jest.fn() + }, + { + path: '/about', + component: jest.fn() + } + ] + }); + }); }); }); \ No newline at end of file diff --git a/src/core/router/v-internal-router.ts b/src/core/router/v-internal-router.ts index 8412d50..cb75635 100644 --- a/src/core/router/v-internal-router.ts +++ b/src/core/router/v-internal-router.ts @@ -129,14 +129,19 @@ export class VInternalRouter { const lowerUrl = url.toLowerCase(); const paramIndex = url.indexOf('?'); if (paramIndex === -1) { - return lowerPath === lowerUrl; + return lowerPath === lowerUrl || this.isWildCard(lowerPath); } else { - return lowerPath === lowerUrl.substring(0, paramIndex); + const pathBeforeParam = lowerUrl.substring(0, paramIndex); + return lowerPath === pathBeforeParam || this.isWildCard(pathBeforeParam); } }); return exactRoute ? exactRoute : null; } + private isWildCard(value: string): boolean { + return value === '*' || value === '/*'; + } + private dispatchNavigationAction(route: VRoute): void { const eventBus: VInternalEventbus = this.options.eventBus; eventBus.unsubscribe(VInternalEventName.ROUTE_DATA);