From e074f676a50a9c78e4874c3bea93b53e2ca7a667 Mon Sep 17 00:00:00 2001 From: Anton Platonov Date: Tue, 1 Oct 2024 12:55:22 +0300 Subject: [PATCH] fix: support using view component name in urlFor --- src/resolver/generateUrls.ts | 10 +++++----- src/resolver/types.d.ts | 8 ++++---- src/router.ts | 8 +++++++- src/types.d.ts | 12 ++---------- test/router/url-for.spec.ts | 13 ++++++++++--- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/resolver/generateUrls.ts b/src/resolver/generateUrls.ts index 763bf4d3..6dac306d 100644 --- a/src/resolver/generateUrls.ts +++ b/src/resolver/generateUrls.ts @@ -10,7 +10,7 @@ import { parse, type ParseOptions, type Token, tokensToFunction, type TokensToFunctionOptions } from 'path-to-regexp'; import type { EmptyObject, Writable } from 'type-fest'; import Resolver from './resolver.js'; -import type { AnyObject, ChildrenCallback, Route } from './types.js'; +import type { AnyObject, ChildrenCallback, Route, Params } from './types.js'; import { getRoutePath, isString } from './utils.js'; export type UrlParams = Readonly | number | string>>; @@ -33,7 +33,7 @@ function cacheRoutes( if (Array.isArray>>>(routes)) { for (const childRoute of routes) { childRoute.parent = route; - cacheRoutes(routesByName, childRoute, childRoute.__children ?? childRoute.children); + cacheRoutes(routesByName, childRoute, childRoute.__children ?? childRoute.children, cacheKeyProvider); } } } @@ -67,7 +67,7 @@ export type GenerateUrlOptions = ParseOptions & * Generates a unique route name based on all parent routes with the specified separator. */ uniqueRouteNameSep?: string; - cacheKeyProvider?(route: Route): string; + cacheKeyProvider?(route: Route): string | undefined; }> & TokensToFunctionOptions; @@ -80,7 +80,7 @@ export type UrlGenerator = (routeName: string, params?: Params) => string; function generateUrls( resolver: Resolver, - options: GenerateUrlOptions = { encode: encodeURIComponent }, + options: GenerateUrlOptions = {}, ): UrlGenerator { if (!(resolver instanceof Resolver)) { throw new TypeError('An instance of Resolver is expected'); @@ -129,7 +129,7 @@ function generateUrls( route.fullPath = fullPath; } - const toPath = tokensToFunction(cached.tokens, options); + const toPath = tokensToFunction(cached.tokens, { encode: encodeURIComponent, ...options }); let url = toPath(params) || '/'; if (options.stringifyQueryParams && params) { diff --git a/src/resolver/types.d.ts b/src/resolver/types.d.ts index e0ce3dee..95bf5276 100644 --- a/src/resolver/types.d.ts +++ b/src/resolver/types.d.ts @@ -59,10 +59,10 @@ export type ChildrenCallback = ( context: RouteChildrenContext, ) => MaybePromise>>; -export type ParamValue = readonly string[] | string; +export type PrimitiveParamValue = string | number | null; -export type IndexedParams = Readonly<{ - [key in keyof any]?: ParamValue; -}>; +export type ParamValue = PrimitiveParamValue | readonly PrimitiveParamValue[]; + +export type IndexedParams = Readonly>; export type Params = IndexedParams | ParamValue[]; diff --git a/src/router.ts b/src/router.ts index 43c6c3e2..2cf35443 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1057,7 +1057,13 @@ export class Router extends Resolver { */ urlForName(name: string, params?: Params | null): string { if (!this.__urlForName) { - this.__urlForName = generateUrls(this); + this.__urlForName = generateUrls(this, { + cacheKeyProvider(route): string | undefined { + return 'component' in route && typeof route.component === 'string' + ? (route as Readonly<{ component: string }>).component + : undefined; + }, + }); } return getPathnameForRouter(this.__urlForName(name, params ?? undefined), this); } diff --git a/src/types.d.ts b/src/types.d.ts index fca0a40e..f88417ee 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,9 +1,9 @@ import type { EmptyObject, RequireAtLeastOne } from 'type-fest'; import type { ResolutionError } from './resolver/resolver.js'; -import type { ChildrenCallback } from './resolver/types.js'; +import type { ChildrenCallback, IndexedParams, Params, ParamValue, PrimitiveParamValue } from './resolver/types.js'; import type { Router } from './router.js'; -export { ResolutionError }; +export type { ResolutionError, IndexedParams, Params, ParamValue, PrimitiveParamValue }; export type VaadinRouterLocationChangedEvent = CustomEvent< Readonly<{ @@ -447,11 +447,3 @@ export type Route = Readonly< path?: string | readonly string[]; } >; - -export type PrimitiveParamValue = string | number | null; - -export type ParamValue = PrimitiveParamValue | readonly PrimitiveParamValue[]; - -export type IndexedParams = Readonly>; - -export type Params = IndexedParams | ParamValue[]; diff --git a/test/router/url-for.spec.ts b/test/router/url-for.spec.ts index da39f9c1..2066f18f 100644 --- a/test/router/url-for.spec.ts +++ b/test/router/url-for.spec.ts @@ -1,4 +1,5 @@ import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; import { Router } from '../../src/router.js'; import '../setup.js'; import { cleanup } from './test-utils.js'; @@ -215,6 +216,8 @@ describe('urlFor', () => { expect(parse).to.be.calledWithMatch(path); expect(result).to.equal('/app/users/42'); } finally { + // @ts-ignore not exposed anymore + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access Router.pathToRegexp.parse.restore(); } }); @@ -246,7 +249,7 @@ describe('urlFor', () => { }); it('should prepend baseUrl', () => { - router.baseUrl = '/base/'; + (router as { baseUrl: string }).baseUrl = '/base/'; expect(router.urlForPath('foo')).to.equal('/base/foo'); expect(router.urlForPath('/bar')).to.equal('/base/bar'); }); @@ -254,11 +257,13 @@ describe('urlFor', () => { // cannot mock the call to `compile()` from the 'pathToRegexp' package xit('should use pathToRegexp', () => { const compiledRegExp = sinon.stub().returns('/ok/url'); + // @ts-ignore not exposed anymore + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access const compile = sinon.stub(Router.pathToRegexp, 'compile').returns(compiledRegExp); try { - const path = '/users/:userId', - parameters = { userId: 42, foo: 'bar' }; + const path = '/users/:userId'; + const parameters = { userId: 42, foo: 'bar' }; const result = router.urlForPath(path, parameters); expect(compile).to.be.calledOnce; @@ -268,6 +273,8 @@ describe('urlFor', () => { expect(compiledRegExp).to.have.returned('/ok/url'); expect(result).to.equal('/ok/url'); } finally { + // @ts-ignore not exposed anymore + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access Router.pathToRegexp.compile.restore(); } });