From bcaba24892e38070f79287f674230186fd3373a2 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 14 Oct 2024 15:09:43 +0200 Subject: [PATCH] chore: test fixes and simplify the types a little bit Signed-off-by: blam --- .../components/Workflow/Workflow.test.tsx | 57 +++++++++------ .../src/next/components/Workflow/Workflow.tsx | 14 ++-- .../createScaffolderFormDecorator.test.ts | 24 ++++++- .../createScaffolderFormDecorator.ts | 9 ++- .../TemplateWizardPage.test.tsx | 7 ++ .../alpha/hooks/useFormDecorators.test.tsx | 70 +++++++++---------- 6 files changed, 112 insertions(+), 69 deletions(-) diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx index 243b9b7e79..b942e5487a 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx @@ -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 = { cancelTask: jest.fn(), @@ -39,14 +45,19 @@ const scaffolderApiMock: jest.Mocked = { listTasks: jest.fn(), autocomplete: jest.fn(), }; +const scaffolderDecoratorsMock: jest.Mocked = { + 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('', () => { @@ -82,31 +93,33 @@ describe('', () => { }); const { getByRole, getAllByRole, getByText } = await renderInTestApp( - - + + ( -

This is a different wrapper for the review page

- ), - reviewButtonText: Onwards, - createButtonText: Make, - }} - extensions={[]} - /> -
, + onCreate={onCreate} + onError={onError} + namespace="default" + templateName="docs-template" + initialState={{ + name: 'prefilled-name', + age: '53', + }} + components={{ + ReviewStateComponent: () => ( +

This is a different wrapper for the review page

+ ), + reviewButtonText: Onwards, + createButtonText: Make, + }} + extensions={[]} + /> +
+ , ); // Test template title is overriden diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 83249c0304..1b0a10c780 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -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>({}); const workflowOnCreate = useCallback( async (originalFormState: Record) => { - setFormState(originalFormState); + let formState: Record = { ...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, + ) => Record, + ) => { + 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, diff --git a/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.test.ts b/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.test.ts index 55d59cc249..cef533d283 100644 --- a/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.test.ts +++ b/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.test.ts @@ -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", + } + `); }); }); diff --git a/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts b/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts index 867435e4c1..59c144249a 100644 --- a/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts +++ b/plugins/scaffolder-react/src/next/extensions/createScaffolderFormDecorator.ts @@ -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 = { input: TInput; formState: Record; - setFormState: Dispatch>>; - setSecrets: Dispatch>>; + setFormState: ( + fn: (currentState: Record) => Record, + ) => void; + setSecrets: ( + fn: (currentState: Record) => Record, + ) => void; }; export type ScaffolderFormDecorator< diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx index 18211d185a..7d978e5c38 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx @@ -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 = { autocomplete: jest.fn(), }; +const scaffolderDecoratorsMock: jest.Mocked = { + 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], diff --git a/plugins/scaffolder/src/alpha/hooks/useFormDecorators.test.tsx b/plugins/scaffolder/src/alpha/hooks/useFormDecorators.test.tsx index 0198dc3fee..d070894206 100644 --- a/plugins/scaffolder/src/alpha/hooks/useFormDecorators.test.tsx +++ b/plugins/scaffolder/src/alpha/hooks/useFormDecorators.test.tsx @@ -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 }) => ( - - {children} - - ), - }); - - const result = renderedHook.result.current!; - expect(result.size).toBe(0); + const renderedHook = renderHook(() => useFormDecorators(), { + wrapper: ({ children }) => ( + + {children} + + ), }); - expect(error[0]).toMatchInlineSnapshot( - `[Error: Failed to resolve apiRef test for form hook test]`, - ); + const result = renderedHook.result.current!; + expect(result.size).toBe(0); }); });