From 1318726b8b2d39bc08132cca006587dfb41b575f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 17 Apr 2020 12:09:50 +0200 Subject: [PATCH 01/34] Test props.onUpdate() to make sure it forwards data to consumer --- .../mappings_editor.test.tsx | 128 +++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 0cf5bf3f4453f..9d2caf699a7b8 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -101,7 +101,6 @@ describe('', () => { // Update the dynamic templates editor value const updatedValueTemplates = [{ after: 'bar' }]; - await act(async () => { await updateJsonEditor('dynamicTemplatesEditor', updatedValueTemplates); await nextTick(); @@ -174,4 +173,131 @@ describe('', () => { expect(isNumericDetectionVisible).toBe(false); }); }); + + describe('component props', () => { + const defaultMappings: any = { + dynamic: true, + numeric_detection: true, + date_detection: true, + properties: {}, + dynamic_templates: [], + _source: { + enabled: true, + includes: [], + excludes: [], + }, + _meta: {}, + _routing: { + required: true, + }, + }; + const onUpdateHandler = jest.fn(); + + const getDataForwarded = async () => { + const mockCalls = onUpdateHandler.mock.calls; + if (mockCalls.length === 0) { + throw new Error( + `Can't access data forwarded as the onUpdate() prop handler hasn't been called.` + ); + } + + const [arg] = mockCalls[mockCalls.length - 1]; + const { isValid, validate, getData } = arg; + + let isMappingsValid: boolean = false; + let data: any; + + await act(async () => { + isMappingsValid = isValid === undefined ? await validate() : isValid; + data = getData(isMappingsValid); + }); + + return { + isValid: isMappingsValid, + data, + }; + }; + + const expectDataUpdated = async (expected: any) => { + const { data } = await getDataForwarded(); + expect(data).toEqual(expected); + }; + + let testBed: MappingsEditorTestBed; + + beforeEach(async () => { + testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + }); + + afterEach(() => { + onUpdateHandler.mockReset(); + }); + + test('onUpdate() => should forward the changes to the consumer component', async () => { + let updatedMappings = { ...defaultMappings }; + + const { + actions: { addField, selectTab, updateJsonEditor }, + component, + form, + } = testBed; + + /** + * Mapped fields + */ + const newField = { name: getRandomString(), type: 'text' }; + updatedMappings = { + ...updatedMappings, + properties: { [newField.name]: { type: 'text' } }, + }; + + await act(async () => { + await addField(newField.name, newField.type); + }); + await expectDataUpdated(updatedMappings); + + /** + * Dynamic templates + */ + await act(async () => { + await selectTab('templates'); + }); + + const updatedTemplatesValue = [{ someTemplateProp: 'updated' }]; + updatedMappings = { + ...updatedMappings, + dynamic_templates: updatedTemplatesValue, + }; + + await act(async () => { + await updateJsonEditor('dynamicTemplatesEditor', updatedTemplatesValue); + await nextTick(); + component.update(); + }); + await expectDataUpdated(updatedMappings); + + /** + * Advanced settings + */ + await act(async () => { + await selectTab('advanced'); + }); + + // Disbable dynamic mappings + await act(async () => { + form.toggleEuiSwitch('advancedConfiguration.dynamicMappingsToggle.input'); + }); + + // When we disable dynamic mappings, we set it to "strict" and remove date and numeric detections + updatedMappings = { + ...updatedMappings, + dynamic: 'strict', + }; + delete updatedMappings.date_detection; + delete updatedMappings.dynamic_date_formats; + delete updatedMappings.numeric_detection; + + await expectDataUpdated(updatedMappings); + }); + }); }); From bf6be1edb9cfd940af0c9fa3f30ebbc7d8743c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 17 Apr 2020 14:42:55 +0200 Subject: [PATCH 02/34] Test props.defaultValue to make sure the editor is pre-populated correctly --- .../helpers/mappings_editor.helpers.tsx | 24 ++- .../mappings_editor.test.tsx | 163 +++++++++++++----- .../meta_field_section/meta_field_section.tsx | 1 + .../configuration_form/routing_section.tsx | 6 +- .../source_field_section.tsx | 6 +- 5 files changed, 152 insertions(+), 48 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index c8c8ef8bfe9b3..e5974485f2108 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -14,6 +14,7 @@ jest.mock('@elastic/eui', () => ({ EuiComboBox: (props: any) => ( { props.onChange([syntheticEvent['0']]); }} @@ -32,11 +33,16 @@ jest.mock('@elastic/eui', () => ({ })); const createActions = (testBed: TestBed) => { - const { find, waitFor, form, component } = testBed; + const { find, exists, waitFor, form, component } = testBed; const addField = async (name: string, type: string) => { const currentCount = find('fieldsListItem').length; + if (!exists('createFieldForm')) { + find('addFieldButton').simulate('click'); + await waitFor('createFieldForm'); + } + form.setInputValue('nameParameterInput', name); find('createFieldForm.fieldType').simulate('change', [ { @@ -87,11 +93,20 @@ const createActions = (testBed: TestBed) => { return value; }; + const getComboBoxValue = (testSubject: TestSubjects) => { + const value = find(testSubject).props()['data-currentvalue']; + if (value === undefined) { + return []; + } + return value.map(({ label }: any) => label); + }; + return { selectTab, addField, updateJsonEditor, getJsonEditorValue, + getComboBoxValue, }; }; @@ -117,6 +132,7 @@ export type TestSubjects = | 'formTab' | 'mappingsEditor' | 'fieldsListItem' + | 'fieldsListItem.fieldName' | 'fieldName' | 'mappingTypesDetectedCallout' | 'documentFields' @@ -126,7 +142,13 @@ export type TestSubjects = | 'advancedConfiguration.numericDetection.input' | 'advancedConfiguration.dynamicMappingsToggle' | 'advancedConfiguration.dynamicMappingsToggle.input' + | 'advancedConfiguration.metaField' + | 'advancedConfiguration.routingRequiredToggle.input' + | 'sourceField.includesField' + | 'sourceField.excludesField' | 'dynamicTemplatesEditor' | 'nameParameterInput' + | 'addFieldButton' + | 'createFieldForm' | 'createFieldForm.fieldType' | 'createFieldForm.addButton'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 9d2caf699a7b8..5ef2a5f173526 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -8,9 +8,44 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from './helpers'; const { setup } = componentHelpers.mappingsEditor; -const mockOnUpdate = () => undefined; +const onUpdateHandler = jest.fn(); + +const getDataForwarded = async () => { + const mockCalls = onUpdateHandler.mock.calls; + + if (mockCalls.length === 0) { + throw new Error( + `Can't access data forwarded as the onUpdate() prop handler hasn't been called.` + ); + } + + const [arg] = mockCalls[mockCalls.length - 1]; + const { isValid, validate, getData } = arg; + + let isMappingsValid: boolean = false; + let data: any; + + await act(async () => { + isMappingsValid = isValid === undefined ? await validate() : isValid; + data = getData(isMappingsValid); + }); + + return { + isValid: isMappingsValid, + data, + }; +}; + +const expectDataUpdated = async (expected: any) => { + const { data } = await getDataForwarded(); + expect(data).toEqual(expected); +}; describe('', () => { + afterEach(() => { + onUpdateHandler.mockReset(); + }); + describe('multiple mappings detection', () => { test('should show a warning when multiple mappings are detected', async () => { const defaultValue = { @@ -29,7 +64,7 @@ describe('', () => { }, }, }; - const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue }); + const testBed = await setup({ onUpdate: onUpdateHandler, defaultValue }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); @@ -45,7 +80,7 @@ describe('', () => { }, }, }; - const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue }); + const testBed = await setup({ onUpdate: onUpdateHandler, defaultValue }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); @@ -175,65 +210,102 @@ describe('', () => { }); describe('component props', () => { + /** + * Note: the "indexSettings" prop will be tested along with the "analyzer" parameter on a field, + * as it is the only place where it is consumed by the mappings editor. + */ const defaultMappings: any = { dynamic: true, - numeric_detection: true, + numeric_detection: false, date_detection: true, - properties: {}, - dynamic_templates: [], + properties: { + title: { type: 'text' }, + address: { + type: 'object', + properties: { + street: { type: 'text' }, + city: { type: 'text' }, + }, + }, + }, + dynamic_templates: [{ initial: 'value' }], _source: { enabled: true, - includes: [], - excludes: [], + includes: ['field1', 'field2'], + excludes: ['field3'], + }, + _meta: { + some: 'metaData', }, - _meta: {}, _routing: { - required: true, + required: false, }, }; - const onUpdateHandler = jest.fn(); - const getDataForwarded = async () => { - const mockCalls = onUpdateHandler.mock.calls; - if (mockCalls.length === 0) { - throw new Error( - `Can't access data forwarded as the onUpdate() prop handler hasn't been called.` - ); - } + let testBed: MappingsEditorTestBed; + + beforeEach(async () => { + testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + }); + + test('props.defaultValue => should prepopulate the editor data', async () => { + const { + actions: { selectTab, getJsonEditorValue, getComboBoxValue }, + find, + } = testBed; - const [arg] = mockCalls[mockCalls.length - 1]; - const { isValid, validate, getData } = arg; + /** + * Mapped fields + */ + expect(find('fieldsListItem').length).toEqual(Object.keys(defaultMappings.properties).length); - let isMappingsValid: boolean = false; - let data: any; + const fields = find('fieldsListItem.fieldName').map(item => item.text()); + expect(fields).toEqual(Object.keys(defaultMappings.properties).sort()); + /** + * Dynamic templates + */ await act(async () => { - isMappingsValid = isValid === undefined ? await validate() : isValid; - data = getData(isMappingsValid); + await selectTab('templates'); }); - return { - isValid: isMappingsValid, - data, - }; - }; + const templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); + expect(templatesValue).toEqual(defaultMappings.dynamic_templates); - const expectDataUpdated = async (expected: any) => { - const { data } = await getDataForwarded(); - expect(data).toEqual(expected); - }; + /** + * Advanced settings + */ + await act(async () => { + await selectTab('advanced'); + }); - let testBed: MappingsEditorTestBed; + const isDynamicMappingsEnabled = find( + 'advancedConfiguration.dynamicMappingsToggle.input' + ).props()['aria-checked']; + expect(isDynamicMappingsEnabled).toBe(defaultMappings.dynamic); - beforeEach(async () => { - testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); - }); + const isNumericDetectionEnabled = find( + 'advancedConfiguration.numericDetection.input' + ).props()['aria-checked']; + expect(isNumericDetectionEnabled).toBe(defaultMappings.numeric_detection); - afterEach(() => { - onUpdateHandler.mockReset(); + expect(getComboBoxValue('sourceField.includesField')).toEqual( + defaultMappings._source.includes + ); + expect(getComboBoxValue('sourceField.excludesField')).toEqual( + defaultMappings._source.excludes + ); + + const metaFieldValue = getJsonEditorValue('advancedConfiguration.metaField'); + expect(metaFieldValue).toEqual(defaultMappings._meta); + + const isRoutingRequired = find('advancedConfiguration.routingRequiredToggle.input').props()[ + 'aria-checked' + ]; + expect(isRoutingRequired).toBe(defaultMappings._routing.required); }); - test('onUpdate() => should forward the changes to the consumer component', async () => { + test('props.onUpdate() => should forward the changes to the consumer component', async () => { let updatedMappings = { ...defaultMappings }; const { @@ -248,7 +320,10 @@ describe('', () => { const newField = { name: getRandomString(), type: 'text' }; updatedMappings = { ...updatedMappings, - properties: { [newField.name]: { type: 'text' } }, + properties: { + ...updatedMappings.properties, + [newField.name]: { type: 'text' }, + }, }; await act(async () => { @@ -292,10 +367,10 @@ describe('', () => { updatedMappings = { ...updatedMappings, dynamic: 'strict', + date_detection: undefined, + dynamic_date_formats: undefined, + numeric_detection: undefined, }; - delete updatedMappings.date_detection; - delete updatedMappings.dynamic_date_formats; - delete updatedMappings.numeric_detection; await expectDataUpdated(updatedMappings); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/meta_field_section/meta_field_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/meta_field_section/meta_field_section.tsx index 68b76a1203ad5..7185016029e00 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/meta_field_section/meta_field_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/meta_field_section/meta_field_section.tsx @@ -46,6 +46,7 @@ export const MetaFieldSection = () => ( 'aria-label': i18n.translate('xpack.idxMgmt.mappingsEditor.metaFieldEditorAriaLabel', { defaultMessage: '_meta field data editor', }), + 'data-test-subj': 'metaField', }, }} /> diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/routing_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/routing_section.tsx index 7f434d6f834b2..f06b292bc33c8 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/routing_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/routing_section.tsx @@ -35,7 +35,11 @@ export const RoutingSection = () => { /> } > - + ); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx index f79741d9a1a9f..4278598dfc7c1 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx @@ -65,7 +65,7 @@ export const SourceFieldSection = () => { ); const renderFormFields = () => ( - <> +
{({ label, helpText, value, setValue }) => ( @@ -89,6 +89,7 @@ export const SourceFieldSection = () => { setValue([...(value as ComboBoxOption[]), newOption]); }} fullWidth + data-test-subj="includesField" /> )} @@ -119,11 +120,12 @@ export const SourceFieldSection = () => { setValue([...(value as ComboBoxOption[]), newOption]); }} fullWidth + data-test-subj="excludesField" /> )} - +
); return ( From 50a6f922e14bfdea5d27b5da9e58192a87b96df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 17 Apr 2020 16:08:53 +0200 Subject: [PATCH 03/34] Fix test and add missing "key" element on UseField components --- .../__jest__/client_integration/mappings_editor.test.tsx | 2 +- .../dynamic_mapping_section/dynamic_mapping_section.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 5ef2a5f173526..a2f52e1c64125 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -366,7 +366,7 @@ describe('', () => { // When we disable dynamic mappings, we set it to "strict" and remove date and numeric detections updatedMappings = { ...updatedMappings, - dynamic: 'strict', + dynamic: false, date_detection: undefined, dynamic_date_formats: undefined, numeric_detection: undefined, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx index cb9b464d270ce..c1a2b195a3f57 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx @@ -67,6 +67,7 @@ export const DynamicMappingSection = () => ( return ( <> @@ -87,6 +88,7 @@ export const DynamicMappingSection = () => ( } else { return ( From 60c87ee4acda397966cb6e445a4c96be0b2943c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Tue, 21 Apr 2020 16:57:20 +0200 Subject: [PATCH 04/34] Move expectDataUpdatedFactory to helpers and add editField helper --- .../client_integration/helpers/index.ts | 8 +- .../helpers/mappings_editor.helpers.tsx | 94 ++++++++++++++++++- .../mappings_editor.test.tsx | 36 +------ 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts index fa6bee56349e9..f481c4a444a96 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts @@ -3,7 +3,11 @@ * 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, MappingsEditorTestBed } from './mappings_editor.helpers'; +import { + setup as mappingsEditorSetup, + MappingsEditorTestBed, + expectDataUpdatedFactory, +} from './mappings_editor.helpers'; export { nextTick, @@ -13,7 +17,7 @@ export { } from '../../../../../../../../../test_utils'; export const componentHelpers = { - mappingsEditor: { setup: mappingsEditorSetup }, + mappingsEditor: { setup: mappingsEditorSetup, expectDataUpdatedFactory }, }; export { MappingsEditorTestBed }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index e5974485f2108..3b60b19515afb 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { act } from 'react-dom/test-utils'; + import { registerTestBed, TestBed, nextTick } from '../../../../../../../../../test_utils'; import { MappingsEditor } from '../../../mappings_editor'; @@ -35,6 +37,44 @@ jest.mock('@elastic/eui', () => ({ const createActions = (testBed: TestBed) => { const { find, exists, waitFor, form, component } = testBed; + const expandAllChildrenAt = async (path: string) => { + const pathToArray = path.split('.'); + + const proceed = async (index = 0): Promise => { + const pathToField = pathToArray.slice(0, index + 1); + const testSubjectField = `${pathToField.join('')}Field`; + + const expandButton = find(`${testSubjectField}.toggleExpandButton` as TestSubjects); + + if (expandButton.length === 0) { + return; + } + const isExpanded = (expandButton.props()['aria-label'] as string).includes('Collapse'); + + if (!isExpanded) { + expandButton.simulate('click'); + } + + // Wait for the children FieldList to be there + await waitFor(`${testSubjectField}.fieldsList` as TestSubjects); + + if (index < pathToArray.length - 1) { + return proceed(++index); + } + }; + + return proceed(); + }; + + // Get a nested field in the rendered DOM tree + const getFieldAt = async (path: string) => { + // First make sure all the parents fields are expanded and all present in the DOM + await expandAllChildrenAt(path); + + const testSubjectField = `${path.split('.').join('')}Field`; + return find(testSubjectField as TestSubjects); + }; + const addField = async (name: string, type: string) => { const currentCount = find('fieldsListItem').length; @@ -60,6 +100,13 @@ const createActions = (testBed: TestBed) => { await waitFor('fieldsListItem', currentCount + 1); }; + const editField = async (path: string) => { + const field = await getFieldAt(path); + find('editFieldButton', field).simulate('click'); + // Wait until the details flyout is open + await waitFor('mappingsEditorFieldEdit'); + }; + const selectTab = async (tab: 'fields' | 'templates' | 'advanced') => { const index = ['fields', 'templates', 'advanced'].indexOf(tab); const tabIdToContentMap: { [key: string]: TestSubjects } = { @@ -103,7 +150,9 @@ const createActions = (testBed: TestBed) => { return { selectTab, + getFieldAt, addField, + editField, updateJsonEditor, getJsonEditorValue, getComboBoxValue, @@ -124,6 +173,45 @@ export const setup = async (props: any = { onUpdate() {} }): Promise) => { + /** + * Helper to access the latest data sent to the onUpdate handler back to the consumer of the . + * Read the latest call with its argument passed and build the mappings object from it. + */ + const getDataForwarded = async () => { + const mockCalls = onUpdateHandler.mock.calls; + + if (mockCalls.length === 0) { + throw new Error( + `Can't access data forwarded as the onUpdate() prop handler hasn't been called.` + ); + } + + const [arg] = mockCalls[mockCalls.length - 1]; + const { isValid, validate, getData } = arg; + + let isMappingsValid: boolean = false; + let data: any; + + await act(async () => { + isMappingsValid = isValid === undefined ? await validate() : isValid; + data = getData(isMappingsValid); + }); + + return { + isValid: isMappingsValid, + data, + }; + }; + + const expectDataUpdated = async (expected: any) => { + const { data } = await getDataForwarded(); + expect(data).toEqual(expected); + }; + + return expectDataUpdated; +}; + export type MappingsEditorTestBed = TestBed & { actions: ReturnType; }; @@ -131,6 +219,7 @@ export type MappingsEditorTestBed = TestBed & { export type TestSubjects = | 'formTab' | 'mappingsEditor' + | 'fieldsList' | 'fieldsListItem' | 'fieldsListItem.fieldName' | 'fieldName' @@ -149,6 +238,9 @@ export type TestSubjects = | 'dynamicTemplatesEditor' | 'nameParameterInput' | 'addFieldButton' + | 'editFieldButton' + | 'toggleExpandButton' | 'createFieldForm' | 'createFieldForm.fieldType' - | 'createFieldForm.addButton'; + | 'createFieldForm.addButton' + | 'mappingsEditorFieldEdit'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index a2f52e1c64125..5ea8dbdbb4ca6 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -7,39 +7,9 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from './helpers'; -const { setup } = componentHelpers.mappingsEditor; +const { setup, expectDataUpdatedFactory } = componentHelpers.mappingsEditor; const onUpdateHandler = jest.fn(); - -const getDataForwarded = async () => { - const mockCalls = onUpdateHandler.mock.calls; - - if (mockCalls.length === 0) { - throw new Error( - `Can't access data forwarded as the onUpdate() prop handler hasn't been called.` - ); - } - - const [arg] = mockCalls[mockCalls.length - 1]; - const { isValid, validate, getData } = arg; - - let isMappingsValid: boolean = false; - let data: any; - - await act(async () => { - isMappingsValid = isValid === undefined ? await validate() : isValid; - data = getData(isMappingsValid); - }); - - return { - isValid: isMappingsValid, - data, - }; -}; - -const expectDataUpdated = async (expected: any) => { - const { data } = await getDataForwarded(); - expect(data).toEqual(expected); -}; +const expectDataUpdated = expectDataUpdatedFactory(onUpdateHandler); describe('', () => { afterEach(() => { @@ -363,7 +333,7 @@ describe('', () => { form.toggleEuiSwitch('advancedConfiguration.dynamicMappingsToggle.input'); }); - // When we disable dynamic mappings, we set it to "strict" and remove date and numeric detections + // When we disable dynamic mappings, we set it to "false" and remove date and numeric detections updatedMappings = { ...updatedMappings, dynamic: false, From 841b8d068a8218a155642bdcee259f73f9510267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 22 Apr 2020 12:18:56 +0200 Subject: [PATCH 05/34] [testbed] Add "waitForFn" helper to wait for a predicate function --- x-pack/test_utils/testbed/testbed.ts | 26 ++++++++++++-------------- x-pack/test_utils/testbed/types.ts | 1 + 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/x-pack/test_utils/testbed/testbed.ts b/x-pack/test_utils/testbed/testbed.ts index 9bf07f953595c..47d3dfc2fa8ba 100644 --- a/x-pack/test_utils/testbed/testbed.ts +++ b/x-pack/test_utils/testbed/testbed.ts @@ -138,33 +138,23 @@ export const registerTestBed = ( }); }; - const waitFor: TestBed['waitFor'] = async (testSubject: T, count = 1) => { + const waitForFn: TestBed['waitForFn'] = async (predicate, errMessage) => { const triggeredAt = Date.now(); - /** - * The way jest run tests in parallel + the not deterministic DOM update from React "hooks" - * add flakiness to the tests. This is especially true for component integration tests that - * make many update to the DOM. - * - * For this reason, when we _know_ that an element should be there after we updated some state, - * we will give it 30 seconds to appear in the DOM, checking every 100 ms for its presence. - */ const MAX_WAIT_TIME = 30000; const WAIT_INTERVAL = 100; const process = async (): Promise => { - const elemFound = exists(testSubject, count); + const isOK = await predicate(); - if (elemFound) { + if (isOK) { // Great! nothing else to do here. return; } const timeElapsed = Date.now() - triggeredAt; if (timeElapsed > MAX_WAIT_TIME) { - throw new Error( - `I waited patiently for the "${testSubject}" test subject to appear with no luck. It is nowhere to be found!` - ); + throw new Error(errMessage); } return new Promise(resolve => setTimeout(resolve, WAIT_INTERVAL)).then(() => { @@ -176,6 +166,13 @@ export const registerTestBed = ( return process(); }; + const waitFor: TestBed['waitFor'] = async (testSubject: T, count = 1) => { + return waitForFn( + () => Promise.resolve(exists(testSubject, count)), + `I waited patiently for the "${testSubject}" test subject to appear with no luck. It is nowhere to be found!` + ); + }; + /** * ---------------------------------------------------------------- * Forms @@ -293,6 +290,7 @@ export const registerTestBed = ( find, setProps, waitFor, + waitForFn, table: { getMetaData, }, diff --git a/x-pack/test_utils/testbed/types.ts b/x-pack/test_utils/testbed/types.ts index f3704bb463ecf..011f8f34120ba 100644 --- a/x-pack/test_utils/testbed/types.ts +++ b/x-pack/test_utils/testbed/types.ts @@ -61,6 +61,7 @@ export interface TestBed { * and we need to wait for the data to be fetched (and bypass any "loading" state). */ waitFor: (testSubject: T, count?: number) => Promise; + waitForFn: (predicate: () => Promise, errMessage: string) => Promise; form: { /** * Set the value of a form text input. From 838548480678f45b16db0f128b5468ba3275dce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 22 Apr 2020 12:20:53 +0200 Subject: [PATCH 06/34] Add test of edit field (flyout) initial view --- .../datatypes/text_datatype.test.tsx | 81 +++++++++++++++++++ .../helpers/mappings_editor.helpers.tsx | 36 ++++++++- .../mappings_editor.test.tsx | 32 ++++---- .../field_parameters/index_parameter.tsx | 1 + .../advanced_parameters_section.tsx | 4 +- .../fields/edit_field/edit_field.tsx | 5 +- .../fields/edit_field/edit_field_form_row.tsx | 18 ++++- .../document_fields/fields/fields_list.tsx | 2 +- .../fields/fields_list_item.tsx | 4 +- 9 files changed, 153 insertions(+), 30 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx new file mode 100644 index 0000000000000..b41f3711148b2 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -0,0 +1,81 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from '../helpers'; +import { getFieldConfig } from '../../../lib'; + +const { setup, expectDataUpdatedFactory } = componentHelpers.mappingsEditor; +const onUpdateHandler = jest.fn(); +const expectDataUpdated = expectDataUpdatedFactory(onUpdateHandler); + +const allProps = { + type: 'text', + index: true, + analyzer: 'standard', + search_quote_analyzer: 'simple', + eager_global_ordinals: false, + index_phrases: false, + norms: true, + fielddata: false, + store: false, + index_options: 'positions', + search_analyzer: 'whitespace', +}; + +describe('text datatype', () => { + const defaultMappings = { + properties: { + user: { + properties: { + address: { + properties: { + street: { type: 'text' }, + }, + }, + }, + }, + }, + }; + let testBed: MappingsEditorTestBed; + + beforeEach(async () => { + testBed = await setup({ defaultValue: defaultMappings, onUpdate() {} }); + + // We edit the field (by opening the flyout) + await act(async () => { + await testBed.actions.startEditField('user.address.street'); + }); + }); + + afterEach(() => { + onUpdateHandler.mockReset(); + }); + + test('flyout details initial view', async () => { + const { + find, + actions: { getToggleValue, showAdvancedSettings }, + } = testBed; + const fieldPath = ['user', 'address', 'street']; + const fieldName = fieldPath[fieldPath.length - 1]; + + // It should have the correct title + expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field '${fieldName}'`); + + // It should have the correct field path + expect(find('mappingsEditorFieldEdit.fieldPath').text()).toEqual(fieldPath.join(' > ')); + + // It should have searchable ("index" param) active by default + const indexFieldConfig = getFieldConfig('index'); + expect(getToggleValue('indexParameter.formRowToggle')).toBe(indexFieldConfig.defaultValue); + + // The advanced settings should be hidden initially + expect(find('mappingsEditorFieldEdit.advancedSettings').props().style.display).toEqual('none'); + + await showAdvancedSettings(); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 3b60b19515afb..5b77811ec8329 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -35,7 +35,7 @@ jest.mock('@elastic/eui', () => ({ })); const createActions = (testBed: TestBed) => { - const { find, exists, waitFor, form, component } = testBed; + const { find, exists, waitFor, waitForFn, form, component } = testBed; const expandAllChildrenAt = async (path: string) => { const pathToArray = path.split('.'); @@ -100,13 +100,30 @@ const createActions = (testBed: TestBed) => { await waitFor('fieldsListItem', currentCount + 1); }; - const editField = async (path: string) => { + const startEditField = async (path: string) => { const field = await getFieldAt(path); find('editFieldButton', field).simulate('click'); // Wait until the details flyout is open await waitFor('mappingsEditorFieldEdit'); }; + const showAdvancedSettings = async () => { + const checkIsVisible = async () => + find('mappingsEditorFieldEdit.advancedSettings').props().style.display === 'block'; + + if (await checkIsVisible()) { + // Already opened, nothing else to do + return; + } + + find('mappingsEditorFieldEdit.toggleAdvancedSetting').simulate('click'); + + await waitForFn( + checkIsVisible, + 'Error waiting for the advanced settings CSS style.display to be "block"' + ); + }; + const selectTab = async (tab: 'fields' | 'templates' | 'advanced') => { const index = ['fields', 'templates', 'advanced'].indexOf(tab); const tabIdToContentMap: { [key: string]: TestSubjects } = { @@ -148,14 +165,19 @@ const createActions = (testBed: TestBed) => { return value.map(({ label }: any) => label); }; + const getToggleValue = (testSubject: TestSubjects): boolean => + find(testSubject).props()['aria-checked']; + return { selectTab, getFieldAt, addField, - editField, + startEditField, + showAdvancedSettings, updateJsonEditor, getJsonEditorValue, getComboBoxValue, + getToggleValue, }; }; @@ -243,4 +265,10 @@ export type TestSubjects = | 'createFieldForm' | 'createFieldForm.fieldType' | 'createFieldForm.addButton' - | 'mappingsEditorFieldEdit'; + | 'mappingsEditorFieldEdit' + | 'mappingsEditorFieldEdit.flyoutTitle' + | 'mappingsEditorFieldEdit.documentationLink' + | 'mappingsEditorFieldEdit.fieldPath' + | 'mappingsEditorFieldEdit.advancedSettings' + | 'mappingsEditorFieldEdit.toggleAdvancedSetting' + | 'indexParameter.formRowToggle'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 5ea8dbdbb4ca6..b2f252efffaf4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -72,7 +72,7 @@ describe('', () => { test('should keep the changes when switching tabs', async () => { const { - actions: { addField, selectTab, updateJsonEditor, getJsonEditorValue }, + actions: { addField, selectTab, updateJsonEditor, getJsonEditorValue, getToggleValue }, component, find, exists, @@ -122,9 +122,9 @@ describe('', () => { await selectTab('advanced'); }); - let isDynamicMappingsEnabled = find( + let isDynamicMappingsEnabled = getToggleValue( 'advancedConfiguration.dynamicMappingsToggle.input' - ).props()['aria-checked']; + ); expect(isDynamicMappingsEnabled).toBe(true); let isNumericDetectionVisible = exists('advancedConfiguration.numericDetection'); @@ -138,9 +138,9 @@ describe('', () => { await nextTick(); }); - isDynamicMappingsEnabled = find('advancedConfiguration.dynamicMappingsToggle.input').props()[ - 'aria-checked' - ]; + isDynamicMappingsEnabled = getToggleValue( + 'advancedConfiguration.dynamicMappingsToggle.input' + ); expect(isDynamicMappingsEnabled).toBe(false); isNumericDetectionVisible = exists('advancedConfiguration.numericDetection'); @@ -170,9 +170,9 @@ describe('', () => { await selectTab('advanced'); }); - isDynamicMappingsEnabled = find('advancedConfiguration.dynamicMappingsToggle.input').props()[ - 'aria-checked' - ]; + isDynamicMappingsEnabled = getToggleValue( + 'advancedConfiguration.dynamicMappingsToggle.input' + ); expect(isDynamicMappingsEnabled).toBe(false); isNumericDetectionVisible = exists('advancedConfiguration.numericDetection'); expect(isNumericDetectionVisible).toBe(false); @@ -220,7 +220,7 @@ describe('', () => { test('props.defaultValue => should prepopulate the editor data', async () => { const { - actions: { selectTab, getJsonEditorValue, getComboBoxValue }, + actions: { selectTab, getJsonEditorValue, getComboBoxValue, getToggleValue }, find, } = testBed; @@ -249,14 +249,14 @@ describe('', () => { await selectTab('advanced'); }); - const isDynamicMappingsEnabled = find( + const isDynamicMappingsEnabled = getToggleValue( 'advancedConfiguration.dynamicMappingsToggle.input' - ).props()['aria-checked']; + ); expect(isDynamicMappingsEnabled).toBe(defaultMappings.dynamic); - const isNumericDetectionEnabled = find( + const isNumericDetectionEnabled = getToggleValue( 'advancedConfiguration.numericDetection.input' - ).props()['aria-checked']; + ); expect(isNumericDetectionEnabled).toBe(defaultMappings.numeric_detection); expect(getComboBoxValue('sourceField.includesField')).toEqual( @@ -269,9 +269,7 @@ describe('', () => { const metaFieldValue = getJsonEditorValue('advancedConfiguration.metaField'); expect(metaFieldValue).toEqual(defaultMappings._meta); - const isRoutingRequired = find('advancedConfiguration.routingRequiredToggle.input').props()[ - 'aria-checked' - ]; + const isRoutingRequired = getToggleValue('advancedConfiguration.routingRequiredToggle.input'); expect(isRoutingRequired).toBe(defaultMappings._routing.required); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx index fec8e49a1991c..3e91e97eef618 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index_parameter.tsx @@ -39,6 +39,7 @@ export const IndexParameter = ({ href: documentationService.getIndexLink(), }} formFieldPath="index" + data-test-subj="indexParameter" > {/* index_options */} {hasIndexOptions ? ( diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/advanced_parameters_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/advanced_parameters_section.tsx index 03c774227924e..2046675881c29 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/advanced_parameters_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/advanced_parameters_section.tsx @@ -23,7 +23,7 @@ export const AdvancedParametersSection = ({ children }: Props) => {
- + {isVisible ? i18n.translate('xpack.idxMgmt.mappingsEditor.advancedSettings.hideButtonLabel', { defaultMessage: 'Hide advanced settings', @@ -33,7 +33,7 @@ export const AdvancedParametersSection = ({ children }: Props) => { })} -
+
{/* We ned to wrap the children inside a "div" to have our css :first-child rule */}
{children}
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx index 489424a07e04d..854270f313e59 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field.tsx @@ -96,7 +96,7 @@ export const EditField = React.memo(({ form, field, allFields, exitEdit }: Props
{/* Title */} -

+

{isMultiField ? i18n.translate( 'xpack.idxMgmt.mappingsEditor.editMultiFieldTitle', @@ -127,6 +127,7 @@ export const EditField = React.memo(({ form, field, allFields, exitEdit }: Props href={linkDocumentation} target="_blank" iconType="help" + data-test-subj="documentationLink" > {i18n.translate( 'xpack.idxMgmt.mappingsEditor.editField.typeDocumentation', @@ -146,7 +147,7 @@ export const EditField = React.memo(({ form, field, allFields, exitEdit }: Props {/* Field path */} - + {field.path.join(' > ')} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx index 97a7d205c1355..1c079c8d5cf87 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_form_row.tsx @@ -42,6 +42,7 @@ interface Props { children?: React.ReactNode | ChildrenFunc; withToggle?: boolean; configPath?: ParameterName; + 'data-test-subj'?: string; } export const EditFieldFormRow = React.memo( @@ -54,6 +55,7 @@ export const EditFieldFormRow = React.memo( children, withToggle = true, configPath, + 'data-test-subj': dataTestSubj, }: Props) => { const form = useFormContext(); @@ -87,7 +89,7 @@ export const EditFieldFormRow = React.memo( label={title} checked={isContentVisible} onChange={onToggle} - data-test-subj="input" + data-test-subj="formRowToggle" showLabel={false} /> ) : ( @@ -99,7 +101,17 @@ export const EditFieldFormRow = React.memo( }} > {field => { - return ; + return ( + + ); }} ); @@ -165,7 +177,7 @@ export const EditFieldFormRow = React.memo( ); return ( - + {toggle} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx index 6df86d561a532..c0d922e0d1d37 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx @@ -18,7 +18,7 @@ export const FieldsList = React.memo(function FieldsListComponent({ fields, tree return null; } return ( -
    +
      {fields.map((field, index) => (
      Date: Wed, 22 Apr 2020 14:07:28 +0200 Subject: [PATCH 07/34] [mappings editor] Strip undefined value from object returned --- .../configuration_form/configuration_form.tsx | 5 +-- .../components/mappings_editor/lib/utils.ts | 33 +++++++++++++++++++ .../mappings_editor/mappings_state.tsx | 8 +++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index 6b33d4450c3ae..f307f0b378352 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -7,6 +7,7 @@ import React, { useEffect, useRef } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { useForm, Form, SerializerFunc } from '../../shared_imports'; +import { GenericObject } from '../../types'; import { Types, useDispatch } from '../../mappings_state'; import { DynamicMappingSection } from './dynamic_mapping_section'; import { SourceFieldSection } from './source_field_section'; @@ -20,7 +21,7 @@ interface Props { defaultValue?: MappingsConfiguration; } -const stringifyJson = (json: { [key: string]: any }) => +const stringifyJson = (json: GenericObject) => Object.keys(json).length ? JSON.stringify(json, null, 2) : '{\n\n}'; const formSerializer: SerializerFunc = formData => { @@ -57,7 +58,7 @@ const formSerializer: SerializerFunc = formData => { }; }; -const formDeserializer = (formData: { [key: string]: any }) => { +const formDeserializer = (formData: GenericObject) => { const { dynamic, numeric_detection, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index cece26618ced8..16f37d3bb205a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -17,6 +17,7 @@ import { ChildFieldName, ParameterName, ComboBoxOption, + GenericObject, } from '../types'; import { @@ -508,3 +509,35 @@ export const isStateValid = (state: State): boolean | undefined => return isValid && value.isValid; }, true as undefined | boolean); + +/** + * This helper removes all the keys on an object with an "undefined" value. + * To avoid sending updates from the mappings editor with this type of object: + * + *``` + * { + * "dyamic": undefined, + * "date_detection": undefined, + * "dynamic": undefined, + * "dynamic_date_formats": undefined, + * "dynamic_templates": undefined, + * "numeric_detection": undefined, + * "properties": { + * "title": { "type": "text" } + * } + * } + *``` + * + * @param obj The object to retrieve the undefined values from + * @param recursive A flag to strip recursively into children objects + */ +export const stripUndefinedValues = (obj: GenericObject, recursive = true): T => + Object.entries(obj).reduce((acc, [key, value]) => { + if (value === undefined) { + return acc; + } + if (recursive && value !== null && typeof value === 'object') { + return { ...acc, [key]: stripUndefinedValues(value, recursive) }; + } + return { ...acc, [key]: value }; + }, {} as T); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx index a9d26b953b96e..4a0d948e0e94b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx @@ -16,7 +16,7 @@ import { Dispatch, } from './reducer'; import { Field } from './types'; -import { normalize, deNormalize } from './lib'; +import { normalize, deNormalize, stripUndefinedValues } from './lib'; type Mappings = MappingsTemplates & MappingsConfiguration & { @@ -135,8 +135,10 @@ export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: P const templatesData = nextState.templates.data.format(); return { - ...configurationData, - ...templatesData, + ...stripUndefinedValues({ + ...configurationData, + ...templatesData, + }), properties: fields, }; }, From 5feedccce32c959c032bc451e56bb651f5279670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 22 Apr 2020 14:20:47 +0200 Subject: [PATCH 08/34] Test that default parameters values are added to the field when updated --- .../datatypes/text_datatype.test.tsx | 50 +++++++++++++++++-- .../helpers/mappings_editor.helpers.tsx | 11 ++++ .../mappings_editor.test.tsx | 2 +- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index b41f3711148b2..7ec154466dd23 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -43,7 +43,7 @@ describe('text datatype', () => { let testBed: MappingsEditorTestBed; beforeEach(async () => { - testBed = await setup({ defaultValue: defaultMappings, onUpdate() {} }); + testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); // We edit the field (by opening the flyout) await act(async () => { @@ -56,9 +56,27 @@ describe('text datatype', () => { }); test('flyout details initial view', async () => { + const updatedMappings = { + _meta: {}, // If undefined, an empty object is returned by the editor + _source: {}, // If undefined, an empty object is returned by the editor + ...defaultMappings, + properties: { + user: { + type: 'object', // As no type was defined, defaults to "object" type + properties: { + address: { + type: 'object', // As no type was defined, defaults to "object" type + properties: { + street: { type: 'text' }, + }, + }, + }, + }, + }, + }; const { find, - actions: { getToggleValue, showAdvancedSettings }, + actions: { getToggleValue, showAdvancedSettings, updateFieldAndCloseFlyout }, } = testBed; const fieldPath = ['user', 'address', 'street']; const fieldName = fieldPath[fieldPath.length - 1]; @@ -76,6 +94,32 @@ describe('text datatype', () => { // The advanced settings should be hidden initially expect(find('mappingsEditorFieldEdit.advancedSettings').props().style.display).toEqual('none'); - await showAdvancedSettings(); + await act(async () => { + await showAdvancedSettings(); + }); + + // TODO: find a way to automate the test that all expected fields are present + // and have their default value correctly set + + await expectDataUpdated(updatedMappings); + + await act(async () => { + await updateFieldAndCloseFlyout(); + }); + + const streetField = { + type: 'text', + // All the default parameters values have been added + eager_global_ordinals: false, + fielddata: false, + index: true, + index_options: 'positions', + index_phrases: false, + norms: true, + store: false, + }; + updatedMappings.properties.user.properties.address.properties.street = streetField; + + await expectDataUpdated(updatedMappings); }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 5b77811ec8329..b786c99331c12 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -107,6 +107,15 @@ const createActions = (testBed: TestBed) => { await waitFor('mappingsEditorFieldEdit'); }; + const updateFieldAndCloseFlyout = async () => { + find('mappingsEditorFieldEdit.editFieldUpdateButton').simulate('click'); + + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + }; + const showAdvancedSettings = async () => { const checkIsVisible = async () => find('mappingsEditorFieldEdit.advancedSettings').props().style.display === 'block'; @@ -173,6 +182,7 @@ const createActions = (testBed: TestBed) => { getFieldAt, addField, startEditField, + updateFieldAndCloseFlyout, showAdvancedSettings, updateJsonEditor, getJsonEditorValue, @@ -266,6 +276,7 @@ export type TestSubjects = | 'createFieldForm.fieldType' | 'createFieldForm.addButton' | 'mappingsEditorFieldEdit' + | 'mappingsEditorFieldEdit.editFieldUpdateButton' | 'mappingsEditorFieldEdit.flyoutTitle' | 'mappingsEditorFieldEdit.documentationLink' | 'mappingsEditorFieldEdit.fieldPath' diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index b2f252efffaf4..8febfd29592f4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -67,7 +67,7 @@ describe('', () => { let testBed: MappingsEditorTestBed; beforeEach(async () => { - testBed = await setup({ defaultValue: defaultMappings, onUpdate() {} }); + testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); }); test('should keep the changes when switching tabs', async () => { From 905bae7debd00ae33aae997801969c03c8f8d5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 22 Apr 2020 14:28:13 +0200 Subject: [PATCH 09/34] Remove unused --- .../datatypes/text_datatype.test.tsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 7ec154466dd23..955a4f51ddcb1 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -5,27 +5,13 @@ */ import { act } from 'react-dom/test-utils'; -import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from '../helpers'; +import { componentHelpers, MappingsEditorTestBed } from '../helpers'; import { getFieldConfig } from '../../../lib'; const { setup, expectDataUpdatedFactory } = componentHelpers.mappingsEditor; const onUpdateHandler = jest.fn(); const expectDataUpdated = expectDataUpdatedFactory(onUpdateHandler); -const allProps = { - type: 'text', - index: true, - analyzer: 'standard', - search_quote_analyzer: 'simple', - eager_global_ordinals: false, - index_phrases: false, - norms: true, - fielddata: false, - store: false, - index_options: 'positions', - search_analyzer: 'whitespace', -}; - describe('text datatype', () => { const defaultMappings = { properties: { From 91a3b7b9f53337230b18da6cb7520851683f7d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 22 Apr 2020 14:46:01 +0200 Subject: [PATCH 10/34] Fix strip undefined logic to support Array and Date instances --- .../mappings_editor/lib/utils.test.ts | 43 ++++++++++++++++++- .../components/mappings_editor/lib/utils.ts | 10 +++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts index 0431ea472643b..6d0ab871eac78 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts @@ -6,7 +6,7 @@ jest.mock('../constants', () => ({ MAIN_DATA_TYPE_DEFINITION: {} })); -import { isStateValid } from './utils'; +import { isStateValid, stripUndefinedValues } from './utils'; describe('utils', () => { describe('isStateValid()', () => { @@ -62,4 +62,45 @@ describe('utils', () => { expect(isStateValid(components)).toBe(false); }); }); + + describe('stripUndefinedValues()', () => { + test('should remove all undefined value recursively', () => { + const myDate = new Date(); + + const dataIN = { + someString: 'world', + stripThis: undefined, + nested: { + value: 'bar', + stripThis: undefined, + someArray: [1, 2, 3], + someBoolean: true, + someNumber: 123, + someEmptyObject: {}, + someDate: myDate, + deepNested: { + value: 'baz', + stripThis: undefined, + }, + }, + }; + + const dataOUT = { + someString: 'world', + nested: { + value: 'bar', + someArray: [1, 2, 3], + someBoolean: true, + someNumber: 123, + someEmptyObject: {}, + someDate: myDate, + deepNested: { + value: 'baz', + }, + }, + }; + + expect(stripUndefinedValues(dataIN)).toEqual(dataOUT); + }); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index 16f37d3bb205a..a6ed187a451fa 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -536,8 +536,12 @@ export const stripUndefinedValues = (obj: GenericObject, recu if (value === undefined) { return acc; } - if (recursive && value !== null && typeof value === 'object') { - return { ...acc, [key]: stripUndefinedValues(value, recursive) }; + + if (Array.isArray(value) || value instanceof Date || value === null) { + return { ...acc, [key]: value }; } - return { ...acc, [key]: value }; + + return recursive && typeof value === 'object' + ? { ...acc, [key]: stripUndefinedValues(value, recursive) } + : { ...acc, [key]: value }; }, {} as T); From ab0ecd7cd3e24c39a82707cf82abf37567df5c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Thu, 23 Apr 2020 11:10:42 +0200 Subject: [PATCH 11/34] Add expandAllFields helper --- .../datatypes/text_datatype.test.tsx | 61 +++++++++------- .../helpers/mappings_editor.helpers.tsx | 69 ++++++++++++------- .../mappings_editor/lib/utils.test.ts | 20 +++--- .../components/mappings_editor/lib/utils.ts | 4 +- x-pack/test_utils/testbed/testbed.ts | 2 +- 5 files changed, 94 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 955a4f51ddcb1..551414126db3f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -26,15 +26,21 @@ describe('text datatype', () => { }, }, }; - let testBed: MappingsEditorTestBed; - beforeEach(async () => { - testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + let testBed: MappingsEditorTestBed; - // We edit the field (by opening the flyout) + beforeEach(async done => { await act(async () => { - await testBed.actions.startEditField('user.address.street'); + testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + const { + actions: { expandAllFields }, + } = testBed; + + // Make sure all the fields are expanded and present in the DOM + await expandAllFields(); }); + + done(); }); afterEach(() => { @@ -42,31 +48,16 @@ describe('text datatype', () => { }); test('flyout details initial view', async () => { - const updatedMappings = { - _meta: {}, // If undefined, an empty object is returned by the editor - _source: {}, // If undefined, an empty object is returned by the editor - ...defaultMappings, - properties: { - user: { - type: 'object', // As no type was defined, defaults to "object" type - properties: { - address: { - type: 'object', // As no type was defined, defaults to "object" type - properties: { - street: { type: 'text' }, - }, - }, - }, - }, - }, - }; const { find, - actions: { getToggleValue, showAdvancedSettings, updateFieldAndCloseFlyout }, + actions: { startEditField, getToggleValue, showAdvancedSettings, updateFieldAndCloseFlyout }, } = testBed; const fieldPath = ['user', 'address', 'street']; const fieldName = fieldPath[fieldPath.length - 1]; + // Open the flyout to edit the field + await startEditField(fieldPath.join('.')); + // It should have the correct title expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field '${fieldName}'`); @@ -84,11 +75,31 @@ describe('text datatype', () => { await showAdvancedSettings(); }); - // TODO: find a way to automate the test that all expected fields are present + // TODO: find a way to automate testing that all expected fields are present // and have their default value correctly set + const updatedMappings = { + _meta: {}, // Was not defined so an empty object is returned by the editor + _source: {}, // Was not defined so an empty object is returned by the editor + ...defaultMappings, + properties: { + user: { + type: 'object', // Was not defined so it defaults to "object" type + properties: { + address: { + type: 'object', // Was not defined so it defaults to "object" type + properties: { + street: { type: 'text' }, + }, + }, + }, + }, + }, + }; + await expectDataUpdated(updatedMappings); + // Save the field and close the flyout await act(async () => { await updateFieldAndCloseFlyout(); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index b786c99331c12..99c41254a874a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; import { act } from 'react-dom/test-utils'; +import { ReactWrapper } from 'enzyme'; import { registerTestBed, TestBed, nextTick } from '../../../../../../../../../test_utils'; import { MappingsEditor } from '../../../mappings_editor'; @@ -37,40 +38,61 @@ jest.mock('@elastic/eui', () => ({ const createActions = (testBed: TestBed) => { const { find, exists, waitFor, waitForFn, form, component } = testBed; - const expandAllChildrenAt = async (path: string) => { - const pathToArray = path.split('.'); + const expandField = async ( + field: ReactWrapper + ): Promise<{ isExpanded: boolean; testSubjectField: string }> => { + /** + * Field list item have 2 test subject assigned to them: + * data-test-subj="fieldsListItem " + * + * We read the second one as it is unique. + */ + const testSubjectField = (field.props() as any)['data-test-subj'] + .split(' ') + .filter((subj: string) => subj !== 'fieldsListItem')[0] as string; + + const expandButton = find(`${testSubjectField}.toggleExpandButton` as TestSubjects); + + // No expand button, so this field is not expanded + if (expandButton.length === 0) { + return { isExpanded: false, testSubjectField }; + } - const proceed = async (index = 0): Promise => { - const pathToField = pathToArray.slice(0, index + 1); - const testSubjectField = `${pathToField.join('')}Field`; + const isExpanded = (expandButton.props()['aria-label'] as string).includes('Collapse'); - const expandButton = find(`${testSubjectField}.toggleExpandButton` as TestSubjects); + if (!isExpanded) { + expandButton.simulate('click'); + } - if (expandButton.length === 0) { - return; - } - const isExpanded = (expandButton.props()['aria-label'] as string).includes('Collapse'); + // Wait for the children FieldList to be in the DOM + await waitFor(`${testSubjectField}.fieldsList` as TestSubjects); - if (!isExpanded) { - expandButton.simulate('click'); - } + return { isExpanded: true, testSubjectField }; + }; + + /** + * Expand all the children of a field. + * + * @param fieldName The field under wich we want to expand all the children. + * If no fieldName is provided, we expand all the **root** level fields. + */ + const expandAllFields = async (fieldName?: string) => { + const fields = find( + fieldName ? (`${fieldName}.fieldsList.fieldsListItem` as TestSubjects) : 'fieldsListItem' + ).map(wrapper => wrapper); // convert to Array for our for of loop below - // Wait for the children FieldList to be there - await waitFor(`${testSubjectField}.fieldsList` as TestSubjects); + for (const field of fields) { + const { isExpanded, testSubjectField } = await expandField(field); - if (index < pathToArray.length - 1) { - return proceed(++index); + if (isExpanded) { + // Expand its children + await expandAllFields(testSubjectField); } - }; - - return proceed(); + } }; // Get a nested field in the rendered DOM tree const getFieldAt = async (path: string) => { - // First make sure all the parents fields are expanded and all present in the DOM - await expandAllChildrenAt(path); - const testSubjectField = `${path.split('.').join('')}Field`; return find(testSubjectField as TestSubjects); }; @@ -181,6 +203,7 @@ const createActions = (testBed: TestBed) => { selectTab, getFieldAt, addField, + expandAllFields, startEditField, updateFieldAndCloseFlyout, showAdvancedSettings, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts index 6d0ab871eac78..4a1d3254ad7af 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts @@ -69,15 +69,15 @@ describe('utils', () => { const dataIN = { someString: 'world', + someNumber: 123, + someBoolean: true, + someArray: [1, 2, 3], + someEmptyObject: {}, + someDate: myDate, stripThis: undefined, nested: { value: 'bar', stripThis: undefined, - someArray: [1, 2, 3], - someBoolean: true, - someNumber: 123, - someEmptyObject: {}, - someDate: myDate, deepNested: { value: 'baz', stripThis: undefined, @@ -87,13 +87,13 @@ describe('utils', () => { const dataOUT = { someString: 'world', + someNumber: 123, + someBoolean: true, + someArray: [1, 2, 3], + someEmptyObject: {}, + someDate: myDate, nested: { value: 'bar', - someArray: [1, 2, 3], - someBoolean: true, - someNumber: 123, - someEmptyObject: {}, - someDate: myDate, deepNested: { value: 'baz', }, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index a6ed187a451fa..cc5630b1c7759 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -33,9 +33,7 @@ import { State } from '../reducer'; import { FieldConfig } from '../shared_imports'; import { TreeItem } from '../components/tree'; -export const getUniqueId = () => { - return uuid.v4(); -}; +export const getUniqueId = () => uuid.v4(); const getChildFieldsName = (dataType: DataType): ChildFieldName | undefined => { if (dataType === 'text' || dataType === 'keyword') { diff --git a/x-pack/test_utils/testbed/testbed.ts b/x-pack/test_utils/testbed/testbed.ts index 47d3dfc2fa8ba..1d722f25f75f8 100644 --- a/x-pack/test_utils/testbed/testbed.ts +++ b/x-pack/test_utils/testbed/testbed.ts @@ -142,7 +142,7 @@ export const registerTestBed = ( const triggeredAt = Date.now(); const MAX_WAIT_TIME = 30000; - const WAIT_INTERVAL = 100; + const WAIT_INTERVAL = 50; const process = async (): Promise => { const isOK = await predicate(); From 04930f79f1f99371743fd31a1af51c381737a247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Thu, 23 Apr 2020 14:45:44 +0200 Subject: [PATCH 12/34] Refactor helper to get data forwarded to consumer component --- .../client_integration/helpers/index.ts | 4 +-- .../helpers/mappings_editor.helpers.tsx | 32 ++++++++----------- .../mappings_editor.test.tsx | 18 ++++++++--- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts index f481c4a444a96..050d0e2635a76 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts @@ -6,7 +6,7 @@ import { setup as mappingsEditorSetup, MappingsEditorTestBed, - expectDataUpdatedFactory, + getDataForwardedFactory, } from './mappings_editor.helpers'; export { @@ -17,7 +17,7 @@ export { } from '../../../../../../../../../test_utils'; export const componentHelpers = { - mappingsEditor: { setup: mappingsEditorSetup, expectDataUpdatedFactory }, + mappingsEditor: { setup: mappingsEditorSetup, getDataForwardedFactory }, }; export { MappingsEditorTestBed }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 99c41254a874a..202590df5a474 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { registerTestBed, TestBed, nextTick } from '../../../../../../../../../test_utils'; @@ -199,6 +198,9 @@ const createActions = (testBed: TestBed) => { const getToggleValue = (testSubject: TestSubjects): boolean => find(testSubject).props()['aria-checked']; + const getCheckboxValue = (testSubject: TestSubjects): boolean => + find(testSubject).props().checked; + return { selectTab, getFieldAt, @@ -211,6 +213,7 @@ const createActions = (testBed: TestBed) => { getJsonEditorValue, getComboBoxValue, getToggleValue, + getCheckboxValue, }; }; @@ -228,12 +231,12 @@ export const setup = async (props: any = { onUpdate() {} }): Promise) => { +export const getDataForwardedFactory = (onUpdateHandler: jest.MockedFunction) => { /** * Helper to access the latest data sent to the onUpdate handler back to the consumer of the . * Read the latest call with its argument passed and build the mappings object from it. */ - const getDataForwarded = async () => { + return async () => { const mockCalls = onUpdateHandler.mock.calls; if (mockCalls.length === 0) { @@ -245,26 +248,14 @@ export const expectDataUpdatedFactory = (onUpdateHandler: jest.MockedFunction { - isMappingsValid = isValid === undefined ? await validate() : isValid; - data = getData(isMappingsValid); - }); + const isMappingsValid = isValid === undefined ? await validate() : isValid; + const data = getData(isMappingsValid); return { isValid: isMappingsValid, data, }; }; - - const expectDataUpdated = async (expected: any) => { - const { data } = await getDataForwarded(); - expect(data).toEqual(expected); - }; - - return expectDataUpdated; }; export type MappingsEditorTestBed = TestBed & { @@ -305,4 +296,9 @@ export type TestSubjects = | 'mappingsEditorFieldEdit.fieldPath' | 'mappingsEditorFieldEdit.advancedSettings' | 'mappingsEditorFieldEdit.toggleAdvancedSetting' - | 'indexParameter.formRowToggle'; + | 'indexParameter.formRowToggle' + | 'analyzerParameters.indexAnalyzer.select' + | 'analyzerParameters.searchAnalyzer' + | 'analyzerParameters.searchAnalyzer.select' + | 'analyzerParameters.searchQuoteAnalyzer.select' + | 'analyzerParameters.useSameAnalyzerForSearchCheckBox.input'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 8febfd29592f4..58c80cbfe6091 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -7,11 +7,16 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from './helpers'; -const { setup, expectDataUpdatedFactory } = componentHelpers.mappingsEditor; +const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; const onUpdateHandler = jest.fn(); -const expectDataUpdated = expectDataUpdatedFactory(onUpdateHandler); +const getDataForwarded = getDataForwardedFactory(onUpdateHandler); describe('', () => { + /** + * Variable to store the mappings data forwarded to the consumer component + */ + let data: any; + afterEach(() => { onUpdateHandler.mockReset(); }); @@ -296,8 +301,9 @@ describe('', () => { await act(async () => { await addField(newField.name, newField.type); + ({ data } = await getDataForwarded()); }); - await expectDataUpdated(updatedMappings); + expect(data).toEqual(updatedMappings); /** * Dynamic templates @@ -316,8 +322,9 @@ describe('', () => { await updateJsonEditor('dynamicTemplatesEditor', updatedTemplatesValue); await nextTick(); component.update(); + ({ data } = await getDataForwarded()); }); - await expectDataUpdated(updatedMappings); + expect(data).toEqual(updatedMappings); /** * Advanced settings @@ -340,7 +347,8 @@ describe('', () => { numeric_detection: undefined, }; - await expectDataUpdated(updatedMappings); + ({ data } = await getDataForwarded()); + expect(data).toEqual(updatedMappings); }); }); }); From 21513e99303268f8dac5be98109668aade216953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Thu, 23 Apr 2020 14:47:41 +0200 Subject: [PATCH 13/34] Move the beforeEach logic inside the test --- .../datatypes/text_datatype.test.tsx | 72 ++++++++++--------- .../field_parameters/analyzer_parameter.tsx | 5 +- .../analyzer_parameter_selects.tsx | 9 ++- .../field_parameters/analyzers_parameter.tsx | 7 ++ 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 551414126db3f..d0e5ef1e0f062 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -5,64 +5,63 @@ */ import { act } from 'react-dom/test-utils'; -import { componentHelpers, MappingsEditorTestBed } from '../helpers'; +import { componentHelpers, MappingsEditorTestBed, nextTick } from '../helpers'; import { getFieldConfig } from '../../../lib'; -const { setup, expectDataUpdatedFactory } = componentHelpers.mappingsEditor; +const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; const onUpdateHandler = jest.fn(); -const expectDataUpdated = expectDataUpdatedFactory(onUpdateHandler); +const getDataForwarded = getDataForwardedFactory(onUpdateHandler); describe('text datatype', () => { - const defaultMappings = { - properties: { - user: { - properties: { - address: { - properties: { - street: { type: 'text' }, + let testBed: MappingsEditorTestBed; + + /** + * Variable to store the mappings data forwarded to the consumer component + */ + let data: any; + + afterEach(() => { + onUpdateHandler.mockReset(); + }); + + test('flyout details initial view', async () => { + const defaultMappings = { + properties: { + user: { + properties: { + address: { + properties: { + street: { type: 'text' }, + }, }, }, }, }, - }, - }; - - let testBed: MappingsEditorTestBed; + }; - beforeEach(async done => { await act(async () => { testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); - const { - actions: { expandAllFields }, - } = testBed; - // Make sure all the fields are expanded and present in the DOM - await expandAllFields(); + await testBed.actions.expandAllFields(); }); - done(); - }); - - afterEach(() => { - onUpdateHandler.mockReset(); - }); - - test('flyout details initial view', async () => { const { find, actions: { startEditField, getToggleValue, showAdvancedSettings, updateFieldAndCloseFlyout }, } = testBed; - const fieldPath = ['user', 'address', 'street']; - const fieldName = fieldPath[fieldPath.length - 1]; + const fieldPathToEdit = ['user', 'address', 'street']; + const fieldName = fieldPathToEdit[fieldPathToEdit.length - 1]; // Open the flyout to edit the field - await startEditField(fieldPath.join('.')); + await act(async () => { + await startEditField(fieldPathToEdit.join('.')); + }); // It should have the correct title expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field '${fieldName}'`); // It should have the correct field path - expect(find('mappingsEditorFieldEdit.fieldPath').text()).toEqual(fieldPath.join(' > ')); + expect(find('mappingsEditorFieldEdit.fieldPath').text()).toEqual(fieldPathToEdit.join(' > ')); // It should have searchable ("index" param) active by default const indexFieldConfig = getFieldConfig('index'); @@ -97,7 +96,10 @@ describe('text datatype', () => { }, }; - await expectDataUpdated(updatedMappings); + await act(async () => { + ({ data } = await getDataForwarded()); + }); + expect(data).toEqual(updatedMappings); // Save the field and close the flyout await act(async () => { @@ -117,6 +119,8 @@ describe('text datatype', () => { }; updatedMappings.properties.user.properties.address.properties.street = streetField; - await expectDataUpdated(updatedMappings); + ({ data } = await getDataForwarded()); + expect(data).toEqual(updatedMappings); }); + }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx index a97e3b227311c..b62a08b848ebc 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx @@ -25,6 +25,7 @@ interface Props { label?: string; config?: FieldConfig; allowsIndexDefaultOption?: boolean; + 'data-test-subj'?: string; } const ANALYZER_OPTIONS = PARAMETERS_OPTIONS.analyzer!; @@ -68,6 +69,7 @@ export const AnalyzerParameter = ({ label, config, allowsIndexDefaultOption = true, + 'data-test-subj': dataTestSubj, }: Props) => { const indexSettings = useIndexSettings(); const customAnalyzers = getCustomAnalyzers(indexSettings); @@ -169,7 +171,7 @@ export const AnalyzerParameter = ({ // around the field. - + ) : ( @@ -180,6 +182,7 @@ export const AnalyzerParameter = ({ config={fieldConfigWithLabel} options={fieldOptions} mapOptionsToSubOptions={mapOptionsToSubOptions} + data-test-subj={dataTestSubj} /> )}
      diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx index a91231352c168..fac14dd6d8558 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx @@ -36,6 +36,7 @@ interface Props { config: FieldConfig; options: Options; mapOptionsToSubOptions: MapOptionsToSubOptions; + 'data-test-subj'?: string; } export const AnalyzerParameterSelects = ({ @@ -45,6 +46,7 @@ export const AnalyzerParameterSelects = ({ config, options, mapOptionsToSubOptions, + 'data-test-subj': dataTestSubj, }: Props) => { const { form } = useForm({ defaultValue: { main: mainDefaultValue, sub: subDefaultValue } }); @@ -76,11 +78,16 @@ export const AnalyzerParameterSelects = ({ const isSuperSelect = areOptionsSuperSelect(opts); return isSuperSelect ? ( - + ) : ( ); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx index 0cf22946bf60a..f99aa4d1eca9a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx @@ -34,6 +34,7 @@ export const AnalyzersParameter = ({ field, withSearchQuoteAnalyzer = false }: P href: documentationService.getAnalyzerLink(), }} withToggle={false} + data-test-subj="analyzerParameters" > {({ useSameAnalyzerForSearch }) => { @@ -50,6 +51,7 @@ export const AnalyzersParameter = ({ field, withSearchQuoteAnalyzer = false }: P path="analyzer" label={label} defaultValue={field.source.analyzer as string} + data-test-subj="indexAnalyzer" /> ); }} @@ -60,6 +62,9 @@ export const AnalyzersParameter = ({ field, withSearchQuoteAnalyzer = false }: P @@ -94,6 +100,7 @@ export const AnalyzersParameter = ({ field, withSearchQuoteAnalyzer = false }: P path="search_quote_analyzer" defaultValue={field.source.search_quote_analyzer as string} config={getFieldConfig('search_quote_analyzer')} + data-test-subj="searchQuoteAnalyzer" /> )} From b26f5c89ef302dd5a61055462701ef79a38850ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Thu, 23 Apr 2020 16:01:25 +0200 Subject: [PATCH 14/34] Add coverage for analyzer default value and changes --- .../datatypes/text_datatype.test.tsx | 128 ++++++++++++++++++ .../helpers/mappings_editor.helpers.tsx | 9 ++ x-pack/test_utils/testbed/testbed.ts | 13 ++ x-pack/test_utils/testbed/types.ts | 7 + 4 files changed, 157 insertions(+) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index d0e5ef1e0f062..7dfd3428197e6 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -123,4 +123,132 @@ describe('text datatype', () => { expect(data).toEqual(updatedMappings); }); + test('analyzer parameter: default values', async () => { + const defaultMappings = { + _meta: {}, + _source: {}, + properties: { + myTextField: { type: 'text' }, + }, + }; + + let updatedMappings: any = { ...defaultMappings }; + + testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + // Make sure all the fields are expanded and present in the DOM + await testBed.actions.expandAllFields(); + + const { + find, + exists, + waitFor, + form: { selectCheckBox, setSelectValue }, + actions: { + startEditField, + getCheckboxValue, + showAdvancedSettings, + updateFieldAndCloseFlyout, + }, + } = testBed; + const fieldToEdit = 'myTextField'; + + // Start edit and immediately save to have all the default values + await startEditField(fieldToEdit); + await showAdvancedSettings(); + + await act(async () => { + await updateFieldAndCloseFlyout(); + ({ data } = await getDataForwarded()); + }); + + updatedMappings = { + ...updatedMappings, + properties: { + myTextField: { + ...updatedMappings.properties.myTextField, + eager_global_ordinals: false, + fielddata: false, + index: true, + index_options: 'positions', + index_phrases: false, + norms: true, + store: false, + }, + }, + }; + + expect(data).toEqual(updatedMappings); + + // Start edit + await startEditField(fieldToEdit); + await showAdvancedSettings(); + + // When no analyzer is defined, defaults to "Index default" + let indexAnalyzerValue = find('analyzerParameters.indexAnalyzer.select').props().value; + expect(indexAnalyzerValue).toEqual('index_default'); + + let searchQuoteAnalyzerValue = find('analyzerParameters.searchQuoteAnalyzer.select').props() + .value; + expect(searchQuoteAnalyzerValue).toEqual('index_default'); + + // When no "search_analyzer" is defined, the checkBox should be checked + let isUseSameAnalyzerForSearchChecked = getCheckboxValue( + 'analyzerParameters.useSameAnalyzerForSearchCheckBox.input' + ); + expect(isUseSameAnalyzerForSearchChecked).toBe(true); + + // And the search analyzer select should not exist + expect(exists('analyzerParameters.searchAnalyzer')).toBe(false); + + // Uncheck the "Use same analyzer for search" checkbox and wait for the select + await act(async () => { + selectCheckBox('analyzerParameters.useSameAnalyzerForSearchCheckBox.input', false); + await waitFor('analyzerParameters.searchAnalyzer'); + }); + + let searchAnalyzerValue = find('analyzerParameters.searchQuoteAnalyzer.select').props().value; + expect(searchAnalyzerValue).toEqual('index_default'); + + await act(async () => { + // Change the value of the 3 analyzers + setSelectValue('analyzerParameters.indexAnalyzer.select', 'standard'); + setSelectValue('analyzerParameters.searchAnalyzer.select', 'simple'); + setSelectValue('analyzerParameters.searchQuoteAnalyzer.select', 'whitespace'); + + // Save & close + await updateFieldAndCloseFlyout(); + }); + + updatedMappings = { + ...updatedMappings, + properties: { + myTextField: { + ...updatedMappings.properties.myTextField, + analyzer: 'standard', + search_analyzer: 'simple', + search_quote_analyzer: 'whitespace', + }, + }, + }; + + ({ data } = await getDataForwarded()); + expect(data).toEqual(updatedMappings); + + // Re-open the flyout and make sure the select have the correct updated value + await startEditField(fieldToEdit); + await showAdvancedSettings(); + + isUseSameAnalyzerForSearchChecked = getCheckboxValue( + 'analyzerParameters.useSameAnalyzerForSearchCheckBox.input' + ); + expect(isUseSameAnalyzerForSearchChecked).toBe(false); + + indexAnalyzerValue = find('analyzerParameters.indexAnalyzer.select').props().value; + searchAnalyzerValue = find('analyzerParameters.searchAnalyzer.select').props().value; + searchQuoteAnalyzerValue = find('analyzerParameters.searchQuoteAnalyzer.select').props().value; + + expect(indexAnalyzerValue).toBe('standard'); + expect(searchAnalyzerValue).toBe('simple'); + expect(searchQuoteAnalyzerValue).toBe('whitespace'); + }, 30000); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 202590df5a474..baa4403707906 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -32,6 +32,15 @@ jest.mock('@elastic/eui', () => ({ }} /> ), + EuiSuperSelect: (props: any) => ( + { + props.onChange(e.target.value); + }} + /> + ), })); const createActions = (testBed: TestBed) => { diff --git a/x-pack/test_utils/testbed/testbed.ts b/x-pack/test_utils/testbed/testbed.ts index 1d722f25f75f8..d0e79288f6166 100644 --- a/x-pack/test_utils/testbed/testbed.ts +++ b/x-pack/test_utils/testbed/testbed.ts @@ -198,6 +198,18 @@ export const registerTestBed = ( return new Promise(resolve => setTimeout(resolve)); }; + const setSelectValue: TestBed['form']['setSelectValue'] = (select, value) => { + const formSelect = typeof select === 'string' ? find(select) : (select as ReactWrapper); + + if (!formSelect.length) { + throw new Error(`Select "${select}" was not found.`); + } + + formSelect.simulate('change', { target: { value } }); + + component.update(); + }; + const selectCheckBox: TestBed['form']['selectCheckBox'] = ( testSubject, isChecked = true @@ -296,6 +308,7 @@ export const registerTestBed = ( }, form: { setInputValue, + setSelectValue, selectCheckBox, toggleEuiSwitch, setComboBoxValue, diff --git a/x-pack/test_utils/testbed/types.ts b/x-pack/test_utils/testbed/types.ts index 011f8f34120ba..fbd254d225425 100644 --- a/x-pack/test_utils/testbed/types.ts +++ b/x-pack/test_utils/testbed/types.ts @@ -80,6 +80,13 @@ export interface TestBed { value: string, isAsync?: boolean ) => Promise | void; + /** + * Set the value of a or a mocked + * + * @param select The form select. Can either be a data-test-subj or a reactWrapper (can be a nested path. e.g. "myForm.myInput"). + * @param value The value to set + */ + setSelectValue: (select: T | ReactWrapper, value: string) => void; /** * Select or unselect a form checkbox. * From 93388a3c2599b8191476ce6db72cfbb36c95f565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Thu, 23 Apr 2020 16:04:29 +0200 Subject: [PATCH 15/34] Fix act() error --- .../__jest__/client_integration/mappings_editor.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 58c80cbfe6091..bdf1c2bad3ace 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -336,6 +336,7 @@ describe('', () => { // Disbable dynamic mappings await act(async () => { form.toggleEuiSwitch('advancedConfiguration.dynamicMappingsToggle.input'); + ({ data } = await getDataForwarded()); }); // When we disable dynamic mappings, we set it to "false" and remove date and numeric detections @@ -347,7 +348,6 @@ describe('', () => { numeric_detection: undefined, }; - ({ data } = await getDataForwarded()); expect(data).toEqual(updatedMappings); }); }); From a01f522960141dcc0670a1244c584e63945560ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 10:50:30 +0200 Subject: [PATCH 16/34] Fix TS issue --- .../client_integration/datatypes/text_datatype.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 7dfd3428197e6..d44cc263f773e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -5,7 +5,7 @@ */ import { act } from 'react-dom/test-utils'; -import { componentHelpers, MappingsEditorTestBed, nextTick } from '../helpers'; +import { componentHelpers, MappingsEditorTestBed } from '../helpers'; import { getFieldConfig } from '../../../lib'; const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; From 9a8d9b553c9c987b9596d87fd7dec9d20d35eadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 11:24:38 +0200 Subject: [PATCH 17/34] Test custom analyzer of external plugins --- .../datatypes/text_datatype.test.tsx | 161 ++++++++++++++---- .../helpers/mappings_editor.helpers.tsx | 18 +- .../field_parameters/analyzer_parameter.tsx | 1 + 3 files changed, 138 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index d44cc263f773e..5aaf40df035ab 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -132,11 +132,23 @@ describe('text datatype', () => { }, }; - let updatedMappings: any = { ...defaultMappings }; + let updatedMappings: any = { + ...defaultMappings, + properties: { + myTextField: { + ...defaultMappings.properties.myTextField, + eager_global_ordinals: false, + fielddata: false, + index: true, + index_options: 'positions', + index_phrases: false, + norms: true, + store: false, + }, + }, + }; testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); - // Make sure all the fields are expanded and present in the DOM - await testBed.actions.expandAllFields(); const { find, @@ -161,59 +173,42 @@ describe('text datatype', () => { ({ data } = await getDataForwarded()); }); - updatedMappings = { - ...updatedMappings, - properties: { - myTextField: { - ...updatedMappings.properties.myTextField, - eager_global_ordinals: false, - fielddata: false, - index: true, - index_options: 'positions', - index_phrases: false, - norms: true, - store: false, - }, - }, - }; - expect(data).toEqual(updatedMappings); - // Start edit + // Re-open the edit panel await startEditField(fieldToEdit); await showAdvancedSettings(); // When no analyzer is defined, defaults to "Index default" - let indexAnalyzerValue = find('analyzerParameters.indexAnalyzer.select').props().value; + let indexAnalyzerValue = find('indexAnalyzer.select').props().value; expect(indexAnalyzerValue).toEqual('index_default'); - let searchQuoteAnalyzerValue = find('analyzerParameters.searchQuoteAnalyzer.select').props() - .value; + let searchQuoteAnalyzerValue = find('searchQuoteAnalyzer.select').props().value; expect(searchQuoteAnalyzerValue).toEqual('index_default'); // When no "search_analyzer" is defined, the checkBox should be checked let isUseSameAnalyzerForSearchChecked = getCheckboxValue( - 'analyzerParameters.useSameAnalyzerForSearchCheckBox.input' + 'useSameAnalyzerForSearchCheckBox.input' ); expect(isUseSameAnalyzerForSearchChecked).toBe(true); // And the search analyzer select should not exist - expect(exists('analyzerParameters.searchAnalyzer')).toBe(false); + expect(exists('searchAnalyzer')).toBe(false); // Uncheck the "Use same analyzer for search" checkbox and wait for the select await act(async () => { - selectCheckBox('analyzerParameters.useSameAnalyzerForSearchCheckBox.input', false); - await waitFor('analyzerParameters.searchAnalyzer'); + selectCheckBox('useSameAnalyzerForSearchCheckBox.input', false); + await waitFor('searchAnalyzer'); }); - let searchAnalyzerValue = find('analyzerParameters.searchQuoteAnalyzer.select').props().value; + let searchAnalyzerValue = find('searchQuoteAnalyzer.select').props().value; expect(searchAnalyzerValue).toEqual('index_default'); await act(async () => { // Change the value of the 3 analyzers - setSelectValue('analyzerParameters.indexAnalyzer.select', 'standard'); - setSelectValue('analyzerParameters.searchAnalyzer.select', 'simple'); - setSelectValue('analyzerParameters.searchQuoteAnalyzer.select', 'whitespace'); + setSelectValue('indexAnalyzer.select', 'standard'); + setSelectValue('searchAnalyzer.select', 'simple'); + setSelectValue('searchQuoteAnalyzer.select', 'whitespace'); // Save & close await updateFieldAndCloseFlyout(); @@ -238,17 +233,109 @@ describe('text datatype', () => { await startEditField(fieldToEdit); await showAdvancedSettings(); - isUseSameAnalyzerForSearchChecked = getCheckboxValue( - 'analyzerParameters.useSameAnalyzerForSearchCheckBox.input' - ); + isUseSameAnalyzerForSearchChecked = getCheckboxValue('useSameAnalyzerForSearchCheckBox.input'); expect(isUseSameAnalyzerForSearchChecked).toBe(false); - indexAnalyzerValue = find('analyzerParameters.indexAnalyzer.select').props().value; - searchAnalyzerValue = find('analyzerParameters.searchAnalyzer.select').props().value; - searchQuoteAnalyzerValue = find('analyzerParameters.searchQuoteAnalyzer.select').props().value; + indexAnalyzerValue = find('indexAnalyzer.select').props().value; + searchAnalyzerValue = find('searchAnalyzer.select').props().value; + searchQuoteAnalyzerValue = find('searchQuoteAnalyzer.select').props().value; expect(indexAnalyzerValue).toBe('standard'); expect(searchAnalyzerValue).toBe('simple'); expect(searchQuoteAnalyzerValue).toBe('whitespace'); }, 30000); + + test('analyzer parameter: custom analyzer', async () => { + const defaultMappings = { + _meta: {}, + _source: {}, + properties: { + myTextField: { + type: 'text', + analyzer: 'myCustomIndexAnalyzer', + search_analyzer: 'myCustomSearchAnalyzer', + search_quote_analyzer: 'myCustomSearchQuoteAnalyzer', + }, + }, + }; + + let updatedMappings: any = { + ...defaultMappings, + properties: { + myTextField: { + ...defaultMappings.properties.myTextField, + eager_global_ordinals: false, + fielddata: false, + index: true, + index_options: 'positions', + index_phrases: false, + norms: true, + store: false, + }, + }, + }; + + testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + + const { + find, + exists, + waitFor, + form: { setInputValue, setSelectValue }, + actions: { startEditField, showAdvancedSettings, updateFieldAndCloseFlyout }, + } = testBed; + const fieldToEdit = 'myTextField'; + + await startEditField(fieldToEdit); + await showAdvancedSettings(); + + expect(exists('indexAnalyzer-custom')).toBe(true); + expect(exists('searchAnalyzer-custom')).toBe(true); + expect(exists('searchQuoteAnalyzer-custom')).toBe(true); + + const indexAnalyzerValue = find('indexAnalyzer-custom.input').props().value; + const searchAnalyzerValue = find('searchAnalyzer-custom.input').props().value; + const searchQuoteAnalyzerValue = find('searchQuoteAnalyzer-custom.input').props().value; + + expect(indexAnalyzerValue).toBe(defaultMappings.properties.myTextField.analyzer); + expect(searchAnalyzerValue).toBe(defaultMappings.properties.myTextField.search_analyzer); + expect(searchQuoteAnalyzerValue).toBe( + defaultMappings.properties.myTextField.search_quote_analyzer + ); + + const updatedIndexAnalyzer = 'updatedAnalyzer'; + const updatedSearchAnalyzer = 'whitespace'; + + await act(async () => { + // Change the index analyzer to another custom one + setInputValue('indexAnalyzer-custom.input', updatedIndexAnalyzer); + + // Change the search analyzer to a built-in analyzer + find('searchAnalyzer-toggleCustomButton').simulate('click'); + await waitFor('searchAnalyzer'); + setSelectValue('searchAnalyzer.select', updatedSearchAnalyzer); + + // Change the searchQuote analyzer using the index default + find('searchQuoteAnalyzer-toggleCustomButton').simulate('click'); + await waitFor('searchQuoteAnalyzer'); + + // Save & close + await updateFieldAndCloseFlyout(); + ({ data } = await getDataForwarded()); + }); + + updatedMappings = { + ...updatedMappings, + properties: { + myTextField: { + ...updatedMappings.properties.myTextField, + analyzer: updatedIndexAnalyzer, + search_analyzer: updatedSearchAnalyzer, + search_quote_analyzer: undefined, // Index default means not declaring the analyzer + }, + }, + }; + + expect(data).toEqual(updatedMappings); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index baa4403707906..a23944785002f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -306,8 +306,16 @@ export type TestSubjects = | 'mappingsEditorFieldEdit.advancedSettings' | 'mappingsEditorFieldEdit.toggleAdvancedSetting' | 'indexParameter.formRowToggle' - | 'analyzerParameters.indexAnalyzer.select' - | 'analyzerParameters.searchAnalyzer' - | 'analyzerParameters.searchAnalyzer.select' - | 'analyzerParameters.searchQuoteAnalyzer.select' - | 'analyzerParameters.useSameAnalyzerForSearchCheckBox.input'; + | 'indexAnalyzer.select' + | 'searchAnalyzer' + | 'searchAnalyzer.select' + | 'searchQuoteAnalyzer' + | 'searchQuoteAnalyzer.select' + | 'indexAnalyzer-custom' + | 'indexAnalyzer-custom.input' + | 'searchAnalyzer-toggleCustomButton' + | 'searchAnalyzer-custom' + | 'searchAnalyzer-custom.input' + | 'searchQuoteAnalyzer-custom' + | 'searchQuoteAnalyzer-custom.input' + | 'useSameAnalyzerForSearchCheckBox.input'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx index b62a08b848ebc..5960965362b48 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx @@ -156,6 +156,7 @@ export const AnalyzerParameter = ({ size="xs" onClick={toggleCustom(field)} className="mappingsEditor__selectWithCustom__button" + data-test-subj={`${dataTestSubj}-toggleCustomButton`} > {isCustom ? i18n.translate('xpack.idxMgmt.mappingsEditor.predefinedButtonLabel', { From 01605dbd10eb09262957633d89ba303c4bfe1a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 14:25:52 +0200 Subject: [PATCH 18/34] Test custom analyzer declared on index settings --- .../datatypes/text_datatype.test.tsx | 152 +++++++++++++++--- .../helpers/mappings_editor.helpers.tsx | 1 + 2 files changed, 127 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 5aaf40df035ab..901f5ff505771 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -12,6 +12,17 @@ const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; const onUpdateHandler = jest.fn(); const getDataForwarded = getDataForwardedFactory(onUpdateHandler); +// Parameters automatically added to the text datatype when saved (with their default values) +const defaultTextParameters = { + eager_global_ordinals: false, + fielddata: false, + index: true, + index_options: 'positions', + index_phrases: false, + norms: true, + store: false, +}; + describe('text datatype', () => { let testBed: MappingsEditorTestBed; @@ -109,13 +120,7 @@ describe('text datatype', () => { const streetField = { type: 'text', // All the default parameters values have been added - eager_global_ordinals: false, - fielddata: false, - index: true, - index_options: 'positions', - index_phrases: false, - norms: true, - store: false, + ...defaultTextParameters, }; updatedMappings.properties.user.properties.address.properties.street = streetField; @@ -128,7 +133,12 @@ describe('text datatype', () => { _meta: {}, _source: {}, properties: { - myTextField: { type: 'text' }, + myTextField: { + type: 'text', + // Should have 2 dropdown selects: + // The first one set to 'language' and the second one set to 'french + search_quote_analyzer: 'french', + }, }, }; @@ -137,13 +147,7 @@ describe('text datatype', () => { properties: { myTextField: { ...defaultMappings.properties.myTextField, - eager_global_ordinals: false, - fielddata: false, - index: true, - index_options: 'positions', - index_phrases: false, - norms: true, - store: false, + ...defaultTextParameters, }, }, }; @@ -183,8 +187,13 @@ describe('text datatype', () => { let indexAnalyzerValue = find('indexAnalyzer.select').props().value; expect(indexAnalyzerValue).toEqual('index_default'); - let searchQuoteAnalyzerValue = find('searchQuoteAnalyzer.select').props().value; - expect(searchQuoteAnalyzerValue).toEqual('index_default'); + let searchQuoteAnalyzerSelects = find('searchQuoteAnalyzer.select'); + + expect(searchQuoteAnalyzerSelects.length).toBe(2); + expect(searchQuoteAnalyzerSelects.at(0).props().value).toBe('language'); + expect(searchQuoteAnalyzerSelects.at(1).props().value).toBe( + defaultMappings.properties.myTextField.search_quote_analyzer + ); // When no "search_analyzer" is defined, the checkBox should be checked let isUseSameAnalyzerForSearchChecked = getCheckboxValue( @@ -209,11 +218,19 @@ describe('text datatype', () => { setSelectValue('indexAnalyzer.select', 'standard'); setSelectValue('searchAnalyzer.select', 'simple'); setSelectValue('searchQuoteAnalyzer.select', 'whitespace'); + }); + + // Make sure the second dropdown select has been removed + searchQuoteAnalyzerSelects = find('searchQuoteAnalyzer.select'); + expect(searchQuoteAnalyzerSelects.length).toBe(1); + await act(async () => { // Save & close await updateFieldAndCloseFlyout(); }); + expect(searchQuoteAnalyzerSelects.length).toBe(2); + updatedMappings = { ...updatedMappings, properties: { @@ -238,14 +255,14 @@ describe('text datatype', () => { indexAnalyzerValue = find('indexAnalyzer.select').props().value; searchAnalyzerValue = find('searchAnalyzer.select').props().value; - searchQuoteAnalyzerValue = find('searchQuoteAnalyzer.select').props().value; + const searchQuoteAnalyzerValue = find('searchQuoteAnalyzer.select').props().value; expect(indexAnalyzerValue).toBe('standard'); expect(searchAnalyzerValue).toBe('simple'); expect(searchQuoteAnalyzerValue).toBe('whitespace'); }, 30000); - test('analyzer parameter: custom analyzer', async () => { + test('analyzer parameter: custom analyzer (external plugin)', async () => { const defaultMappings = { _meta: {}, _source: {}, @@ -264,13 +281,7 @@ describe('text datatype', () => { properties: { myTextField: { ...defaultMappings.properties.myTextField, - eager_global_ordinals: false, - fielddata: false, - index: true, - index_options: 'positions', - index_phrases: false, - norms: true, - store: false, + ...defaultTextParameters, }, }, }; @@ -338,4 +349,93 @@ describe('text datatype', () => { expect(data).toEqual(updatedMappings); }); + + test('analyzer parameter: custom analyzer (from index settings)', async () => { + const indexSettings = { + analysis: { + analyzer: { + customAnalyzer_1: {}, + customAnalyzer_2: {}, + customAnalyzer_3: {}, + }, + }, + }; + + const customAnalyzers = Object.keys(indexSettings.analysis.analyzer); + + const defaultMappings = { + _meta: {}, + _source: {}, + properties: { + myTextField: { + type: 'text', + analyzer: customAnalyzers[0], + }, + }, + }; + + let updatedMappings: any = { + ...defaultMappings, + properties: { + myTextField: { + ...defaultMappings.properties.myTextField, + ...defaultTextParameters, + }, + }, + }; + + testBed = await setup({ + defaultValue: defaultMappings, + onUpdate: onUpdateHandler, + indexSettings, + }); + + const { + find, + form: { setSelectValue }, + actions: { startEditField, showAdvancedSettings, updateFieldAndCloseFlyout }, + } = testBed; + const fieldToEdit = 'myTextField'; + + await startEditField(fieldToEdit); + await showAdvancedSettings(); + + // It should have 2 selects + const indexAnalyzerSelects = find('indexAnalyzer.select'); + + expect(indexAnalyzerSelects.length).toBe(2); + expect(indexAnalyzerSelects.at(0).props().value).toBe('custom'); + expect(indexAnalyzerSelects.at(1).props().value).toBe( + defaultMappings.properties.myTextField.analyzer + ); + + // Access the list of option of the second dropdown select + const subSelectOptions = indexAnalyzerSelects + .at(1) + .find('option') + .map(wrapper => wrapper.text()); + + expect(subSelectOptions).toEqual(customAnalyzers); + + await act(async () => { + // Change the custom analyzer dropdown to another one from the index settings + setSelectValue(find('indexAnalyzer.select').at(1), customAnalyzers[2]); + + // Save & close + await updateFieldAndCloseFlyout(); + ({ data } = await getDataForwarded()); + }); + + updatedMappings = { + ...updatedMappings, + properties: { + myTextField: { + ...updatedMappings.properties.myTextField, + analyzer: customAnalyzers[2], + }, + }, + }; + + expect(data).toEqual(updatedMappings); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index a23944785002f..71e1b5db63794 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -317,5 +317,6 @@ export type TestSubjects = | 'searchAnalyzer-custom' | 'searchAnalyzer-custom.input' | 'searchQuoteAnalyzer-custom' + | 'searchQuoteAnalyzer-toggleCustomButton' | 'searchQuoteAnalyzer-custom.input' | 'useSameAnalyzerForSearchCheckBox.input'; From 2fe79d03342f504306020f4fd51abb6ecb9d65f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 15:04:00 +0200 Subject: [PATCH 19/34] Fix default value for second dropdown select of analyzers --- .../field_parameters/analyzer_parameter_selects.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx index fac14dd6d8558..a44fd2257f52b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter_selects.tsx @@ -109,9 +109,9 @@ export const AnalyzerParameterSelects = ({ From 0f97d1f343c7cde8e7c1934c26de3fedf265b3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 15:05:41 +0200 Subject: [PATCH 20/34] Fix failing test --- .../datatypes/text_datatype.test.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 901f5ff505771..8db4f1f113955 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -158,6 +158,7 @@ describe('text datatype', () => { find, exists, waitFor, + waitForFn, form: { selectCheckBox, setSelectValue }, actions: { startEditField, @@ -187,7 +188,7 @@ describe('text datatype', () => { let indexAnalyzerValue = find('indexAnalyzer.select').props().value; expect(indexAnalyzerValue).toEqual('index_default'); - let searchQuoteAnalyzerSelects = find('searchQuoteAnalyzer.select'); + const searchQuoteAnalyzerSelects = find('searchQuoteAnalyzer.select'); expect(searchQuoteAnalyzerSelects.length).toBe(2); expect(searchQuoteAnalyzerSelects.at(0).props().value).toBe('language'); @@ -204,33 +205,33 @@ describe('text datatype', () => { // And the search analyzer select should not exist expect(exists('searchAnalyzer')).toBe(false); - // Uncheck the "Use same analyzer for search" checkbox and wait for the select + // Uncheck the "Use same analyzer for search" checkbox and wait for the search analyzer select await act(async () => { selectCheckBox('useSameAnalyzerForSearchCheckBox.input', false); await waitFor('searchAnalyzer'); }); - let searchAnalyzerValue = find('searchQuoteAnalyzer.select').props().value; + let searchAnalyzerValue = find('searchAnalyzer.select').props().value; expect(searchAnalyzerValue).toEqual('index_default'); await act(async () => { // Change the value of the 3 analyzers setSelectValue('indexAnalyzer.select', 'standard'); setSelectValue('searchAnalyzer.select', 'simple'); - setSelectValue('searchQuoteAnalyzer.select', 'whitespace'); + setSelectValue(find('searchQuoteAnalyzer.select').at(0), 'whitespace'); }); // Make sure the second dropdown select has been removed - searchQuoteAnalyzerSelects = find('searchQuoteAnalyzer.select'); - expect(searchQuoteAnalyzerSelects.length).toBe(1); + await waitForFn( + async () => find('searchQuoteAnalyzer.select').length === 1, + 'Error waiting for the second dropdown select of search quote analyzer to be removed' + ); await act(async () => { // Save & close await updateFieldAndCloseFlyout(); }); - expect(searchQuoteAnalyzerSelects.length).toBe(2); - updatedMappings = { ...updatedMappings, properties: { @@ -326,7 +327,8 @@ describe('text datatype', () => { await waitFor('searchAnalyzer'); setSelectValue('searchAnalyzer.select', updatedSearchAnalyzer); - // Change the searchQuote analyzer using the index default + // Change the searchQuote to use built-in analyzer + // By default it means using the "index default" find('searchQuoteAnalyzer-toggleCustomButton').simulate('click'); await waitFor('searchQuoteAnalyzer'); From 2b631421a459591531f426c83666121de96dd34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 17:16:50 +0200 Subject: [PATCH 21/34] Add mapped field test to make sure the DOM tree represents the mappings provided --- .../datatypes/text_datatype.test.tsx | 2 +- .../client_integration/helpers/index.ts | 3 +- .../helpers/mappings_editor.helpers.tsx | 41 +++++++++-- .../client_integration/mapped_fields.test.tsx | 72 +++++++++++++++++++ .../fields/fields_list_item.tsx | 20 +++--- .../components/mappings_editor/lib/utils.ts | 2 +- 6 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 8db4f1f113955..83746533693bf 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -53,7 +53,7 @@ describe('text datatype', () => { await act(async () => { testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); // Make sure all the fields are expanded and present in the DOM - await testBed.actions.expandAllFields(); + await testBed.actions.expandAllFieldsAndReturnMetadata(); }); const { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts index 050d0e2635a76..df3d3e9fe2594 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts @@ -6,6 +6,7 @@ import { setup as mappingsEditorSetup, MappingsEditorTestBed, + DomFields, getDataForwardedFactory, } from './mappings_editor.helpers'; @@ -20,4 +21,4 @@ export const componentHelpers = { mappingsEditor: { setup: mappingsEditorSetup, getDataForwardedFactory }, }; -export { MappingsEditorTestBed }; +export { MappingsEditorTestBed, DomFields }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 71e1b5db63794..fcaa003f06c0b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import { registerTestBed, TestBed, nextTick } from '../../../../../../../../../test_utils'; +import { getChildFieldsName } from '../../../lib'; import { MappingsEditor } from '../../../mappings_editor'; jest.mock('@elastic/eui', () => ({ @@ -43,9 +44,23 @@ jest.mock('@elastic/eui', () => ({ ), })); +export interface DomFields { + [key: string]: { + type: string; + properties?: DomFields; + fields?: DomFields; + }; +} + const createActions = (testBed: TestBed) => { const { find, exists, waitFor, waitForFn, form, component } = testBed; + const getFieldInfo = (testSubjectField: string): { name: string; type: string } => { + const name = find(`${testSubjectField}-fieldName` as TestSubjects).text(); + const type = find(`${testSubjectField}-datatype` as TestSubjects).props()['data-type-value']; + return { name, type }; + }; + const expandField = async ( field: ReactWrapper ): Promise<{ isExpanded: boolean; testSubjectField: string }> => { @@ -79,12 +94,15 @@ const createActions = (testBed: TestBed) => { }; /** - * Expand all the children of a field. + * Expand all the children of a field and return a metadata object of the fields found in the DOM. * * @param fieldName The field under wich we want to expand all the children. * If no fieldName is provided, we expand all the **root** level fields. */ - const expandAllFields = async (fieldName?: string) => { + const expandAllFieldsAndReturnMetadata = async ( + fieldName?: string, + domTreeMetadata: DomFields = {} + ): Promise => { const fields = find( fieldName ? (`${fieldName}.fieldsList.fieldsListItem` as TestSubjects) : 'fieldsListItem' ).map(wrapper => wrapper); // convert to Array for our for of loop below @@ -92,11 +110,26 @@ const createActions = (testBed: TestBed) => { for (const field of fields) { const { isExpanded, testSubjectField } = await expandField(field); + // Read the info from the DOM about that field and add it to our domFieldMeta + const { name, type } = getFieldInfo(testSubjectField); + domTreeMetadata[name] = { + type, + }; + if (isExpanded) { + // Update our metadata object + const childFieldName = getChildFieldsName(type as any)!; + domTreeMetadata[name][childFieldName] = {}; + // Expand its children - await expandAllFields(testSubjectField); + await expandAllFieldsAndReturnMetadata( + testSubjectField, + domTreeMetadata[name][childFieldName] + ); } } + + return domTreeMetadata; }; // Get a nested field in the rendered DOM tree @@ -214,7 +247,7 @@ const createActions = (testBed: TestBed) => { selectTab, getFieldAt, addField, - expandAllFields, + expandAllFieldsAndReturnMetadata, startEditField, updateFieldAndCloseFlyout, showAdvancedSettings, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx new file mode 100644 index 0000000000000..d6e10f5126cd8 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx @@ -0,0 +1,72 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import { componentHelpers, MappingsEditorTestBed, DomFields } from './helpers'; + +const { setup } = componentHelpers.mappingsEditor; +const onUpdateHandler = jest.fn(); + +describe('Mapped fields', () => { + afterEach(() => { + onUpdateHandler.mockReset(); + }); + + describe('', () => { + let testBed: MappingsEditorTestBed; + const defaultMappings = { + properties: { + myField: { + type: 'text', + fields: { + raw: { + type: 'keyword', + }, + simpleAnalyzer: { + type: 'text', + }, + }, + }, + myObject: { + type: 'object', + properties: { + deeplyNested: { + type: 'object', + properties: { + title: { + type: 'text', + fields: { + raw: { type: 'keyword' }, + }, + }, + }, + }, + }, + }, + }, + }; + + test('should correctly represent the fields in the DOM tree', async () => { + await act(async () => { + testBed = await setup({ + defaultValue: defaultMappings, + onUpdate: onUpdateHandler, + }); + }); + + const { + actions: { expandAllFieldsAndReturnMetadata }, + } = testBed; + + let domTreeMetadata: DomFields = {}; + await act(async () => { + domTreeMetadata = await expandAllFieldsAndReturnMetadata(); + }); + + expect(domTreeMetadata).toEqual(defaultMappings.properties); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index cc09d3e08bddb..7b2e57f1215af 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -17,11 +17,7 @@ import { i18n } from '@kbn/i18n'; import { NormalizedField, NormalizedFields } from '../../../types'; import { getTypeLabelFromType } from '../../../lib'; -import { - TYPE_DEFINITION, - CHILD_FIELD_INDENT_SIZE, - LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER, -} from '../../../constants'; +import { CHILD_FIELD_INDENT_SIZE, LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER } from '../../../constants'; import { FieldsList } from './fields_list'; import { CreateField } from './create_field'; @@ -192,12 +188,14 @@ function FieldListItemComponent( ); }; + const dataTestSubj = `${path.join('')}Field`; + return (
    • {source.name} - + {isMultiField ? i18n.translate('xpack.idxMgmt.mappingsEditor.multiFieldBadgeLabel', { defaultMessage: '{dataType} multi-field', values: { - dataType: TYPE_DEFINITION[source.type].label, + dataType: getTypeLabelFromType(source.type), }, }) : getTypeLabelFromType(source.type)} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index cc5630b1c7759..306e0448df379 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -35,7 +35,7 @@ import { TreeItem } from '../components/tree'; export const getUniqueId = () => uuid.v4(); -const getChildFieldsName = (dataType: DataType): ChildFieldName | undefined => { +export const getChildFieldsName = (dataType: DataType): ChildFieldName | undefined => { if (dataType === 'text' || dataType === 'keyword') { return 'fields'; } else if (dataType === 'object' || dataType === 'nested') { From 4e42d31dbd9bf10cf87ce83ced7d4ee1f7e9cc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 17:32:30 +0200 Subject: [PATCH 22/34] Refactor "defaultValue" to "value" to respect convention --- .../datatypes/text_datatype.test.tsx | 8 +++--- .../client_integration/mapped_fields.test.tsx | 2 +- .../mappings_editor.test.tsx | 14 +++++----- .../configuration_form/configuration_form.tsx | 10 +++---- .../templates_form/templates_form.tsx | 10 +++---- .../mappings_editor/mappings_editor.tsx | 18 ++++++------ .../mappings_editor/mappings_state.tsx | 28 +++++++++---------- .../template_form/steps/step_mappings.tsx | 2 +- 8 files changed, 45 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 83746533693bf..4ed955049b221 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -51,7 +51,7 @@ describe('text datatype', () => { }; await act(async () => { - testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); // Make sure all the fields are expanded and present in the DOM await testBed.actions.expandAllFieldsAndReturnMetadata(); }); @@ -152,7 +152,7 @@ describe('text datatype', () => { }, }; - testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); const { find, @@ -287,7 +287,7 @@ describe('text datatype', () => { }, }; - testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); const { find, @@ -387,7 +387,7 @@ describe('text datatype', () => { }; testBed = await setup({ - defaultValue: defaultMappings, + value: defaultMappings, onUpdate: onUpdateHandler, indexSettings, }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx index d6e10f5126cd8..55828f165574b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx @@ -52,7 +52,7 @@ describe('Mapped fields', () => { test('should correctly represent the fields in the DOM tree', async () => { await act(async () => { testBed = await setup({ - defaultValue: defaultMappings, + value: defaultMappings, onUpdate: onUpdateHandler, }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index bdf1c2bad3ace..231049d2d5e70 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -23,7 +23,7 @@ describe('', () => { describe('multiple mappings detection', () => { test('should show a warning when multiple mappings are detected', async () => { - const defaultValue = { + const value = { type1: { properties: { name1: { @@ -39,7 +39,7 @@ describe('', () => { }, }, }; - const testBed = await setup({ onUpdate: onUpdateHandler, defaultValue }); + const testBed = await setup({ onUpdate: onUpdateHandler, value }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); @@ -48,14 +48,14 @@ describe('', () => { }); test('should not show a warning when mappings a single-type', async () => { - const defaultValue = { + const value = { properties: { name1: { type: 'keyword', }, }, }; - const testBed = await setup({ onUpdate: onUpdateHandler, defaultValue }); + const testBed = await setup({ onUpdate: onUpdateHandler, value }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); @@ -72,7 +72,7 @@ describe('', () => { let testBed: MappingsEditorTestBed; beforeEach(async () => { - testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); }); test('should keep the changes when switching tabs', async () => { @@ -220,10 +220,10 @@ describe('', () => { let testBed: MappingsEditorTestBed; beforeEach(async () => { - testBed = await setup({ defaultValue: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); }); - test('props.defaultValue => should prepopulate the editor data', async () => { + test('props.value => should prepopulate the editor data', async () => { const { actions: { selectTab, getJsonEditorValue, getComboBoxValue, getToggleValue }, find, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index f307f0b378352..c84756cab8e88 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -18,7 +18,7 @@ import { configurationFormSchema } from './configuration_form_schema'; type MappingsConfiguration = Types['MappingsConfiguration']; interface Props { - defaultValue?: MappingsConfiguration; + value?: MappingsConfiguration; } const stringifyJson = (json: GenericObject) => @@ -87,14 +87,14 @@ const formDeserializer = (formData: GenericObject) => { }; }; -export const ConfigurationForm = React.memo(({ defaultValue }: Props) => { +export const ConfigurationForm = React.memo(({ value }: Props) => { const didMountRef = useRef(false); const { form } = useForm({ schema: configurationFormSchema, serializer: formSerializer, deserializer: formDeserializer, - defaultValue, + defaultValue: value, }); const dispatch = useDispatch(); @@ -115,14 +115,14 @@ export const ConfigurationForm = React.memo(({ defaultValue }: Props) => { useEffect(() => { if (didMountRef.current) { - // If the defaultValue has changed (it probably means that we have loaded a new JSON) + // If the value has changed (it probably means that we have loaded a new JSON) // we need to reset the form to update the fields values. form.reset({ resetValues: true }); } else { // Avoid reseting the form on component mount. didMountRef.current = true; } - }, [defaultValue]); // eslint-disable-line react-hooks/exhaustive-deps + }, [value]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { return () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx index 3c4d6b08ebe44..f4aa17bf6fed9 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx @@ -16,7 +16,7 @@ import { documentationService } from '../../../../services/documentation'; type MappingsTemplates = Types['MappingsTemplates']; interface Props { - defaultValue?: MappingsTemplates; + value?: MappingsTemplates; } const stringifyJson = (json: { [key: string]: any }) => @@ -50,14 +50,14 @@ const formDeserializer = (formData: { [key: string]: any }) => { }; }; -export const TemplatesForm = React.memo(({ defaultValue }: Props) => { +export const TemplatesForm = React.memo(({ value }: Props) => { const didMountRef = useRef(false); const { form } = useForm({ schema: templatesFormSchema, serializer: formSerializer, deserializer: formDeserializer, - defaultValue, + defaultValue: value, }); const dispatch = useDispatch(); @@ -73,14 +73,14 @@ export const TemplatesForm = React.memo(({ defaultValue }: Props) => { useEffect(() => { if (didMountRef.current) { - // If the defaultValue has changed (it probably means that we have loaded a new JSON) + // If the value has changed (it probably means that we have loaded a new JSON) // we need to reset the form to update the fields values. form.reset({ resetValues: true }); } else { // Avoid reseting the form on component mount. didMountRef.current = true; } - }, [defaultValue]); // eslint-disable-line react-hooks/exhaustive-deps + }, [value]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { return () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx index 316fee55526a3..11a1ef45b522f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx @@ -22,17 +22,17 @@ import { IndexSettingsProvider } from './index_settings_context'; interface Props { onUpdate: MappingsStateProps['onUpdate']; - defaultValue?: { [key: string]: any }; + value?: { [key: string]: any }; indexSettings?: IndexSettings; } type TabName = 'fields' | 'advanced' | 'templates'; -export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSettings }: Props) => { +export const MappingsEditor = React.memo(({ onUpdate, value, indexSettings }: Props) => { const [selectedTab, selectTab] = useState('fields'); const { parsedDefaultValue, multipleMappingsDeclared } = useMemo(() => { - const mappingsDefinition = extractMappingsDefinition(defaultValue); + const mappingsDefinition = extractMappingsDefinition(value); if (mappingsDefinition === null) { return { multipleMappingsDeclared: true }; @@ -67,18 +67,18 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting }; return { parsedDefaultValue: parsed, multipleMappingsDeclared: false }; - }, [defaultValue]); + }, [value]); useEffect(() => { if (multipleMappingsDeclared) { // We set the data getter here as the user won't be able to make any changes onUpdate({ - getData: () => defaultValue! as Types['Mappings'], + getData: () => value! as Types['Mappings'], validate: () => Promise.resolve(true), isValid: true, }); } - }, [multipleMappingsDeclared, onUpdate, defaultValue]); + }, [multipleMappingsDeclared, onUpdate, value]); const changeTab = async (tab: TabName, state: State) => { if (selectedTab === 'advanced') { @@ -108,12 +108,12 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting ) : ( - + {({ state }) => { const tabToContentMap = { fields: , - templates: , - advanced: , + templates: , + advanced: , }; return ( diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx index 4a0d948e0e94b..4a06ef97bd65b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx @@ -43,7 +43,7 @@ const DispatchContext = createContext(undefined); export interface Props { children: (params: { state: State }) => React.ReactNode; - defaultValue: { + value: { templates: MappingsTemplates; configuration: MappingsConfiguration; fields: { [key: string]: Field }; @@ -51,28 +51,26 @@ export interface Props { onUpdate: OnUpdateHandler; } -export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: Props) => { +export const MappingsState = React.memo(({ children, onUpdate, value }: Props) => { const didMountRef = useRef(false); - const parsedFieldsDefaultValue = useMemo(() => normalize(defaultValue.fields), [ - defaultValue.fields, - ]); + const parsedFieldsDefaultValue = useMemo(() => normalize(value.fields), [value.fields]); const initialState: State = { isValid: undefined, configuration: { - defaultValue: defaultValue.configuration, + defaultValue: value.configuration, data: { - raw: defaultValue.configuration, - format: () => defaultValue.configuration, + raw: value.configuration, + format: () => value.configuration, }, validate: () => Promise.resolve(true), }, templates: { - defaultValue: defaultValue.templates, + defaultValue: value.templates, data: { - raw: defaultValue.templates, - format: () => defaultValue.templates, + raw: value.templates, + format: () => value.templates, }, validate: () => Promise.resolve(true), }, @@ -175,22 +173,22 @@ export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: P useEffect(() => { /** - * If the defaultValue has changed that probably means that we have loaded + * If the value has changed that probably means that we have loaded * new data from JSON. We need to update our state with the new mappings. */ if (didMountRef.current) { dispatch({ type: 'editor.replaceMappings', value: { - configuration: defaultValue.configuration, - templates: defaultValue.templates, + configuration: value.configuration, + templates: value.templates, fields: parsedFieldsDefaultValue, }, }); } else { didMountRef.current = true; } - }, [defaultValue, parsedFieldsDefaultValue]); + }, [value, parsedFieldsDefaultValue]); return ( diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx index cf9b57dcbcb14..e506c24af3015 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx @@ -101,7 +101,7 @@ export const StepMappings: React.FunctionComponent = ({ {/* Mappings code editor */} From d978d2a7d9a99d64d5c2b04a643a2966691a17d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 17:39:31 +0200 Subject: [PATCH 23/34] Refactor "onUpdate" to "onChange" to respect convention --- .../datatypes/text_datatype.test.tsx | 14 +++++++------- .../helpers/mappings_editor.helpers.tsx | 4 ++-- .../client_integration/mapped_fields.test.tsx | 6 +++--- .../client_integration/mappings_editor.test.tsx | 16 ++++++++-------- .../mappings_editor/mappings_editor.tsx | 10 +++++----- .../mappings_editor/mappings_state.tsx | 8 ++++---- .../template_form/steps/step_mappings.tsx | 2 +- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 4ed955049b221..7f6f8304cbdd2 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -9,8 +9,8 @@ import { componentHelpers, MappingsEditorTestBed } from '../helpers'; import { getFieldConfig } from '../../../lib'; const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; -const onUpdateHandler = jest.fn(); -const getDataForwarded = getDataForwardedFactory(onUpdateHandler); +const onChangeHandler = jest.fn(); +const getDataForwarded = getDataForwardedFactory(onChangeHandler); // Parameters automatically added to the text datatype when saved (with their default values) const defaultTextParameters = { @@ -32,7 +32,7 @@ describe('text datatype', () => { let data: any; afterEach(() => { - onUpdateHandler.mockReset(); + onChangeHandler.mockReset(); }); test('flyout details initial view', async () => { @@ -51,7 +51,7 @@ describe('text datatype', () => { }; await act(async () => { - testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); // Make sure all the fields are expanded and present in the DOM await testBed.actions.expandAllFieldsAndReturnMetadata(); }); @@ -152,7 +152,7 @@ describe('text datatype', () => { }, }; - testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); const { find, @@ -287,7 +287,7 @@ describe('text datatype', () => { }, }; - testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); const { find, @@ -388,7 +388,7 @@ describe('text datatype', () => { testBed = await setup({ value: defaultMappings, - onUpdate: onUpdateHandler, + onChange: onChangeHandler, indexSettings, }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index fcaa003f06c0b..1269de2f02c15 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -273,13 +273,13 @@ export const setup = async (props: any = { onUpdate() {} }): Promise) => { +export const getDataForwardedFactory = (onChangeHandler: jest.MockedFunction) => { /** * Helper to access the latest data sent to the onUpdate handler back to the consumer of the . * Read the latest call with its argument passed and build the mappings object from it. */ return async () => { - const mockCalls = onUpdateHandler.mock.calls; + const mockCalls = onChangeHandler.mock.calls; if (mockCalls.length === 0) { throw new Error( diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx index 55828f165574b..6276b28633fe6 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx @@ -8,11 +8,11 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed, DomFields } from './helpers'; const { setup } = componentHelpers.mappingsEditor; -const onUpdateHandler = jest.fn(); +const onChangeHandler = jest.fn(); describe('Mapped fields', () => { afterEach(() => { - onUpdateHandler.mockReset(); + onChangeHandler.mockReset(); }); describe('', () => { @@ -53,7 +53,7 @@ describe('Mapped fields', () => { await act(async () => { testBed = await setup({ value: defaultMappings, - onUpdate: onUpdateHandler, + onChange: onChangeHandler, }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 231049d2d5e70..d7ce71489dc8a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -8,8 +8,8 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from './helpers'; const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; -const onUpdateHandler = jest.fn(); -const getDataForwarded = getDataForwardedFactory(onUpdateHandler); +const onChangeHandler = jest.fn(); +const getDataForwarded = getDataForwardedFactory(onChangeHandler); describe('', () => { /** @@ -18,7 +18,7 @@ describe('', () => { let data: any; afterEach(() => { - onUpdateHandler.mockReset(); + onChangeHandler.mockReset(); }); describe('multiple mappings detection', () => { @@ -39,7 +39,7 @@ describe('', () => { }, }, }; - const testBed = await setup({ onUpdate: onUpdateHandler, value }); + const testBed = await setup({ onChange: onChangeHandler, value }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); @@ -55,7 +55,7 @@ describe('', () => { }, }, }; - const testBed = await setup({ onUpdate: onUpdateHandler, value }); + const testBed = await setup({ onChange: onChangeHandler, value }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); @@ -72,7 +72,7 @@ describe('', () => { let testBed: MappingsEditorTestBed; beforeEach(async () => { - testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); }); test('should keep the changes when switching tabs', async () => { @@ -220,7 +220,7 @@ describe('', () => { let testBed: MappingsEditorTestBed; beforeEach(async () => { - testBed = await setup({ value: defaultMappings, onUpdate: onUpdateHandler }); + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); }); test('props.value => should prepopulate the editor data', async () => { @@ -278,7 +278,7 @@ describe('', () => { expect(isRoutingRequired).toBe(defaultMappings._routing.required); }); - test('props.onUpdate() => should forward the changes to the consumer component', async () => { + test('props.onChange() => should forward the changes to the consumer component', async () => { let updatedMappings = { ...defaultMappings }; const { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx index 11a1ef45b522f..46dc1176f62b4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx @@ -21,14 +21,14 @@ import { MappingsState, Props as MappingsStateProps, Types } from './mappings_st import { IndexSettingsProvider } from './index_settings_context'; interface Props { - onUpdate: MappingsStateProps['onUpdate']; + onChange: MappingsStateProps['onChange']; value?: { [key: string]: any }; indexSettings?: IndexSettings; } type TabName = 'fields' | 'advanced' | 'templates'; -export const MappingsEditor = React.memo(({ onUpdate, value, indexSettings }: Props) => { +export const MappingsEditor = React.memo(({ onChange, value, indexSettings }: Props) => { const [selectedTab, selectTab] = useState('fields'); const { parsedDefaultValue, multipleMappingsDeclared } = useMemo(() => { @@ -72,13 +72,13 @@ export const MappingsEditor = React.memo(({ onUpdate, value, indexSettings }: Pr useEffect(() => { if (multipleMappingsDeclared) { // We set the data getter here as the user won't be able to make any changes - onUpdate({ + onChange({ getData: () => value! as Types['Mappings'], validate: () => Promise.resolve(true), isValid: true, }); } - }, [multipleMappingsDeclared, onUpdate, value]); + }, [multipleMappingsDeclared, onChange, value]); const changeTab = async (tab: TabName, state: State) => { if (selectedTab === 'advanced') { @@ -108,7 +108,7 @@ export const MappingsEditor = React.memo(({ onUpdate, value, indexSettings }: Pr ) : ( - + {({ state }) => { const tabToContentMap = { fields: , diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx index 4a06ef97bd65b..280ea5c3dd28c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state.tsx @@ -48,10 +48,10 @@ export interface Props { configuration: MappingsConfiguration; fields: { [key: string]: Field }; }; - onUpdate: OnUpdateHandler; + onChange: OnUpdateHandler; } -export const MappingsState = React.memo(({ children, onUpdate, value }: Props) => { +export const MappingsState = React.memo(({ children, onChange, value }: Props) => { const didMountRef = useRef(false); const parsedFieldsDefaultValue = useMemo(() => normalize(value.fields), [value.fields]); @@ -103,7 +103,7 @@ export const MappingsState = React.memo(({ children, onUpdate, value }: Props) = const bypassFieldFormValidation = state.documentFields.status === 'creatingField' && emptyNameValue; - onUpdate({ + onChange({ // Output a mappings object from the user's input. getData: (isValid: boolean) => { let nextState = state; @@ -169,7 +169,7 @@ export const MappingsState = React.memo(({ children, onUpdate, value }: Props) = }, isValid: state.isValid, }); - }, [state, onUpdate]); + }, [state, onChange]); useEffect(() => { /** diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx index e506c24af3015..d74dd435ecdae 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings.tsx @@ -102,7 +102,7 @@ export const StepMappings: React.FunctionComponent = ({ {/* Mappings code editor */} From b45953b431bfa4ba42c1162ba1d8f1a449a40a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 18:50:22 +0200 Subject: [PATCH 24/34] Add test to make sure the MappingsEditor can be controlled --- .../client_integration/mapped_fields.test.tsx | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx index 6276b28633fe6..a727cc0e022ab 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx @@ -5,7 +5,7 @@ */ import { act } from 'react-dom/test-utils'; -import { componentHelpers, MappingsEditorTestBed, DomFields } from './helpers'; +import { componentHelpers, MappingsEditorTestBed, DomFields, nextTick } from './helpers'; const { setup } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); @@ -68,5 +68,43 @@ describe('Mapped fields', () => { expect(domTreeMetadata).toEqual(defaultMappings.properties); }); + + test('should allow to be controlled by parent component and update on prop change', async () => { + await act(async () => { + testBed = await setup({ + value: defaultMappings, + onChange: onChangeHandler, + }); + }); + + const { + component, + setProps, + actions: { expandAllFieldsAndReturnMetadata }, + } = testBed; + + let domTreeMetadata: DomFields = {}; + await act(async () => { + domTreeMetadata = await expandAllFieldsAndReturnMetadata(); + }); + + expect(domTreeMetadata).toEqual(defaultMappings.properties); + + // Change the `value` prop of our + const newMappings = { properties: { hello: { type: 'text' } } }; + + await act(async () => { + setProps({ value: newMappings }); + + // Don't ask me why but the 3 following lines are all required + component.update(); + await nextTick(); + component.update(); + + domTreeMetadata = await expandAllFieldsAndReturnMetadata(); + }); + + expect(domTreeMetadata).toEqual(newMappings.properties); + }); }); }); From 41735fb708ae518296840df4e95f036dbb67de28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 24 Apr 2020 18:58:23 +0200 Subject: [PATCH 25/34] Add TS doc on how to mock --- x-pack/test_utils/testbed/types.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/x-pack/test_utils/testbed/types.ts b/x-pack/test_utils/testbed/types.ts index fbd254d225425..4cc7deac60156 100644 --- a/x-pack/test_utils/testbed/types.ts +++ b/x-pack/test_utils/testbed/types.ts @@ -41,7 +41,7 @@ export interface TestBed { * * @example * - ```ts + ```typescript find('nameInput'); // or more specific, // "nameInput" is a child of "myForm" @@ -82,7 +82,22 @@ export interface TestBed { ) => Promise | void; /** * Set the value of a or a mocked - * + * For the you need to mock it like this + * + ```typescript + jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + EuiSuperSelect: (props: any) => ( + { + props.onChange(e.target.value); + }} + /> + ), + })); + ``` * @param select The form select. Can either be a data-test-subj or a reactWrapper (can be a nested path. e.g. "myForm.myInput"). * @param value The value to set */ From d797ae2a85c63ec40e24c49a5e5f0f1e111d5c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Mon, 27 Apr 2020 10:00:51 +0200 Subject: [PATCH 26/34] Restructure tests --- .../datatypes/text_datatype.test.tsx | 70 ++++--------------- .../client_integration/edit_field.test.tsx | 70 +++++++++++++++++++ .../client_integration/mapped_fields.test.tsx | 2 +- .../mappings_editor.test.tsx | 36 +++++++++- 4 files changed, 117 insertions(+), 61 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 7f6f8304cbdd2..a60189028fc5b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -13,7 +13,7 @@ const onChangeHandler = jest.fn(); const getDataForwarded = getDataForwardedFactory(onChangeHandler); // Parameters automatically added to the text datatype when saved (with their default values) -const defaultTextParameters = { +export const defaultTextParameters = { eager_global_ordinals: false, fielddata: false, index: true, @@ -35,21 +35,19 @@ describe('text datatype', () => { onChangeHandler.mockReset(); }); - test('flyout details initial view', async () => { + test('initial view and default parameters values', async () => { const defaultMappings = { + _meta: {}, + _source: {}, properties: { - user: { - properties: { - address: { - properties: { - street: { type: 'text' }, - }, - }, - }, + myTextField: { + type: 'text', }, }, }; + const updatedMappings = { ...defaultMappings }; + await act(async () => { testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); // Make sure all the fields are expanded and present in the DOM @@ -57,72 +55,28 @@ describe('text datatype', () => { }); const { - find, - actions: { startEditField, getToggleValue, showAdvancedSettings, updateFieldAndCloseFlyout }, + actions: { startEditField, getToggleValue, updateFieldAndCloseFlyout }, } = testBed; - const fieldPathToEdit = ['user', 'address', 'street']; - const fieldName = fieldPathToEdit[fieldPathToEdit.length - 1]; // Open the flyout to edit the field await act(async () => { - await startEditField(fieldPathToEdit.join('.')); + await startEditField('myTextField'); }); - // It should have the correct title - expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field '${fieldName}'`); - - // It should have the correct field path - expect(find('mappingsEditorFieldEdit.fieldPath').text()).toEqual(fieldPathToEdit.join(' > ')); - // It should have searchable ("index" param) active by default const indexFieldConfig = getFieldConfig('index'); expect(getToggleValue('indexParameter.formRowToggle')).toBe(indexFieldConfig.defaultValue); - // The advanced settings should be hidden initially - expect(find('mappingsEditorFieldEdit.advancedSettings').props().style.display).toEqual('none'); - - await act(async () => { - await showAdvancedSettings(); - }); - - // TODO: find a way to automate testing that all expected fields are present - // and have their default value correctly set - - const updatedMappings = { - _meta: {}, // Was not defined so an empty object is returned by the editor - _source: {}, // Was not defined so an empty object is returned by the editor - ...defaultMappings, - properties: { - user: { - type: 'object', // Was not defined so it defaults to "object" type - properties: { - address: { - type: 'object', // Was not defined so it defaults to "object" type - properties: { - street: { type: 'text' }, - }, - }, - }, - }, - }, - }; - - await act(async () => { - ({ data } = await getDataForwarded()); - }); - expect(data).toEqual(updatedMappings); - // Save the field and close the flyout await act(async () => { await updateFieldAndCloseFlyout(); }); - const streetField = { + // It should have the default parameters values added + updatedMappings.properties.myTextField = { type: 'text', - // All the default parameters values have been added ...defaultTextParameters, }; - updatedMappings.properties.user.properties.address.properties.street = streetField; ({ data } = await getDataForwarded()); expect(data).toEqual(updatedMappings); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx new file mode 100644 index 0000000000000..a278aadf8374a --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx @@ -0,0 +1,70 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import { componentHelpers, MappingsEditorTestBed } from './helpers'; + +const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; +const onChangeHandler = jest.fn(); +const getDataForwarded = getDataForwardedFactory(onChangeHandler); + +describe('Mappings editor: edit field', () => { + /** + * Variable to store the mappings data forwarded to the consumer component + */ + let data: any; + let testBed: MappingsEditorTestBed; + + afterEach(() => { + onChangeHandler.mockReset(); + }); + + test('should open a flyout with the correct field to edit', async () => { + const defaultMappings = { + properties: { + user: { + type: 'object', + properties: { + address: { + type: 'object', + properties: { + street: { type: 'text' }, + }, + }, + }, + }, + }, + }; + + await act(async () => { + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); + // Make sure all the fields are expanded and present in the DOM + await testBed.actions.expandAllFieldsAndReturnMetadata(); + }); + + const { + find, + actions: { startEditField }, + } = testBed; + const fieldPathToEdit = ['user', 'address', 'street']; + const fieldName = fieldPathToEdit[fieldPathToEdit.length - 1]; + + // Open the flyout to edit the field + await act(async () => { + await startEditField(fieldPathToEdit.join('.')); + }); + + // It should have the correct title + expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field '${fieldName}'`); + + // It should have the correct field path + expect(find('mappingsEditorFieldEdit.fieldPath').text()).toEqual(fieldPathToEdit.join(' > ')); + + // The advanced settings should be hidden initially + expect(find('mappingsEditorFieldEdit.advancedSettings').props().style.display).toEqual('none'); + }); + +}); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx index a727cc0e022ab..8def5c024525b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx @@ -10,7 +10,7 @@ import { componentHelpers, MappingsEditorTestBed, DomFields, nextTick } from './ const { setup } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); -describe('Mapped fields', () => { +describe('Mappings editor: mapped fields', () => { afterEach(() => { onChangeHandler.mockReset(); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index d7ce71489dc8a..84f298aa32754 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -11,7 +11,7 @@ const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); const getDataForwarded = getDataForwardedFactory(onChangeHandler); -describe('', () => { +describe('Mappings editor: core', () => { /** * Variable to store the mappings data forwarded to the consumer component */ @@ -21,6 +21,38 @@ describe('', () => { onChangeHandler.mockReset(); }); + test('default behaviour', async () => { + const defaultMappings = { + properties: { + user: { + // No type defined for user + properties: { + name: { type: 'text' }, + }, + }, + }, + }; + + await setup({ value: defaultMappings, onChange: onChangeHandler }); + + const expectedMappings = { + _meta: {}, // Was not defined so an empty object is returned + _source: {}, // Was not defined so an empty object is returned + ...defaultMappings, + properties: { + user: { + type: 'object', // Was not defined so it defaults to "object" type + ...defaultMappings.properties.user, + }, + }, + }; + + await act(async () => { + ({ data } = await getDataForwarded()); + }); + expect(data).toEqual(expectedMappings); + }); + describe('multiple mappings detection', () => { test('should show a warning when multiple mappings are detected', async () => { const value = { @@ -186,7 +218,7 @@ describe('', () => { describe('component props', () => { /** - * Note: the "indexSettings" prop will be tested along with the "analyzer" parameter on a field, + * Note: the "indexSettings" prop will be tested along with the "analyzer" parameter on a text datatype field, * as it is the only place where it is consumed by the mappings editor. */ const defaultMappings: any = { From 21b5b8fadf909962a9d278ae9bcf1dcbdb0b0bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Mon, 27 Apr 2020 11:25:37 +0200 Subject: [PATCH 27/34] Test that the edit field form updates when switching datatype --- .../client_integration/datatypes/index.ts | 8 ++ .../datatypes/shape_datatype.test.tsx | 77 +++++++++++++++ .../datatypes/text_datatype.test.tsx | 99 ++++++++++++------- .../client_integration/edit_field.test.tsx | 64 +++++++++++- .../helpers/mappings_editor.helpers.tsx | 7 +- 5 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/index.ts create mode 100644 x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/index.ts new file mode 100644 index 0000000000000..eac68770d3de2 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { defaultShapeParameters } from './shape_datatype.test'; +export { defaultTextParameters } from './text_datatype.test'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx new file mode 100644 index 0000000000000..6b8f8f368a1bd --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx @@ -0,0 +1,77 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import { componentHelpers, MappingsEditorTestBed } from '../helpers'; + +const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; +const onChangeHandler = jest.fn(); +const getDataForwarded = getDataForwardedFactory(onChangeHandler); + +// Parameters automatically added to the shape datatype when saved (with the default values) +export const defaultShapeParameters = { + coerce: false, + ignore_malformed: false, + ignore_z_value: true, +}; + +describe('Mappings editor: shape datatype', () => { + let testBed: MappingsEditorTestBed; + + /** + * Variable to store the mappings data forwarded to the consumer component + */ + let data: any; + + test('initial view and default parameters values', async () => { + const defaultMappings = { + _meta: {}, + _source: {}, + properties: { + myField: { + type: 'shape', + }, + }, + }; + + const updatedMappings = { ...defaultMappings }; + + await act(async () => { + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + + const { + exists, + waitForFn, + actions: { startEditField, updateFieldAndCloseFlyout }, + } = testBed; + + // Open the flyout to edit the field + await act(async () => { + await startEditField('myField'); + }); + + // Save the field and close the flyout + await act(async () => { + await updateFieldAndCloseFlyout(); + }); + + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + + // It should have the default parameters values added + updatedMappings.properties.myField = { + type: 'shape', + ...defaultShapeParameters, + }; + + ({ data } = await getDataForwarded()); + expect(data).toEqual(updatedMappings); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index a60189028fc5b..c4c1f79c55f25 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -12,7 +12,7 @@ const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); const getDataForwarded = getDataForwardedFactory(onChangeHandler); -// Parameters automatically added to the text datatype when saved (with their default values) +// Parameters automatically added to the text datatype when saved (with the default values) export const defaultTextParameters = { eager_global_ordinals: false, fielddata: false, @@ -23,7 +23,7 @@ export const defaultTextParameters = { store: false, }; -describe('text datatype', () => { +describe('Mappings editor: text datatype', () => { let testBed: MappingsEditorTestBed; /** @@ -40,7 +40,7 @@ describe('text datatype', () => { _meta: {}, _source: {}, properties: { - myTextField: { + myField: { type: 'text', }, }, @@ -50,17 +50,17 @@ describe('text datatype', () => { await act(async () => { testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); - // Make sure all the fields are expanded and present in the DOM - await testBed.actions.expandAllFieldsAndReturnMetadata(); }); const { + exists, + waitForFn, actions: { startEditField, getToggleValue, updateFieldAndCloseFlyout }, } = testBed; // Open the flyout to edit the field await act(async () => { - await startEditField('myTextField'); + await startEditField('myField'); }); // It should have searchable ("index" param) active by default @@ -72,8 +72,13 @@ describe('text datatype', () => { await updateFieldAndCloseFlyout(); }); + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + // It should have the default parameters values added - updatedMappings.properties.myTextField = { + updatedMappings.properties.myField = { type: 'text', ...defaultTextParameters, }; @@ -87,7 +92,7 @@ describe('text datatype', () => { _meta: {}, _source: {}, properties: { - myTextField: { + myField: { type: 'text', // Should have 2 dropdown selects: // The first one set to 'language' and the second one set to 'french @@ -99,8 +104,8 @@ describe('text datatype', () => { let updatedMappings: any = { ...defaultMappings, properties: { - myTextField: { - ...defaultMappings.properties.myTextField, + myField: { + ...defaultMappings.properties.myField, ...defaultTextParameters, }, }, @@ -121,7 +126,7 @@ describe('text datatype', () => { updateFieldAndCloseFlyout, }, } = testBed; - const fieldToEdit = 'myTextField'; + const fieldToEdit = 'myField'; // Start edit and immediately save to have all the default values await startEditField(fieldToEdit); @@ -129,9 +134,15 @@ describe('text datatype', () => { await act(async () => { await updateFieldAndCloseFlyout(); - ({ data } = await getDataForwarded()); }); + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + + ({ data } = await getDataForwarded()); + expect(data).toEqual(updatedMappings); // Re-open the edit panel @@ -147,7 +158,7 @@ describe('text datatype', () => { expect(searchQuoteAnalyzerSelects.length).toBe(2); expect(searchQuoteAnalyzerSelects.at(0).props().value).toBe('language'); expect(searchQuoteAnalyzerSelects.at(1).props().value).toBe( - defaultMappings.properties.myTextField.search_quote_analyzer + defaultMappings.properties.myField.search_quote_analyzer ); // When no "search_analyzer" is defined, the checkBox should be checked @@ -186,11 +197,16 @@ describe('text datatype', () => { await updateFieldAndCloseFlyout(); }); + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + updatedMappings = { ...updatedMappings, properties: { - myTextField: { - ...updatedMappings.properties.myTextField, + myField: { + ...updatedMappings.properties.myField, analyzer: 'standard', search_analyzer: 'simple', search_quote_analyzer: 'whitespace', @@ -222,7 +238,7 @@ describe('text datatype', () => { _meta: {}, _source: {}, properties: { - myTextField: { + myField: { type: 'text', analyzer: 'myCustomIndexAnalyzer', search_analyzer: 'myCustomSearchAnalyzer', @@ -234,8 +250,8 @@ describe('text datatype', () => { let updatedMappings: any = { ...defaultMappings, properties: { - myTextField: { - ...defaultMappings.properties.myTextField, + myField: { + ...defaultMappings.properties.myField, ...defaultTextParameters, }, }, @@ -247,10 +263,11 @@ describe('text datatype', () => { find, exists, waitFor, + waitForFn, form: { setInputValue, setSelectValue }, actions: { startEditField, showAdvancedSettings, updateFieldAndCloseFlyout }, } = testBed; - const fieldToEdit = 'myTextField'; + const fieldToEdit = 'myField'; await startEditField(fieldToEdit); await showAdvancedSettings(); @@ -263,11 +280,9 @@ describe('text datatype', () => { const searchAnalyzerValue = find('searchAnalyzer-custom.input').props().value; const searchQuoteAnalyzerValue = find('searchQuoteAnalyzer-custom.input').props().value; - expect(indexAnalyzerValue).toBe(defaultMappings.properties.myTextField.analyzer); - expect(searchAnalyzerValue).toBe(defaultMappings.properties.myTextField.search_analyzer); - expect(searchQuoteAnalyzerValue).toBe( - defaultMappings.properties.myTextField.search_quote_analyzer - ); + expect(indexAnalyzerValue).toBe(defaultMappings.properties.myField.analyzer); + expect(searchAnalyzerValue).toBe(defaultMappings.properties.myField.search_analyzer); + expect(searchQuoteAnalyzerValue).toBe(defaultMappings.properties.myField.search_quote_analyzer); const updatedIndexAnalyzer = 'updatedAnalyzer'; const updatedSearchAnalyzer = 'whitespace'; @@ -288,14 +303,20 @@ describe('text datatype', () => { // Save & close await updateFieldAndCloseFlyout(); - ({ data } = await getDataForwarded()); }); + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + + ({ data } = await getDataForwarded()); + updatedMappings = { ...updatedMappings, properties: { - myTextField: { - ...updatedMappings.properties.myTextField, + myField: { + ...updatedMappings.properties.myField, analyzer: updatedIndexAnalyzer, search_analyzer: updatedSearchAnalyzer, search_quote_analyzer: undefined, // Index default means not declaring the analyzer @@ -323,7 +344,7 @@ describe('text datatype', () => { _meta: {}, _source: {}, properties: { - myTextField: { + myField: { type: 'text', analyzer: customAnalyzers[0], }, @@ -333,8 +354,8 @@ describe('text datatype', () => { let updatedMappings: any = { ...defaultMappings, properties: { - myTextField: { - ...defaultMappings.properties.myTextField, + myField: { + ...defaultMappings.properties.myField, ...defaultTextParameters, }, }, @@ -348,10 +369,12 @@ describe('text datatype', () => { const { find, + exists, + waitForFn, form: { setSelectValue }, actions: { startEditField, showAdvancedSettings, updateFieldAndCloseFlyout }, } = testBed; - const fieldToEdit = 'myTextField'; + const fieldToEdit = 'myField'; await startEditField(fieldToEdit); await showAdvancedSettings(); @@ -362,7 +385,7 @@ describe('text datatype', () => { expect(indexAnalyzerSelects.length).toBe(2); expect(indexAnalyzerSelects.at(0).props().value).toBe('custom'); expect(indexAnalyzerSelects.at(1).props().value).toBe( - defaultMappings.properties.myTextField.analyzer + defaultMappings.properties.myField.analyzer ); // Access the list of option of the second dropdown select @@ -379,14 +402,20 @@ describe('text datatype', () => { // Save & close await updateFieldAndCloseFlyout(); - ({ data } = await getDataForwarded()); }); + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + + ({ data } = await getDataForwarded()); + updatedMappings = { ...updatedMappings, properties: { - myTextField: { - ...updatedMappings.properties.myTextField, + myField: { + ...updatedMappings.properties.myField, analyzer: customAnalyzers[2], }, }, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx index a278aadf8374a..67dec23c01754 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx @@ -6,7 +6,7 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed } from './helpers'; - +import { defaultTextParameters, defaultShapeParameters } from './datatypes'; const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); const getDataForwarded = getDataForwardedFactory(onChangeHandler); @@ -67,4 +67,66 @@ describe('Mappings editor: edit field', () => { expect(find('mappingsEditorFieldEdit.advancedSettings').props().style.display).toEqual('none'); }); + test('should update form parameters when changing the field datatype', async () => { + const defaultMappings = { + _meta: {}, + _source: {}, + properties: { + myField: { + type: 'text', + ...defaultTextParameters, + }, + }, + }; + + let updatedMappings: any = { ...defaultMappings }; + + await act(async () => { + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + + const { + find, + exists, + waitForFn, + component, + actions: { startEditField, updateFieldAndCloseFlyout }, + } = testBed; + + // Open the flyout, change the field type and save it + await act(async () => { + await startEditField('myField'); + }); + + await act(async () => { + // Change the field type + find('mappingsEditorFieldEdit.fieldType').simulate('change', [ + { label: 'Shape', value: 'shape' }, + ]); + component.update(); + }); + + await act(async () => { + await updateFieldAndCloseFlyout(); + }); + + await waitForFn( + async () => exists('mappingsEditorFieldEdit') === false, + 'Error waiting for the details flyout to close' + ); + + ({ data } = await getDataForwarded()); + + updatedMappings = { + ...updatedMappings, + properties: { + myField: { + type: 'shape', + ...defaultShapeParameters, + }, + }, + }; + + expect(data).toEqual(updatedMappings); + }, 15000); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 1269de2f02c15..eb555f7fc8034 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -172,11 +172,7 @@ const createActions = (testBed: TestBed) => { const updateFieldAndCloseFlyout = async () => { find('mappingsEditorFieldEdit.editFieldUpdateButton').simulate('click'); - - await waitForFn( - async () => exists('mappingsEditorFieldEdit') === false, - 'Error waiting for the details flyout to close' - ); + component.update(); }; const showAdvancedSettings = async () => { @@ -332,6 +328,7 @@ export type TestSubjects = | 'createFieldForm.fieldType' | 'createFieldForm.addButton' | 'mappingsEditorFieldEdit' + | 'mappingsEditorFieldEdit.fieldType' | 'mappingsEditorFieldEdit.editFieldUpdateButton' | 'mappingsEditorFieldEdit.flyoutTitle' | 'mappingsEditorFieldEdit.documentationLink' From 760efa27443692b3d57a37fb9eda40ddc73ec79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 29 Apr 2020 10:07:06 +0200 Subject: [PATCH 28/34] Move "waitFor" outside act() calls --- .../datatypes/shape_datatype.test.tsx | 3 ++ .../datatypes/text_datatype.test.tsx | 32 +++++++++++++++---- .../client_integration/edit_field.test.tsx | 6 ++++ .../helpers/mappings_editor.helpers.tsx | 9 +++--- .../mappings_editor.test.tsx | 13 ++++---- 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx index 6b8f8f368a1bd..2cfc0216c3950 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx @@ -46,6 +46,7 @@ describe('Mappings editor: shape datatype', () => { const { exists, + waitFor, waitForFn, actions: { startEditField, updateFieldAndCloseFlyout }, } = testBed; @@ -55,6 +56,8 @@ describe('Mappings editor: shape datatype', () => { await startEditField('myField'); }); + await waitFor('mappingsEditorFieldEdit'); + // Save the field and close the flyout await act(async () => { await updateFieldAndCloseFlyout(); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index c4c1f79c55f25..ee1cda490c8d0 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -54,6 +54,7 @@ describe('Mappings editor: text datatype', () => { const { exists, + waitFor, waitForFn, actions: { startEditField, getToggleValue, updateFieldAndCloseFlyout }, } = testBed; @@ -63,6 +64,8 @@ describe('Mappings editor: text datatype', () => { await startEditField('myField'); }); + await waitFor('mappingsEditorFieldEdit'); + // It should have searchable ("index" param) active by default const indexFieldConfig = getFieldConfig('index'); expect(getToggleValue('indexParameter.formRowToggle')).toBe(indexFieldConfig.defaultValue); @@ -129,7 +132,10 @@ describe('Mappings editor: text datatype', () => { const fieldToEdit = 'myField'; // Start edit and immediately save to have all the default values - await startEditField(fieldToEdit); + await act(async () => { + await startEditField(fieldToEdit); + }); + await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); await act(async () => { @@ -146,7 +152,10 @@ describe('Mappings editor: text datatype', () => { expect(data).toEqual(updatedMappings); // Re-open the edit panel - await startEditField(fieldToEdit); + await act(async () => { + await startEditField('myField'); + }); + await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); // When no analyzer is defined, defaults to "Index default" @@ -173,9 +182,10 @@ describe('Mappings editor: text datatype', () => { // Uncheck the "Use same analyzer for search" checkbox and wait for the search analyzer select await act(async () => { selectCheckBox('useSameAnalyzerForSearchCheckBox.input', false); - await waitFor('searchAnalyzer'); }); + await waitFor('searchAnalyzer'); + let searchAnalyzerValue = find('searchAnalyzer.select').props().value; expect(searchAnalyzerValue).toEqual('index_default'); @@ -218,7 +228,10 @@ describe('Mappings editor: text datatype', () => { expect(data).toEqual(updatedMappings); // Re-open the flyout and make sure the select have the correct updated value - await startEditField(fieldToEdit); + await act(async () => { + await startEditField('myField'); + }); + await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); isUseSameAnalyzerForSearchChecked = getCheckboxValue('useSameAnalyzerForSearchCheckBox.input'); @@ -269,7 +282,10 @@ describe('Mappings editor: text datatype', () => { } = testBed; const fieldToEdit = 'myField'; - await startEditField(fieldToEdit); + await act(async () => { + await startEditField(fieldToEdit); + }); + await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); expect(exists('indexAnalyzer-custom')).toBe(true); @@ -370,13 +386,17 @@ describe('Mappings editor: text datatype', () => { const { find, exists, + waitFor, waitForFn, form: { setSelectValue }, actions: { startEditField, showAdvancedSettings, updateFieldAndCloseFlyout }, } = testBed; const fieldToEdit = 'myField'; - await startEditField(fieldToEdit); + await act(async () => { + await startEditField(fieldToEdit); + }); + await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); // It should have 2 selects diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx index 67dec23c01754..2df830b85d108 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx @@ -47,6 +47,7 @@ describe('Mappings editor: edit field', () => { const { find, + waitFor, actions: { startEditField }, } = testBed; const fieldPathToEdit = ['user', 'address', 'street']; @@ -57,6 +58,8 @@ describe('Mappings editor: edit field', () => { await startEditField(fieldPathToEdit.join('.')); }); + await waitFor('mappingsEditorFieldEdit'); + // It should have the correct title expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field '${fieldName}'`); @@ -88,6 +91,7 @@ describe('Mappings editor: edit field', () => { const { find, exists, + waitFor, waitForFn, component, actions: { startEditField, updateFieldAndCloseFlyout }, @@ -98,6 +102,8 @@ describe('Mappings editor: edit field', () => { await startEditField('myField'); }); + await waitFor('mappingsEditorFieldEdit'); + await act(async () => { // Change the field type find('mappingsEditorFieldEdit.fieldType').simulate('change', [ diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index eb555f7fc8034..5dedac3d910a4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { registerTestBed, TestBed, nextTick } from '../../../../../../../../../test_utils'; @@ -133,7 +134,7 @@ const createActions = (testBed: TestBed) => { }; // Get a nested field in the rendered DOM tree - const getFieldAt = async (path: string) => { + const getFieldAt = (path: string) => { const testSubjectField = `${path.split('.').join('')}Field`; return find(testSubjectField as TestSubjects); }; @@ -164,10 +165,8 @@ const createActions = (testBed: TestBed) => { }; const startEditField = async (path: string) => { - const field = await getFieldAt(path); + const field = getFieldAt(path); find('editFieldButton', field).simulate('click'); - // Wait until the details flyout is open - await waitFor('mappingsEditorFieldEdit'); }; const updateFieldAndCloseFlyout = async () => { @@ -286,7 +285,7 @@ export const getDataForwardedFactory = (onChangeHandler: jest.MockedFunction { }, }; - await act(async () => { - ({ data } = await getDataForwarded()); - }); + ({ data } = await getDataForwarded()); expect(data).toEqual(expectedMappings); }); @@ -333,8 +331,9 @@ describe('Mappings editor: core', () => { await act(async () => { await addField(newField.name, newField.type); - ({ data } = await getDataForwarded()); }); + + ({ data } = await getDataForwarded()); expect(data).toEqual(updatedMappings); /** @@ -354,8 +353,9 @@ describe('Mappings editor: core', () => { await updateJsonEditor('dynamicTemplatesEditor', updatedTemplatesValue); await nextTick(); component.update(); - ({ data } = await getDataForwarded()); }); + + ({ data } = await getDataForwarded()); expect(data).toEqual(updatedMappings); /** @@ -368,9 +368,10 @@ describe('Mappings editor: core', () => { // Disbable dynamic mappings await act(async () => { form.toggleEuiSwitch('advancedConfiguration.dynamicMappingsToggle.input'); - ({ data } = await getDataForwarded()); }); + ({ data } = await getDataForwarded()); + // When we disable dynamic mappings, we set it to "false" and remove date and numeric detections updatedMappings = { ...updatedMappings, From 49876f30d47ec2400abc9e246c1a8b1bcef75039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 29 Apr 2020 16:40:05 +0200 Subject: [PATCH 29/34] [mappings editor] Fix regression when switching from "custom" to "built-in" analyzer --- .../field_parameters/analyzer_parameter.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx index 5960965362b48..569af5d21cdb0 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx @@ -133,6 +133,11 @@ export const AnalyzerParameter = ({ !isDefaultValueInOptions && !isDefaultValueInSubOptions ); + const [selectsDefaultValue, setSelectsDefaultValue] = useState({ + main: mainValue, + sub: subValue, + }); + const fieldConfig = config ? config : getFieldConfig('analyzer'); const fieldConfigWithLabel = label !== undefined ? { ...fieldConfig, label } : fieldConfig; @@ -144,6 +149,7 @@ export const AnalyzerParameter = ({ } field.reset({ resetValue: false }); + setSelectsDefaultValue({ main: undefined, sub: undefined }); setIsCustom(!isCustom); }; @@ -178,8 +184,8 @@ export const AnalyzerParameter = ({ ) : ( Date: Wed, 29 Apr 2020 17:05:56 +0200 Subject: [PATCH 30/34] Add web worker stub to remove console warning on missing Worker --- .../__jest__/components/index_table.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index 15c3ef0b84562..84fbc04aa5a31 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -8,6 +8,15 @@ import React from 'react'; import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { MemoryRouter } from 'react-router-dom'; + +/** + * The below import is required to avoid a console error warn from brace package + * console.warn ../node_modules/brace/index.js:3999 + Could not load worker ReferenceError: Worker is not defined + at createWorker (//node_modules/brace/index.js:17992:5) + */ +import * as stubWebWorker from '../../../../test_utils/stub_web_worker'; // eslint-disable-line no-unused-vars + import { AppWithoutRouter } from '../../public/application/app'; import { AppContextProvider } from '../../public/application/app_context'; import { Provider } from 'react-redux'; From 49d07525a751133d118bbfb044ad6a3dc29082e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 29 Apr 2020 17:12:08 +0200 Subject: [PATCH 31/34] Fix textDatatype failing test --- .../datatypes/text_datatype.test.tsx | 45 ++++++++++++------- .../helpers/mappings_editor.helpers.tsx | 9 ++-- x-pack/test_utils/testbed/testbed.ts | 3 +- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index ee1cda490c8d0..fcad86e1a5d22 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -61,7 +61,7 @@ describe('Mappings editor: text datatype', () => { // Open the flyout to edit the field await act(async () => { - await startEditField('myField'); + startEditField('myField'); }); await waitFor('mappingsEditorFieldEdit'); @@ -72,7 +72,7 @@ describe('Mappings editor: text datatype', () => { // Save the field and close the flyout await act(async () => { - await updateFieldAndCloseFlyout(); + updateFieldAndCloseFlyout(); }); await waitForFn( @@ -133,13 +133,13 @@ describe('Mappings editor: text datatype', () => { // Start edit and immediately save to have all the default values await act(async () => { - await startEditField(fieldToEdit); + startEditField(fieldToEdit); }); await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); await act(async () => { - await updateFieldAndCloseFlyout(); + updateFieldAndCloseFlyout(); }); await waitForFn( @@ -153,7 +153,7 @@ describe('Mappings editor: text datatype', () => { // Re-open the edit panel await act(async () => { - await startEditField('myField'); + startEditField('myField'); }); await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); @@ -204,7 +204,7 @@ describe('Mappings editor: text datatype', () => { await act(async () => { // Save & close - await updateFieldAndCloseFlyout(); + updateFieldAndCloseFlyout(); }); await waitForFn( @@ -229,7 +229,7 @@ describe('Mappings editor: text datatype', () => { // Re-open the flyout and make sure the select have the correct updated value await act(async () => { - await startEditField('myField'); + startEditField('myField'); }); await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); @@ -270,21 +270,25 @@ describe('Mappings editor: text datatype', () => { }, }; - testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); + await act(async () => { + testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); + }); const { find, exists, waitFor, waitForFn, + component, form: { setInputValue, setSelectValue }, actions: { startEditField, showAdvancedSettings, updateFieldAndCloseFlyout }, } = testBed; const fieldToEdit = 'myField'; await act(async () => { - await startEditField(fieldToEdit); + startEditField(fieldToEdit); }); + await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); @@ -300,7 +304,7 @@ describe('Mappings editor: text datatype', () => { expect(searchAnalyzerValue).toBe(defaultMappings.properties.myField.search_analyzer); expect(searchQuoteAnalyzerValue).toBe(defaultMappings.properties.myField.search_quote_analyzer); - const updatedIndexAnalyzer = 'updatedAnalyzer'; + const updatedIndexAnalyzer = 'newCustomIndexAnalyzer'; const updatedSearchAnalyzer = 'whitespace'; await act(async () => { @@ -309,16 +313,25 @@ describe('Mappings editor: text datatype', () => { // Change the search analyzer to a built-in analyzer find('searchAnalyzer-toggleCustomButton').simulate('click'); - await waitFor('searchAnalyzer'); + component.update(); + }); + + await waitFor('searchAnalyzer'); + + await act(async () => { setSelectValue('searchAnalyzer.select', updatedSearchAnalyzer); // Change the searchQuote to use built-in analyzer // By default it means using the "index default" find('searchQuoteAnalyzer-toggleCustomButton').simulate('click'); - await waitFor('searchQuoteAnalyzer'); + component.update(); + }); + + await waitFor('searchQuoteAnalyzer'); + await act(async () => { // Save & close - await updateFieldAndCloseFlyout(); + updateFieldAndCloseFlyout(); }); await waitForFn( @@ -341,7 +354,7 @@ describe('Mappings editor: text datatype', () => { }; expect(data).toEqual(updatedMappings); - }); + }, 30000); test('analyzer parameter: custom analyzer (from index settings)', async () => { const indexSettings = { @@ -394,7 +407,7 @@ describe('Mappings editor: text datatype', () => { const fieldToEdit = 'myField'; await act(async () => { - await startEditField(fieldToEdit); + startEditField(fieldToEdit); }); await waitFor('mappingsEditorFieldEdit'); await showAdvancedSettings(); @@ -421,7 +434,7 @@ describe('Mappings editor: text datatype', () => { setSelectValue(find('indexAnalyzer.select').at(1), customAnalyzers[2]); // Save & close - await updateFieldAndCloseFlyout(); + updateFieldAndCloseFlyout(); }); await waitForFn( diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 5dedac3d910a4..3fb434648cf7d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -164,12 +164,13 @@ const createActions = (testBed: TestBed) => { await waitFor('fieldsListItem', currentCount + 1); }; - const startEditField = async (path: string) => { + const startEditField = (path: string) => { const field = getFieldAt(path); find('editFieldButton', field).simulate('click'); + component.update(); }; - const updateFieldAndCloseFlyout = async () => { + const updateFieldAndCloseFlyout = () => { find('mappingsEditorFieldEdit.editFieldUpdateButton').simulate('click'); component.update(); }; @@ -183,7 +184,9 @@ const createActions = (testBed: TestBed) => { return; } - find('mappingsEditorFieldEdit.toggleAdvancedSetting').simulate('click'); + await act(async () => { + find('mappingsEditorFieldEdit.toggleAdvancedSetting').simulate('click'); + }); await waitForFn( checkIsVisible, diff --git a/x-pack/test_utils/testbed/testbed.ts b/x-pack/test_utils/testbed/testbed.ts index d0e79288f6166..b6ec0f8997e1c 100644 --- a/x-pack/test_utils/testbed/testbed.ts +++ b/x-pack/test_utils/testbed/testbed.ts @@ -5,6 +5,7 @@ */ import { ComponentType, ReactWrapper } from 'enzyme'; + import { findTestSubject } from '../find_test_subject'; import { reactRouterMock } from '../router_helpers'; import { @@ -166,7 +167,7 @@ export const registerTestBed = ( return process(); }; - const waitFor: TestBed['waitFor'] = async (testSubject: T, count = 1) => { + const waitFor: TestBed['waitFor'] = (testSubject: T, count = 1) => { return waitForFn( () => Promise.resolve(exists(testSubject, count)), `I waited patiently for the "${testSubject}" test subject to appear with no luck. It is nowhere to be found!` From ac17d91fbca347b07a813eeab30235905cbfd941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Thu, 30 Apr 2020 11:04:18 +0200 Subject: [PATCH 32/34] Address CR changes --- .../datatypes/shape_datatype.test.tsx | 9 +++--- .../datatypes/text_datatype.test.tsx | 15 ++++----- .../client_integration/edit_field.test.tsx | 31 ++++++------------- .../client_integration/helpers/index.ts | 4 +-- .../helpers/mappings_editor.helpers.tsx | 18 ++++++----- .../client_integration/mapped_fields.test.tsx | 10 ++---- .../mappings_editor.test.tsx | 20 ++++++------ 7 files changed, 48 insertions(+), 59 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx index 2cfc0216c3950..19bf6973472ff 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx @@ -8,12 +8,13 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed } from '../helpers'; -const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; +const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); -const getDataForwarded = getDataForwardedFactory(onChangeHandler); +const getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); // Parameters automatically added to the shape datatype when saved (with the default values) export const defaultShapeParameters = { + type: 'shape', coerce: false, ignore_malformed: false, ignore_z_value: true, @@ -53,7 +54,7 @@ describe('Mappings editor: shape datatype', () => { // Open the flyout to edit the field await act(async () => { - await startEditField('myField'); + startEditField('myField'); }); await waitFor('mappingsEditorFieldEdit'); @@ -74,7 +75,7 @@ describe('Mappings editor: shape datatype', () => { ...defaultShapeParameters, }; - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); expect(data).toEqual(updatedMappings); }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index fcad86e1a5d22..4d2b0e8daa2f7 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -8,12 +8,13 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed } from '../helpers'; import { getFieldConfig } from '../../../lib'; -const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; +const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); -const getDataForwarded = getDataForwardedFactory(onChangeHandler); +const getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); // Parameters automatically added to the text datatype when saved (with the default values) export const defaultTextParameters = { + type: 'text', eager_global_ordinals: false, fielddata: false, index: true, @@ -86,7 +87,7 @@ describe('Mappings editor: text datatype', () => { ...defaultTextParameters, }; - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); expect(data).toEqual(updatedMappings); }); @@ -147,7 +148,7 @@ describe('Mappings editor: text datatype', () => { 'Error waiting for the details flyout to close' ); - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); expect(data).toEqual(updatedMappings); @@ -224,7 +225,7 @@ describe('Mappings editor: text datatype', () => { }, }; - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); expect(data).toEqual(updatedMappings); // Re-open the flyout and make sure the select have the correct updated value @@ -339,7 +340,7 @@ describe('Mappings editor: text datatype', () => { 'Error waiting for the details flyout to close' ); - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); updatedMappings = { ...updatedMappings, @@ -442,7 +443,7 @@ describe('Mappings editor: text datatype', () => { 'Error waiting for the details flyout to close' ); - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); updatedMappings = { ...updatedMappings, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx index 2df830b85d108..4af5f82d851e3 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx @@ -7,15 +7,11 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed } from './helpers'; import { defaultTextParameters, defaultShapeParameters } from './datatypes'; -const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; +const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); -const getDataForwarded = getDataForwardedFactory(onChangeHandler); +const getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); describe('Mappings editor: edit field', () => { - /** - * Variable to store the mappings data forwarded to the consumer component - */ - let data: any; let testBed: MappingsEditorTestBed; afterEach(() => { @@ -50,21 +46,18 @@ describe('Mappings editor: edit field', () => { waitFor, actions: { startEditField }, } = testBed; - const fieldPathToEdit = ['user', 'address', 'street']; - const fieldName = fieldPathToEdit[fieldPathToEdit.length - 1]; - // Open the flyout to edit the field await act(async () => { - await startEditField(fieldPathToEdit.join('.')); + startEditField('user.address.street'); }); await waitFor('mappingsEditorFieldEdit'); // It should have the correct title - expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field '${fieldName}'`); + expect(find('mappingsEditorFieldEdit.flyoutTitle').text()).toEqual(`Edit field 'street'`); // It should have the correct field path - expect(find('mappingsEditorFieldEdit.fieldPath').text()).toEqual(fieldPathToEdit.join(' > ')); + expect(find('mappingsEditorFieldEdit.fieldPath').text()).toEqual('user > address > street'); // The advanced settings should be hidden initially expect(find('mappingsEditorFieldEdit.advancedSettings').props().style.display).toEqual('none'); @@ -76,14 +69,11 @@ describe('Mappings editor: edit field', () => { _source: {}, properties: { myField: { - type: 'text', ...defaultTextParameters, }, }, }; - let updatedMappings: any = { ...defaultMappings }; - await act(async () => { testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); }); @@ -99,7 +89,7 @@ describe('Mappings editor: edit field', () => { // Open the flyout, change the field type and save it await act(async () => { - await startEditField('myField'); + startEditField('myField'); }); await waitFor('mappingsEditorFieldEdit'); @@ -107,7 +97,7 @@ describe('Mappings editor: edit field', () => { await act(async () => { // Change the field type find('mappingsEditorFieldEdit.fieldType').simulate('change', [ - { label: 'Shape', value: 'shape' }, + { label: 'Shape', value: defaultShapeParameters.type }, ]); component.update(); }); @@ -121,13 +111,12 @@ describe('Mappings editor: edit field', () => { 'Error waiting for the details flyout to close' ); - ({ data } = await getDataForwarded()); + const { data } = await getMappingsEditorData(); - updatedMappings = { - ...updatedMappings, + const updatedMappings = { + ...defaultMappings, properties: { myField: { - type: 'shape', ...defaultShapeParameters, }, }, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts index df3d3e9fe2594..afdc039ae77d2 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts @@ -7,7 +7,7 @@ import { setup as mappingsEditorSetup, MappingsEditorTestBed, DomFields, - getDataForwardedFactory, + getMappingsEditorDataFactory, } from './mappings_editor.helpers'; export { @@ -18,7 +18,7 @@ export { } from '../../../../../../../../../test_utils'; export const componentHelpers = { - mappingsEditor: { setup: mappingsEditorSetup, getDataForwardedFactory }, + mappingsEditor: { setup: mappingsEditorSetup, getMappingsEditorDataFactory }, }; export { MappingsEditorTestBed, DomFields }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 3fb434648cf7d..58242ec35018c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -34,6 +34,8 @@ jest.mock('@elastic/eui', () => ({ }} /> ), + // Mocking EuiSuperSelect to be able to easily change its value + // with a `myWrapper.simulate('change', { target: { value: 'someValue' } })` EuiSuperSelect: (props: any) => ( ) => { const expandField = async ( field: ReactWrapper - ): Promise<{ isExpanded: boolean; testSubjectField: string }> => { + ): Promise<{ hasChildren: boolean; testSubjectField: string }> => { /** * Field list item have 2 test subject assigned to them: * data-test-subj="fieldsListItem " @@ -79,7 +81,7 @@ const createActions = (testBed: TestBed) => { // No expand button, so this field is not expanded if (expandButton.length === 0) { - return { isExpanded: false, testSubjectField }; + return { hasChildren: false, testSubjectField }; } const isExpanded = (expandButton.props()['aria-label'] as string).includes('Collapse'); @@ -91,7 +93,7 @@ const createActions = (testBed: TestBed) => { // Wait for the children FieldList to be in the DOM await waitFor(`${testSubjectField}.fieldsList` as TestSubjects); - return { isExpanded: true, testSubjectField }; + return { hasChildren: true, testSubjectField }; }; /** @@ -109,7 +111,7 @@ const createActions = (testBed: TestBed) => { ).map(wrapper => wrapper); // convert to Array for our for of loop below for (const field of fields) { - const { isExpanded, testSubjectField } = await expandField(field); + const { hasChildren, testSubjectField } = await expandField(field); // Read the info from the DOM about that field and add it to our domFieldMeta const { name, type } = getFieldInfo(testSubjectField); @@ -117,7 +119,7 @@ const createActions = (testBed: TestBed) => { type, }; - if (isExpanded) { + if (hasChildren) { // Update our metadata object const childFieldName = getChildFieldsName(type as any)!; domTreeMetadata[name][childFieldName] = {}; @@ -271,9 +273,9 @@ export const setup = async (props: any = { onUpdate() {} }): Promise) => { +export const getMappingsEditorDataFactory = (onChangeHandler: jest.MockedFunction) => { /** - * Helper to access the latest data sent to the onUpdate handler back to the consumer of the . + * Helper to access the latest data sent to the onChange handler back to the consumer of the . * Read the latest call with its argument passed and build the mappings object from it. */ return async () => { @@ -281,7 +283,7 @@ export const getDataForwardedFactory = (onChangeHandler: jest.MockedFunction { actions: { expandAllFieldsAndReturnMetadata }, } = testBed; - let domTreeMetadata: DomFields = {}; - await act(async () => { - domTreeMetadata = await expandAllFieldsAndReturnMetadata(); - }); - - expect(domTreeMetadata).toEqual(defaultMappings.properties); - - // Change the `value` prop of our const newMappings = { properties: { hello: { type: 'text' } } }; + let domTreeMetadata: DomFields = {}; await act(async () => { + // Change the `value` prop of our setProps({ value: newMappings }); // Don't ask me why but the 3 following lines are all required diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index e9373fb6d08ea..e26dbf6f56ce3 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -7,9 +7,9 @@ import { act } from 'react-dom/test-utils'; import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from './helpers'; -const { setup, getDataForwardedFactory } = componentHelpers.mappingsEditor; +const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); -const getDataForwarded = getDataForwardedFactory(onChangeHandler); +const getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); describe('Mappings editor: core', () => { /** @@ -47,7 +47,7 @@ describe('Mappings editor: core', () => { }, }; - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); expect(data).toEqual(expectedMappings); }); @@ -119,7 +119,7 @@ describe('Mappings editor: core', () => { // ------------------------------------- expect(find('fieldsListItem').length).toEqual(0); // Check that we start with an empty list - const newField = { name: getRandomString(), type: 'text' }; + const newField = { name: 'John', type: 'text' }; await act(async () => { await addField(newField.name, newField.type); }); @@ -218,6 +218,8 @@ describe('Mappings editor: core', () => { /** * Note: the "indexSettings" prop will be tested along with the "analyzer" parameter on a text datatype field, * as it is the only place where it is consumed by the mappings editor. + * + * The test that covers it is text_datatype.test.tsx: "analyzer parameter: custom analyzer (from index settings)" */ const defaultMappings: any = { dynamic: true, @@ -262,8 +264,7 @@ describe('Mappings editor: core', () => { /** * Mapped fields */ - expect(find('fieldsListItem').length).toEqual(Object.keys(defaultMappings.properties).length); - + // Test that root-level mappings "properties" are rendered as root-level "DOM tree items" const fields = find('fieldsListItem.fieldName').map(item => item.text()); expect(fields).toEqual(Object.keys(defaultMappings.properties).sort()); @@ -274,6 +275,7 @@ describe('Mappings editor: core', () => { await selectTab('templates'); }); + // Test that dynamic templates JSON is rendered in the templates editor const templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); expect(templatesValue).toEqual(defaultMappings.dynamic_templates); @@ -333,7 +335,7 @@ describe('Mappings editor: core', () => { await addField(newField.name, newField.type); }); - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); expect(data).toEqual(updatedMappings); /** @@ -355,7 +357,7 @@ describe('Mappings editor: core', () => { component.update(); }); - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); expect(data).toEqual(updatedMappings); /** @@ -370,7 +372,7 @@ describe('Mappings editor: core', () => { form.toggleEuiSwitch('advancedConfiguration.dynamicMappingsToggle.input'); }); - ({ data } = await getDataForwarded()); + ({ data } = await getMappingsEditorData()); // When we disable dynamic mappings, we set it to "false" and remove date and numeric detections updatedMappings = { From c6691229f452803bc601e7fc4b9db8360d14b32c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Fri, 1 May 2020 09:21:39 +0200 Subject: [PATCH 33/34] Inscrease timeout in test to 30s --- .../client_integration/datatypes/text_datatype.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 4d2b0e8daa2f7..4386f584b5d49 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -89,7 +89,7 @@ describe('Mappings editor: text datatype', () => { ({ data } = await getMappingsEditorData()); expect(data).toEqual(updatedMappings); - }); + }, 30000); test('analyzer parameter: default values', async () => { const defaultMappings = { @@ -456,5 +456,5 @@ describe('Mappings editor: text datatype', () => { }; expect(data).toEqual(updatedMappings); - }); + }, 30000); }); From ab76582083b379ff0c89a16c7a72ab4b1ad04034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Loix?= Date: Wed, 6 May 2020 14:52:09 +0200 Subject: [PATCH 34/34] Address CR changes --- .../datatypes/text_datatype.test.tsx | 19 +++++++++---------- .../mappings_editor.test.tsx | 4 ++-- .../mappings_editor/lib/utils.test.ts | 4 ++++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index 4386f584b5d49..2bfaa884a0132 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -105,16 +105,6 @@ describe('Mappings editor: text datatype', () => { }, }; - let updatedMappings: any = { - ...defaultMappings, - properties: { - myField: { - ...defaultMappings.properties.myField, - ...defaultTextParameters, - }, - }, - }; - testBed = await setup({ value: defaultMappings, onChange: onChangeHandler }); const { @@ -150,6 +140,15 @@ describe('Mappings editor: text datatype', () => { ({ data } = await getMappingsEditorData()); + let updatedMappings: any = { + ...defaultMappings, + properties: { + myField: { + ...defaultMappings.properties.myField, + ...defaultTextParameters, + }, + }, + }; expect(data).toEqual(updatedMappings); // Re-open the edit panel diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index e26dbf6f56ce3..f516dfdb372ce 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -5,7 +5,7 @@ */ import { act } from 'react-dom/test-utils'; -import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from './helpers'; +import { componentHelpers, MappingsEditorTestBed, nextTick } from './helpers'; const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; const onChangeHandler = jest.fn(); @@ -322,7 +322,7 @@ describe('Mappings editor: core', () => { /** * Mapped fields */ - const newField = { name: getRandomString(), type: 'text' }; + const newField = { name: 'someNewField', type: 'text' }; updatedMappings = { ...updatedMappings, properties: { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts index 4a1d3254ad7af..4b610ff0b401d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts @@ -74,6 +74,8 @@ describe('utils', () => { someArray: [1, 2, 3], someEmptyObject: {}, someDate: myDate, + falsey1: 0, + falsey2: '', stripThis: undefined, nested: { value: 'bar', @@ -92,6 +94,8 @@ describe('utils', () => { someArray: [1, 2, 3], someEmptyObject: {}, someDate: myDate, + falsey1: 0, + falsey2: '', nested: { value: 'bar', deepNested: {