test(editor): Fix unit test

This commit is contained in:
Csaba Tuncsik 2025-03-05 15:16:32 +01:00
parent a62eb6f303
commit 99dce3f8c6
No known key found for this signature in database

View file

@ -6,7 +6,13 @@ import { createComponentRenderer } from '@/__tests__/render';
import { type MockedStore, mockedStore } from '@/__tests__/utils'; import { type MockedStore, mockedStore } from '@/__tests__/utils';
import { mockNode, mockNodeTypeDescription } from '@/__tests__/mocks'; import { mockNode, mockNodeTypeDescription } from '@/__tests__/mocks';
import { nodeViewEventBus } from '@/event-bus'; import { nodeViewEventBus } from '@/event-bus';
import { CHAT_TRIGGER_NODE_TYPE, SET_NODE_TYPE } from '@/constants'; import { AI_TRANSFORM_NODE_TYPE, AI_TRANSFORM_CODE_GENERATED_FOR_PROMPT } from 'n8n-workflow';
import {
CHAT_TRIGGER_NODE_TYPE,
FORM_TRIGGER_NODE_TYPE,
SET_NODE_TYPE,
WEBHOOK_NODE_TYPE,
} from '@/constants';
import NodeExecuteButton from '@/components/NodeExecuteButton.vue'; import NodeExecuteButton from '@/components/NodeExecuteButton.vue';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
@ -14,6 +20,10 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { useRunWorkflow } from '@/composables/useRunWorkflow'; import { useRunWorkflow } from '@/composables/useRunWorkflow';
import { useExternalHooks } from '@/composables/useExternalHooks'; import { useExternalHooks } from '@/composables/useExternalHooks';
import { usePinnedData } from '@/composables/usePinnedData';
import { useMessage } from '@/composables/useMessage';
import { useToast } from '@/composables/useToast';
import * as buttonParameterUtils from '@/components/ButtonParameter/utils';
vi.mock('vue-router', () => ({ vi.mock('vue-router', () => ({
useRouter: () => ({}), useRouter: () => ({}),
@ -21,18 +31,56 @@ vi.mock('vue-router', () => ({
RouterLink: vi.fn(), RouterLink: vi.fn(),
})); }));
vi.mock('@/composables/useRunWorkflow', () => ({ vi.mock('@/composables/useToast', () => {
useRunWorkflow: () => ({ const showError = vi.fn();
runWorkflow: vi.fn(), const showMessage = vi.fn();
stopCurrentExecution: vi.fn(), return {
useToast: () => ({
showError,
showMessage,
}), }),
})); };
});
vi.mock('@/composables/useExternalHooks', () => ({ vi.mock('@/composables/useRunWorkflow', () => {
useExternalHooks: () => ({ const runWorkflow = vi.fn();
run: vi.fn(), const stopCurrentExecution = vi.fn();
return {
useRunWorkflow: () => ({
runWorkflow,
stopCurrentExecution,
}), }),
})); };
});
vi.mock('@/composables/useExternalHooks', () => {
const run = vi.fn();
return {
useExternalHooks: () => ({
run,
}),
};
});
vi.mock('@/composables/usePinnedData', () => {
const hasData = {};
const unsetData = vi.fn();
return {
usePinnedData: () => ({
hasData,
unsetData,
}),
};
});
vi.mock('@/composables/useMessage', () => {
const confirm = vi.fn(async () => 'confirm');
return {
useMessage: () => ({
confirm,
}),
};
});
let renderComponent: ReturnType<typeof createComponentRenderer>; let renderComponent: ReturnType<typeof createComponentRenderer>;
let workflowsStore: MockedStore<typeof useWorkflowsStore>; let workflowsStore: MockedStore<typeof useWorkflowsStore>;
@ -42,6 +90,9 @@ let ndvStore: MockedStore<typeof useNDVStore>;
let runWorkflow: ReturnType<typeof useRunWorkflow>; let runWorkflow: ReturnType<typeof useRunWorkflow>;
let externalHooks: ReturnType<typeof useExternalHooks>; let externalHooks: ReturnType<typeof useExternalHooks>;
let pinnedData: ReturnType<typeof usePinnedData>;
let message: ReturnType<typeof useMessage>;
let toast: ReturnType<typeof useToast>;
const nodeViewEventBusEmitSpy = vi.spyOn(nodeViewEventBus, 'emit'); const nodeViewEventBusEmitSpy = vi.spyOn(nodeViewEventBus, 'emit');
@ -64,6 +115,8 @@ describe('NodeExecuteButton', () => {
runWorkflow = useRunWorkflow({ router: useRouter() }); runWorkflow = useRunWorkflow({ router: useRouter() });
externalHooks = useExternalHooks(); externalHooks = useExternalHooks();
message = useMessage();
toast = useToast();
workflowsStore.workflowId = 'abc123'; workflowsStore.workflowId = 'abc123';
}); });
@ -72,6 +125,90 @@ describe('NodeExecuteButton', () => {
expect(() => renderComponent()).not.toThrow(); expect(() => renderComponent()).not.toThrow();
}); });
it('displays correct button label for regular node', () => {
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Test step');
});
it('displays correct button label for webhook node', () => {
const node = mockNode({ name: 'test-node', type: WEBHOOK_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
nodeTypesStore.getNodeType = () => ({
...mockNodeTypeDescription(),
name: WEBHOOK_NODE_TYPE,
});
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Listen for test event');
});
it('displays correct button label for form trigger node', () => {
const node = mockNode({ name: 'test-node', type: FORM_TRIGGER_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
nodeTypesStore.getNodeType = () => ({
...mockNodeTypeDescription(),
name: FORM_TRIGGER_NODE_TYPE,
});
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Test step');
});
it('displays correct button label for chat node', () => {
const node = mockNode({ name: 'test-node', type: CHAT_TRIGGER_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
nodeTypesStore.getNodeType = () => ({
...mockNodeTypeDescription(),
name: CHAT_TRIGGER_NODE_TYPE,
});
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Test chat');
});
it('displays correct button label for polling node', () => {
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
nodeTypesStore.getNodeType = () => ({
...mockNodeTypeDescription(),
polling: true,
});
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Fetch Test Event');
});
it('displays "Stop Listening" when node is listening for events', () => {
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
workflowsStore.executionWaitingForWebhook = true;
nodeTypesStore.isTriggerNode = () => true;
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Stop Listening');
});
it('displays "Stop Listening" when node is running and is a trigger node', () => {
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
workflowsStore.isNodeExecuting = vi.fn(() => true);
nodeTypesStore.isTriggerNode = () => true;
uiStore.isActionActive.workflowRunning = true;
const { getByRole } = renderComponent();
expect(getByRole('button').textContent).toBe('Stop Listening');
});
it('sets button to loading state when node is executing', () => {
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
workflowsStore.isNodeExecuting = vi.fn(() => true);
uiStore.isActionActive.workflowRunning = true;
const { getByRole } = renderComponent();
expect(getByRole('button').querySelector('.n8n-spinner')).toBeVisible();
});
it('should be disabled if the node is disabled and show tooltip', async () => { it('should be disabled if the node is disabled and show tooltip', async () => {
workflowsStore.getNodeByName.mockReturnValue( workflowsStore.getNodeByName.mockReturnValue(
mockNode({ name: 'test', type: SET_NODE_TYPE, disabled: true }), mockNode({ name: 'test', type: SET_NODE_TYPE, disabled: true }),
@ -150,14 +287,13 @@ describe('NodeExecuteButton', () => {
await userEvent.click(getByRole('button')); await userEvent.click(getByRole('button'));
expect(runWorkflow.stopCurrentExecution).toHaveBeenCalledWith('test-execution-id'); expect(runWorkflow.stopCurrentExecution).toHaveBeenCalledTimes(1);
expect(emitted().stopExecution).toBeTruthy(); expect(emitted().stopExecution).toBeTruthy();
}); });
it('runs workflow when clicking button normally', async () => { it('runs workflow when clicking button normally', async () => {
workflowsStore.getNodeByName.mockReturnValue( const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
mockNode({ name: 'test-node', type: SET_NODE_TYPE }), workflowsStore.getNodeByName.mockReturnValue(node);
);
nodeTypesStore.getNodeType = () => mockNodeTypeDescription(); nodeTypesStore.getNodeType = () => mockNodeTypeDescription();
const { getByRole, emitted } = renderComponent(); const { getByRole, emitted } = renderComponent();
@ -166,16 +302,15 @@ describe('NodeExecuteButton', () => {
expect(externalHooks.run).toHaveBeenCalledWith('nodeExecuteButton.onClick', expect.any(Object)); expect(externalHooks.run).toHaveBeenCalledWith('nodeExecuteButton.onClick', expect.any(Object));
expect(runWorkflow.runWorkflow).toHaveBeenCalledWith({ expect(runWorkflow.runWorkflow).toHaveBeenCalledWith({
destinationNode: 'test-node', destinationNode: node.name,
source: 'RunData.ExecuteNodeButton', source: 'RunData.ExecuteNodeButton',
}); });
expect(emitted().execute).toBeTruthy(); expect(emitted().execute).toBeTruthy();
}); });
it('opens chat when clicking button for chat node', async () => { it('opens chat when clicking button for chat node', async () => {
workflowsStore.getNodeByName.mockReturnValue( const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
mockNode({ name: 'test-node', type: SET_NODE_TYPE }), workflowsStore.getNodeByName.mockReturnValue(node);
);
nodeTypesStore.getNodeType = () => mockNodeTypeDescription({ name: CHAT_TRIGGER_NODE_TYPE }); nodeTypesStore.getNodeType = () => mockNodeTypeDescription({ name: CHAT_TRIGGER_NODE_TYPE });
const { getByRole } = renderComponent(); const { getByRole } = renderComponent();
@ -183,14 +318,13 @@ describe('NodeExecuteButton', () => {
await userEvent.click(getByRole('button')); await userEvent.click(getByRole('button'));
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null); expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe('test-node'); expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat'); expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat');
}); });
it('opens chat when clicking button for chat child node', async () => { it('opens chat when clicking button for chat child node', async () => {
workflowsStore.getNodeByName.mockReturnValue( const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
mockNode({ name: 'test-node', type: SET_NODE_TYPE }), workflowsStore.getNodeByName.mockReturnValue(node);
);
workflowsStore.checkIfNodeHasChatParent.mockReturnValue(true); workflowsStore.checkIfNodeHasChatParent.mockReturnValue(true);
const { getByRole } = renderComponent(); const { getByRole } = renderComponent();
@ -198,7 +332,55 @@ describe('NodeExecuteButton', () => {
await userEvent.click(getByRole('button')); await userEvent.click(getByRole('button'));
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null); expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe('test-node'); expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat'); expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat');
}); });
it('prompts for confirmation when pinned data exists', async () => {
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
workflowsStore.getNodeByName.mockReturnValue(node);
pinnedData = usePinnedData(node);
Object.defineProperty(pinnedData.hasData, 'value', { value: true });
const { getByRole } = renderComponent();
await userEvent.click(getByRole('button'));
expect(message.confirm).toHaveBeenCalledTimes(1);
expect(pinnedData.unsetData).toHaveBeenCalledWith('unpin-and-execute-modal');
expect(runWorkflow.runWorkflow).toHaveBeenCalledTimes(1);
});
it('generates code for AI Transform node', async () => {
const generateCodeForAiTransformSpy = vi
.spyOn(buttonParameterUtils, 'generateCodeForAiTransform')
.mockImplementation(async () => ({
name: 'test',
value: 'Test',
}));
const node = mockNode({
name: 'test-node',
type: AI_TRANSFORM_NODE_TYPE,
parameters: {
instructions: 'Test instructions',
[AI_TRANSFORM_CODE_GENERATED_FOR_PROMPT]: 'Test prompt',
},
});
workflowsStore.getNodeByName.mockReturnValue(node);
const { getByRole, emitted } = renderComponent();
await userEvent.click(getByRole('button'));
expect(generateCodeForAiTransformSpy).toHaveBeenCalledTimes(1);
expect(toast.showMessage).toHaveBeenCalledTimes(1);
expect(emitted().valueChanged).toEqual([
[{ name: 'test', value: 'Test' }],
[
{
name: `parameters.${AI_TRANSFORM_CODE_GENERATED_FOR_PROMPT}`,
value: 'Test instructions',
},
],
]);
});
}); });