mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge c39685bbc0
into d2dd1796a8
This commit is contained in:
commit
f89fcb20f4
|
@ -0,0 +1,389 @@
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import { type MockedStore, mockedStore } from '@/__tests__/utils';
|
||||||
|
import { mockNode, mockNodeTypeDescription } from '@/__tests__/mocks';
|
||||||
|
import { nodeViewEventBus } from '@/event-bus';
|
||||||
|
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 { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
import { useRunWorkflow } from '@/composables/useRunWorkflow';
|
||||||
|
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', () => ({
|
||||||
|
useRouter: () => ({}),
|
||||||
|
useRoute: () => reactive({}),
|
||||||
|
RouterLink: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/composables/useToast', () => {
|
||||||
|
const showError = vi.fn();
|
||||||
|
const showMessage = vi.fn();
|
||||||
|
return {
|
||||||
|
useToast: () => ({
|
||||||
|
showError,
|
||||||
|
showMessage,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('@/composables/useRunWorkflow', () => {
|
||||||
|
const runWorkflow = 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 workflowsStore: MockedStore<typeof useWorkflowsStore>;
|
||||||
|
let uiStore: MockedStore<typeof useUIStore>;
|
||||||
|
let nodeTypesStore: MockedStore<typeof useNodeTypesStore>;
|
||||||
|
let ndvStore: MockedStore<typeof useNDVStore>;
|
||||||
|
|
||||||
|
let runWorkflow: ReturnType<typeof useRunWorkflow>;
|
||||||
|
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');
|
||||||
|
|
||||||
|
describe('NodeExecuteButton', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
renderComponent = createComponentRenderer(NodeExecuteButton, {
|
||||||
|
pinia: createTestingPinia(),
|
||||||
|
props: {
|
||||||
|
nodeName: 'test-node',
|
||||||
|
telemetrySource: 'test-source',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
uiStore = mockedStore(useUIStore);
|
||||||
|
nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||||
|
ndvStore = mockedStore(useNDVStore);
|
||||||
|
|
||||||
|
runWorkflow = useRunWorkflow({ router: useRouter() });
|
||||||
|
externalHooks = useExternalHooks();
|
||||||
|
message = useMessage();
|
||||||
|
toast = useToast();
|
||||||
|
|
||||||
|
workflowsStore.workflowId = 'abc123';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without error', () => {
|
||||||
|
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 () => {
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(
|
||||||
|
mockNode({ name: 'test', type: SET_NODE_TYPE, disabled: true }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getByRole, queryByRole } = renderComponent();
|
||||||
|
|
||||||
|
const button = getByRole('button');
|
||||||
|
expect(button).toBeDisabled();
|
||||||
|
expect(queryByRole('tooltip')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.hover(button);
|
||||||
|
|
||||||
|
expect(getByRole('tooltip')).toBeVisible();
|
||||||
|
expect(getByRole('tooltip')).toHaveTextContent('Enable node to execute');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be disabled when workflow is running but node is not executing', async () => {
|
||||||
|
uiStore.isActionActive.workflowRunning = true;
|
||||||
|
workflowsStore.isNodeExecuting.mockReturnValue(false);
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(
|
||||||
|
mockNode({ name: 'test-node', type: SET_NODE_TYPE }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getByRole, queryByRole } = renderComponent();
|
||||||
|
|
||||||
|
const button = getByRole('button');
|
||||||
|
expect(button).toBeDisabled();
|
||||||
|
expect(queryByRole('tooltip')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.hover(button);
|
||||||
|
|
||||||
|
expect(getByRole('tooltip')).toBeVisible();
|
||||||
|
expect(getByRole('tooltip')).toHaveTextContent('Workflow is already running');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables button when trigger node has issues', async () => {
|
||||||
|
nodeTypesStore.isTriggerNode = () => true;
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(
|
||||||
|
mockNode({
|
||||||
|
name: 'test',
|
||||||
|
type: SET_NODE_TYPE,
|
||||||
|
issues: {
|
||||||
|
typeUnknown: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getByRole } = renderComponent();
|
||||||
|
|
||||||
|
expect(getByRole('button')).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stops webhook when clicking button while listening for events', async () => {
|
||||||
|
workflowsStore.executionWaitingForWebhook = true;
|
||||||
|
nodeTypesStore.isTriggerNode = () => true;
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(
|
||||||
|
mockNode({ name: 'test-node', type: SET_NODE_TYPE }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getByRole } = renderComponent();
|
||||||
|
|
||||||
|
await userEvent.click(getByRole('button'));
|
||||||
|
|
||||||
|
expect(workflowsStore.removeTestWebhook).toHaveBeenCalledWith('abc123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stops execution when clicking button while workflow is running', async () => {
|
||||||
|
uiStore.isActionActive.workflowRunning = true;
|
||||||
|
nodeTypesStore.isTriggerNode = () => true;
|
||||||
|
workflowsStore.activeExecutionId = 'test-execution-id';
|
||||||
|
workflowsStore.isNodeExecuting.mockReturnValue(true);
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(
|
||||||
|
mockNode({ name: 'test-node', type: SET_NODE_TYPE }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getByRole, emitted } = renderComponent();
|
||||||
|
|
||||||
|
await userEvent.click(getByRole('button'));
|
||||||
|
|
||||||
|
expect(runWorkflow.stopCurrentExecution).toHaveBeenCalledTimes(1);
|
||||||
|
expect(emitted().stopExecution).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs workflow when clicking button normally', async () => {
|
||||||
|
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(node);
|
||||||
|
nodeTypesStore.getNodeType = () => mockNodeTypeDescription();
|
||||||
|
|
||||||
|
const { getByRole, emitted } = renderComponent();
|
||||||
|
|
||||||
|
await userEvent.click(getByRole('button'));
|
||||||
|
|
||||||
|
expect(externalHooks.run).toHaveBeenCalledWith('nodeExecuteButton.onClick', expect.any(Object));
|
||||||
|
expect(runWorkflow.runWorkflow).toHaveBeenCalledWith({
|
||||||
|
destinationNode: node.name,
|
||||||
|
source: 'RunData.ExecuteNodeButton',
|
||||||
|
});
|
||||||
|
expect(emitted().execute).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens chat when clicking button for chat node', async () => {
|
||||||
|
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(node);
|
||||||
|
nodeTypesStore.getNodeType = () => mockNodeTypeDescription({ name: CHAT_TRIGGER_NODE_TYPE });
|
||||||
|
|
||||||
|
const { getByRole } = renderComponent();
|
||||||
|
|
||||||
|
await userEvent.click(getByRole('button'));
|
||||||
|
|
||||||
|
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
|
||||||
|
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
|
||||||
|
expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens chat when clicking button for chat child node', async () => {
|
||||||
|
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
|
||||||
|
workflowsStore.getNodeByName.mockReturnValue(node);
|
||||||
|
workflowsStore.checkIfNodeHasChatParent.mockReturnValue(true);
|
||||||
|
|
||||||
|
const { getByRole } = renderComponent();
|
||||||
|
|
||||||
|
await userEvent.click(getByRole('button'));
|
||||||
|
|
||||||
|
expect(ndvStore.setActiveNodeName).toHaveBeenCalledWith(null);
|
||||||
|
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -26,7 +26,7 @@ import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { type IUpdateInformation } from '../Interface';
|
import { type IUpdateInformation } from '@/Interface';
|
||||||
import { generateCodeForAiTransform } from '@/components/ButtonParameter/utils';
|
import { generateCodeForAiTransform } from '@/components/ButtonParameter/utils';
|
||||||
|
|
||||||
const NODE_TEST_STEP_POPUP_COUNT_KEY = 'N8N_NODE_TEST_STEP_POPUP_COUNT';
|
const NODE_TEST_STEP_POPUP_COUNT_KEY = 'N8N_NODE_TEST_STEP_POPUP_COUNT';
|
||||||
|
@ -155,7 +155,7 @@ const disabledHint = computed(() => {
|
||||||
return i18n.baseText('ndv.execute.generatingCode');
|
return i18n.baseText('ndv.execute.generatingCode');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTriggerNode.value && node?.value?.disabled) {
|
if (node?.value?.disabled) {
|
||||||
return i18n.baseText('ndv.execute.nodeIsDisabled');
|
return i18n.baseText('ndv.execute.nodeIsDisabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,28 +367,21 @@ async function onClick() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<N8nTooltip placement="right" :disabled="!tooltipText" :content="tooltipText">
|
||||||
<n8n-tooltip placement="right" :disabled="!tooltipText">
|
<N8nButton
|
||||||
<template #content>
|
v-bind="$attrs"
|
||||||
<div>{{ tooltipText }}</div>
|
:loading="isLoading"
|
||||||
</template>
|
:disabled="disabled || !!disabledHint"
|
||||||
<div>
|
:label="buttonLabel"
|
||||||
<n8n-button
|
:type="type"
|
||||||
v-bind="$attrs"
|
:size="size"
|
||||||
:loading="isLoading"
|
:icon="buttonIcon"
|
||||||
:disabled="disabled || !!disabledHint"
|
:transparent-background="transparent"
|
||||||
:label="buttonLabel"
|
:title="
|
||||||
:type="type"
|
!isTriggerNode && !tooltipText ? i18n.baseText('ndv.execute.testNode.description') : ''
|
||||||
:size="size"
|
"
|
||||||
:icon="buttonIcon"
|
@mouseover="onMouseOver"
|
||||||
:transparent-background="transparent"
|
@click="onClick"
|
||||||
:title="
|
/>
|
||||||
!isTriggerNode && !tooltipText ? i18n.baseText('ndv.execute.testNode.description') : ''
|
</N8nTooltip>
|
||||||
"
|
|
||||||
@mouseover="onMouseOver"
|
|
||||||
@click="onClick"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</n8n-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { fireEvent, waitFor } from '@testing-library/vue';
|
import { waitFor } from '@testing-library/vue';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import CanvasNodeToolbar from '@/components/canvas/elements/nodes/CanvasNodeToolbar.vue';
|
import CanvasNodeToolbar from '@/components/canvas/elements/nodes/CanvasNodeToolbar.vue';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import { createCanvasNodeProvide, createCanvasProvide } from '@/__tests__/data';
|
import { createCanvasNodeProvide, createCanvasProvide } from '@/__tests__/data';
|
||||||
|
@ -35,6 +36,29 @@ describe('CanvasNodeToolbar', () => {
|
||||||
expect(getByTestId('execute-node-button')).toBeDisabled();
|
expect(getByTestId('execute-node-button')).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render disabled execute node button when node is deactivated', async () => {
|
||||||
|
const { getByTestId, getByRole } = renderComponent({
|
||||||
|
global: {
|
||||||
|
provide: {
|
||||||
|
...createCanvasNodeProvide({
|
||||||
|
data: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...createCanvasProvide(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = getByTestId('execute-node-button');
|
||||||
|
expect(button).toBeDisabled();
|
||||||
|
|
||||||
|
await userEvent.hover(button);
|
||||||
|
|
||||||
|
expect(getByRole('tooltip')).toBeVisible();
|
||||||
|
expect(getByRole('tooltip')).toHaveTextContent("This node is deactivated and can't be run");
|
||||||
|
});
|
||||||
|
|
||||||
it('should not render execute node button when renderType is configuration', async () => {
|
it('should not render execute node button when renderType is configuration', async () => {
|
||||||
const { queryByTestId } = renderComponent({
|
const { queryByTestId } = renderComponent({
|
||||||
global: {
|
global: {
|
||||||
|
@ -65,7 +89,7 @@ describe('CanvasNodeToolbar', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.click(getByTestId('execute-node-button'));
|
await userEvent.click(getByTestId('execute-node-button'));
|
||||||
|
|
||||||
expect(emitted('run')[0]).toEqual([]);
|
expect(emitted('run')[0]).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -80,7 +104,7 @@ describe('CanvasNodeToolbar', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.click(getByTestId('disable-node-button'));
|
await userEvent.click(getByTestId('disable-node-button'));
|
||||||
|
|
||||||
expect(emitted('toggle')[0]).toEqual([]);
|
expect(emitted('toggle')[0]).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -95,7 +119,7 @@ describe('CanvasNodeToolbar', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.click(getByTestId('delete-node-button'));
|
await userEvent.click(getByTestId('delete-node-button'));
|
||||||
|
|
||||||
expect(emitted('delete')[0]).toEqual([]);
|
expect(emitted('delete')[0]).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -110,7 +134,7 @@ describe('CanvasNodeToolbar', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.click(getByTestId('overflow-node-button'));
|
await userEvent.click(getByTestId('overflow-node-button'));
|
||||||
|
|
||||||
expect(emitted('open:contextmenu')[0]).toEqual([expect.any(MouseEvent)]);
|
expect(emitted('open:contextmenu')[0]).toEqual([expect.any(MouseEvent)]);
|
||||||
});
|
});
|
||||||
|
@ -132,8 +156,8 @@ describe('CanvasNodeToolbar', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.click(getByTestId('change-sticky-color'));
|
await userEvent.click(getByTestId('change-sticky-color'));
|
||||||
await fireEvent.click(getAllByTestId('color')[0]);
|
await userEvent.click(getAllByTestId('color')[0]);
|
||||||
|
|
||||||
expect(emitted('update')[0]).toEqual([{ color: 1 }]);
|
expect(emitted('update')[0]).toEqual([{ color: 1 }]);
|
||||||
});
|
});
|
||||||
|
@ -150,7 +174,7 @@ describe('CanvasNodeToolbar', () => {
|
||||||
|
|
||||||
const toolbar = getByTestId('canvas-node-toolbar');
|
const toolbar = getByTestId('canvas-node-toolbar');
|
||||||
|
|
||||||
await fireEvent.mouseEnter(toolbar);
|
await userEvent.hover(toolbar);
|
||||||
|
|
||||||
expect(toolbar).toHaveClass('forceVisible');
|
expect(toolbar).toHaveClass('forceVisible');
|
||||||
});
|
});
|
||||||
|
@ -174,7 +198,7 @@ describe('CanvasNodeToolbar', () => {
|
||||||
|
|
||||||
const toolbar = getByTestId('canvas-node-toolbar');
|
const toolbar = getByTestId('canvas-node-toolbar');
|
||||||
|
|
||||||
await fireEvent.click(getByTestId('change-sticky-color'));
|
await userEvent.click(getByTestId('change-sticky-color'));
|
||||||
|
|
||||||
await waitFor(() => expect(toolbar).toHaveClass('forceVisible'));
|
await waitFor(() => expect(toolbar).toHaveClass('forceVisible'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -94,17 +94,23 @@ function onMouseLeave() {
|
||||||
@mouseleave="onMouseLeave"
|
@mouseleave="onMouseLeave"
|
||||||
>
|
>
|
||||||
<div :class="$style.canvasNodeToolbarItems">
|
<div :class="$style.canvasNodeToolbarItems">
|
||||||
<N8nIconButton
|
<N8nTooltip
|
||||||
v-if="isExecuteNodeVisible"
|
placement="top"
|
||||||
data-test-id="execute-node-button"
|
:disabled="!isDisabled"
|
||||||
type="tertiary"
|
:content="i18n.baseText('ndv.execute.deactivated')"
|
||||||
text
|
>
|
||||||
size="small"
|
<N8nIconButton
|
||||||
icon="play"
|
v-if="isExecuteNodeVisible"
|
||||||
:disabled="isExecuting"
|
data-test-id="execute-node-button"
|
||||||
:title="i18n.baseText('node.testStep')"
|
type="tertiary"
|
||||||
@click="executeNode"
|
text
|
||||||
/>
|
size="small"
|
||||||
|
icon="play"
|
||||||
|
:disabled="isExecuting || isDisabled"
|
||||||
|
:title="i18n.baseText('node.testStep')"
|
||||||
|
@click="executeNode"
|
||||||
|
/>
|
||||||
|
</N8nTooltip>
|
||||||
<N8nIconButton
|
<N8nIconButton
|
||||||
v-if="isDisableNodeVisible"
|
v-if="isDisableNodeVisible"
|
||||||
data-test-id="disable-node-button"
|
data-test-id="disable-node-button"
|
||||||
|
|
|
@ -980,6 +980,7 @@
|
||||||
"ndv.execute.requiredFieldsMissing": "Complete required fields first",
|
"ndv.execute.requiredFieldsMissing": "Complete required fields first",
|
||||||
"ndv.execute.stopWaitingForWebhook.error": "Problem deleting test webhook",
|
"ndv.execute.stopWaitingForWebhook.error": "Problem deleting test webhook",
|
||||||
"ndv.execute.workflowAlreadyRunning": "Workflow is already running",
|
"ndv.execute.workflowAlreadyRunning": "Workflow is already running",
|
||||||
|
"ndv.execute.deactivated": "This node is deactivated and can't be run",
|
||||||
"ndv.featureRequest": "I wish this node would...",
|
"ndv.featureRequest": "I wish this node would...",
|
||||||
"ndv.input": "Input",
|
"ndv.input": "Input",
|
||||||
"ndv.input.nodeDistance": "{count} node back | {count} nodes back",
|
"ndv.input.nodeDistance": "{count} node back | {count} nodes back",
|
||||||
|
|
Loading…
Reference in a new issue