diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap index 2ab8037639f85..2b71d1882b0cf 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.tsx.snap @@ -43,6 +43,7 @@ exports[`BytesFormatEditor should render normally 1`] = ` labelType="label" > { this.onColorChange( { @@ -120,6 +121,7 @@ export class ColorFormatEditor extends DefaultFormatEditor { this.onColorChange( { @@ -144,6 +146,7 @@ export class ColorFormatEditor extends DefaultFormatEditor { this.onColorChange( { @@ -168,6 +171,7 @@ export class ColorFormatEditor extends DefaultFormatEditor { this.onColorChange( { @@ -220,6 +224,7 @@ export class ColorFormatEditor extends DefaultFormatEditor items.length > 1, + 'data-test-subj': 'colorEditorRemoveColor', }, ], }, @@ -229,7 +234,12 @@ export class ColorFormatEditor extends DefaultFormatEditor - + { return { @@ -126,6 +127,7 @@ export class DurationFormatEditor extends DefaultFormatEditor< isInvalid={!!error} > { return { diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap index 4d42e3848d3cd..8b59c0da10167 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/number/__snapshots__/number.test.tsx.snap @@ -43,6 +43,7 @@ exports[`NumberFormatEditor should render normally 1`] = ` labelType="label" > { diff --git a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap index fce51e8fa3871..7d8ab5e682a3e 100644 --- a/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap +++ b/src/plugins/index_pattern_field_editor/public/components/field_format_editor/editors/percent/__snapshots__/percent.test.tsx.snap @@ -43,6 +43,7 @@ exports[`PercentFormatEditor should render normally 1`] = ` labelType="label" > { this.onLookupChange( { @@ -105,6 +106,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor { this.onLookupChange( { @@ -136,6 +138,7 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor items.length > 1, }, ], @@ -147,7 +150,12 @@ export class StaticLookupFormatEditor extends DefaultFormatEditor - + { if (e.target.checkValidity()) { this.onChange({ diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 370d289093517..c04bd778468a9 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -15,6 +15,7 @@ import { RandomnessService } from './randomness'; import { SecurityServiceProvider } from './security'; import { EsDeleteAllIndicesProvider } from './es_delete_all_indices'; import { SavedObjectInfoService } from './saved_object_info'; +import { IndexPatternsService } from './index_patterns'; export const services = { deployment: DeploymentService, @@ -26,4 +27,5 @@ export const services = { security: SecurityServiceProvider, esDeleteAllIndices: EsDeleteAllIndicesProvider, savedObjectInfo: SavedObjectInfoService, + indexPatterns: IndexPatternsService, }; diff --git a/test/common/services/index_patterns.ts b/test/common/services/index_patterns.ts new file mode 100644 index 0000000000000..5b6d20990b6d1 --- /dev/null +++ b/test/common/services/index_patterns.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrService } from '../ftr_provider_context'; +import { IndexPatternSpec } from '../../../src/plugins/data/common'; + +export class IndexPatternsService extends FtrService { + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + + /** + * Create a new index pattern + */ + async create( + indexPattern: { title: string }, + { override = false }: { override: boolean } = { override: false } + ): Promise { + const response = await this.kibanaServer.request<{ + index_pattern: IndexPatternSpec; + }>({ + path: '/api/index_patterns/index_pattern', + method: 'POST', + body: { + override, + index_pattern: indexPattern, + }, + }); + + return response.data.index_pattern; + } +} diff --git a/test/functional/apps/management/_field_formatter.js b/test/functional/apps/management/_field_formatter.js deleted file mode 100644 index 383b4faecc40c..0000000000000 --- a/test/functional/apps/management/_field_formatter.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export default function ({ getService, getPageObjects }) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const browser = getService('browser'); - const PageObjects = getPageObjects(['settings']); - const testSubjects = getService('testSubjects'); - - describe('field formatter', function () { - this.tags(['skipFirefox']); - - before(async function () { - await browser.setWindowSize(1200, 800); - await esArchiver.load('test/functional/fixtures/es_archiver/discover'); - await kibanaServer.uiSettings.replace({}); - await kibanaServer.uiSettings.update({}); - }); - - after(async function afterAll() { - await PageObjects.settings.navigateTo(); - await esArchiver.emptyKibanaIndex(); - }); - - describe('set and change field formatter', function describeIndexTests() { - // addresses https://github.com/elastic/kibana/issues/93349 - it('can change format more than once', async function () { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndexPatterns(); - await PageObjects.settings.clickIndexPatternLogstash(); - await PageObjects.settings.clickAddField(); - await PageObjects.settings.setFieldType('Long'); - const formatRow = await testSubjects.find('formatRow'); - const formatRowToggle = ( - await formatRow.findAllByCssSelector('[data-test-subj="toggle"]') - )[0]; - - await formatRowToggle.click(); - await PageObjects.settings.setFieldFormat('duration'); - await PageObjects.settings.setFieldFormat('bytes'); - await PageObjects.settings.setFieldFormat('duration'); - await testSubjects.click('euiFlyoutCloseButton'); - await PageObjects.settings.closeIndexPatternFieldEditor(); - }); - }); - }); -} diff --git a/test/functional/apps/management/_field_formatter.ts b/test/functional/apps/management/_field_formatter.ts new file mode 100644 index 0000000000000..60c1bbe7b3d1d --- /dev/null +++ b/test/functional/apps/management/_field_formatter.ts @@ -0,0 +1,571 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ES_FIELD_TYPES } from '@kbn/field-types'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { FIELD_FORMAT_IDS } from '../../../../src/plugins/field_formats/common'; +import { WebElementWrapper } from '../../services/lib/web_element_wrapper'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const browser = getService('browser'); + const PageObjects = getPageObjects(['settings', 'common']); + const testSubjects = getService('testSubjects'); + const es = getService('es'); + const indexPatterns = getService('indexPatterns'); + const toasts = getService('toasts'); + + describe('field formatter', function () { + this.tags(['skipFirefox']); + + before(async function () { + await browser.setWindowSize(1200, 800); + await esArchiver.load('test/functional/fixtures/es_archiver/discover'); + await kibanaServer.uiSettings.replace({}); + await kibanaServer.uiSettings.update({}); + }); + + after(async function afterAll() { + await PageObjects.settings.navigateTo(); + await esArchiver.emptyKibanaIndex(); + }); + + describe('set and change field formatter', function describeIndexTests() { + // addresses https://github.com/elastic/kibana/issues/93349 + it('can change format more than once', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndexPatterns(); + await PageObjects.settings.clickIndexPatternLogstash(); + await PageObjects.settings.clickAddField(); + await PageObjects.settings.setFieldType('Long'); + const formatRow = await testSubjects.find('formatRow'); + const formatRowToggle = ( + await formatRow.findAllByCssSelector('[data-test-subj="toggle"]') + )[0]; + + await formatRowToggle.click(); + await PageObjects.settings.setFieldFormat('duration'); + await PageObjects.settings.setFieldFormat('bytes'); + await PageObjects.settings.setFieldFormat('duration'); + await testSubjects.click('euiFlyoutCloseButton'); + await PageObjects.settings.closeIndexPatternFieldEditor(); + }); + }); + + /** + * The purpose of these tests is to cover **editing experience** of different field formats editors, + * The logic of each converter is extensively covered by unit tests. + * TODO: these tests also could check field formats behaviour with different combinations of browser locale, timezone and ui settings + */ + describe('field format editors', () => { + describe('String format', () => { + testFormatEditors([ + { + fieldType: ES_FIELD_TYPES.TEXT, + fieldValue: 'A regular text', + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: 'A regular text', + + // check available formats for ES_FIELD_TYPES.TEXT + expectFormatterTypes: [ + FIELD_FORMAT_IDS.BOOLEAN, + FIELD_FORMAT_IDS.COLOR, + FIELD_FORMAT_IDS.STATIC_LOOKUP, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.TRUNCATE, + FIELD_FORMAT_IDS.URL, + ], + }, + { + fieldType: ES_FIELD_TYPES.TEXT, + fieldValue: 'A regular text', + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: 'a regular text', + beforeSave: async () => { + await testSubjects.selectValue('stringEditorTransform', 'lower'); + }, + }, + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: 'a keyword', + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: 'A KEYWORD', + beforeSave: async () => { + await testSubjects.selectValue('stringEditorTransform', 'upper'); + }, + // check available formats for ES_FIELD_TYPES.KEYWORD + expectFormatterTypes: [ + FIELD_FORMAT_IDS.BOOLEAN, + FIELD_FORMAT_IDS.COLOR, + FIELD_FORMAT_IDS.STATIC_LOOKUP, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.TRUNCATE, + FIELD_FORMAT_IDS.URL, + ], + }, + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: 'a keyword', + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: 'A Keyword', + beforeSave: async () => { + await testSubjects.selectValue('stringEditorTransform', 'title'); + }, + }, + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: 'com.organizations.project.ClassName', + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: 'c.o.p.ClassName', + beforeSave: async () => { + await testSubjects.selectValue('stringEditorTransform', 'short'); + }, + }, + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: 'SGVsbG8gd29ybGQ=', + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: 'Hello world', + beforeSave: async () => { + await testSubjects.selectValue('stringEditorTransform', 'base64'); + }, + }, + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: '%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98', + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: '안녕 키바나', + beforeSave: async () => { + await testSubjects.selectValue('stringEditorTransform', 'urlparam'); + }, + }, + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: '123456789', + applyFormatterType: FIELD_FORMAT_IDS.TRUNCATE, + expectFormattedValue: '123...', + beforeSave: async () => { + await testSubjects.setValue('truncateEditorLength', '3'); + }, + }, + { + fieldType: ES_FIELD_TYPES.INTEGER, + fieldValue: 324, + applyFormatterType: FIELD_FORMAT_IDS.STRING, + expectFormattedValue: '324', + // check available formats for ES_FIELD_TYPES.INTEGER + expectFormatterTypes: [ + FIELD_FORMAT_IDS.BOOLEAN, + FIELD_FORMAT_IDS.BYTES, + FIELD_FORMAT_IDS.COLOR, + FIELD_FORMAT_IDS.DURATION, + FIELD_FORMAT_IDS.NUMBER, + FIELD_FORMAT_IDS.PERCENT, + FIELD_FORMAT_IDS.STATIC_LOOKUP, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.URL, + ], + }, + ]); + }); + + describe('Number format', () => { + testFormatEditors([ + { + fieldType: ES_FIELD_TYPES.LONG, + fieldValue: 324, + applyFormatterType: FIELD_FORMAT_IDS.NUMBER, + expectFormattedValue: '324', + // check available formats for ES_FIELD_TYPES.LONG + expectFormatterTypes: [ + FIELD_FORMAT_IDS.BOOLEAN, + FIELD_FORMAT_IDS.BYTES, + FIELD_FORMAT_IDS.COLOR, + FIELD_FORMAT_IDS.DURATION, + FIELD_FORMAT_IDS.NUMBER, + FIELD_FORMAT_IDS.PERCENT, + FIELD_FORMAT_IDS.STATIC_LOOKUP, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.URL, + ], + }, + { + fieldType: ES_FIELD_TYPES.LONG, + fieldValue: 324, + applyFormatterType: FIELD_FORMAT_IDS.NUMBER, + expectFormattedValue: '+324', + beforeSave: async () => { + await testSubjects.setValue('numberEditorFormatPattern', '+0,0'); + }, + }, + ]); + }); + + describe('URL format', () => { + testFormatEditors([ + { + fieldType: ES_FIELD_TYPES.LONG, + fieldValue: 100, + applyFormatterType: FIELD_FORMAT_IDS.URL, + expectFormattedValue: 'https://elastic.co/?value=100', + beforeSave: async () => { + await testSubjects.setValue( + 'urlEditorUrlTemplate', + 'https://elastic.co/?value={{value}}' + ); + }, + expect: async (renderedValueContainer) => { + expect( + await (await renderedValueContainer.findByTagName('a')).getAttribute('href') + ).to.be('https://elastic.co/?value=100'); + }, + }, + { + fieldType: ES_FIELD_TYPES.LONG, + fieldValue: 100, + applyFormatterType: FIELD_FORMAT_IDS.URL, + expectFormattedValue: 'url label', + beforeSave: async () => { + await testSubjects.setValue( + 'urlEditorUrlTemplate', + 'https://elastic.co/?value={{value}}' + ); + await testSubjects.setValue('urlEditorLabelTemplate', 'url label'); + }, + expect: async (renderedValueContainer) => { + expect( + await (await renderedValueContainer.findByTagName('a')).getAttribute('href') + ).to.be('https://elastic.co/?value=100'); + }, + }, + ]); + }); + + describe('Date format', () => { + testFormatEditors([ + { + fieldType: ES_FIELD_TYPES.DATE, + fieldValue: '2021-08-05T15:05:37.151Z', + applyFormatterType: FIELD_FORMAT_IDS.DATE, + expectFormattedValue: 'Aug 5, 2021', + beforeSave: async () => { + await testSubjects.setValue('dateEditorPattern', 'MMM D, YYYY'); + }, + // check available formats for ES_FIELD_TYPES.DATE + expectFormatterTypes: [ + FIELD_FORMAT_IDS.DATE, + FIELD_FORMAT_IDS.DATE_NANOS, + FIELD_FORMAT_IDS.RELATIVE_DATE, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.URL, + ], + }, + { + fieldType: ES_FIELD_TYPES.DATE_NANOS, + fieldValue: '2015-01-01T12:10:30.123456789Z', + applyFormatterType: FIELD_FORMAT_IDS.DATE, + expectFormattedValue: 'Jan 1, 2015 @ 12:10:30.123', + // check available formats for ES_FIELD_TYPES.DATE_NANOS + expectFormatterTypes: [ + FIELD_FORMAT_IDS.DATE, + FIELD_FORMAT_IDS.DATE_NANOS, + FIELD_FORMAT_IDS.RELATIVE_DATE, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.URL, + ], + }, + { + fieldType: ES_FIELD_TYPES.DATE_NANOS, + fieldValue: '2015-01-01T12:10:30.123456789Z', + applyFormatterType: FIELD_FORMAT_IDS.DATE_NANOS, + expectFormattedValue: 'Jan 1, 2015 @ 12:10:30.123456789', + }, + ]); + }); + + describe('Static lookup format', () => { + testFormatEditors([ + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: 'look me up', + applyFormatterType: FIELD_FORMAT_IDS.STATIC_LOOKUP, + expectFormattedValue: 'looked up!', + beforeSave: async () => { + await testSubjects.click('staticLookupEditorAddEntry'); + await testSubjects.setValue('~staticLookupEditorKey', 'look me up'); + await testSubjects.setValue('~staticLookupEditorValue', 'looked up!'); + }, + }, + { + fieldType: ES_FIELD_TYPES.BOOLEAN, + fieldValue: 'true', + applyFormatterType: FIELD_FORMAT_IDS.STATIC_LOOKUP, + // check available formats for ES_FIELD_TYPES.BOOLEAN + expectFormatterTypes: [ + FIELD_FORMAT_IDS.BOOLEAN, + FIELD_FORMAT_IDS.STATIC_LOOKUP, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.URL, + ], + expectFormattedValue: 'yes', + beforeSave: async () => { + await testSubjects.click('staticLookupEditorAddEntry'); + await testSubjects.setValue('~staticLookupEditorKey', 'true'); + await testSubjects.setValue('~staticLookupEditorValue', 'yes'); + await testSubjects.setValue('staticLookupEditorUnknownValue', 'no'); + }, + }, + { + fieldType: ES_FIELD_TYPES.BOOLEAN, + fieldValue: 'false', + applyFormatterType: FIELD_FORMAT_IDS.STATIC_LOOKUP, + expectFormattedValue: 'no', + beforeSave: async () => { + await testSubjects.click('staticLookupEditorAddEntry'); + await testSubjects.setValue('~staticLookupEditorKey', 'true'); + await testSubjects.setValue('~staticLookupEditorValue', 'yes'); + await testSubjects.setValue('staticLookupEditorUnknownValue', 'no'); + }, + }, + { + fieldType: ES_FIELD_TYPES.BOOLEAN, + fieldValue: 'false', + applyFormatterType: FIELD_FORMAT_IDS.STATIC_LOOKUP, + expectFormattedValue: 'false', + beforeSave: async () => { + await testSubjects.click('staticLookupEditorAddEntry'); + await testSubjects.setValue('~staticLookupEditorKey', 'true'); + await testSubjects.setValue('~staticLookupEditorValue', 'yes'); + }, + }, + ]); + }); + + describe('Other formats', () => { + testFormatEditors([ + { + fieldType: ES_FIELD_TYPES.LONG, + fieldValue: 123292, + applyFormatterType: FIELD_FORMAT_IDS.DURATION, + expectFormattedValue: '2 minutes', + beforeSave: async () => { + await testSubjects.setValue('durationEditorInputFormat', 'milliseconds'); + }, + }, + { + fieldType: ES_FIELD_TYPES.DOUBLE, + fieldValue: 0.1, + applyFormatterType: FIELD_FORMAT_IDS.PERCENT, + // check available formats for ES_FIELD_TYPES.DOUBLE + expectFormatterTypes: [ + FIELD_FORMAT_IDS.BOOLEAN, + FIELD_FORMAT_IDS.BYTES, + FIELD_FORMAT_IDS.COLOR, + FIELD_FORMAT_IDS.DURATION, + FIELD_FORMAT_IDS.NUMBER, + FIELD_FORMAT_IDS.PERCENT, + FIELD_FORMAT_IDS.STATIC_LOOKUP, + FIELD_FORMAT_IDS.STRING, + FIELD_FORMAT_IDS.URL, + ], + expectFormattedValue: '10.0%', + beforeSave: async () => { + await testSubjects.setValue('numberEditorFormatPattern', '0.0%'); + }, + }, + { + fieldType: ES_FIELD_TYPES.LONG, + fieldValue: 1990000000, + applyFormatterType: FIELD_FORMAT_IDS.BYTES, + expectFormattedValue: '2GB', + beforeSave: async () => { + await testSubjects.setValue('numberEditorFormatPattern', '0b'); + }, + }, + { + fieldType: ES_FIELD_TYPES.KEYWORD, + fieldValue: 'red', + applyFormatterType: FIELD_FORMAT_IDS.COLOR, + expectFormattedValue: 'red', + beforeSave: async () => { + await testSubjects.click('colorEditorAddColor'); + await testSubjects.setValue('~colorEditorKeyPattern', 'red'); + await testSubjects.setValue('~colorEditorColorPicker', '#ffffff'); + await testSubjects.setValue('~colorEditorBackgroundPicker', '#ff0000'); + }, + expect: async (renderedValueContainer) => { + const span = await renderedValueContainer.findByTagName('span'); + expect(await span.getComputedStyle('color')).to.be('rgba(255, 255, 255, 1)'); + expect(await span.getComputedStyle('background-color')).to.be('rgba(255, 0, 0, 1)'); + }, + }, + ]); + }); + }); + }); + + /** + * Runs a field format editors tests covering data setup, editing a field and checking a resulting formatting in Discover app + * TODO: might be useful to reuse this util for runtime fields formats tests + * @param specs - {@link FieldFormatEditorSpecDescriptor} + */ + function testFormatEditors(specs: FieldFormatEditorSpecDescriptor[]) { + const indexTitle = 'field_formats_management_functional_tests'; + let indexPatternId: string; + let testDocumentId: string; + + before(async () => { + if ((await es.indices.exists({ index: indexTitle })).body) { + await es.indices.delete({ index: indexTitle }); + } + + await es.indices.create({ + index: indexTitle, + body: { + mappings: { + properties: specs.reduce((properties, spec, index) => { + properties[`${index}`] = { type: spec.fieldType }; + return properties; + }, {} as Record), + }, + }, + }); + + const docResult = await es.index({ + index: indexTitle, + body: specs.reduce((properties, spec, index) => { + properties[`${index}`] = spec.fieldValue; + return properties; + }, {} as Record), + refresh: 'wait_for', + }); + testDocumentId = docResult.body._id; + + const indexPatternResult = await indexPatterns.create( + { title: indexTitle }, + { override: true } + ); + indexPatternId = indexPatternResult.id!; + }); + + describe('edit formats', () => { + before(async () => { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaIndexPatterns(); + await PageObjects.settings.clickIndexPatternByName(indexTitle); + }); + + afterEach(async () => { + try { + await PageObjects.settings.controlChangeSave(); + } catch (e) { + // in case previous test failed in a state when save is disabled + await PageObjects.settings.controlChangeCancel(); + } + + await toasts.dismissAllToasts(); // dismiss "saved" toast, otherwise it could overlap save button for a next test + }); + + specs.forEach((spec, index) => { + const fieldName = `${index}`; + it( + `edit field format of "${fieldName}" field to "${spec.applyFormatterType}"` + + spec.expectFormatterTypes + ? `, and check available formats types` + : '', + async () => { + await PageObjects.settings.filterField(fieldName); + await PageObjects.settings.openControlsByName(fieldName); + await PageObjects.settings.toggleRow('formatRow'); + + if (spec.expectFormatterTypes) { + expect( + ( + await Promise.all( + ( + await (await testSubjects.find('editorSelectedFormatId')).findAllByTagName( + 'option' + ) + ).map((option) => option.getAttribute('value')) + ) + ).filter(Boolean) + ).to.eql(spec.expectFormatterTypes); + } + + await PageObjects.settings.setFieldFormat(spec.applyFormatterType); + if (spec.beforeSave) { + await spec.beforeSave(await testSubjects.find('formatRow')); + } + } + ); + }); + }); + + describe('check formats', async () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { + hash: `/doc/${indexPatternId}/${indexTitle}?id=${testDocumentId}`, + }); + await testSubjects.exists('doc-hit'); + }); + + specs.forEach((spec, index) => { + it(`check field format of "${index}" field`, async () => { + const renderedValue = await testSubjects.find(`tableDocViewRow-${index}-value`); + const text = await renderedValue.getVisibleText(); + expect(text).to.be(spec.expectFormattedValue); + if (spec.expect) { + await spec.expect(renderedValue); + } + }); + }); + }); + } +} + +/** + * Describes a field format editor test + */ +interface FieldFormatEditorSpecDescriptor { + /** + * Raw field value to put into document + */ + fieldValue: string | number | boolean | null; + /** + * Explicitly specify a type for a {@link fieldValue} + */ + fieldType: ES_FIELD_TYPES; + /** + * Type of a field formatter to apply + */ + applyFormatterType: FIELD_FORMAT_IDS; + + /** + * Optionally check available formats for {@link fieldType} + */ + expectFormatterTypes?: FIELD_FORMAT_IDS[]; + + /** + * Function to execute before field format is applied. + * Use it set specific configuration params for applied field formatter + * @param formatRowContainer - field format editor container + */ + beforeSave?: (formatRowContainer: WebElementWrapper) => Promise; + + /** + * An expected formatted value rendered by Discover app, + * Use this for final assertion + */ + expectFormattedValue: string; + + /** + * Run additional assertions on rendered element + */ + expect?: (renderedValueContainer: WebElementWrapper) => Promise; +}