diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 014ccf4a40..e33bdcb9a1 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -767,35 +767,37 @@ describe('createExtension', () => { opt: inputs.opt?.get(testDataRef1) ?? 'none', single: inputs.single.get(testDataRef1), singleOpt: inputs.single.get(testDataRef2) ?? 'none', - multi: inputs.multi.map(i => i.get(testDataRef1)).join(','), + multi: inputs.multi + .map(i => `${i.node.spec.id}=${i.get(testDataRef1)}`) + .join(','), }), ]; }, }); const optExt = createExtension({ - name: 'opt', + name: 'o', attachTo: { id: 'subject', input: 'opt' }, output: [testDataRef1], factory: () => [testDataRef1('orig-opt')], }); const singleExt = createExtension({ - name: 'single', + name: 's', attachTo: { id: 'subject', input: 'single' }, output: [testDataRef1, testDataRef2.optional()], factory: () => [testDataRef1('orig-single')], }); const multi1Ext = createExtension({ - name: 'multi1', + name: 'm1', attachTo: { id: 'subject', input: 'multi' }, output: [testDataRef1], factory: () => [testDataRef1('orig-multi1')], }); const multi2Ext = createExtension({ - name: 'multi2', + name: 'm2', attachTo: { id: 'subject', input: 'multi' }, output: [testDataRef1], factory: () => [testDataRef1('orig-multi2')], @@ -812,7 +814,7 @@ describe('createExtension', () => { opt: 'orig-opt', single: 'orig-single', singleOpt: 'none', - multi: 'orig-multi1,orig-multi2', + multi: 'm1=orig-multi1,m2=orig-multi2', }); // All values provided @@ -839,7 +841,7 @@ describe('createExtension', () => { opt: 'opt', single: 'single', singleOpt: 'singleOpt', - multi: 'multi1,multi2', + multi: 'm1=multi1,m2=multi2', }); // Minimal values provided @@ -888,7 +890,7 @@ describe('createExtension', () => { opt: 'orig-opt', single: 'orig-single', singleOpt: 'none', - multi: 'orig-multi1,orig-multi2', + multi: 'm1=orig-multi1,m2=orig-multi2', }); // Forward inputs separately @@ -915,7 +917,61 @@ describe('createExtension', () => { opt: 'orig-opt', single: 'orig-single', singleOpt: 'none', - multi: 'orig-multi1,orig-multi2', + multi: 'm1=orig-multi1,m2=orig-multi2', + }); + + // Reordering inputs + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: [inputs.multi[1], inputs.multi[0]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'm2=orig-multi2,m1=orig-multi1', + }); + + // Filter out inputs + expect( + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + opt: inputs.opt, + single: inputs.single, + multi: inputs.multi.filter(i => i.node.spec.id.endsWith('2')), + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toEqual({ + opt: 'orig-opt', + single: 'orig-single', + singleOpt: 'none', + multi: 'm2=orig-multi2', }); // Overriding based on original input @@ -946,7 +1002,7 @@ describe('createExtension', () => { opt: 'none', single: 'override-orig-single', singleOpt: 'new-singleOpt', - multi: 'override-orig-multi1,override-orig-multi2', + multi: 'm1=override-orig-multi1,m2=override-orig-multi2', }); // Mismatched input override length @@ -972,6 +1028,29 @@ describe('createExtension', () => { `"Failed to instantiate extension 'subject', override data provided for input 'multi' must match the length of the original inputs"`, ); + // Mix forward and data override + expect(() => + createExtensionTester( + subject.override({ + factory(originalFactory, { inputs }) { + return originalFactory({ + inputs: { + ...inputs, + multi: [inputs.multi[0], [testDataRef1('multi2')]], + }, + }); + }, + }), + ) + .add(optExt) + .add(singleExt) + .add(multi1Ext) + .add(multi2Ext) + .data(outputRef), + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'subject', override data for input 'multi' may not mix forwarded inputs with data overrides"`, + ); + // Required input not provided expect(() => createExtensionTester( diff --git a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts index 06143549bb..cf201779e0 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts @@ -129,7 +129,7 @@ export function resolveInputOverrides( ); } newInputs[name] = Object.assign(providedContainer, { - name: (originalInput as ResolvedExtensionInput).node, + node: (originalInput as ResolvedExtensionInput).node, }) as any; } } else { @@ -139,23 +139,40 @@ export function resolveInputOverrides( `override data provided for input '${name}' must be an array`, ); } - if ( - originalInput.length !== providedData.length && - providedData.length > 0 - ) { - throw new Error( - `override data provided for input '${name}' must match the length of the original inputs`, - ); + + // Regular inputs can be overridden in two different ways: + // 1) Forward a subset of the original inputs in a new order + // 2) Provide new data for each original input + + // First check if all inputs are being removed + if (providedData.length === 0) { + newInputs[name] = []; + } else { + // Check how many of the provided data items have a node property, i.e. is a forwarded input + const withNodesCount = providedData.filter(d => 'node' in d).length; + if (withNodesCount === 0) { + if (originalInput.length !== providedData.length) { + throw new Error( + `override data provided for input '${name}' must match the length of the original inputs`, + ); + } + newInputs[name] = providedData.map((data, i) => { + const providedContainer = createExtensionDataContainer( + data as Iterable>, + declaredInput.extensionData, + ); + return Object.assign(providedContainer, { + node: (originalInput[i] as ResolvedExtensionInput).node, + }) as any; + }); + } else if (withNodesCount === providedData.length) { + newInputs[name] = providedData as any; + } else { + throw new Error( + `override data for input '${name}' may not mix forwarded inputs with data overrides`, + ); + } } - newInputs[name] = providedData.map((data, i) => { - const providedContainer = createExtensionDataContainer( - data as Iterable>, - declaredInput.extensionData, - ); - return Object.assign(providedContainer, { - name: (originalInput[i] as ResolvedExtensionInput).node, - }) as any; - }); } } return newInputs;