From 19a10d09554382cd2e644e819f1472bc5183f700 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 4 Dec 2024 11:05:16 +0100 Subject: [PATCH] fix(editor): Use optional chaining for all members in execution data when using the debug feature (#12024) --- .../composables/useExecutionDebugging.test.ts | 79 +++++++++++++++++-- .../src/composables/useExecutionDebugging.ts | 2 +- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/packages/editor-ui/src/composables/useExecutionDebugging.test.ts b/packages/editor-ui/src/composables/useExecutionDebugging.test.ts index a3c0b02ac7d5f5..2cde7a70a67829 100644 --- a/packages/editor-ui/src/composables/useExecutionDebugging.test.ts +++ b/packages/editor-ui/src/composables/useExecutionDebugging.test.ts @@ -1,14 +1,84 @@ import { createTestingPinia } from '@pinia/testing'; -import { setActivePinia } from 'pinia'; import { mockedStore } from '@/__tests__/utils'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useExecutionDebugging } from './useExecutionDebugging'; import type { INodeUi, IExecutionResponse } from '@/Interface'; import type { Workflow } from 'n8n-workflow'; +import { useToast } from '@/composables/useToast'; + +vi.mock('@/composables/useToast', () => { + const showToast = vi.fn(); + return { + useToast: () => ({ + showToast, + }), + }; +}); + +let executionDebugging: ReturnType; +let toast: ReturnType; describe('useExecutionDebugging()', () => { + beforeEach(() => { + vi.clearAllMocks(); + createTestingPinia(); + executionDebugging = useExecutionDebugging(); + toast = useToast(); + }); + + it('should not throw when runData node is an empty array', async () => { + const mockExecution = { + data: { + resultData: { + runData: { + testNode: [], + }, + }, + }, + } as unknown as IExecutionResponse; + + const workflowStore = mockedStore(useWorkflowsStore); + workflowStore.getNodes.mockReturnValue([{ name: 'testNode' }] as INodeUi[]); + workflowStore.getExecution.mockResolvedValueOnce(mockExecution); + workflowStore.getCurrentWorkflow.mockReturnValue({ + pinData: {}, + getParentNodes: vi.fn().mockReturnValue([]), + } as unknown as Workflow); + + await expect(executionDebugging.applyExecutionData('1')).resolves.not.toThrowError(); + }); + + it('should show missing nodes warning toast', async () => { + const mockExecution = { + data: { + resultData: { + runData: { + testNode: [ + { + data: {}, + }, + ], + }, + }, + }, + } as unknown as IExecutionResponse; + + const workflowStore = mockedStore(useWorkflowsStore); + workflowStore.getNodes.mockReturnValue([{ name: 'testNode2' }] as INodeUi[]); + workflowStore.getExecution.mockResolvedValueOnce(mockExecution); + workflowStore.getCurrentWorkflow.mockReturnValue({ + pinData: {}, + getParentNodes: vi.fn().mockReturnValue([]), + } as unknown as Workflow); + + await executionDebugging.applyExecutionData('1'); + + expect(workflowStore.setWorkflowExecutionData).toHaveBeenCalledWith(mockExecution); + expect(toast.showToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'info' })); + expect(toast.showToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'warning' })); + }); + it('should applyExecutionData', async () => { - setActivePinia(createTestingPinia()); const mockExecution = { data: { resultData: { @@ -31,10 +101,9 @@ describe('useExecutionDebugging()', () => { getParentNodes: vi.fn().mockReturnValue([]), } as unknown as Workflow); - const { applyExecutionData } = useExecutionDebugging(); - - await applyExecutionData('1'); + await executionDebugging.applyExecutionData('1'); expect(workflowStore.setWorkflowExecutionData).toHaveBeenCalledWith(mockExecution); + expect(toast.showToast).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/editor-ui/src/composables/useExecutionDebugging.ts b/packages/editor-ui/src/composables/useExecutionDebugging.ts index 90f2a32cae9075..632eac195b863e 100644 --- a/packages/editor-ui/src/composables/useExecutionDebugging.ts +++ b/packages/editor-ui/src/composables/useExecutionDebugging.ts @@ -108,7 +108,7 @@ export const useExecutionDebugging = () => { let pinnings = 0; pinnableNodes.forEach((node: INodeUi) => { - const nodeData = runData[node.name]?.[0].data?.main?.[0]; + const nodeData = runData[node.name]?.[0]?.data?.main?.[0]; if (nodeData) { pinnings++; workflowsStore.pinData({