fix(editor): Show test URL when trigger is listening because of a connected node (#18403)

Co-authored-by: Your Name <you@example.com>
This commit is contained in:
Elias Meire
2025-08-21 06:27:52 +02:00
committed by GitHub
parent 53393508ea
commit 010b6dc7a5
3 changed files with 102 additions and 12 deletions

View File

@@ -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<INodeTypeDescription>({
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,
});

View File

@@ -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<typeof useWorkflowsStore>;
let nodeTypesStore: MockedStore<typeof useNodeTypesStore>;
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();
});
});

View File

@@ -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 = () => {
<div :class="$style.container">
<transition name="fade" mode="out-in">
<div v-if="hasIssues || hideContent" key="empty"></div>
<div v-else-if="isListeningForEvents" key="listening">
<div v-else-if="isListeningForEvents" key="listening" data-test-id="trigger-listening">
<n8n-pulse>
<NodeIcon :node-type="nodeType" :size="40"></NodeIcon>
</n8n-pulse>
@@ -434,7 +440,7 @@ const onNodeExecute = () => {
</div>
<div :class="$style.action">
<div :class="$style.header">
<div data-test-id="trigger-header" :class="$style.header">
<n8n-heading v-if="header" tag="h1" bold>
{{ header }}
</n8n-heading>