Skip to content

Commit

Permalink
[7.7] [Index management] Fix regression on mappings editor for… (elas…
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga authored Apr 7, 2020
1 parent 0e16f5b commit 80e0b67
Show file tree
Hide file tree
Showing 18 changed files with 371 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ export const useField = (
errors,
form,
isPristine,
isValid: errors.length === 0,
isValidating,
isValidated,
isChangingValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ export function useForm<T extends FormData = FormData>(
}, [] as string[]);
};

const isFieldValid = (field: FieldHook) =>
field.getErrorsMessages() === null && !field.isValidating;
const isFieldValid = (field: FieldHook) => field.isValid && !field.isValidating;

const updateFormValidity = () => {
const fieldsArray = fieldsToArray();
Expand Down Expand Up @@ -167,14 +166,24 @@ export function useForm<T extends FormData = FormData>(
};

const validateAllFields = async (): Promise<boolean> => {
const fieldsToValidate = fieldsToArray().filter(field => !field.isValidated);
const fieldsArray = fieldsToArray();
const fieldsToValidate = fieldsArray.filter(field => !field.isValidated);

let isFormValid: boolean | undefined = isValid;

if (fieldsToValidate.length === 0) {
// Nothing left to validate, all fields are already validated.
return isValid!;
if (isFormValid === undefined) {
// We should never enter this condition as the form validity is updated each time
// a field is validated. But sometimes, during tests it does not happen and we need
// to wait the next tick (hooks lifecycle being tricky) to make sure the "isValid" state is updated.
// In order to avoid this unintentional behaviour, we add this if condition here.
isFormValid = fieldsArray.every(isFieldValid);
setIsValid(isFormValid);
}
return isFormValid;
}

const { isFormValid } = await validateFields(fieldsToValidate.map(field => field.path));
({ isFormValid } = await validateFields(fieldsToValidate.map(field => field.path)));

return isFormValid!;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export interface FieldHook {
readonly type: string;
readonly value: unknown;
readonly errors: ValidationError[];
readonly isValid: boolean;
readonly isPristine: boolean;
readonly isValidating: boolean;
readonly isValidated: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
testBed.find('editFieldUpdateButton').simulate('click');
};

const clickRemoveButtonAtField = (index: number) => {
const deleteMappingsFieldAt = (index: number) => {
testBed
.find('removeFieldButton')
.at(index)
Expand All @@ -55,7 +55,7 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
};

const clickCancelCreateFieldButton = () => {
testBed.find('createFieldWrapper.cancelButton').simulate('click');
testBed.find('createFieldForm.cancelButton').simulate('click');
};

const completeStepOne = async ({
Expand Down Expand Up @@ -157,7 +157,7 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
const { find, form, component } = testBed;

form.setInputValue('nameParameterInput', name);
find('createFieldWrapper.mockComboBox').simulate('change', [
find('createFieldForm.mockComboBox').simulate('change', [
{
label: type,
value: type,
Expand All @@ -167,7 +167,7 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
await nextTick(50);
component.update();

find('createFieldWrapper.addButton').simulate('click');
find('createFieldForm.addButton').simulate('click');

await nextTick();
component.update();
Expand All @@ -181,7 +181,7 @@ export const formSetup = async (initTestBed: SetupFunc<TestSubjects>) => {
clickSubmitButton,
clickEditButtonAtField,
clickEditFieldUpdateButton,
clickRemoveButtonAtField,
deleteMappingsFieldAt,
clickCancelCreateFieldButton,
completeStepOne,
completeStepTwo,
Expand All @@ -199,16 +199,16 @@ export type TestSubjects =
| 'backButton'
| 'codeEditorContainer'
| 'confirmModalConfirmButton'
| 'createFieldWrapper.addPropertyButton'
| 'createFieldWrapper.addButton'
| 'createFieldWrapper.addFieldButton'
| 'createFieldWrapper.addMultiFieldButton'
| 'createFieldWrapper.cancelButton'
| 'createFieldWrapper.mockComboBox'
| 'createFieldForm.addPropertyButton'
| 'createFieldForm.addButton'
| 'createFieldForm.addFieldButton'
| 'createFieldForm.addMultiFieldButton'
| 'createFieldForm.cancelButton'
| 'createFieldForm.mockComboBox'
| 'editFieldButton'
| 'editFieldUpdateButton'
| 'fieldsListItem'
| 'fieldTypeComboBox'
| 'fieldType'
| 'indexPatternsField'
| 'indexPatternsWarning'
| 'indexPatternsWarningDescription'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ describe('<TemplateCreate />', () => {

actions.clickCancelCreateFieldButton();
// Remove first field
actions.clickRemoveButtonAtField(0);
actions.deleteMappingsFieldAt(0);

expect(find('fieldsListItem').length).toBe(1);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { setup as mappingsEditorSetup } from './mappings_editor.helpers';
import { setup as mappingsEditorSetup, MappingsEditorTestBed } from './mappings_editor.helpers';

export {
nextTick,
Expand All @@ -15,3 +15,5 @@ export {
export const componentHelpers = {
mappingsEditor: { setup: mappingsEditorSetup },
};

export { MappingsEditorTestBed };

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { registerTestBed, TestBed, nextTick } from '../../../../../../../../../test_utils';
import { MappingsEditor } from '../../../mappings_editor';

jest.mock('@elastic/eui', () => ({
...jest.requireActual('@elastic/eui'),
// Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions,
// which does not produce a valid component wrapper
EuiComboBox: (props: any) => (
<input
data-test-subj={props['data-test-subj'] || 'mockComboBox'}
onChange={async (syntheticEvent: any) => {
props.onChange([syntheticEvent['0']]);
}}
/>
),
// Mocking EuiCodeEditor, which uses React Ace under the hood
EuiCodeEditor: (props: any) => (
<input
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
data-currentvalue={props.value}
onChange={(e: any) => {
props.onChange(e.jsonContent);
}}
/>
),
}));

const createActions = (testBed: TestBed<TestSubjects>) => {
const { find, waitFor, form, component } = testBed;

const addField = async (name: string, type: string) => {
const currentCount = find('fieldsListItem').length;

form.setInputValue('nameParameterInput', name);
find('createFieldForm.fieldType').simulate('change', [
{
label: type,
value: type,
},
]);

await nextTick();
component.update();

find('createFieldForm.addButton').simulate('click');

// We wait until there is one more field in the DOM
await waitFor('fieldsListItem', currentCount + 1);
};

const selectTab = async (tab: 'fields' | 'templates' | 'advanced') => {
const index = ['fields', 'templates', 'advanced'].indexOf(tab);
const tabIdToContentMap: { [key: string]: TestSubjects } = {
fields: 'documentFields',
templates: 'dynamicTemplates',
advanced: 'advancedConfiguration',
};

const tabElement = find('formTab').at(index);
if (tabElement.length === 0) {
throw new Error(`Tab not found: "${tab}"`);
}
tabElement.simulate('click');

await waitFor(tabIdToContentMap[tab]);
};

const updateJsonEditor = async (testSubject: TestSubjects, value: object) => {
find(testSubject).simulate('change', { jsonContent: JSON.stringify(value) });
};

const getJsonEditorValue = (testSubject: TestSubjects) => {
const value = find(testSubject).props()['data-currentvalue'];
if (typeof value === 'string') {
try {
return JSON.parse(value);
} catch {
return { errorParsingJson: true, props: find(testSubject).props() };
}
}
return value;
};

return {
selectTab,
addField,
updateJsonEditor,
getJsonEditorValue,
};
};

export const setup = async (props: any = { onUpdate() {} }): Promise<MappingsEditorTestBed> => {
const testBed = await registerTestBed<TestSubjects>(MappingsEditor, {
memoryRouter: {
wrapComponent: false,
},
defaultProps: props,
})();

return {
...testBed,
actions: createActions(testBed),
};
};

export type MappingsEditorTestBed = TestBed<TestSubjects> & {
actions: ReturnType<typeof createActions>;
};

export type TestSubjects =
| 'formTab'
| 'mappingsEditor'
| 'fieldsListItem'
| 'fieldName'
| 'mappingTypesDetectedCallout'
| 'documentFields'
| 'dynamicTemplates'
| 'advancedConfiguration'
| 'advancedConfiguration.numericDetection'
| 'advancedConfiguration.numericDetection.input'
| 'advancedConfiguration.dynamicMappingsToggle'
| 'advancedConfiguration.dynamicMappingsToggle.input'
| 'dynamicTemplatesEditor'
| 'nameParameterInput'
| 'createFieldForm.fieldType'
| 'createFieldForm.addButton';
Loading

0 comments on commit 80e0b67

Please sign in to comment.