fix(editor): Make deleting Call n8n Workflow Tool fromAI workflow input descriptions work (#15459)

This commit is contained in:
Jaakko Husso
2025-05-19 16:43:59 +03:00
committed by GitHub
parent 5967c13165
commit 0e708ddb54
4 changed files with 181 additions and 0 deletions

View File

@@ -31,6 +31,8 @@ describe('N8nSelectableList', () => {
await fireEvent.click(wrapper.getByTestId('selectable-list-remove-slot-propA'));
expect(wrapper.queryByTestId('selectable-list-slot-propA')).not.toBeInTheDocument();
expect(wrapper.emitted('removeItem')).toHaveLength(1);
expect(wrapper.emitted('removeItem')[0]).toEqual(['propA']);
});
it('renders multiple elements with some pre-selected', () => {

View File

@@ -12,6 +12,10 @@ defineSlots<{
displayItem: (props: Item) => unknown;
}>();
const emit = defineEmits<{
removeItem: [name: string];
}>();
type SelectableListProps = {
inputs: Item[];
disabled?: boolean;
@@ -50,6 +54,7 @@ function addToSelectedItems(name: string) {
function removeFromSelectedItems(name: string) {
delete selectedItems.value[name];
emit('removeItem', name);
}
function itemComparator(a: Item, b: Item) {

View File

@@ -0,0 +1,165 @@
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';
import ParameterOverrideSelectableList from './ParameterOverrideSelectableList.vue';
import { createTestingPinia } from '@pinia/testing';
import { ref } from 'vue';
import { createAppModals } from '@/__tests__/utils';
import { STORES } from '@n8n/stores';
vi.mock('vue-router', () => {
return {
useRouter: () => ({
push: vi.fn(),
resolve: vi.fn().mockReturnValue({ href: 'https://test.com' }),
}),
useRoute: () => ({}),
RouterLink: vi.fn(),
};
});
beforeEach(() => {
createAppModals();
});
const renderComponent = createComponentRenderer(ParameterOverrideSelectableList, {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: {
enterprise: {
externalSecrets: false,
},
},
},
},
}),
});
describe('ParameterOverrideSelectableList', () => {
it('should render the component', () => {
const model = ref({
type: 'fromAI',
extraProps: {
description: {
initialValue: 'Test',
type: 'string',
typeOptions: { rows: 2 },
tooltip: '',
},
},
extraPropValues: { description: 'Test description' },
});
const update = vi.fn((v) => (model.value = v));
const { getByTestId } = renderComponent({
props: {
isReadOnly: false,
parameter: {
displayName: 'test',
name: 'value["test"]',
type: 'string',
default: '',
required: false,
description: '',
readOnly: false,
},
path: 'parameters.workflowInputs.value["test"]',
modelValue: model.value,
'onUpdate:modelValue': update,
},
});
expect(getByTestId('parameter-input-field')).toBeInTheDocument();
expect(getByTestId('selectable-list-remove-slot-description')).toBeInTheDocument();
});
it('should update extra prop value when input changes', async () => {
const model = ref({
type: 'fromAI',
extraProps: {
description: {
initialValue: 'Test',
type: 'string',
typeOptions: { rows: 2 },
tooltip: '',
},
},
extraPropValues: { description: 'Test description' },
});
const update = vi.fn((v) => (model.value = v));
const { getByTestId, emitted } = renderComponent({
props: {
isReadOnly: false,
parameter: {
displayName: 'test',
name: 'value["test"]',
type: 'string',
default: '',
required: false,
description: '',
readOnly: false,
},
path: 'parameters.workflowInputs.value["test"]',
modelValue: model.value,
'onUpdate:modelValue': update,
},
});
await userEvent.type(getByTestId('parameter-input-field'), '2');
expect(model.value.extraPropValues.description).toBe('Test description2');
expect(emitted('update')).toHaveLength(1);
expect(emitted('update')[0]).toEqual([
{
name: 'parameters.workflowInputs.value["test"]',
value:
"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('test', `Test description2`, 'string') }}",
},
]);
});
it('should reset extra prop back to default when removed', async () => {
const model = ref({
type: 'fromAI',
extraProps: {
description: {
initialValue: '',
type: 'string',
typeOptions: { rows: 2 },
tooltip: '',
},
},
extraPropValues: { description: 'Test description' },
});
const update = vi.fn((v) => (model.value = v));
const { getByTestId, emitted } = renderComponent({
props: {
isReadOnly: false,
parameter: {
displayName: 'test',
name: 'value["test"]',
type: 'string',
default: '',
required: false,
description: '',
readOnly: false,
},
path: 'parameters.workflowInputs.value["test"]',
modelValue: model.value,
'onUpdate:modelValue': update,
},
});
expect(model.value.extraPropValues.description).toBeDefined();
await userEvent.click(getByTestId('selectable-list-remove-slot-description'));
expect(model.value.extraPropValues.description).toBeUndefined();
expect(emitted('update')).toHaveLength(1);
expect(emitted('update')[0]).toEqual([
{
name: 'parameters.workflowInputs.value["test"]',
value: "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('test', ``, 'string') }}",
},
]);
});
});

View File

@@ -36,6 +36,14 @@ const emit = defineEmits<{
function valueChanged(parameterData: IUpdateInformation) {
emit('update', parameterData);
}
function onExtraPropValueRemove(name: string) {
delete parameterOverride.value.extraPropValues[name];
valueChanged({
name: props.path,
value: buildValueFromOverride(parameterOverride.value, props, true),
});
}
</script>
<template>
@@ -44,6 +52,7 @@ function valueChanged(parameterData: IUpdateInformation) {
class="mt-2xs"
:inputs="inputs"
:disabled="isReadOnly"
@remove-item="onExtraPropValueRemove"
>
<template #displayItem="{ name, tooltip, initialValue, type, typeOptions }">
<ParameterInputFull