feat(editor): Improve schema view empty state when node has binary data (#14044)

This commit is contained in:
Elias Meire
2025-03-24 14:40:48 +01:00
committed by GitHub
parent b616ceb08b
commit 22ddf1b644
4 changed files with 87 additions and 62 deletions

View File

@@ -1,33 +1,33 @@
import { createComponentRenderer } from '@/__tests__/render';
import VirtualSchema from '@/components/VirtualSchema.vue';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { userEvent } from '@testing-library/user-event';
import { cleanup, waitFor } from '@testing-library/vue';
import { createPinia, setActivePinia } from 'pinia';
import {
createTestNode,
defaultNodeDescriptions,
mockNodeTypeDescription,
} from '@/__tests__/mocks';
import { IF_NODE_TYPE, SET_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE } from '@/constants';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { mock } from 'vitest-mock-extended';
import { createComponentRenderer } from '@/__tests__/render';
import VirtualSchema from '@/components/VirtualSchema.vue';
import * as nodeHelpers from '@/composables/useNodeHelpers';
import { useTelemetry } from '@/composables/useTelemetry';
import { IF_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, SET_NODE_TYPE } from '@/constants';
import type { IWorkflowDb } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { createTestingPinia } from '@pinia/testing';
import { fireEvent } from '@testing-library/dom';
import { userEvent } from '@testing-library/user-event';
import { cleanup, waitFor } from '@testing-library/vue';
import {
createResultOk,
NodeConnectionTypes,
type IDataObject,
type IBinaryData,
type INodeExecutionData,
} from 'n8n-workflow';
import * as nodeHelpers from '@/composables/useNodeHelpers';
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
import { useNDVStore } from '@/stores/ndv.store';
import { fireEvent } from '@testing-library/dom';
import { useTelemetry } from '@/composables/useTelemetry';
import { useSchemaPreviewStore } from '../stores/schemaPreview.store';
import { usePostHog } from '../stores/posthog.store';
import { useSettingsStore } from '../stores/settings.store';
import { setActivePinia } from 'pinia';
import { mock } from 'vitest-mock-extended';
import { defaultSettings } from '../__tests__/defaults';
import { usePostHog } from '../stores/posthog.store';
import { useSchemaPreviewStore } from '../stores/schemaPreview.store';
import { useSettingsStore } from '../stores/settings.store';
const mockNode1 = createTestNode({
name: 'Manual Trigger',
@@ -65,6 +65,14 @@ const aiTool = createTestNode({
disabled: false,
});
const nodeWithCredential = createTestNode({
name: 'Notion',
type: 'n8n-nodes-base.notion',
typeVersion: 1,
credentials: { notionApi: { id: 'testId', name: 'testName' } },
disabled: false,
});
const unknownNodeType = createTestNode({
name: 'Unknown Node Type',
type: 'unknown',
@@ -76,15 +84,23 @@ const defaultNodes = [
];
async function setupStore() {
const workflow = mock<IWorkflowDb>({
const workflow = {
id: '123',
name: 'Test Workflow',
connections: {},
active: true,
nodes: [mockNode1, mockNode2, disabledNode, ifNode, aiTool, unknownNodeType],
});
nodes: [
mockNode1,
mockNode2,
disabledNode,
ifNode,
aiTool,
unknownNodeType,
nodeWithCredential,
],
};
const pinia = createPinia();
const pinia = createTestingPinia({ stubActions: false });
setActivePinia(pinia);
const workflowsStore = useWorkflowsStore();
@@ -102,20 +118,24 @@ async function setupStore() {
name: IF_NODE_TYPE,
outputs: [NodeConnectionTypes.Main, NodeConnectionTypes.Main],
}),
mockNodeTypeDescription({
name: 'n8n-nodes-base.notion',
outputs: [NodeConnectionTypes.Main],
}),
]);
workflowsStore.workflow = workflow;
workflowsStore.workflow = workflow as IWorkflowDb;
return pinia;
}
function mockNodeOutputData(nodeName: string, data: IDataObject[], outputIndex = 0) {
function mockNodeOutputData(nodeName: string, data: INodeExecutionData[], outputIndex = 0) {
const originalNodeHelpers = nodeHelpers.useNodeHelpers();
vi.spyOn(nodeHelpers, 'useNodeHelpers').mockImplementation(() => {
return {
...originalNodeHelpers,
getNodeInputData: vi.fn((node, _, output) => {
if (node.name === nodeName && output === outputIndex) {
return data.map((json) => ({ json }));
return data;
}
return [];
}),
@@ -146,7 +166,7 @@ describe('VirtualSchema.vue', () => {
beforeEach(async () => {
cleanup();
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue(123);
vi.resetAllMocks();
vi.setSystemTime('2025-01-01');
renderComponent = createComponentRenderer(VirtualSchema, {
global: {
@@ -183,6 +203,20 @@ describe('VirtualSchema.vue', () => {
expect(getAllByText("No fields - item(s) exist, but they're empty").length).toBe(1);
});
it('renders schema for empty data with binary', async () => {
mockNodeOutputData(mockNode1.name, [{ json: {}, binary: { data: mock<IBinaryData>() } }]);
const { getByText } = renderComponent({
props: { nodes: [{ name: mockNode1.name, indicies: [], depth: 1 }] },
});
await waitFor(() =>
expect(
getByText("Only binary data exists. View it using the 'Binary' tab"),
).toBeInTheDocument(),
);
});
it('renders schema for data', async () => {
useWorkflowsStore().pinData({
node: mockNode1,
@@ -307,10 +341,7 @@ describe('VirtualSchema.vue', () => {
it('renders schema for correct output branch', async () => {
mockNodeOutputData(
'If',
[
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
],
[{ json: { id: 1, name: 'John' } }, { json: { id: 2, name: 'Jane' } }],
1,
);
const { getAllByTestId } = renderComponent({
@@ -330,10 +361,7 @@ describe('VirtualSchema.vue', () => {
it('renders previous nodes schema for AI tools', async () => {
mockNodeOutputData(
'If',
[
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
],
[{ json: { id: 1, name: 'John' } }, { json: { id: 2, name: 'Jane' } }],
0,
);
const { getAllByTestId } = renderComponent({
@@ -481,14 +509,6 @@ describe('VirtualSchema.vue', () => {
});
it('should track data pill drag and drop for schema preview', async () => {
useWorkflowsStore().pinData({
node: {
...mockNode2,
credentials: { myCredential: { id: 'myCredential', name: 'myCredential' } },
},
data: [],
});
const telemetry = useTelemetry();
const trackSpy = vi.spyOn(telemetry, 'track');
const posthogStore = usePostHog();
@@ -513,7 +533,7 @@ describe('VirtualSchema.vue', () => {
const { getAllByTestId } = renderComponent({
props: {
nodes: [{ name: mockNode2.name, indicies: [], depth: 1 }],
nodes: [{ name: nodeWithCredential.name, indicies: [], depth: 1 }],
},
});
@@ -639,4 +659,8 @@ describe('VirtualSchema.vue', () => {
expect(container).toMatchSnapshot();
});
it('should do something', () => {
expect(true).toBe(true);
});
});