diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx new file mode 100644 index 0000000000..7dacd9c3e8 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx @@ -0,0 +1,129 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyEntityCardExtension } from './convertLegacyEntityCardExtension'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '../blueprints'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyEntityCardExtension', () => { + it('should convert an entity card extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleCard', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityCardExtension(LegacyExtension); + expect(converted.kind).toBe('entity-card'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + undefined, + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should convert an entity card extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleCard', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityCardExtension(LegacyExtension, { + name: 'other', + filter: 'my-filter', + }); + expect(converted.kind).toBe('entity-card'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + 'my-filter', + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should support various naming patterns for entity card extensions', async () => { + const withName = (name: string) => { + const converted = convertLegacyEntityCardExtension( + legacyPlugin.provide( + createRoutableExtension({ + name, + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ), + ); + return converted.name; + }; + + expect(withName('EntityTestCard')).toBe(undefined); + expect(withName('EntityTestTrimCard')).toBe('trim'); + expect(withName('EntityTeStTrimCard')).toBe('trim'); + expect(withName('EntityExampleCard')).toBe('example'); + expect(withName('EntityExAmpleCard')).toBe('ex-ample'); + expect(withName('ExampleCard')).toBe('example-card'); + }); +}); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx index afe5211d1b..76442f526d 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx @@ -19,6 +19,7 @@ import { BackstagePlugin, getComponentData } from '@backstage/core-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import React, { ComponentType } from 'react'; import { EntityCardBlueprint } from '../blueprints'; +import kebabCase from 'lodash/kebabCase'; /** @alpha */ export function convertLegacyEntityCardExtension( @@ -40,7 +41,7 @@ export function convertLegacyEntityCardExtension( const plugin = getComponentData(element, 'core.plugin'); const pluginId = plugin?.getId(); - const match = extName.match(/^Entity(.*)Content$/); + const match = extName.match(/^Entity(.*)Card$/); const infix = match?.[1] ?? extName; let name: string | undefined = infix; @@ -55,6 +56,7 @@ export function convertLegacyEntityCardExtension( name = undefined; } } + name = name && kebabCase(name); return EntityCardBlueprint.make({ name: overrides?.name ?? name, diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx new file mode 100644 index 0000000000..6d7c8f0420 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { coreExtensionData } from '@backstage/frontend-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyEntityContentExtension } from './convertLegacyEntityContentExtension'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '../blueprints'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyEntityContentExtension', () => { + it('should convert an entity content extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleContent', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityContentExtension(LegacyExtension); + expect(converted.kind).toBe('entity-content'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/example'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + undefined, + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should convert an entity content extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleContent', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityContentExtension(LegacyExtension, { + name: 'other', + defaultPath: '/other', + defaultTitle: 'Other', + filter: 'my-filter', + }); + expect(converted.kind).toBe('entity-content'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/other'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + 'my-filter', + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should support various naming patterns for entity content extensions', async () => { + const withName = (name: string) => { + const converted = convertLegacyEntityContentExtension( + legacyPlugin.provide( + createRoutableExtension({ + name, + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ), + ); + return converted.name; + }; + + expect(withName('EntityTestContent')).toBe(undefined); + expect(withName('EntityTestTrimContent')).toBe('trim'); + expect(withName('EntityTeStTrimContent')).toBe('trim'); + expect(withName('EntityExampleContent')).toBe('example'); + expect(withName('EntityExAmpleContent')).toBe('ex-ample'); + expect(withName('ExampleContent')).toBe('example-content'); + }); +}); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx index a44a6281da..e44b5671fc 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx @@ -71,6 +71,7 @@ export function convertLegacyEntityContentExtension( name = undefined; } } + name = name && kebabCase(name); return EntityContentBlueprint.make({ name: overrides?.name ?? name,