diff --git a/packages/frontend-plugin-api/src/routing/RouteRef.test.ts b/packages/frontend-plugin-api/src/routing/RouteRef.test.ts index b8e86d1b99..cb1c85ccea 100644 --- a/packages/frontend-plugin-api/src/routing/RouteRef.test.ts +++ b/packages/frontend-plugin-api/src/routing/RouteRef.test.ts @@ -15,13 +15,30 @@ */ import { AnyRouteParams } from './types'; -import { RouteRef, createRouteRef } from './RouteRef'; +import { RouteRef, createRouteRef, toInternalRouteRef } from './RouteRef'; describe('RouteRef', () => { - it('should be created', () => { + it('should be created and have a mutable ID', () => { const routeRef: RouteRef = createRouteRef(); - expect(() => routeRef.T).toThrow(); - expect(String(routeRef)).toBe('RouteRef{}'); + const internal = toInternalRouteRef(routeRef); + expect(() => internal.T).toThrow(); + expect(internal.getParams()).toEqual([]); + expect(internal.getDescription()).toMatch(/RouteRef\.test\.ts/); + + expect(String(internal)).toMatch( + /^RouteRef\{created at .*RouteRef\.test\.ts.*\}$/, + ); + + expect(() => internal.setId('')).toThrow( + 'RouteRef id must be a non-empty string', + ); + + internal.setId('some-id'); + expect(String(internal)).toBe('RouteRef{some-id}'); + + expect(() => internal.setId('some-other-id')).toThrow( + "RouteRef was referenced twice as both 'some-id' and 'some-other-id'", + ); }); it('should be created with params', () => { @@ -31,7 +48,10 @@ describe('RouteRef', () => { }> = createRouteRef({ params: ['x', 'y'], }); - expect(() => routeRef.T).toThrow(); + const internal = toInternalRouteRef(routeRef); + expect(internal.getParams()).toEqual(['x', 'y']); + expect(internal.getDescription()).toMatch(/RouteRef\.test\.ts/); + expect(() => internal.T).toThrow(); }); it('should properly infer and validate parameter types and assignments', () => { diff --git a/packages/frontend-plugin-api/src/routing/RouteRef.ts b/packages/frontend-plugin-api/src/routing/RouteRef.ts index b19b77d298..5f1f7a135d 100644 --- a/packages/frontend-plugin-api/src/routing/RouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/RouteRef.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { describeParentCallSite } from './describeParentCallSite'; import { AnyRouteParams } from './types'; /** @@ -36,6 +37,9 @@ export interface InternalRouteRef< > extends RouteRef { readonly version: 'v1'; getParams(): string[]; + getDescription(): string; + + setId(id: string): void; } /** @internal */ @@ -60,10 +64,13 @@ export class RouteRefImpl implements InternalRouteRef { readonly $$type = '@backstage/RouteRef'; readonly version = 'v1'; + #id?: string; #params: string[]; + #creationSite: string; - constructor(readonly params: string[] = []) { + constructor(readonly params: string[] = [], creationSite: string) { this.#params = params; + this.#creationSite = creationSite; } get T(): never { @@ -74,8 +81,27 @@ export class RouteRefImpl implements InternalRouteRef { return this.#params; } + getDescription(): string { + if (this.#id) { + return this.#id; + } + return `created at '${this.#creationSite}'`; + } + + setId(id: string): void { + if (!id) { + throw new Error('RouteRef id must be a non-empty string'); + } + if (this.#id) { + throw new Error( + `RouteRef was referenced twice as both '${this.#id}' and '${id}'`, + ); + } + this.#id = id; + } + toString(): string { - return `RouteRef{}`; + return `RouteRef{${this.getDescription()}}`; } } @@ -100,7 +126,9 @@ export function createRouteRef< ? TParams : { [param in TParamKeys]: string } > { + const creationSite = describeParentCallSite(); return new RouteRefImpl( config?.params as string[] | undefined, + creationSite, ) as RouteRef; }