Merge pull request #25892 from backstage/rugvip/generators

frontend-plugin-api: add tests for generator factories + error on undeclared output
This commit is contained in:
Patrik Oldsberg
2024-08-05 08:26:41 +02:00
committed by GitHub
4 changed files with 150 additions and 4 deletions
@@ -914,7 +914,7 @@ describe('instantiateAppNodeTree', () => {
namespace: 'app',
name: 'test',
attachTo: { id: 'ignored', input: 'ignored' },
output: [],
output: [testDataRef],
factory() {
const error = new Error('NOPE');
error.name = 'NopeError';
@@ -981,11 +981,12 @@ describe('instantiateAppNodeTree', () => {
createAppNodeInstance({
node: makeNode(
resolveExtensionDefinition(
// @ts-expect-error
createExtension({
namespace: 'app',
name: 'test',
attachTo: { id: 'ignored', input: 'ignored' },
output: [],
output: [], // Output not declared
factory({}) {
return [testDataRef('test')] as any;
},
@@ -394,6 +394,18 @@ describe('createExtension', () => {
}),
).toMatchObject({ version: 'v2' });
expect(
// @ts-expect-error
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef],
factory() {
return [stringDataRef('hello'), numberDataRef(4)];
},
}),
).toMatchObject({ version: 'v2' });
expect(
createExtension({
namespace: 'test',
@@ -428,6 +440,93 @@ describe('createExtension', () => {
).toMatchObject({ version: 'v2' });
});
it('should support new form of outputs with a generator', () => {
expect(
// @ts-expect-error
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
*factory() {
// Missing all outputs
},
}),
).toMatchObject({ version: 'v2' });
expect(
// @ts-expect-error
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
*factory() {
yield stringDataRef('hello'); // Missing number output
},
}),
).toMatchObject({ version: 'v2' });
// Duplicate output, we won't attempt to handle this a compile time and instead error out at runtime
expect(
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef],
*factory() {
yield stringDataRef('hello');
yield stringDataRef('hello');
},
}),
).toMatchObject({ version: 'v2' });
expect(
// @ts-expect-error
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef],
*factory() {
yield stringDataRef('hello');
yield numberDataRef(4); // No declared output
},
}),
).toMatchObject({ version: 'v2' });
expect(
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
*factory() {
yield stringDataRef('hello');
yield numberDataRef(4);
},
}),
).toMatchObject({ version: 'v2' });
expect(
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef.optional()],
*factory() {
yield stringDataRef('hello');
yield numberDataRef(4);
},
}),
).toMatchObject({ version: 'v2' });
expect(
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef.optional()],
*factory() {
yield stringDataRef('hello'); // Missing number output, but it's optional so that's allowed
},
}),
).toMatchObject({ version: 'v2' });
});
it('should support new form of inputs', () => {
expect(
createExtension({
@@ -157,9 +157,16 @@ export type VerifyExtensionFactoryOutput<
: never
) extends infer IRequiredOutputIds
? [IRequiredOutputIds] extends [UFactoryOutput['id']]
? {}
? [UFactoryOutput['id']] extends [UDeclaredOutput['id']]
? {}
: {
'Error: The extension factory has undeclared output(s)': Exclude<
UFactoryOutput['id'],
UDeclaredOutput['id']
>;
}
: {
'Error: The extension factory is missing the following outputs': Exclude<
'Error: The extension factory is missing the following output(s)': Exclude<
IRequiredOutputIds,
UFactoryOutput['id']
>;
@@ -64,6 +64,45 @@ describe('createExtensionBlueprint', () => {
expect(container.querySelector('h1')).toHaveTextContent('Hello, world!');
});
it('should allow creation of extension blueprints with a generator', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
output: [coreExtensionData.reactElement],
*factory(params: { text: string }) {
yield coreExtensionData.reactElement(<h1>{params.text}</h1>);
},
});
const extension = TestExtensionBlueprint.make({
name: 'my-extension',
params: {
text: 'Hello, world!',
},
});
expect(extension).toEqual({
$$type: '@backstage/ExtensionDefinition',
attachTo: {
id: 'test',
input: 'default',
},
configSchema: undefined,
disabled: false,
inputs: {},
kind: 'test-extension',
name: 'my-extension',
namespace: undefined,
output: [coreExtensionData.reactElement],
factory: expect.any(Function),
toString: expect.any(Function),
version: 'v2',
});
const { container } = createExtensionTester(extension).render();
expect(container.querySelector('h1')).toHaveTextContent('Hello, world!');
});
it('should allow overriding of the default factory', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',