frontend-plugin-api: update createExtensionInput to accept an array of refs too + tests

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-07-26 13:56:14 +02:00
parent 733cc4f85a
commit cd9f3a526e
2 changed files with 238 additions and 3 deletions
@@ -0,0 +1,181 @@
/*
* 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 { createExtensionDataRef } from './createExtensionDataRef';
import { ExtensionInput, createExtensionInput } from './createExtensionInput';
const stringDataRef = createExtensionDataRef<string>().with({ id: 'str' });
const numberDataRef = createExtensionDataRef<number>().with({ id: 'num' });
function unused(..._any: any[]) {}
describe('createExtensionInput', () => {
it('should create a regular input', () => {
const input = createExtensionInput([stringDataRef, numberDataRef]);
expect(input).toEqual({
$$type: '@backstage/ExtensionInput',
extensionData: [stringDataRef, numberDataRef],
config: { singleton: false, optional: false },
});
const x1: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x2: ExtensionInput<
typeof stringDataRef,
never,
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x3: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: true; optional: false }
> = input;
// @ts-expect-error
const x4: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: false; optional: true }
> = input;
unused(x1, x2, x3, x4);
});
it('should create a singleton input', () => {
const input = createExtensionInput([stringDataRef, numberDataRef], {
singleton: true,
});
expect(input).toEqual({
$$type: '@backstage/ExtensionInput',
extensionData: [stringDataRef, numberDataRef],
config: { singleton: true, optional: false },
});
const x1: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: true; optional: false }
> = input;
// @ts-expect-error
const x2: ExtensionInput<
typeof stringDataRef,
never,
{ singleton: true; optional: false }
> = input;
// @ts-expect-error
const x3: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x4: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: false; optional: true }
> = input;
unused(x1, x2, x3, x4);
});
it('should create an optional singleton input', () => {
const input = createExtensionInput([stringDataRef, numberDataRef], {
singleton: true,
optional: true,
});
expect(input).toEqual({
$$type: '@backstage/ExtensionInput',
extensionData: [stringDataRef, numberDataRef],
config: { singleton: true, optional: true },
});
const x1: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: true; optional: true }
> = input;
// @ts-expect-error
const x2: ExtensionInput<
typeof stringDataRef,
never,
{ singleton: true; optional: true }
> = input;
// @ts-expect-error
const x3: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x4: ExtensionInput<
typeof stringDataRef | typeof numberDataRef,
never,
{ singleton: false; optional: true }
> = input;
unused(x1, x2, x3, x4);
});
it('should not allow duplicate data refs', () => {
expect(() =>
createExtensionInput([stringDataRef, stringDataRef], { singleton: true }),
).toThrow("ExtensionInput may not have duplicate data refs: 'str'");
});
describe('old api', () => {
it('should create a regular input', () => {
const input = createExtensionInput({
str: stringDataRef,
num: numberDataRef,
});
expect(input).toEqual({
$$type: '@backstage/ExtensionInput',
extensionData: { str: stringDataRef, num: numberDataRef },
config: { singleton: false, optional: false },
});
const x1: ExtensionInput<
never,
{ str: typeof stringDataRef; num: typeof numberDataRef },
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x2: ExtensionInput<
never,
{ str: typeof numberDataRef; num: typeof stringDataRef }, // switched
{ singleton: false; optional: false }
> = input;
// @ts-expect-error
const x3: ExtensionInput<
never,
{ str: typeof stringDataRef; num: typeof numberDataRef },
{ singleton: true; optional: false }
> = input;
// @ts-expect-error
const x4: ExtensionInput<
never,
{ str: typeof stringDataRef; num: typeof numberDataRef },
{ singleton: false; optional: true }
> = input;
unused(x1, x2, x3, x4);
});
});
});
@@ -15,31 +15,85 @@
*/
import { AnyExtensionDataMap } from './createExtension';
import { ExtensionDataRef } from './createExtensionDataRef';
/** @public */
export interface ExtensionInput<
TExtensionData extends AnyExtensionDataMap,
TExtensionData extends ExtensionDataRef<unknown, string, { optional?: true }>,
TExtensionDataMap extends AnyExtensionDataMap,
TConfig extends { singleton: boolean; optional: boolean },
> {
$$type: '@backstage/ExtensionInput';
extensionData: TExtensionData;
extensionData: TExtensionData | TExtensionDataMap;
config: TConfig;
}
/**
* @public
* @deprecated Use the following form instead: `createExtensionInput([dataRef1, dataRef2])`
*/
export function createExtensionInput<
TExtensionDataMap extends AnyExtensionDataMap,
TConfig extends { singleton?: boolean; optional?: boolean },
>(
extensionData: TExtensionDataMap,
config?: TConfig,
): ExtensionInput<
never,
TExtensionDataMap,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
>;
/** @public */
export function createExtensionInput<
TExtensionData extends AnyExtensionDataMap,
UExtensionData extends ExtensionDataRef<unknown, string, { optional?: true }>,
TConfig extends { singleton?: boolean; optional?: boolean },
>(
extensionData: Array<UExtensionData>,
config?: TConfig,
): ExtensionInput<
UExtensionData,
never,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
>;
export function createExtensionInput<
TExtensionData extends ExtensionDataRef<unknown, string, { optional?: true }>,
TExtensionDataMap extends AnyExtensionDataMap,
TConfig extends { singleton?: boolean; optional?: boolean },
>(
extensionData: TExtensionData,
config?: TConfig,
): ExtensionInput<
TExtensionData,
TExtensionDataMap,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
}
> {
if (Array.isArray(extensionData)) {
const seen = new Set();
const duplicates = [];
for (const dataRef of extensionData) {
if (seen.has(dataRef.id)) {
duplicates.push(dataRef.id);
} else {
seen.add(dataRef.id);
}
}
if (duplicates.length > 0) {
throw new Error(
`ExtensionInput may not have duplicate data refs: '${duplicates.join(
"', '",
)}'`,
);
}
}
return {
$$type: '@backstage/ExtensionInput',
extensionData,