added tests to createPlugin, plus minor refactors
Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com> Co-authored-by: Johan Haals <johan.haals@gmail.com> Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -29,5 +29,5 @@ export const GraphiqlPage = createPageExtension({
|
||||
|
||||
export const graphiqlPlugin = createPlugin({
|
||||
id: 'graphiql',
|
||||
defaultExtensions: [GraphiqlPage],
|
||||
extensions: [GraphiqlPage],
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ export function createApp(options: { plugins: BackstagePlugin[] }): {
|
||||
// apply config to adjust default extension instances and add more
|
||||
const extensionParams = mergeExtensionParameters(
|
||||
[
|
||||
...options.plugins.flatMap(plugin => plugin.defaultExtensions),
|
||||
...options.plugins.flatMap(plugin => plugin.extensions),
|
||||
...builtinExtensions,
|
||||
],
|
||||
readAppExtensionParameters(appConfig),
|
||||
@@ -116,8 +116,8 @@ export function createApp(options: { plugins: BackstagePlugin[] }): {
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{rootComponents.map(Component => (
|
||||
<Component />
|
||||
{rootComponents.map((Component, i) => (
|
||||
<Component key={i} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Core API used by Backstage frontend apps.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
@@ -19,15 +19,7 @@ export interface BackstagePlugin {
|
||||
// (undocumented)
|
||||
$$type: 'backstage-plugin';
|
||||
// (undocumented)
|
||||
defaultExtensions: Extension<unknown>[];
|
||||
// (undocumented)
|
||||
id: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BackstagePluginOptions {
|
||||
// (undocumented)
|
||||
defaultExtensions?: Extension<unknown>[];
|
||||
extensions: Extension<unknown>[];
|
||||
// (undocumented)
|
||||
id: string;
|
||||
}
|
||||
@@ -121,7 +113,7 @@ export function createPageExtension<
|
||||
): Extension<TConfig>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createPlugin(options: BackstagePluginOptions): BackstagePlugin;
|
||||
export function createPlugin(options: PluginOptions): BackstagePlugin;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createSchemaFromZod<TOutput, TInput>(
|
||||
@@ -174,6 +166,14 @@ export type ExtensionDataValue<TData extends AnyExtensionDataMap> = {
|
||||
[K in keyof TData]: TData[K]['T'];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PluginOptions {
|
||||
// (undocumented)
|
||||
extensions?: Extension<unknown>[];
|
||||
// (undocumented)
|
||||
id: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type PortableSchema<TOutput> = {
|
||||
parse: (input: unknown) => TOutput;
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@testing-library/jest-dom": "^5.10.1"
|
||||
"@backstage/frontend-app-api": "workspace:^",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2023 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 React from 'react';
|
||||
import { PortableSchema } from '../createSchemaFromZod';
|
||||
import { coreExtensionData } from '../types';
|
||||
import { createPageExtension } from './createPageExtension';
|
||||
|
||||
describe('createPageExtension', () => {
|
||||
it('creates the extension properly', () => {
|
||||
const configSchema: PortableSchema<{ path: string }> = {
|
||||
parse: jest.fn(),
|
||||
schema: {} as any,
|
||||
};
|
||||
|
||||
expect(
|
||||
createPageExtension({
|
||||
id: 'test',
|
||||
configSchema,
|
||||
component: async () => <div />,
|
||||
}),
|
||||
).toEqual({
|
||||
$$type: 'extension',
|
||||
id: 'test',
|
||||
at: 'core.router/routes',
|
||||
configSchema: expect.anything(),
|
||||
disabled: false,
|
||||
inputs: {},
|
||||
output: {
|
||||
component: expect.anything(),
|
||||
path: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(
|
||||
createPageExtension({
|
||||
id: 'test',
|
||||
at: 'other/place',
|
||||
disabled: true,
|
||||
configSchema,
|
||||
inputs: {
|
||||
first: {
|
||||
extensionData: { component: coreExtensionData.reactComponent },
|
||||
},
|
||||
},
|
||||
component: async () => <div />,
|
||||
}),
|
||||
).toEqual({
|
||||
$$type: 'extension',
|
||||
id: 'test',
|
||||
at: 'other/place',
|
||||
configSchema: expect.anything(),
|
||||
disabled: true,
|
||||
inputs: {
|
||||
first: {
|
||||
extensionData: { component: coreExtensionData.reactComponent },
|
||||
},
|
||||
},
|
||||
output: {
|
||||
component: expect.anything(),
|
||||
path: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(
|
||||
createPageExtension({
|
||||
id: 'test',
|
||||
defaultPath: '/here',
|
||||
component: async () => <div />,
|
||||
}),
|
||||
).toEqual({
|
||||
$$type: 'extension',
|
||||
id: 'test',
|
||||
at: 'core.router/routes',
|
||||
configSchema: expect.anything(),
|
||||
disabled: false,
|
||||
inputs: {},
|
||||
output: {
|
||||
component: expect.anything(),
|
||||
path: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createSchemaFromZod, PortableSchema } from './createSchemaFromZod';
|
||||
import { createSchemaFromZod, PortableSchema } from '../createSchemaFromZod';
|
||||
import {
|
||||
AnyExtensionDataMap,
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
Extension,
|
||||
ExtensionDataValue,
|
||||
} from './types';
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* Helper for creating extensions for a routable React page component.
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
export { createPageExtension } from './createPageExtension';
|
||||
@@ -15,26 +15,24 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Core API used by Backstage frontend plugins.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { createPageExtension } from './createPageExtension';
|
||||
export {
|
||||
createSchemaFromZod,
|
||||
type PortableSchema,
|
||||
} from './createSchemaFromZod';
|
||||
export * from './extensions';
|
||||
export {
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createPlugin,
|
||||
type AnyExtensionDataMap,
|
||||
type BackstagePlugin,
|
||||
type BackstagePluginOptions,
|
||||
type CreateExtensionOptions,
|
||||
type Extension,
|
||||
type ExtensionDataBind,
|
||||
type ExtensionDataRef,
|
||||
type ExtensionDataValue,
|
||||
} from './types';
|
||||
export * from './wiring';
|
||||
|
||||
@@ -25,6 +25,7 @@ export type ExtensionDataRef<T> = {
|
||||
};
|
||||
|
||||
/** @public */
|
||||
// TODO: change to options object with ID.
|
||||
export function createExtensionDataRef<T>(id: string): ExtensionDataRef<T> {
|
||||
return { id, $$type: 'extension-data' } as ExtensionDataRef<T>;
|
||||
}
|
||||
@@ -100,25 +101,3 @@ export function createExtension<
|
||||
inputs: options.inputs ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface BackstagePluginOptions {
|
||||
id: string;
|
||||
defaultExtensions?: Extension<unknown>[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface BackstagePlugin {
|
||||
$$type: 'backstage-plugin';
|
||||
id: string;
|
||||
defaultExtensions: Extension<unknown>[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createPlugin(options: BackstagePluginOptions): BackstagePlugin {
|
||||
return {
|
||||
...options,
|
||||
$$type: 'backstage-plugin',
|
||||
defaultExtensions: options.defaultExtensions ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2023 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 React from 'react';
|
||||
import { createApp } from '@backstage/frontend-app-api';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { createSchemaFromZod } from '../createSchemaFromZod';
|
||||
import {
|
||||
createExtension,
|
||||
coreExtensionData,
|
||||
createExtensionDataRef,
|
||||
} from '../types';
|
||||
import { createPlugin, BackstagePlugin } from './createPlugin';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
const nameExtensionDataRef = createExtensionDataRef<string>('name');
|
||||
|
||||
const TechRadarPage = createExtension({
|
||||
id: 'plugin.techradar.page',
|
||||
at: 'test.output/names',
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind.name('TechRadar');
|
||||
},
|
||||
});
|
||||
|
||||
const CatalogPage = createExtension({
|
||||
id: 'plugin.catalog.page',
|
||||
at: 'test.output/names',
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
configSchema: createSchemaFromZod(z =>
|
||||
z.object({ name: z.string().default('Catalog') }),
|
||||
),
|
||||
factory({ bind, config }) {
|
||||
bind.name(config.name);
|
||||
},
|
||||
});
|
||||
|
||||
const TechDocsAddon = createExtension({
|
||||
id: 'plugin.techdocs.addon.example',
|
||||
at: 'plugin.techdocs.page/addons',
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
configSchema: createSchemaFromZod(z =>
|
||||
z.object({ name: z.string().default('TechDocsAddon') }),
|
||||
),
|
||||
factory({ bind, config }) {
|
||||
bind.name(config.name);
|
||||
},
|
||||
});
|
||||
|
||||
const TechDocsPage = createExtension({
|
||||
id: 'plugin.techdocs.page',
|
||||
at: 'test.output/names',
|
||||
inputs: {
|
||||
addons: {
|
||||
extensionData: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind.name(`TechDocs-${inputs.addons.map(n => n.name).join('-')}`);
|
||||
},
|
||||
});
|
||||
|
||||
const outputExtension = createExtension({
|
||||
id: 'test.output',
|
||||
at: 'root',
|
||||
inputs: {
|
||||
names: {
|
||||
extensionData: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
component: coreExtensionData.reactComponent,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind.component(() =>
|
||||
React.createElement('span', {}, [
|
||||
`Names: ${inputs.names.map(n => n.name).join(', ')}`,
|
||||
]),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function createTestAppRoot({
|
||||
plugins,
|
||||
config = {},
|
||||
}: {
|
||||
plugins: BackstagePlugin[];
|
||||
config: JsonObject;
|
||||
}) {
|
||||
Object.defineProperty(process.env, 'APP_CONFIG', {
|
||||
value: [
|
||||
{
|
||||
data: config,
|
||||
context: 'test',
|
||||
},
|
||||
],
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
return createApp({ plugins: plugins }).createRoot();
|
||||
}
|
||||
|
||||
describe('createPlugin', () => {
|
||||
it('should create an empty plugin', () => {
|
||||
const plugin = createPlugin({ id: 'empty' });
|
||||
|
||||
expect(plugin).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create a plugin with extension instances', () => {
|
||||
const plugin = createPlugin({
|
||||
id: 'empty',
|
||||
extensions: [TechRadarPage, CatalogPage, outputExtension],
|
||||
});
|
||||
expect(plugin).toBeDefined();
|
||||
|
||||
render(
|
||||
createTestAppRoot({
|
||||
plugins: [plugin],
|
||||
config: { app: { extensions: [{ 'core.router': false }] } },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(screen.getByText('Names: TechRadar, Catalog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should create a plugin with nested extension instances', () => {
|
||||
const plugin = createPlugin({
|
||||
id: 'empty',
|
||||
extensions: [
|
||||
TechRadarPage,
|
||||
CatalogPage,
|
||||
TechDocsPage,
|
||||
TechDocsAddon,
|
||||
outputExtension,
|
||||
],
|
||||
});
|
||||
expect(plugin).toBeDefined();
|
||||
|
||||
render(
|
||||
createTestAppRoot({
|
||||
plugins: [plugin],
|
||||
config: {
|
||||
app: {
|
||||
extensions: [
|
||||
{ 'core.router': false },
|
||||
{
|
||||
'plugin.catalog.page': {
|
||||
config: { name: 'CatalogRenamed' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Names: TechRadar, CatalogRenamed, TechDocs-TechDocsAddon',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Extension } from '../types';
|
||||
|
||||
/** @public */
|
||||
export interface PluginOptions {
|
||||
id: string;
|
||||
extensions?: Extension<unknown>[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface BackstagePlugin {
|
||||
$$type: 'backstage-plugin';
|
||||
id: string;
|
||||
extensions: Extension<unknown>[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createPlugin(options: PluginOptions): BackstagePlugin {
|
||||
return {
|
||||
...options,
|
||||
$$type: 'backstage-plugin',
|
||||
extensions: options.extensions ?? [],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
createPlugin,
|
||||
type BackstagePlugin,
|
||||
type PluginOptions,
|
||||
} from './createPlugin';
|
||||
@@ -4298,8 +4298,10 @@ __metadata:
|
||||
resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/frontend-app-api": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@testing-library/jest-dom": ^5.10.1
|
||||
"@testing-library/react": ^12.1.3
|
||||
lodash: ^4.17.21
|
||||
zod: ^3.21.4
|
||||
zod-to-json-schema: ^3.21.4
|
||||
|
||||
Reference in New Issue
Block a user