test(editor): Add tests for node settings parameters composable (no-changelog) (#17232)

This commit is contained in:
Daria
2025-07-11 17:21:14 +03:00
committed by GitHub
parent 25139b2c77
commit 4eb18b7dc7
10 changed files with 272 additions and 59 deletions

View File

@@ -24,7 +24,7 @@ export default defineConfig(frontendConfig, {
'@typescript-eslint/dot-notation': 'warn', '@typescript-eslint/dot-notation': 'warn',
'@stylistic/lines-between-class-members': 'warn', '@stylistic/lines-between-class-members': 'warn',
'@stylistic/member-delimiter-style': 'warn', '@stylistic/member-delimiter-style': 'warn',
'@typescript-eslint/naming-convention': 'warn', '@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-empty-interface': 'warn', '@typescript-eslint/no-empty-interface': 'warn',
'@typescript-eslint/no-for-in-array': 'warn', '@typescript-eslint/no-for-in-array': 'warn',
'@typescript-eslint/no-loop-func': 'warn', '@typescript-eslint/no-loop-func': 'warn',

View File

@@ -132,7 +132,6 @@ declare global {
disallowReturnToOpener?: boolean; disallowReturnToOpener?: boolean;
}) => Promise<Window>; }) => Promise<Window>;
}; };
// eslint-disable-next-line @typescript-eslint/naming-convention
Cypress: unknown; Cypress: unknown;
} }
} }

View File

@@ -303,7 +303,6 @@ describe('useActionsGenerator', () => {
noDataExpression: true, noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'@version': [1], '@version': [1],
resource: ['user'], resource: ['user'],
}, },
@@ -324,7 +323,6 @@ describe('useActionsGenerator', () => {
noDataExpression: true, noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'@version': [2], '@version': [2],
resource: ['user'], resource: ['user'],
}, },
@@ -369,7 +367,6 @@ describe('useActionsGenerator', () => {
noDataExpression: true, noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'@version': [1, 2], '@version': [1, 2],
resource: ['user'], resource: ['user'],
}, },

View File

@@ -42,6 +42,7 @@ import {
isResourceLocatorParameterType, isResourceLocatorParameterType,
isValidParameterOption, isValidParameterOption,
parseFromExpression, parseFromExpression,
shouldSkipParamValidation,
} from '@/utils/nodeSettingsUtils'; } from '@/utils/nodeSettingsUtils';
import { hasExpressionMapping, isValueExpression } from '@/utils/nodeTypesUtils'; import { hasExpressionMapping, isValueExpression } from '@/utils/nodeTypesUtils';
@@ -426,7 +427,7 @@ const getIssues = computed<string[]>(() => {
let checkValues: string[] = []; let checkValues: string[] = [];
if (!nodeSettingsParameters.shouldSkipParamValidation(displayValue.value)) { if (!shouldSkipParamValidation(displayValue.value)) {
if (Array.isArray(displayValue.value)) { if (Array.isArray(displayValue.value)) {
checkValues = checkValues.concat(displayValue.value); checkValues = checkValues.concat(displayValue.value);
} else { } else {

View File

@@ -10,6 +10,7 @@ import { computed } from 'vue';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { usePostHog } from '@/stores/posthog.store'; import { usePostHog } from '@/stores/posthog.store';
import { AI_TRANSFORM_NODE_TYPE, FOCUS_PANEL_EXPERIMENT } from '@/constants'; import { AI_TRANSFORM_NODE_TYPE, FOCUS_PANEL_EXPERIMENT } from '@/constants';
import { getParameterTypeOption } from '@/utils/nodeSettingsUtils';
interface Props { interface Props {
parameter: INodeProperties; parameter: INodeProperties;
@@ -41,12 +42,28 @@ const i18n = useI18n();
const ndvStore = useNDVStore(); const ndvStore = useNDVStore();
const posthogStore = usePostHog(); const posthogStore = usePostHog();
const activeNode = computed(() => ndvStore.activeNode);
const isDefault = computed(() => props.parameter.default === props.value); const isDefault = computed(() => props.parameter.default === props.value);
const isValueAnExpression = computed(() => isValueExpression(props.parameter, props.value)); const isValueAnExpression = computed(() => isValueExpression(props.parameter, props.value));
const isHtmlEditor = computed(() => getArgument('editor') === 'htmlEditor'); const isHtmlEditor = computed(
() => getParameterTypeOption(props.parameter, 'editor') === 'htmlEditor',
);
const shouldShowExpressionSelector = computed( const shouldShowExpressionSelector = computed(
() => !props.parameter.noDataExpression && props.showExpressionSelector && !props.isReadOnly, () => !props.parameter.noDataExpression && props.showExpressionSelector && !props.isReadOnly,
); );
const isFocusPanelFeatureEnabled = computed(() => {
return posthogStore.getVariant(FOCUS_PANEL_EXPERIMENT.name) === FOCUS_PANEL_EXPERIMENT.variant;
});
const hasFocusAction = computed(
() =>
isFocusPanelFeatureEnabled.value &&
!props.parameter.isNodeSetting &&
!props.isReadOnly &&
activeNode.value && // checking that it's inside ndv
(props.parameter.type === 'string' || props.parameter.type === 'json'),
);
const shouldShowOptions = computed(() => { const shouldShowOptions = computed(() => {
if (props.isReadOnly) { if (props.isReadOnly) {
return false; return false;
@@ -71,7 +88,6 @@ const shouldShowOptions = computed(() => {
return false; return false;
}); });
const selectedView = computed(() => (isValueAnExpression.value ? 'expression' : 'fixed')); const selectedView = computed(() => (isValueAnExpression.value ? 'expression' : 'fixed'));
const activeNode = computed(() => ndvStore.activeNode);
const hasRemoteMethod = computed( const hasRemoteMethod = computed(
() => () =>
!!props.parameter.typeOptions?.loadOptionsMethod || !!props.parameter.typeOptions?.loadOptions, !!props.parameter.typeOptions?.loadOptionsMethod || !!props.parameter.typeOptions?.loadOptions,
@@ -84,18 +100,6 @@ const resetValueLabel = computed(() => {
return i18n.baseText('parameterInput.resetValue'); return i18n.baseText('parameterInput.resetValue');
}); });
const isFocusPanelFeatureEnabled = computed(() => {
return posthogStore.getVariant(FOCUS_PANEL_EXPERIMENT.name) === FOCUS_PANEL_EXPERIMENT.variant;
});
const hasFocusAction = computed(
() =>
isFocusPanelFeatureEnabled.value &&
!props.parameter.isNodeSetting &&
!props.isReadOnly &&
activeNode.value && // checking that it's inside ndv
(props.parameter.type === 'string' || props.parameter.type === 'json'),
);
const actions = computed(() => { const actions = computed(() => {
if (Array.isArray(props.customActions) && props.customActions.length > 0) { if (Array.isArray(props.customActions) && props.customActions.length > 0) {
return props.customActions; return props.customActions;
@@ -161,17 +165,6 @@ const onViewSelected = (selected: string) => {
emit('update:modelValue', 'removeExpression'); emit('update:modelValue', 'removeExpression');
} }
}; };
const getArgument = (argumentName: string) => {
if (props.parameter.typeOptions === undefined) {
return undefined;
}
if (props.parameter.typeOptions[argumentName] === undefined) {
return undefined;
}
return props.parameter.typeOptions[argumentName];
};
</script> </script>
<template> <template>

View File

@@ -45,10 +45,11 @@ import { STORES } from '@n8n/stores';
import type { Connection } from '@vue-flow/core'; import type { Connection } from '@vue-flow/core';
import { useClipboard } from '@/composables/useClipboard'; import { useClipboard } from '@/composables/useClipboard';
import { createCanvasConnectionHandleString } from '@/utils/canvasUtils'; import { createCanvasConnectionHandleString } from '@/utils/canvasUtils';
import { nextTick } from 'vue'; import { nextTick, ref } from 'vue';
import type { CanvasLayoutEvent } from './useCanvasLayout'; import type { CanvasLayoutEvent } from './useCanvasLayout';
import { useTelemetry } from './useTelemetry'; import { useTelemetry } from './useTelemetry';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import * as nodeHelpers from '@/composables/useNodeHelpers';
vi.mock('n8n-workflow', async (importOriginal) => { vi.mock('n8n-workflow', async (importOriginal) => {
const actual = await importOriginal<{}>(); const actual = await importOriginal<{}>();
@@ -2823,7 +2824,15 @@ describe('useCanvasOperations', () => {
const uiStore = mockedStore(useUIStore); const uiStore = mockedStore(useUIStore);
const executionsStore = mockedStore(useExecutionsStore); const executionsStore = mockedStore(useExecutionsStore);
const nodeHelpers = { credentialsUpdated: { value: true } }; const credentialsUpdatedRef = ref(true);
const credentialsSpy = vi.spyOn(credentialsUpdatedRef, 'value', 'set');
const nodeHelpersOriginal = nodeHelpers.useNodeHelpers();
vi.spyOn(nodeHelpers, 'useNodeHelpers').mockImplementation(() => {
return {
...nodeHelpersOriginal,
credentialsUpdated: credentialsUpdatedRef,
};
});
nodeCreatorStore.setNodeCreatorState = vi.fn(); nodeCreatorStore.setNodeCreatorState = vi.fn();
nodeCreatorStore.setShowScrim = vi.fn(); nodeCreatorStore.setShowScrim = vi.fn();
@@ -2854,7 +2863,6 @@ describe('useCanvasOperations', () => {
startedAt: new Date(), startedAt: new Date(),
}, },
]; ];
nodeHelpers.credentialsUpdated.value = true;
const { resetWorkspace } = useCanvasOperations(); const { resetWorkspace } = useCanvasOperations();
@@ -2872,6 +2880,8 @@ describe('useCanvasOperations', () => {
expect(uiStore.resetLastInteractedWith).toHaveBeenCalled(); expect(uiStore.resetLastInteractedWith).toHaveBeenCalled();
expect(uiStore.stateIsDirty).toBe(false); expect(uiStore.stateIsDirty).toBe(false);
expect(executionsStore.activeExecution).toBeNull(); expect(executionsStore.activeExecution).toBeNull();
expect(credentialsSpy).toHaveBeenCalledWith(false);
expect(credentialsUpdatedRef.value).toBe(false);
}); });
it('should not call removeTestWebhook if executionWaitingForWebhook is false', () => { it('should not call removeTestWebhook if executionWaitingForWebhook is false', () => {

View File

@@ -1,9 +1,14 @@
import { createTestingPinia } from '@pinia/testing'; import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia'; import { setActivePinia } from 'pinia';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useFocusPanelStore } from '@/stores/focusPanel.store'; import { useFocusPanelStore } from '@/stores/focusPanel.store';
import { useNodeSettingsParameters } from './useNodeSettingsParameters'; import { useNodeSettingsParameters } from './useNodeSettingsParameters';
import type { INodeProperties } from 'n8n-workflow'; import * as nodeHelpers from '@/composables/useNodeHelpers';
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
import * as nodeSettingsUtils from '@/utils/nodeSettingsUtils';
import * as nodeTypesUtils from '@/utils/nodeTypesUtils';
import type { INodeProperties, INodeTypeDescription } from 'n8n-workflow';
import type { MockedStore } from '@/__tests__/utils'; import type { MockedStore } from '@/__tests__/utils';
import { mockedStore } from '@/__tests__/utils'; import { mockedStore } from '@/__tests__/utils';
import type { INodeUi } from '@/Interface'; import type { INodeUi } from '@/Interface';
@@ -55,7 +60,7 @@ describe('useNodeSettingsParameters', () => {
let focusPanelStore: MockedStore<typeof useFocusPanelStore>; let focusPanelStore: MockedStore<typeof useFocusPanelStore>;
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); setActivePinia(createTestingPinia());
ndvStore = mockedStore(useNDVStore); ndvStore = mockedStore(useNDVStore);
focusPanelStore = mockedStore(useFocusPanelStore); focusPanelStore = mockedStore(useFocusPanelStore);
@@ -75,6 +80,10 @@ describe('useNodeSettingsParameters', () => {
focusPanelStore.focusPanelActive = false; focusPanelStore.focusPanelActive = false;
}); });
afterEach(() => {
vi.clearAllMocks();
});
it('sets focused node parameter', () => { it('sets focused node parameter', () => {
const { handleFocus } = useNodeSettingsParameters(); const { handleFocus } = useNodeSettingsParameters();
const node: INodeUi = { const node: INodeUi = {
@@ -120,4 +129,204 @@ describe('useNodeSettingsParameters', () => {
expect(focusPanelStore.openWithFocusedNodeParameter).not.toHaveBeenCalled(); expect(focusPanelStore.openWithFocusedNodeParameter).not.toHaveBeenCalled();
}); });
}); });
describe('shouldDisplayNodeParameter', () => {
const displayParameterSpy = vi.fn();
function mockNodeHelpers({ isCustomApiCallSelected = false } = {}) {
const originalNodeHelpers = nodeHelpers.useNodeHelpers();
vi.spyOn(nodeHelpers, 'useNodeHelpers').mockImplementation(() => {
return {
...originalNodeHelpers,
isCustomApiCallSelected: vi.fn(() => isCustomApiCallSelected),
displayParameter: displayParameterSpy,
};
});
}
let nodeTypesStore: MockedStore<typeof useNodeTypesStore>;
const mockParameter: INodeProperties = {
name: 'foo',
type: 'string',
displayName: 'Foo',
displayOptions: {},
default: '',
};
const mockNodeType: INodeTypeDescription = {
version: 1,
name: 'testNode',
displayName: 'Test Node',
description: 'A test node',
group: ['input'],
defaults: {
name: 'Test Node',
},
inputs: ['main'],
outputs: [],
properties: [mockParameter],
};
beforeEach(() => {
setActivePinia(createTestingPinia());
nodeTypesStore = mockedStore(useNodeTypesStore);
nodeTypesStore.getNodeType = vi.fn().mockReturnValueOnce(mockNodeType);
});
afterEach(() => {
vi.clearAllMocks();
});
it('returns false for hidden parameter type', () => {
mockNodeHelpers();
const { shouldDisplayNodeParameter } = useNodeSettingsParameters();
const result = shouldDisplayNodeParameter({}, null, { ...mockParameter, type: 'hidden' });
expect(result).toBe(false);
});
it('returns false for custom API call with mustHideDuringCustomApiCall', () => {
vi.spyOn(nodeSettingsUtils, 'mustHideDuringCustomApiCall').mockReturnValueOnce(true);
mockNodeHelpers({ isCustomApiCallSelected: true });
const { shouldDisplayNodeParameter } = useNodeSettingsParameters();
const result = shouldDisplayNodeParameter({}, null, mockParameter);
expect(result).toBe(false);
});
it('returns false if parameter is auth-related', () => {
vi.spyOn(nodeTypesUtils, 'isAuthRelatedParameter').mockReturnValueOnce(true);
vi.spyOn(nodeTypesUtils, 'getMainAuthField').mockReturnValueOnce(mockParameter);
mockNodeHelpers();
const { shouldDisplayNodeParameter } = useNodeSettingsParameters();
const result = shouldDisplayNodeParameter({}, null, mockParameter);
expect(result).toBe(false);
});
it('returns true if displayOptions is undefined', () => {
vi.spyOn(nodeTypesUtils, 'isAuthRelatedParameter').mockReturnValueOnce(false);
mockNodeHelpers();
const { shouldDisplayNodeParameter } = useNodeSettingsParameters();
const result = shouldDisplayNodeParameter({}, null, {
...mockParameter,
displayOptions: undefined,
});
expect(result).toBe(true);
});
it('calls displayParameter with correct arguments', () => {
vi.spyOn(nodeTypesUtils, 'isAuthRelatedParameter').mockReturnValueOnce(false);
mockNodeHelpers();
displayParameterSpy.mockReturnValueOnce(false);
const { shouldDisplayNodeParameter } = useNodeSettingsParameters();
const parameter: INodeProperties = {
name: 'foo',
type: 'string',
displayName: 'Foo',
disabledOptions: {},
default: '',
};
const nodeParameters = { foo: 'bar' };
const node: INodeUi = {
id: '1',
name: 'Node1',
position: [0, 0],
typeVersion: 1,
type: 'n8n-nodes-base.set',
parameters: nodeParameters,
};
const result = shouldDisplayNodeParameter(
nodeParameters,
node,
parameter,
'',
'disabledOptions',
);
expect(displayParameterSpy).toHaveBeenCalledWith(
nodeParameters,
parameter,
'',
node,
'disabledOptions',
);
expect(result).toBe(false);
});
it('calls displayParameter with default displayOptions', () => {
vi.spyOn(nodeTypesUtils, 'isAuthRelatedParameter').mockReturnValueOnce(false);
mockNodeHelpers();
displayParameterSpy.mockReturnValueOnce(true);
const { shouldDisplayNodeParameter } = useNodeSettingsParameters();
const nodeParameters = { foo: 'bar' };
const node: INodeUi = {
id: '1',
name: 'Node1',
position: [0, 0],
typeVersion: 1,
type: 'n8n-nodes-base.set',
parameters: nodeParameters,
};
const result = shouldDisplayNodeParameter(nodeParameters, node, mockParameter);
expect(displayParameterSpy).toHaveBeenCalledWith(
nodeParameters,
mockParameter,
'',
node,
'displayOptions',
);
expect(result).toBe(true);
});
it('resolves expressions and calls displayParameter with resolved parameters', () => {
vi.spyOn(nodeTypesUtils, 'isAuthRelatedParameter').mockReturnValueOnce(false);
mockNodeHelpers();
displayParameterSpy.mockReturnValueOnce(true);
const originalWorkflowHelpers = workflowHelpers.useWorkflowHelpers();
vi.spyOn(workflowHelpers, 'useWorkflowHelpers').mockImplementation(() => ({
...originalWorkflowHelpers,
resolveExpression: (expr: string) => (expr === '=1+1' ? 2 : expr),
}));
const { shouldDisplayNodeParameter } = useNodeSettingsParameters();
const nodeParameters = { foo: '=1+1' };
const node: INodeUi = {
id: '1',
name: 'Node1',
position: [0, 0],
typeVersion: 1,
type: 'n8n-nodes-base.set',
parameters: nodeParameters,
};
const result = shouldDisplayNodeParameter(nodeParameters, node, mockParameter);
expect(displayParameterSpy).toHaveBeenCalledWith(
{ foo: 2 },
mockParameter,
'',
node,
'displayOptions',
);
expect(displayParameterSpy).toHaveBeenCalled();
expect(result).toBe(true);
});
});
}); });

View File

@@ -24,7 +24,7 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useFocusPanelStore } from '@/stores/focusPanel.store'; import { useFocusPanelStore } from '@/stores/focusPanel.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { CUSTOM_API_CALL_KEY, KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants'; import { KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
import { omitKey } from '@/utils/objectUtils'; import { omitKey } from '@/utils/objectUtils';
import { import {
getMainAuthField, getMainAuthField,
@@ -322,7 +322,7 @@ export function useNodeSettingsParameters() {
value, value,
nodeParams, nodeParams,
) as NodeParameterValue; ) as NodeParameterValue;
} catch (e) { } catch {
// If expression is invalid ignore // If expression is invalid ignore
nodeParams[key] = ''; nodeParams[key] = '';
} }
@@ -352,10 +352,6 @@ export function useNodeSettingsParameters() {
return nodeHelpers.displayParameter(nodeParameters, parameter, path, node, displayKey); return nodeHelpers.displayParameter(nodeParameters, parameter, path, node, displayKey);
} }
function shouldSkipParamValidation(value: string | number | boolean | null) {
return typeof value === 'string' && value.includes(CUSTOM_API_CALL_KEY);
}
return { return {
nodeValues, nodeValues,
setValue, setValue,
@@ -363,6 +359,5 @@ export function useNodeSettingsParameters() {
updateParameterByPath, updateParameterByPath,
updateNodeParameter, updateNodeParameter,
handleFocus, handleFocus,
shouldSkipParamValidation,
}; };
} }

View File

@@ -70,7 +70,7 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
}), }),
); );
const _setOptions = ({ function _setOptions({
parameters, parameters,
isActive, isActive,
wid = workflowsStore.workflowId, wid = workflowsStore.workflowId,
@@ -80,7 +80,7 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
parameters?: FocusedNodeParameter[]; parameters?: FocusedNodeParameter[];
wid?: string; wid?: string;
removeEmpty?: boolean; removeEmpty?: boolean;
}) => { }) {
const focusPanelDataCurrent = focusPanelData.value; const focusPanelDataCurrent = focusPanelData.value;
if (removeEmpty && PLACEHOLDER_EMPTY_WORKFLOW_ID in focusPanelDataCurrent) { if (removeEmpty && PLACEHOLDER_EMPTY_WORKFLOW_ID in focusPanelDataCurrent) {
@@ -94,10 +94,10 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
parameters: parameters ?? _focusedNodeParameters.value, parameters: parameters ?? _focusedNodeParameters.value,
}, },
}); });
}; }
// When a new workflow is saved, we should update the focus panel data with the new workflow ID // When a new workflow is saved, we should update the focus panel data with the new workflow ID
const onNewWorkflowSave = (wid: string) => { function onNewWorkflowSave(wid: string) {
if (!currentFocusPanelData.value || !(PLACEHOLDER_EMPTY_WORKFLOW_ID in focusPanelData.value)) { if (!currentFocusPanelData.value || !(PLACEHOLDER_EMPTY_WORKFLOW_ID in focusPanelData.value)) {
return; return;
} }
@@ -109,29 +109,29 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
isActive: latestWorkflowData.isActive, isActive: latestWorkflowData.isActive,
removeEmpty: true, removeEmpty: true,
}); });
}; }
const openWithFocusedNodeParameter = (nodeParameter: FocusedNodeParameter) => { function openWithFocusedNodeParameter(nodeParameter: FocusedNodeParameter) {
const parameters = [nodeParameter]; const parameters = [nodeParameter];
// TODO: uncomment when tabs are implemented // TODO: uncomment when tabs are implemented
// ...focusedNodeParameters.value.filter((p) => p.parameterPath !== nodeParameter.parameterPath), // ...focusedNodeParameters.value.filter((p) => p.parameterPath !== nodeParameter.parameterPath),
_setOptions({ parameters, isActive: true }); _setOptions({ parameters, isActive: true });
}; }
const closeFocusPanel = () => { function closeFocusPanel() {
_setOptions({ isActive: false }); _setOptions({ isActive: false });
}; }
const toggleFocusPanel = () => { function toggleFocusPanel() {
_setOptions({ isActive: !focusPanelActive.value }); _setOptions({ isActive: !focusPanelActive.value });
}; }
const isRichParameter = ( function isRichParameter(
p: RichFocusedNodeParameter | FocusedNodeParameter, p: RichFocusedNodeParameter | FocusedNodeParameter,
): p is RichFocusedNodeParameter => { ): p is RichFocusedNodeParameter {
return 'value' in p && 'node' in p; return 'value' in p && 'node' in p;
}; }
return { return {
focusPanelActive, focusPanelActive,

View File

@@ -19,7 +19,7 @@ import {
isResourceLocatorValue, isResourceLocatorValue,
} from 'n8n-workflow'; } from 'n8n-workflow';
import type { INodeUi, IUpdateInformation } from '@/Interface'; import type { INodeUi, IUpdateInformation } from '@/Interface';
import { SWITCH_NODE_TYPE } from '@/constants'; import { CUSTOM_API_CALL_KEY, SWITCH_NODE_TYPE } from '@/constants';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import get from 'lodash/get'; import get from 'lodash/get';
import set from 'lodash/set'; import set from 'lodash/set';
@@ -166,7 +166,12 @@ export function removeMismatchedOptionValues(
nodeType.properties.forEach((prop) => { nodeType.properties.forEach((prop) => {
const displayOptions = prop.displayOptions; const displayOptions = prop.displayOptions;
// Not processing parameters that are not set or don't have options // Not processing parameters that are not set or don't have options
if (!nodeParameterValues?.hasOwnProperty(prop.name) || !displayOptions || !prop.options) { if (
!nodeParameterValues ||
!Object.prototype.hasOwnProperty.call(nodeParameterValues, prop.name) ||
!displayOptions ||
!prop.options
) {
return; return;
} }
// Only process the parameters that depend on the updated parameter // Only process the parameters that depend on the updated parameter
@@ -353,3 +358,7 @@ export function parseFromExpression(
return null; return null;
} }
export function shouldSkipParamValidation(value: string | number | boolean | null) {
return typeof value === 'string' && value.includes(CUSTOM_API_CALL_KEY);
}