chore: test fixes and simplify the types a little bit

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-10-14 15:09:43 +02:00
parent f6874fbfa8
commit bcaba24892
6 changed files with 112 additions and 69 deletions
@@ -27,6 +27,12 @@ import { analyticsApiRef } from '@backstage/core-plugin-api';
import { ScaffolderApi, scaffolderApiRef } from '../../../api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
import { SecretsContextProvider } from '../../../secrets';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { ScaffolderFormDecoratorsApi } from '../../../../../scaffolder/src/alpha/api/types';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { formDecoratorsApiRef } from '../../../../../scaffolder/src/alpha/api/ref';
const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
cancelTask: jest.fn(),
@@ -39,14 +45,19 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
listTasks: jest.fn(),
autocomplete: jest.fn(),
};
const scaffolderDecoratorsMock: jest.Mocked<ScaffolderFormDecoratorsApi> = {
getFormDecorators: jest.fn().mockResolvedValue([]),
};
const catalogApi = catalogApiMock.mock();
const analyticsMock = mockApis.analytics();
const apis = TestApiRegistry.from(
[scaffolderApiRef, scaffolderApiMock],
[formDecoratorsApiRef, scaffolderDecoratorsMock],
[catalogApiRef, catalogApi],
[analyticsApiRef, analyticsMock],
[catalogApiRef, catalogApi],
);
describe('<Workflow />', () => {
@@ -82,31 +93,33 @@ describe('<Workflow />', () => {
});
const { getByRole, getAllByRole, getByText } = await renderInTestApp(
<ApiProvider apis={apis}>
<Workflow
title="Different title than template"
description={`
<SecretsContextProvider initialSecrets={{}}>
<ApiProvider apis={apis}>
<Workflow
title="Different title than template"
description={`
## This is markdown
- overriding the template description
`}
onCreate={onCreate}
onError={onError}
namespace="default"
templateName="docs-template"
initialState={{
name: 'prefilled-name',
age: '53',
}}
components={{
ReviewStateComponent: () => (
<h1>This is a different wrapper for the review page</h1>
),
reviewButtonText: <i>Onwards</i>,
createButtonText: <b>Make</b>,
}}
extensions={[]}
/>
</ApiProvider>,
onCreate={onCreate}
onError={onError}
namespace="default"
templateName="docs-template"
initialState={{
name: 'prefilled-name',
age: '53',
}}
components={{
ReviewStateComponent: () => (
<h1>This is a different wrapper for the review page</h1>
),
reviewButtonText: <i>Onwards</i>,
createButtonText: <b>Make</b>,
}}
extensions={[]}
/>
</ApiProvider>
</SecretsContextProvider>,
);
// Test template title is overriden
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect } from 'react';
import {
Content,
InfoCard,
@@ -90,11 +90,10 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
const minutesSaved = useTemplateTimeSavedMinutes(templateRef);
const { setSecrets } = useInternalTemplateSecrets();
const formDecorators = useFormDecorators();
const [formState, setFormState] = useState<Record<string, JsonValue>>({});
const workflowOnCreate = useCallback(
async (originalFormState: Record<string, JsonValue>) => {
setFormState(originalFormState);
let formState: Record<string, JsonValue> = { ...originalFormState };
if (manifest?.EXPERIMENTAL_formDecorators && formDecorators?.size) {
// for each of the form decorators, go and call the decorator with the context
@@ -109,7 +108,13 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
await formDecorator.fn({
setSecrets,
setFormState,
setFormState: (
handler: (
oldState: Record<string, JsonValue>,
) => Record<string, JsonValue>,
) => {
formState = { ...handler(formState) };
},
formState,
input: decorator.input,
});
@@ -129,7 +134,6 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
manifest?.EXPERIMENTAL_formDecorators,
formDecorators,
onCreate,
formState,
analytics,
templateName,
minutesSaved,
@@ -23,7 +23,14 @@ describe('createScaffolderFormDecorator', () => {
fn: async () => {},
});
expect(decorator).toMatchInlineSnapshot();
expect(decorator).toMatchInlineSnapshot(`
{
"deps": {},
"fn": [Function],
"id": "test",
"version": "v1",
}
`);
});
it('should allow passing schema and be typesafe', () => {
@@ -46,6 +53,19 @@ describe('createScaffolderFormDecorator', () => {
},
});
expect(decorator).toMatchInlineSnapshot();
expect(decorator).toMatchInlineSnapshot(`
{
"deps": {},
"fn": [Function],
"id": "test",
"schema": {
"input": {
"age": [Function],
"name": [Function],
},
},
"version": "v1",
}
`);
});
});
@@ -15,15 +15,18 @@
*/
import { AnyApiRef } from '@backstage/core-plugin-api';
import { JsonValue } from '@backstage/types';
import { Dispatch, SetStateAction } from 'react';
import { z } from 'zod';
export type ScaffolderFormDecoratorContext<TInput> = {
input: TInput;
formState: Record<string, JsonValue>;
setFormState: Dispatch<SetStateAction<Record<string, JsonValue>>>;
setSecrets: Dispatch<SetStateAction<Record<string, string>>>;
setFormState: (
fn: (currentState: Record<string, JsonValue>) => Record<string, JsonValue>,
) => void;
setSecrets: (
fn: (currentState: Record<string, string>) => Record<string, string>,
) => void;
};
export type ScaffolderFormDecorator<
@@ -33,6 +33,8 @@ import { rootRouteRef } from '../../../routes';
import { ANNOTATION_EDIT_URL } from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
import { ScaffolderFormDecoratorsApi } from '../../api/types';
import { formDecoratorsApiRef } from '../../api/ref';
jest.mock('react-router-dom', () => {
return {
@@ -55,11 +57,16 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
autocomplete: jest.fn(),
};
const scaffolderDecoratorsMock: jest.Mocked<ScaffolderFormDecoratorsApi> = {
getFormDecorators: jest.fn().mockResolvedValue([]),
};
const catalogApi = catalogApiMock.mock();
const analyticsApi = mockApis.analytics();
const apis = TestApiRegistry.from(
[scaffolderApiRef, scaffolderApiMock],
[formDecoratorsApiRef, scaffolderDecoratorsMock],
[catalogApiRef, catalogApi],
[analyticsApiRef, analyticsApi],
[catalogApiRef, catalogApi],
@@ -17,8 +17,8 @@ import { DefaultScaffolderFormDecoratorsApi } from '../api/FormDecoratorsApi';
import { createScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
import { createApiRef } from '@backstage/core-plugin-api';
import { TestApiProvider, withLogCollector } from '@backstage/test-utils';
import { renderHook } from '@testing-library/react';
import { TestApiProvider } from '@backstage/test-utils';
import { renderHook, waitFor } from '@testing-library/react';
import { useFormDecorators } from './useFormDecorators';
import React from 'react';
import { formDecoratorsApiRef } from '../api/ref';
@@ -63,49 +63,45 @@ describe('useFormDecorators', () => {
),
});
const result = renderedHook.result.current!;
await waitFor(async () => {
const result = renderedHook.result.current!;
expect(result.size).toBe(1);
expect(result.size).toBe(1);
const testDecorator = result.get('test')!;
expect(testDecorator).toBeDefined();
const testDecorator = result.get('test')!;
expect(testDecorator).toBeDefined();
await testDecorator.fn({
formState: {},
setFormState: () => {},
setSecrets: () => {},
input: { test: 'input value' },
await testDecorator.fn({
formState: {},
setFormState: () => {},
setSecrets: () => {},
input: { test: 'input value' },
});
expect(mockApiImplementation.test).toHaveBeenCalledWith('input value');
});
expect(mockApiImplementation.test).toHaveBeenCalledWith('input value');
});
it('should skip failing deps', async () => {
const { error } = await withLogCollector(async () => {
const renderedHook = renderHook(() => useFormDecorators(), {
wrapper: ({ children }) => (
<TestApiProvider
apis={[
[
formDecoratorsApiRef,
DefaultScaffolderFormDecoratorsApi.create({
// @ts-expect-error - todo
decorators: [mockDecorator],
}),
],
]}
>
{children}
</TestApiProvider>
),
});
const result = renderedHook.result.current!;
expect(result.size).toBe(0);
const renderedHook = renderHook(() => useFormDecorators(), {
wrapper: ({ children }) => (
<TestApiProvider
apis={[
[
formDecoratorsApiRef,
DefaultScaffolderFormDecoratorsApi.create({
// @ts-expect-error - todo
decorators: [mockDecorator],
}),
],
]}
>
{children}
</TestApiProvider>
),
});
expect(error[0]).toMatchInlineSnapshot(
`[Error: Failed to resolve apiRef test for form hook test]`,
);
const result = renderedHook.result.current!;
expect(result.size).toBe(0);
});
});