diff --git a/packages/frontend/editor-ui/src/__tests__/mocks.ts b/packages/frontend/editor-ui/src/__tests__/mocks.ts index 0d86bf435c..af3587e1c0 100644 --- a/packages/frontend/editor-ui/src/__tests__/mocks.ts +++ b/packages/frontend/editor-ui/src/__tests__/mocks.ts @@ -56,6 +56,7 @@ export const mockNode = ({ export const mockNodeTypeDescription = ({ name = SET_NODE_TYPE, + displayName = name, icon = 'fa:pen', version = 1, credentials = [], @@ -66,8 +67,10 @@ export const mockNodeTypeDescription = ({ group, hidden, description, + webhooks, }: { name?: INodeTypeDescription['name']; + displayName?: INodeTypeDescription['displayName']; icon?: INodeTypeDescription['icon']; version?: INodeTypeDescription['version']; credentials?: INodeTypeDescription['credentials']; @@ -78,11 +81,12 @@ export const mockNodeTypeDescription = ({ group?: INodeTypeDescription['group']; hidden?: INodeTypeDescription['hidden']; description?: INodeTypeDescription['description']; + webhooks?: INodeTypeDescription['webhooks']; } = {}) => mock({ name, icon, - displayName: name, + displayName, description: description ?? '', version, defaults: { @@ -98,7 +102,7 @@ export const mockNodeTypeDescription = ({ credentials, documentationUrl: 'https://docs', iconUrl: 'nodes/test-node/icon.svg', - webhooks: undefined, + webhooks, parameterPane: undefined, hidden, }); diff --git a/packages/frontend/editor-ui/src/components/TriggerPanel.test.ts b/packages/frontend/editor-ui/src/components/TriggerPanel.test.ts new file mode 100644 index 0000000000..41a4a91a49 --- /dev/null +++ b/packages/frontend/editor-ui/src/components/TriggerPanel.test.ts @@ -0,0 +1,80 @@ +import { renderComponent } from '@/__tests__/render'; +import { mockedStore, type MockedStore } from '@/__tests__/utils'; +import TriggerPanel from './TriggerPanel.vue'; +import { createTestingPinia } from '@pinia/testing'; +import { useWorkflowsStore } from '@/stores/workflows.store'; +import { createTestNode, mockNodeTypeDescription } from '../__tests__/mocks'; +import { useNodeTypesStore } from '@/stores/nodeTypes.store'; +import { setActivePinia } from 'pinia'; + +let workflowsStore: MockedStore; +let nodeTypesStore: MockedStore; + +describe('TriggerPanel.vue', () => { + beforeEach(async () => { + setActivePinia(createTestingPinia()); + workflowsStore = mockedStore(useWorkflowsStore); + workflowsStore.workflowName = 'Test Workflow'; + workflowsStore.workflowId = '1'; + const node = createTestNode({ id: '0', name: 'Webhook', type: 'n8n-nodes-base.webhook' }); + workflowsStore.getNodeByName.mockReturnValue(node); + nodeTypesStore = mockedStore(useNodeTypesStore); + const nodeTypeDescription = mockNodeTypeDescription({ + name: 'n8n-nodes-base.webhook', + displayName: 'Webhook', + webhooks: [{ name: 'default', httpMethod: 'POST', path: 'webhook' }], + }); + nodeTypesStore.getNodeType = vi.fn(() => nodeTypeDescription); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('renders default state', () => { + const { getByTestId } = renderComponent(TriggerPanel, { + props: { nodeName: 'Webhook' }, + }); + expect(getByTestId('trigger-header')).toBeInTheDocument(); + expect(getByTestId('trigger-header')).toHaveTextContent('Pull in events from Webhook'); + expect(getByTestId('trigger-execute-button')).toBeInTheDocument(); + }); + + it('renders listening state for webhook node', () => { + workflowsStore.executionWaitingForWebhook = true; + workflowsStore.executedNode = 'Webhook'; + const { getByTestId } = renderComponent(TriggerPanel, { + props: { nodeName: 'Webhook' }, + }); + expect(getByTestId('trigger-listening')).toBeInTheDocument(); + }); + + it('does not render listening state for other nodes', () => { + workflowsStore.executionWaitingForWebhook = true; + workflowsStore.executedNode = 'OtherNode'; + const { queryByTestId } = renderComponent(TriggerPanel, { + props: { nodeName: 'Webhook' }, + }); + expect(queryByTestId('trigger-listening')).not.toBeInTheDocument(); + }); + + it('renders listening state when executedNode is a child of the current node', () => { + workflowsStore.executionWaitingForWebhook = true; + workflowsStore.executedNode = 'ChildNode'; + workflowsStore.workflowObject.getParentNodes = vi.fn(() => ['Webhook']); + const { getByTestId } = renderComponent(TriggerPanel, { + props: { nodeName: 'Webhook' }, + }); + expect(getByTestId('trigger-listening')).toBeInTheDocument(); + }); + + it('does not render listening state when executedNode is not a child or current node', () => { + workflowsStore.executionWaitingForWebhook = true; + workflowsStore.executedNode = 'UnrelatedNode'; + workflowsStore.workflowObject.getParentNodes = vi.fn(() => []); + const { queryByTestId } = renderComponent(TriggerPanel, { + props: { nodeName: 'Webhook' }, + }); + expect(queryByTestId('trigger-listening')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/frontend/editor-ui/src/components/TriggerPanel.vue b/packages/frontend/editor-ui/src/components/TriggerPanel.vue index 414faf72b4..616564caa0 100644 --- a/packages/frontend/editor-ui/src/components/TriggerPanel.vue +++ b/packages/frontend/editor-ui/src/components/TriggerPanel.vue @@ -154,15 +154,21 @@ const isPollingNode = computed(() => { }); const isListeningForEvents = computed(() => { - const waitingOnWebhook = workflowsStore.executionWaitingForWebhook; + if (!node.value || node.value.disabled || !isWebhookBasedNode.value) { + return false; + } + + if (!workflowsStore.executionWaitingForWebhook) { + return false; + } + const executedNode = workflowsStore.executedNode; - return ( - !!node.value && - !node.value.disabled && - isWebhookBasedNode.value && - waitingOnWebhook && - (!executedNode || executedNode === props.nodeName) - ); + const isCurrentNodeExecuted = executedNode === props.nodeName; + const isChildNodeExecuted = executedNode + ? workflowsStore.workflowObject.getParentNodes(executedNode).includes(props.nodeName) + : false; + + return !executedNode || isCurrentNodeExecuted || isChildNodeExecuted; }); const workflowRunning = computed(() => workflowsStore.isWorkflowRunning); @@ -370,7 +376,7 @@ const onNodeExecute = () => {
-
+
@@ -434,7 +440,7 @@ const onNodeExecute = () => {
-
+
{{ header }}