feat(editor): Allow sticky notes alongside fallback nodes in new canvas (no-changelog) (#10583)

This commit is contained in:
Alex Grozav 2024-08-29 08:21:19 +03:00 committed by GitHub
parent 07600b42fd
commit 04363bf4bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 157 additions and 4 deletions

View file

@ -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: {},

View 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();
});
});

View file

@ -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({