diff --git a/workspaces/orchestrator/.changeset/tame-dragons-begin.md b/workspaces/orchestrator/.changeset/tame-dragons-begin.md new file mode 100644 index 0000000000..2a2666aa5d --- /dev/null +++ b/workspaces/orchestrator/.changeset/tame-dragons-begin.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-orchestrator': patch +--- + +Add frontend unit test coverage for orchestrator hooks and status components, and harden the status indicator fallback for unknown states. diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/MissingSchemaNotice.test.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/MissingSchemaNotice.test.tsx new file mode 100644 index 0000000000..7e3bf8cade --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/MissingSchemaNotice.test.tsx @@ -0,0 +1,158 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 '@testing-library/jest-dom'; + +import { ReactNode } from 'react'; + +import { fireEvent, render, screen } from '@testing-library/react'; + +import MissingSchemaNotice from './MissingSchemaNotice'; + +jest.mock('../../hooks/useTranslation', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +jest.mock( + '@red-hat-developer-hub/backstage-plugin-orchestrator-form-react', + () => ({ + SubmitButton: ({ + children, + handleClick, + submitting, + }: { + children: ReactNode; + handleClick: () => void; + submitting?: boolean; + }) => ( + + ), + }), +); + +describe('MissingSchemaNotice', () => { + it('renders the missing schema guidance and run button', () => { + render( + , + ); + + expect( + screen.getByText('messages.missingJsonSchema.title'), + ).toBeInTheDocument(); + expect( + screen.getByText(/messages\.missingJsonSchema\.message/), + ).toBeInTheDocument(); + expect(screen.getByText('dataInputSchema')).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'common.run' }), + ).toBeInTheDocument(); + }); + + it('invokes handleExecute with empty payload when run is clicked', () => { + const handleExecute = jest.fn().mockResolvedValue(undefined); + + render( + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'common.run' })); + expect(handleExecute).toHaveBeenCalledWith({}); + }); + + it('renders and invokes execute-as-event button when configured', () => { + const handleExecuteAsEvent = jest.fn().mockResolvedValue(undefined); + + render( + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Run as event' })); + expect(handleExecuteAsEvent).toHaveBeenCalledWith({}); + }); + + it('does not render execute-as-event button when label is missing', () => { + render( + , + ); + + expect(screen.queryByRole('button', { name: 'Run as event' })).toBeNull(); + }); + + it('disables action buttons while execution is in progress', () => { + render( + , + ); + + expect(screen.getByRole('button', { name: 'common.run' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Run as event' })).toBeDisabled(); + }); + + it('re-enables action buttons when execution completes', () => { + const { rerender } = render( + , + ); + + expect(screen.getByRole('button', { name: 'common.run' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Run as event' })).toBeDisabled(); + + rerender( + , + ); + + expect( + screen.getByRole('button', { name: 'common.run' }), + ).not.toBeDisabled(); + expect( + screen.getByRole('button', { name: 'Run as event' }), + ).not.toBeDisabled(); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowInstanceStatusIndicator.test.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowInstanceStatusIndicator.test.tsx new file mode 100644 index 0000000000..40a6fe52c7 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowInstanceStatusIndicator.test.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 '@testing-library/jest-dom'; + +import { ReactNode } from 'react'; + +import { render, screen } from '@testing-library/react'; + +import { ProcessInstanceStatusDTO } from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { VALUE_UNAVAILABLE } from '../../constants'; +import { useWorkflowInstanceStateColors } from '../../hooks/useWorkflowInstanceStatusColors'; +import { WorkflowInstanceStatusIndicator } from './WorkflowInstanceStatusIndicator'; + +jest.mock('@backstage/core-components', () => ({ + Link: ({ to, children }: { to: string; children: ReactNode }) => ( + {children} + ), +})); + +jest.mock('../../hooks/useTranslation', () => ({ + useTranslation: () => ({ + t: (key: string) => + ({ + 'table.status.running': 'Running', + 'table.status.completed': 'Completed', + 'tooltips.suspended': 'Suspended', + 'table.status.aborted': 'Aborted', + 'table.status.failed': 'Failed', + 'table.status.pending': 'Pending', + })[key] ?? key, + }), +})); + +jest.mock('../../hooks/useWorkflowInstanceStatusColors', () => ({ + useWorkflowInstanceStateColors: jest.fn(() => 'status-icon'), +})); + +describe('WorkflowInstanceStatusIndicator', () => { + it('renders unavailable value when status is missing', () => { + render(); + + expect(screen.getByText(VALUE_UNAVAILABLE)).toBeInTheDocument(); + }); + + it('renders completed status text without link when instanceLink is not provided', () => { + render( + , + ); + + expect(screen.getByText('Completed')).toBeInTheDocument(); + expect(screen.queryByRole('link', { name: 'Completed' })).toBeNull(); + }); + + it('renders status title as a link when instanceLink is provided', () => { + render( + , + ); + + const link = screen.getByRole('link', { name: 'Failed' }); + expect(link).toHaveAttribute('href', '/orchestrator/instances/1'); + }); + + it('renders pending title for pending status', () => { + render( + , + ); + + expect(screen.getByText('Pending')).toBeInTheDocument(); + }); + + it('renders running title for active status', () => { + render( + , + ); + + expect(screen.getByText('Running')).toBeInTheDocument(); + }); + + it('renders suspended title for suspended status', () => { + render( + , + ); + + expect(screen.getByText('Suspended')).toBeInTheDocument(); + }); + + it('aborted title for aborted status', () => { + render( + , + ); + + expect(screen.getByText('Aborted')).toBeInTheDocument(); + }); + + it('renders unavailable for unknown status', () => { + render( + , + ); + + expect(screen.getByText(VALUE_UNAVAILABLE)).toBeInTheDocument(); + }); + + it('calls useWorkflowInstanceStateColors with correct status', () => { + render( + , + ); + + expect(useWorkflowInstanceStateColors).toHaveBeenCalledWith( + ProcessInstanceStatusDTO.Completed, + ); + }); + + it('applies color class to the rendered icon element', () => { + const { container } = render( + , + ); + + expect(useWorkflowInstanceStateColors).toHaveBeenCalledWith( + ProcessInstanceStatusDTO.Completed, + ); + expect(container.querySelector('svg.status-icon')).toBeInTheDocument(); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowInstanceStatusIndicator.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowInstanceStatusIndicator.tsx index 8c63db16c8..3a6bef4eba 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowInstanceStatusIndicator.tsx +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowInstanceStatusIndicator.tsx @@ -73,8 +73,7 @@ export const WorkflowInstanceStatusIndicator = ({ title = t('table.status.pending'); break; default: - icon = VALUE_UNAVAILABLE; - break; + return <>{VALUE_UNAVAILABLE}; } return ( diff --git a/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowStatus.test.tsx b/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowStatus.test.tsx new file mode 100644 index 0000000000..986f0514dc --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/components/ui/WorkflowStatus.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 '@testing-library/jest-dom'; + +import { render, screen } from '@testing-library/react'; + +import { AVAILABLE, UNAVAILABLE } from '../../constants'; +import { WorkflowStatus } from './WorkflowStatus'; + +jest.mock('../../hooks/useTranslation', () => ({ + useTranslation: () => ({ + t: (key: string) => + ({ + 'workflow.status.available': 'Available', + 'workflow.status.unavailable': 'Unavailable', + 'tooltips.workflowDown': 'Workflow down', + })[key] ?? key, + }), +})); + +describe('WorkflowStatus', () => { + it('renders available status for AVAILABLE string', () => { + render(); + + expect(screen.getByText('Available')).toBeInTheDocument(); + }); + + it('renders available status for true boolean', () => { + render(); + + expect(screen.getByText('Available')).toBeInTheDocument(); + }); + + it('renders unavailable status for false boolean', () => { + render(); + + expect(screen.getByText('Unavailable')).toBeInTheDocument(); + }); + + it('renders raw value for unsupported availability content', () => { + render(); + + expect(screen.getByText('Unknown')).toBeInTheDocument(); + }); + + it('renders unavailable status for UNAVAILABLE string', () => { + render(); + + expect(screen.getByText('Unavailable')).toBeInTheDocument(); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/hooks/useKafkaEnabled.test.ts b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useKafkaEnabled.test.ts new file mode 100644 index 0000000000..22edda1212 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useKafkaEnabled.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { renderHook } from '@testing-library/react'; + +import { useKafkaEnabled } from './useKafkaEnabled'; + +const mockUseApi = jest.fn(); + +jest.mock('@backstage/core-plugin-api', () => ({ + configApiRef: {}, + useApi: (...args: unknown[]) => mockUseApi(...args), +})); + +describe('useKafkaEnabled', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns true when kafka config exists', () => { + const configApi = { + getOptionalConfig: jest.fn().mockReturnValue({}), + }; + mockUseApi.mockReturnValue(configApi); + + const { result } = renderHook(() => useKafkaEnabled()); + + expect(result.current).toBe(true); + expect(configApi.getOptionalConfig).toHaveBeenCalledWith( + 'orchestrator.kafka', + ); + }); + + it('returns false when kafka config is missing', () => { + const configApi = { + getOptionalConfig: jest.fn().mockReturnValue(undefined), + }; + mockUseApi.mockReturnValue(configApi); + + const { result } = renderHook(() => useKafkaEnabled()); + + expect(result.current).toBe(false); + expect(configApi.getOptionalConfig).toHaveBeenCalledWith( + 'orchestrator.kafka', + ); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/hooks/useLanguage.test.ts b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useLanguage.test.ts new file mode 100644 index 0000000000..6d72fc7ca2 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useLanguage.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { renderHook } from '@testing-library/react'; + +import { useLanguage } from './useLanguage'; + +const mockUseApi = jest.fn(); + +jest.mock('@backstage/core-plugin-api', () => ({ + useApi: (...args: unknown[]) => mockUseApi(...args), +})); + +jest.mock('@backstage/core-plugin-api/alpha', () => ({ + appLanguageApiRef: {}, +})); + +describe('useLanguage', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns the language code from app language api', () => { + mockUseApi.mockReturnValue({ + getLanguage: jest.fn().mockReturnValue({ language: 'ja' }), + }); + + const { result } = renderHook(() => useLanguage()); + + expect(result.current).toBe('ja'); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/hooks/useLogsEnabled.test.ts b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useLogsEnabled.test.ts new file mode 100644 index 0000000000..9a9c3a53de --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useLogsEnabled.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { renderHook } from '@testing-library/react'; + +import { useLogsEnabled } from './useLogsEnabled'; + +const mockUseApi = jest.fn(); + +jest.mock('@backstage/core-plugin-api', () => ({ + configApiRef: {}, + useApi: (...args: unknown[]) => mockUseApi(...args), +})); + +describe('useLogsEnabled', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns true when workflow log provider config exists', () => { + const configApi = { + getOptionalConfig: jest.fn().mockReturnValue({}), + }; + mockUseApi.mockReturnValue(configApi); + + const { result } = renderHook(() => useLogsEnabled()); + + expect(result.current).toBe(true); + expect(configApi.getOptionalConfig).toHaveBeenCalledWith( + 'orchestrator.workflowLogProvider', + ); + }); + + it('returns false when workflow log provider config is missing', () => { + const configApi = { + getOptionalConfig: jest.fn().mockReturnValue(undefined), + }; + mockUseApi.mockReturnValue(configApi); + + const { result } = renderHook(() => useLogsEnabled()); + + expect(result.current).toBe(false); + expect(configApi.getOptionalConfig).toHaveBeenCalledWith( + 'orchestrator.workflowLogProvider', + ); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/hooks/usePermissionArray.test.tsx b/workspaces/orchestrator/plugins/orchestrator/src/hooks/usePermissionArray.test.tsx new file mode 100644 index 0000000000..637763098b --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/hooks/usePermissionArray.test.tsx @@ -0,0 +1,159 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { ReactNode } from 'react'; + +import { AuthorizeResult } from '@backstage/plugin-permission-common'; + +import { renderHook, waitFor } from '@testing-library/react'; +import { SWRConfig } from 'swr'; + +import { + orchestratorWorkflowPermission, + orchestratorWorkflowUsePermission, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { + usePermissionArray, + usePermissionArrayDecision, +} from './usePermissionArray'; + +const mockUseApi = jest.fn(); + +jest.mock('@backstage/core-plugin-api', () => ({ + useApi: (...args: unknown[]) => mockUseApi(...args), +})); + +jest.mock('@backstage/plugin-permission-react', () => ({ + permissionApiRef: {}, +})); + +const permissions = [ + orchestratorWorkflowPermission, + orchestratorWorkflowUsePermission, +]; + +describe('usePermissionArray', () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + new Map() }}>{children} + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns loading state before authorization resolves', () => { + const authorize = jest.fn().mockReturnValue(new Promise(() => {})); + mockUseApi.mockReturnValue({ authorize }); + + const { result } = renderHook(() => usePermissionArray(permissions), { + wrapper, + }); + + expect(result.current.loading).toBe(true); + expect(result.current.allowed).toEqual([false, false]); + }); + + it('returns allowed flags based on authorization responses', async () => { + const authorize = jest + .fn() + .mockResolvedValueOnce({ result: AuthorizeResult.ALLOW }) + .mockResolvedValueOnce({ result: AuthorizeResult.DENY }); + mockUseApi.mockReturnValue({ authorize }); + + const { result } = renderHook(() => usePermissionArray(permissions), { + wrapper, + }); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + expect(result.current.allowed).toEqual([true, false]); + expect(result.current.error).toBeUndefined(); + expect(authorize).toHaveBeenNthCalledWith(1, { + permission: permissions[0], + }); + expect(authorize).toHaveBeenNthCalledWith(2, { + permission: permissions[1], + }); + }); + + it('returns error state when authorization request fails', async () => { + const error = new Error('permission backend unavailable'); + const authorize = jest.fn().mockRejectedValue(error); + mockUseApi.mockReturnValue({ authorize }); + + const { result } = renderHook(() => usePermissionArray(permissions), { + wrapper, + }); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + expect(result.current.error).toBe(error); + expect(result.current.allowed).toEqual([false, false]); + }); +}); + +describe('usePermissionArrayDecision', () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + new Map() }}>{children} + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns allowed=true when at least one permission is allowed', async () => { + const authorize = jest + .fn() + .mockResolvedValueOnce({ result: AuthorizeResult.DENY }) + .mockResolvedValueOnce({ result: AuthorizeResult.ALLOW }); + mockUseApi.mockReturnValue({ authorize }); + + const { result } = renderHook( + () => usePermissionArrayDecision(permissions), + { + wrapper, + }, + ); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + expect(result.current.allowed).toBe(true); + }); + + it('returns allowed=false when no permission is allowed', async () => { + const authorize = jest + .fn() + .mockResolvedValueOnce({ result: AuthorizeResult.DENY }) + .mockResolvedValueOnce({ result: AuthorizeResult.CONDITIONAL }); + mockUseApi.mockReturnValue({ authorize }); + + const { result } = renderHook( + () => usePermissionArrayDecision(permissions), + { + wrapper, + }, + ); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + expect(result.current.allowed).toBe(false); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/hooks/useTranslation.test.ts b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useTranslation.test.ts new file mode 100644 index 0000000000..9f7e6b456c --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useTranslation.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { renderHook } from '@testing-library/react'; + +import { orchestratorTranslationRef } from '../translations'; +import { useTranslation } from './useTranslation'; + +const mockUseTranslationRef = jest.fn(); + +jest.mock('@backstage/core-plugin-api/alpha', () => ({ + useTranslationRef: (...args: unknown[]) => mockUseTranslationRef(...args), +})); + +jest.mock('../translations', () => ({ + orchestratorTranslationRef: { id: 'plugin.orchestrator.test' }, +})); + +describe('useTranslation', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('delegates to useTranslationRef with orchestrator translation ref', () => { + const translator = { t: jest.fn() }; + mockUseTranslationRef.mockReturnValue(translator); + + const { result } = renderHook(() => useTranslation()); + + expect(mockUseTranslationRef).toHaveBeenCalledWith( + orchestratorTranslationRef, + ); + expect(result.current).toBe(translator); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/hooks/useWorkflowInstanceCardHeightMode.test.ts b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useWorkflowInstanceCardHeightMode.test.ts new file mode 100644 index 0000000000..809082e7da --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useWorkflowInstanceCardHeightMode.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { renderHook } from '@testing-library/react'; + +import { useWorkflowInstanceCardHeightMode } from './useWorkflowInstanceCardHeightMode'; + +const mockUseApi = jest.fn(); + +jest.mock('@backstage/core-plugin-api', () => ({ + configApiRef: {}, + useApi: (...args: unknown[]) => mockUseApi(...args), +})); + +describe('useWorkflowInstanceCardHeightMode', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns fixed when config is missing', () => { + const configApi = { + getOptionalString: jest.fn().mockReturnValue(undefined), + }; + mockUseApi.mockReturnValue(configApi); + + const { result } = renderHook(() => useWorkflowInstanceCardHeightMode()); + + expect(result.current).toBe('fixed'); + }); + + it('returns content when config value is content', () => { + const configApi = { + getOptionalString: jest.fn().mockReturnValue('content'), + }; + mockUseApi.mockReturnValue(configApi); + + const { result } = renderHook(() => useWorkflowInstanceCardHeightMode()); + + expect(result.current).toBe('content'); + }); + + it('returns fixed and warns when config value is invalid', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const configApi = { + getOptionalString: jest.fn().mockReturnValue('stretch'), + }; + mockUseApi.mockReturnValue(configApi); + + const { result } = renderHook(() => useWorkflowInstanceCardHeightMode()); + + expect(result.current).toBe('fixed'); + expect(warnSpy).toHaveBeenCalledWith( + 'Unknown cardHeightMode "stretch", falling back to "fixed"', + ); + warnSpy.mockRestore(); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator/src/hooks/useWorkflowInstanceStatusColors.test.tsx b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useWorkflowInstanceStatusColors.test.tsx new file mode 100644 index 0000000000..54b1a47118 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator/src/hooks/useWorkflowInstanceStatusColors.test.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { renderHook } from '@testing-library/react'; + +import { ProcessInstanceStatusDTO } from '@red-hat-developer-hub/backstage-plugin-orchestrator-common'; + +import { useWorkflowInstanceStateColors } from './useWorkflowInstanceStatusColors'; + +jest.mock('tss-react/mui', () => ({ + makeStyles: () => () => () => ({ + classes: { + [ProcessInstanceStatusDTO.Active]: 'active-class', + [ProcessInstanceStatusDTO.Completed]: 'completed-class', + [ProcessInstanceStatusDTO.Suspended]: 'suspended-class', + [ProcessInstanceStatusDTO.Aborted]: 'aborted-class', + [ProcessInstanceStatusDTO.Error]: 'error-class', + [ProcessInstanceStatusDTO.Pending]: 'pending-class', + }, + }), +})); + +describe('useWorkflowInstanceStateColors', () => { + it('returns undefined for undefined status', () => { + const { result } = renderHook(() => useWorkflowInstanceStateColors()); + + expect(result.current).toBeUndefined(); + }); + + it('returns the class mapped to a completed status', () => { + const { result } = renderHook(() => + useWorkflowInstanceStateColors(ProcessInstanceStatusDTO.Completed), + ); + + expect(result.current).toBe('completed-class'); + }); + + it('returns the class mapped to an error status', () => { + const { result } = renderHook(() => + useWorkflowInstanceStateColors(ProcessInstanceStatusDTO.Error), + ); + + expect(result.current).toBe('error-class'); + }); +});