mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 05:17:28 -08:00
fix(editor): Testing flaky resource mapper feature in e2e tests (#7165)
This commit is contained in:
parent
0c6169ee22
commit
aaf87c3edd
|
@ -17,7 +17,7 @@ describe('Resource Mapper', () => {
|
||||||
.resourceMapperFieldsContainer()
|
.resourceMapperFieldsContainer()
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.findChildByTestId('parameter-input')
|
.findChildByTestId('parameter-input')
|
||||||
.should('have.length', 2);
|
.should('have.length', 3);
|
||||||
|
|
||||||
ndv.actions.setInvalidExpression('fieldId');
|
ndv.actions.setInvalidExpression('fieldId');
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ describe('Resource Mapper', () => {
|
||||||
.resourceMapperFieldsContainer()
|
.resourceMapperFieldsContainer()
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.findChildByTestId('parameter-input')
|
.findChildByTestId('parameter-input')
|
||||||
.should('have.length', 2);
|
.should('have.length', 3);
|
||||||
|
|
||||||
ndv.actions.setInvalidExpression('otherField');
|
ndv.actions.setInvalidExpression('otherField');
|
||||||
|
|
||||||
|
@ -43,6 +43,53 @@ describe('Resource Mapper', () => {
|
||||||
.resourceMapperFieldsContainer()
|
.resourceMapperFieldsContainer()
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.findChildByTestId('parameter-input')
|
.findChildByTestId('parameter-input')
|
||||||
.should('have.length', 2);
|
.should('have.length', 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly delete single field', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('E2e Test', {
|
||||||
|
action: 'Resource Mapping Component',
|
||||||
|
});
|
||||||
|
ndv.getters.parameterInput('id').type('001');
|
||||||
|
ndv.getters.parameterInput('name').type('John');
|
||||||
|
ndv.getters.parameterInput('age').type('30');
|
||||||
|
ndv.getters.nodeExecuteButton().click();
|
||||||
|
ndv.getters.outputTableHeaderByText('id').should('exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('name').should('exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('age').should('exist');
|
||||||
|
// Remove the 'name' field
|
||||||
|
ndv.getters.resourceMapperRemoveFieldButton('name').should('exist').click({ force: true });
|
||||||
|
ndv.getters.nodeExecuteButton().click();
|
||||||
|
ndv.getters.parameterInput('id').should('exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('id').should('exist');
|
||||||
|
// After removing the field, text field and the output table column for the 'name' should not be there anymore
|
||||||
|
ndv.getters.parameterInput('age').should('exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('age').should('exist');
|
||||||
|
ndv.getters.parameterInput('name').should('not.exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('name').should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly delete all fields', () => {
|
||||||
|
workflowPage.actions.addInitialNodeToCanvas('E2e Test', {
|
||||||
|
action: 'Resource Mapping Component',
|
||||||
|
});
|
||||||
|
ndv.getters.parameterInput('id').type('001');
|
||||||
|
ndv.getters.parameterInput('name').type('John');
|
||||||
|
ndv.getters.parameterInput('age').type('30');
|
||||||
|
ndv.getters.nodeExecuteButton().click();
|
||||||
|
ndv.getters.outputTableHeaderByText('id').should('exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('name').should('exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('age').should('exist');
|
||||||
|
ndv.getters.resourceMapperColumnsOptionsButton().click();
|
||||||
|
// Click on the 'Remove All Fields' option
|
||||||
|
ndv.getters.resourceMapperRemoveAllFieldsOption().should('be.visible').click();
|
||||||
|
ndv.getters.nodeExecuteButton().click();
|
||||||
|
ndv.getters.parameterInput('id').should('exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('id').should('exist');
|
||||||
|
// After removing the all fields, only required one should be in UI and output table
|
||||||
|
ndv.getters.parameterInput('name').should('not.exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('name').should('not.exist');
|
||||||
|
ndv.getters.parameterInput('age').should('not.exist');
|
||||||
|
ndv.getters.outputTableHeaderByText('age').should('not.exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,7 @@ export class NDV extends BasePage {
|
||||||
this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'),
|
this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'),
|
||||||
outputTableRows: () => this.getters.outputDataContainer().find('table tr'),
|
outputTableRows: () => this.getters.outputDataContainer().find('table tr'),
|
||||||
outputTableHeaders: () => this.getters.outputDataContainer().find('table thead th'),
|
outputTableHeaders: () => this.getters.outputDataContainer().find('table thead th'),
|
||||||
|
outputTableHeaderByText: (text: string) => this.getters.outputTableHeaders().contains(text),
|
||||||
outputTableRow: (row: number) => this.getters.outputTableRows().eq(row),
|
outputTableRow: (row: number) => this.getters.outputTableRows().eq(row),
|
||||||
outputTbodyCell: (row: number, col: number) =>
|
outputTbodyCell: (row: number, col: number) =>
|
||||||
this.getters.outputTableRow(row).find('td').eq(col),
|
this.getters.outputTableRow(row).find('td').eq(col),
|
||||||
|
@ -71,6 +72,9 @@ export class NDV extends BasePage {
|
||||||
this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'),
|
this.getters.resourceLocator(paramName).find('[data-test-id="rlc-mode-selector"]'),
|
||||||
resourceMapperFieldsContainer: () => cy.getByTestId('mapping-fields-container'),
|
resourceMapperFieldsContainer: () => cy.getByTestId('mapping-fields-container'),
|
||||||
resourceMapperSelectColumn: () => cy.getByTestId('matching-column-select'),
|
resourceMapperSelectColumn: () => cy.getByTestId('matching-column-select'),
|
||||||
|
resourceMapperRemoveFieldButton: (fieldName: string) => cy.getByTestId(`remove-field-button-${fieldName}`),
|
||||||
|
resourceMapperColumnsOptionsButton: () => cy.getByTestId('columns-parameter-input-options-container'),
|
||||||
|
resourceMapperRemoveAllFieldsOption: () => cy.getByTestId('action-removeAllFields'),
|
||||||
};
|
};
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
:expressionEvaluated="expressionValueComputed"
|
:expressionEvaluated="expressionValueComputed"
|
||||||
:additionalExpressionData="resolvedAdditionalExpressionData"
|
:additionalExpressionData="resolvedAdditionalExpressionData"
|
||||||
:label="label"
|
:label="label"
|
||||||
:data-test-id="`parameter-input-${parameter.name}`"
|
:data-test-id="`parameter-input-${parsedParameterName}`"
|
||||||
:event-bus="eventBus"
|
:event-bus="eventBus"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
|
@ -61,7 +61,7 @@ import type {
|
||||||
import { isResourceLocatorValue } from 'n8n-workflow';
|
import { isResourceLocatorValue } from 'n8n-workflow';
|
||||||
import type { INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
|
import type { INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import { isValueExpression } from '@/utils';
|
import { isValueExpression, parseResourceMapperFieldName } from '@/utils';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useEnvironmentsStore, useExternalSecretsStore } from '@/stores';
|
import { useEnvironmentsStore, useExternalSecretsStore } from '@/stores';
|
||||||
|
|
||||||
|
@ -226,6 +226,9 @@ export default defineComponent({
|
||||||
...this.additionalExpressionData,
|
...this.additionalExpressionData,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
parsedParameterName() {
|
||||||
|
return parseResourceMapperFieldName(this.parameter?.name ?? '');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onFocus() {
|
onFocus() {
|
||||||
|
|
|
@ -242,6 +242,10 @@ function getParamType(field: ResourceMapperField): NodePropertyTypes {
|
||||||
return 'string';
|
return 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getParsedFieldName(fullName: string): string {
|
||||||
|
return parseResourceMapperFieldName(fullName) ?? fullName;
|
||||||
|
}
|
||||||
|
|
||||||
function onValueChanged(value: IUpdateInformation): void {
|
function onValueChanged(value: IUpdateInformation): void {
|
||||||
emit('fieldValueChanged', value);
|
emit('fieldValueChanged', value);
|
||||||
}
|
}
|
||||||
|
@ -344,7 +348,7 @@ defineExpose({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
data-test-id="remove-field-button"
|
:data-test-id="`remove-field-button-${getParsedFieldName(field.name)}`"
|
||||||
@click="removeField(field.name)"
|
@click="removeField(field.name)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -223,7 +223,11 @@ describe('ResourceMapper.vue', () => {
|
||||||
expect(
|
expect(
|
||||||
getByTestId('mapping-fields-container').querySelectorAll('.parameter-input').length,
|
getByTestId('mapping-fields-container').querySelectorAll('.parameter-input').length,
|
||||||
).toBe(4);
|
).toBe(4);
|
||||||
expect(queryAllByTestId('remove-field-button').length).toBe(1);
|
expect(
|
||||||
|
getByTestId('mapping-fields-container').querySelectorAll(
|
||||||
|
'[data-test-id^="remove-field-button"]',
|
||||||
|
).length,
|
||||||
|
).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render correct options based on saved schema', async () => {
|
it('should render correct options based on saved schema', async () => {
|
||||||
|
@ -291,55 +295,4 @@ describe('ResourceMapper.vue', () => {
|
||||||
await waitAllPromises();
|
await waitAllPromises();
|
||||||
expect(fetchFieldsSpy).not.toHaveBeenCalled();
|
expect(fetchFieldsSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('should delete fields from UI and parameter value when they are deleted', async () => {
|
|
||||||
const { getByTestId, emitted } = renderComponent({
|
|
||||||
props: {
|
|
||||||
node: {
|
|
||||||
parameters: {
|
|
||||||
columns: {
|
|
||||||
schema: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await waitAllPromises();
|
|
||||||
// Add some values so we can test if they are gone after deletion
|
|
||||||
const idInput = getByTestId('parameter-input-value["id"]').querySelector('input');
|
|
||||||
const firstNameInput = getByTestId('parameter-input-value["First name"]').querySelector(
|
|
||||||
'input',
|
|
||||||
);
|
|
||||||
const lastNameInput = getByTestId('parameter-input-value["Last name"]').querySelector('input');
|
|
||||||
const usernameInput = getByTestId('parameter-input-value["Username"]').querySelector('input');
|
|
||||||
const addressInput = getByTestId('parameter-input-value["Address"]').querySelector('input');
|
|
||||||
if (idInput && firstNameInput && lastNameInput && usernameInput && addressInput) {
|
|
||||||
await userEvent.type(idInput, '123');
|
|
||||||
await userEvent.type(firstNameInput, 'John');
|
|
||||||
await userEvent.type(lastNameInput, 'Doe');
|
|
||||||
await userEvent.type(usernameInput, 'johndoe');
|
|
||||||
await userEvent.type(addressInput, '123 Main St');
|
|
||||||
// All field values should be in parameter value
|
|
||||||
const valueBeforeRemove = getLatestValueChangeEvent(emitted());
|
|
||||||
expect(valueBeforeRemove[0].value.value).toHaveProperty('id');
|
|
||||||
expect(valueBeforeRemove[0].value.value).toHaveProperty('First name');
|
|
||||||
expect(valueBeforeRemove[0].value.value).toHaveProperty('Last name');
|
|
||||||
expect(valueBeforeRemove[0].value.value).toHaveProperty('Username');
|
|
||||||
expect(valueBeforeRemove[0].value.value).toHaveProperty('Address');
|
|
||||||
// Click on 'Remove all fields' option
|
|
||||||
await userEvent.click(getByTestId('columns-parameter-input-options-container'));
|
|
||||||
await userEvent.click(getByTestId('action-removeAllFields'));
|
|
||||||
// Should delete all non-mandatory fields:
|
|
||||||
// 1. From UI
|
|
||||||
expect(
|
|
||||||
getByTestId('resource-mapper-container').querySelectorAll('.parameter-item').length,
|
|
||||||
).toBe(3);
|
|
||||||
// 2. And their values from parameter value
|
|
||||||
const valueAfterRemove = getLatestValueChangeEvent(emitted());
|
|
||||||
expect(valueAfterRemove[0].value.value).not.toHaveProperty('Username');
|
|
||||||
expect(valueAfterRemove[0].value.value).not.toHaveProperty('Address');
|
|
||||||
} else {
|
|
||||||
throw new Error('Could not find input fields');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -202,6 +202,14 @@ export class E2eTest implements INodeType {
|
||||||
};
|
};
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
// For resource mapper testing, return actual node values
|
||||||
|
if (operation === 'resourceMapper') {
|
||||||
|
const rmValue = this.getNodeParameter('resourceMapper.value', 0);
|
||||||
|
if (rmValue) {
|
||||||
|
return [[{ json: rmValue as INodeExecutionData }]];
|
||||||
|
}
|
||||||
|
}
|
||||||
return [returnData];
|
return [returnData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,15 @@ export const resourceMapperFields: ResourceMapperFields = {
|
||||||
display: true,
|
display: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'age',
|
||||||
|
displayName: 'Age',
|
||||||
|
defaultMatch: false,
|
||||||
|
canBeUsedToMatch: false,
|
||||||
|
required: false,
|
||||||
|
display: true,
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue