diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index b4090185a3..de8889daf0 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -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; }, diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 327639af37..ab6a6c19a5 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -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({ diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 4d31982ed3..a8bf4ef17c 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -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'] >; diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index f57cc2b70e..6cf10d7f08 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -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(