mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
feat(editor): Allow sticky notes alongside fallback nodes in new canvas (no-changelog) (#10583)
This commit is contained in:
parent
07600b42fd
commit
04363bf4bc
|
@ -26,6 +26,7 @@ import {
|
|||
STICKY_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import type { INodeUi, IWorkflowDb } from '@/Interface';
|
||||
import { CanvasNodeRenderType } from '@/types';
|
||||
|
||||
export const mockNode = ({
|
||||
id = uuid(),
|
||||
|
@ -94,6 +95,7 @@ export const mockNodes = [
|
|||
mockNode({ name: 'Chat Trigger', type: CHAT_TRIGGER_NODE_TYPE }),
|
||||
mockNode({ name: 'Agent', type: AGENT_NODE_TYPE }),
|
||||
mockNode({ name: 'Sticky', type: STICKY_NODE_TYPE }),
|
||||
mockNode({ name: CanvasNodeRenderType.AddNodes, type: CanvasNodeRenderType.AddNodes }),
|
||||
mockNode({ name: 'End', type: NO_OP_NODE_TYPE }),
|
||||
];
|
||||
|
||||
|
@ -180,7 +182,7 @@ export function createTestNode(node: Partial<INode> = {}): INode {
|
|||
return {
|
||||
id: uuid(),
|
||||
name: 'Node',
|
||||
type: 'n8n-nodes-base.test',
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0] as [number, number],
|
||||
parameters: {},
|
||||
|
|
146
packages/editor-ui/src/components/canvas/WorkflowCanvas.spec.ts
Normal file
146
packages/editor-ui/src/components/canvas/WorkflowCanvas.spec.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
import { waitFor } from '@testing-library/vue';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import WorkflowCanvas from '@/components/canvas/WorkflowCanvas.vue';
|
||||
import { createEventBus } from 'n8n-design-system';
|
||||
import { createCanvasNodeElement, createCanvasConnection } from '@/__tests__/data';
|
||||
import type { Workflow } from 'n8n-workflow';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import { STICKY_NODE_TYPE } from '@/constants';
|
||||
import { CanvasNodeRenderType } from '@/types';
|
||||
import {
|
||||
createTestNode,
|
||||
createTestWorkflow,
|
||||
createTestWorkflowObject,
|
||||
defaultNodeDescriptions,
|
||||
} from '@/__tests__/mocks';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
||||
const renderComponent = createComponentRenderer(WorkflowCanvas, {
|
||||
props: {
|
||||
id: 'canvas',
|
||||
workflow: {
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
nodes: [],
|
||||
connections: [],
|
||||
},
|
||||
workflowObject: {} as Workflow,
|
||||
eventBus: createEventBus(),
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
nodeTypesStore.setNodeTypes(defaultNodeDescriptions);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('WorkflowCanvas', () => {
|
||||
it('should initialize with default props', () => {
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
expect(getByTestId('canvas')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should render nodes and connections', async () => {
|
||||
const nodes = [
|
||||
createCanvasNodeElement({ id: '1', label: 'Node 1' }),
|
||||
createCanvasNodeElement({ id: '2', label: 'Node 2' }),
|
||||
];
|
||||
const connections = [createCanvasConnection(nodes[0], nodes[1])];
|
||||
|
||||
const { container } = renderComponent({
|
||||
props: {
|
||||
nodes,
|
||||
connections,
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => expect(container.querySelectorAll('.vue-flow__node')).toHaveLength(2));
|
||||
|
||||
expect(container.querySelector(`[data-id="${nodes[0].id}"]`)).toBeInTheDocument();
|
||||
expect(container.querySelector(`[data-id="${nodes[1].id}"]`)).toBeInTheDocument();
|
||||
expect(container.querySelector(`[data-id="${connections[0].id}"]`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle empty nodes and connections gracefully', async () => {
|
||||
const { container } = renderComponent();
|
||||
|
||||
await waitFor(() => expect(container.querySelectorAll('.vue-flow__node')).toHaveLength(0));
|
||||
expect(container.querySelectorAll('.vue-flow__connection')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should render fallback nodes when sticky nodes are present', async () => {
|
||||
const stickyNodes = [createTestNode({ id: '2', name: 'Sticky Node', type: STICKY_NODE_TYPE })];
|
||||
const fallbackNodes = [
|
||||
createTestNode({
|
||||
id: CanvasNodeRenderType.AddNodes,
|
||||
type: CanvasNodeRenderType.AddNodes,
|
||||
name: CanvasNodeRenderType.AddNodes,
|
||||
}),
|
||||
];
|
||||
|
||||
const workflow = createTestWorkflow({
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
nodes: [...stickyNodes],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { container } = renderComponent({
|
||||
props: {
|
||||
workflow,
|
||||
workflowObject,
|
||||
fallbackNodes,
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => expect(container.querySelectorAll('.vue-flow__node')).toHaveLength(2));
|
||||
|
||||
expect(container.querySelector(`[data-id="${stickyNodes[0].id}"]`)).toBeInTheDocument();
|
||||
expect(container.querySelector(`[data-id="${fallbackNodes[0].id}"]`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render fallback nodes when non-sticky nodes are present', async () => {
|
||||
const nonStickyNodes = [createTestNode({ id: '1', name: 'Non-Sticky Node 1' })];
|
||||
const stickyNodes = [createTestNode({ id: '2', name: 'Sticky Node', type: STICKY_NODE_TYPE })];
|
||||
const fallbackNodes = [
|
||||
createTestNode({
|
||||
id: CanvasNodeRenderType.AddNodes,
|
||||
type: CanvasNodeRenderType.AddNodes,
|
||||
name: CanvasNodeRenderType.AddNodes,
|
||||
}),
|
||||
];
|
||||
|
||||
const workflow = createTestWorkflow({
|
||||
id: '1',
|
||||
name: 'Test Workflow',
|
||||
nodes: [...nonStickyNodes, ...stickyNodes],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
const workflowObject = createTestWorkflowObject(workflow);
|
||||
|
||||
const { container } = renderComponent({
|
||||
props: {
|
||||
workflow,
|
||||
workflowObject,
|
||||
fallbackNodes,
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => expect(container.querySelectorAll('.vue-flow__node')).toHaveLength(2));
|
||||
|
||||
expect(container.querySelector(`[data-id="${nonStickyNodes[0].id}"]`)).toBeInTheDocument();
|
||||
expect(container.querySelector(`[data-id="${stickyNodes[0].id}"]`)).toBeInTheDocument();
|
||||
expect(container.querySelector(`[data-id="${fallbackNodes[0].id}"]`)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -6,6 +6,7 @@ import type { IWorkflowDb } from '@/Interface';
|
|||
import { useCanvasMapping } from '@/composables/useCanvasMapping';
|
||||
import type { EventBus } from 'n8n-design-system';
|
||||
import { createEventBus } from 'n8n-design-system';
|
||||
import { STICKY_NODE_TYPE } from '@/constants';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
|
@ -32,9 +33,13 @@ const $style = useCssModule();
|
|||
const workflow = toRef(props, 'workflow');
|
||||
const workflowObject = toRef(props, 'workflowObject');
|
||||
|
||||
const nodes = computed(() =>
|
||||
props.workflow.nodes.length > 0 ? props.workflow.nodes : props.fallbackNodes,
|
||||
);
|
||||
const nodes = computed(() => {
|
||||
const stickyNoteNodes = props.workflow.nodes.filter((node) => node.type === STICKY_NODE_TYPE);
|
||||
|
||||
return props.workflow.nodes.length > stickyNoteNodes.length
|
||||
? props.workflow.nodes
|
||||
: [...props.fallbackNodes, ...stickyNoteNodes];
|
||||
});
|
||||
const connections = computed(() => props.workflow.connections);
|
||||
|
||||
const { nodes: mappedNodes, connections: mappedConnections } = useCanvasMapping({
|
||||
|
|
Loading…
Reference in a new issue