diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.test.tsx
new file mode 100644
index 0000000000000..4e56753bfa7e2
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.test.tsx
@@ -0,0 +1,44 @@
+/*
+ * 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 '../../../../../__mocks__/shallow_useeffect.mock';
+
+import { setMockValues } from '../../../../../__mocks__';
+import { shallow } from 'enzyme';
+
+import React from 'react';
+
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
+import { CustomSourceIcon } from './custom_source_icon';
+
+import { ExampleStandoutResult } from './example_standout_result';
+
+describe('ExampleStandoutResult', () => {
+ beforeEach(() => {
+ setMockValues({ ...exampleResult });
+ });
+
+ it('renders', () => {
+ const wrapper = shallow(
);
+
+ expect(wrapper.find('[data-test-subj="ExampleStandoutResult"]')).toHaveLength(1);
+ });
+
+ it('sets correct color prop when dark', () => {
+ setMockValues({ ...exampleResult, searchResultConfig: { color: '#000', detailFields: [] } });
+ const wrapper = shallow(
);
+
+ expect(wrapper.find(CustomSourceIcon).prop('color')).toEqual('white');
+ });
+
+ it('shows fallback URL label when no override set', () => {
+ setMockValues({ ...exampleResult, searchResultConfig: { detailFields: [], color: '#111' } });
+ const wrapper = shallow(
);
+
+ expect(wrapper.find('[data-test-subj="DefaultDescriptionLabel"]')).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
index 4ef3b1fe14b93..a80680d219aef 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
@@ -30,7 +30,7 @@ export const ExampleStandoutResult: React.FC = () => {
const result = exampleDocuments[0];
return (
-
+
{
{descriptionField ? (
{result[descriptionField]}
) : (
- Description
+
+ Description
+
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.test.tsx
new file mode 100644
index 0000000000000..5471df9a6f0be
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.test.tsx
@@ -0,0 +1,102 @@
+/*
+ * 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 '../../../../../__mocks__/shallow_useeffect.mock';
+
+import { setMockValues, setMockActions } from '../../../../../__mocks__';
+import { shallow } from 'enzyme';
+
+import React from 'react';
+
+import { EuiModal, EuiSelect, EuiFieldText } from '@elastic/eui';
+
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
+import { FieldEditorModal } from './field_editor_modal';
+
+describe('FieldEditorModal', () => {
+ const { searchResultConfig } = exampleResult;
+ const fieldOptions = [
+ {
+ value: 'foo',
+ text: 'Foo',
+ },
+ ];
+ const availableFieldOptions = [
+ {
+ value: 'bar',
+ text: 'Bar',
+ },
+ ];
+ const toggleFieldEditorModal = jest.fn();
+ const addDetailField = jest.fn();
+ const updateDetailField = jest.fn();
+
+ beforeEach(() => {
+ setMockActions({
+ toggleFieldEditorModal,
+ addDetailField,
+ updateDetailField,
+ });
+ setMockValues({
+ searchResultConfig,
+ fieldOptions,
+ availableFieldOptions,
+ editFieldIndex: 0,
+ });
+ });
+
+ it('renders', () => {
+ const wrapper = shallow(
);
+
+ expect(wrapper.find(EuiModal)).toHaveLength(1);
+ });
+
+ it('sets value on select change', () => {
+ const wrapper = shallow(
);
+ const select = wrapper.find(EuiSelect);
+
+ select.simulate('change', { target: { value: 'cats' } });
+
+ expect(select.prop('value')).toEqual('cats');
+ });
+
+ it('sets value on input change', () => {
+ const wrapper = shallow(
);
+ const input = wrapper.find(EuiFieldText);
+
+ input.simulate('change', { target: { value: 'Felines' } });
+
+ expect(input.prop('value')).toEqual('Felines');
+ });
+
+ it('handles form submission when creating', () => {
+ setMockValues({
+ searchResultConfig,
+ fieldOptions,
+ availableFieldOptions,
+ editFieldIndex: null,
+ });
+
+ const wrapper = shallow(
);
+
+ const preventDefault = jest.fn();
+ wrapper.find('form').simulate('submit', { preventDefault });
+
+ expect(preventDefault).toHaveBeenCalled();
+ expect(addDetailField).toHaveBeenCalled();
+ });
+
+ it('handles form submission when editing', () => {
+ const wrapper = shallow(
);
+
+ const preventDefault = jest.fn();
+ wrapper.find('form').simulate('submit', { preventDefault });
+
+ expect(preventDefault).toHaveBeenCalled();
+ expect(updateDetailField).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.test.tsx
new file mode 100644
index 0000000000000..6d9d60351f3db
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.test.tsx
@@ -0,0 +1,131 @@
+/*
+ * 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 '../../../../../__mocks__/shallow_useeffect.mock';
+
+import { setMockValues, setMockActions } from '../../../../../__mocks__';
+import { shallow, mount } from 'enzyme';
+
+/**
+ * Mocking necessary due to console warnings from react d-n-d, which EUI uses
+ * https://stackoverflow.com/a/56674119/1949235
+ */
+jest.mock('react-beautiful-dnd', () => ({
+ Droppable: ({ children }: { children: any }) =>
+ children(
+ {
+ draggableProps: {
+ style: {},
+ },
+ innerRef: jest.fn(),
+ },
+ {}
+ ),
+ Draggable: ({ children }: { children: any }) =>
+ children(
+ {
+ draggableProps: {
+ style: {},
+ },
+ innerRef: jest.fn(),
+ },
+ {}
+ ),
+ DragDropContext: ({ children }: { children: any }) => children,
+}));
+
+import React from 'react';
+
+import { EuiTextColor } from '@elastic/eui';
+
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
+import { ExampleResultDetailCard } from './example_result_detail_card';
+
+import { ResultDetail } from './result_detail';
+
+describe('ResultDetail', () => {
+ const { searchResultConfig, exampleDocuments } = exampleResult;
+ const availableFieldOptions = [
+ {
+ value: 'foo',
+ text: 'Foo',
+ },
+ ];
+ const toggleFieldEditorModal = jest.fn();
+ const setDetailFields = jest.fn();
+ const openEditDetailField = jest.fn();
+ const removeDetailField = jest.fn();
+
+ beforeEach(() => {
+ setMockActions({
+ toggleFieldEditorModal,
+ setDetailFields,
+ openEditDetailField,
+ removeDetailField,
+ });
+ setMockValues({
+ searchResultConfig,
+ availableFieldOptions,
+ exampleDocuments,
+ });
+ });
+
+ it('renders', () => {
+ const wrapper = shallow(
);
+
+ expect(wrapper.find(ExampleResultDetailCard)).toHaveLength(1);
+ });
+
+ it('calls setTitleField on change', () => {
+ const wrapper = shallow(
);
+ wrapper.find('[data-test-subj="AddFieldButton"]').simulate('click');
+
+ expect(toggleFieldEditorModal).toHaveBeenCalled();
+ });
+
+ it('handles empty detailFields', () => {
+ setMockValues({
+ searchResultConfig: {
+ ...searchResultConfig,
+ detailFields: [],
+ },
+ availableFieldOptions,
+ exampleDocuments,
+ });
+ const wrapper = shallow(
);
+
+ expect(wrapper.find('[data-test-subj="EmptyFieldsDescription"]')).toHaveLength(1);
+ });
+
+ it('handles drag and drop', () => {
+ const wrapper = mount(
);
+ wrapper.find('[data-test-subj="EditFieldButton"]').first().simulate('click');
+ wrapper.find('[data-test-subj="RemoveFieldButton"]').first().simulate('click');
+
+ expect(openEditDetailField).toHaveBeenCalled();
+ expect(removeDetailField).toHaveBeenCalled();
+ expect(wrapper.find(EuiTextColor).first().text()).toEqual('“Felines”');
+ });
+
+ it('handles empty label fallback', () => {
+ setMockValues({
+ searchResultConfig: {
+ ...searchResultConfig,
+ detailFields: [
+ {
+ fieldName: 'foo',
+ },
+ ],
+ },
+ availableFieldOptions,
+ exampleDocuments,
+ });
+ const wrapper = mount(
);
+
+ expect(wrapper.find(EuiTextColor).first().text()).toEqual('“”');
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx
index cb65d8ef671e6..5ee484250ca62 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx
@@ -80,7 +80,7 @@ export const ResultDetail: React.FC = () => {
<>
{detailFields.map(({ fieldName, label }, index) => (
{
openEditDetailField(index)}
/>
removeDetailField(index)}
@@ -125,7 +127,9 @@ export const ResultDetail: React.FC = () => {
) : (
- Add fields and move them into the order you want them to appear.
+
+ Add fields and move them into the order you want them to appear.
+
)}
>
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.test.tsx
new file mode 100644
index 0000000000000..776bf012aa895
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.test.tsx
@@ -0,0 +1,124 @@
+/*
+ * 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 '../../../../../__mocks__/shallow_useeffect.mock';
+
+import { setMockValues, setMockActions } from '../../../../../__mocks__';
+import { shallow } from 'enzyme';
+
+import React from 'react';
+
+import { exampleResult } from '../../../../__mocks__/content_sources.mock';
+
+import { ExampleSearchResultGroup } from './example_search_result_group';
+import { ExampleStandoutResult } from './example_standout_result';
+
+import { LEAVE_UNASSIGNED_FIELD } from './constants';
+import { SearchResults } from './search_results';
+
+describe('SearchResults', () => {
+ const { searchResultConfig } = exampleResult;
+ const fieldOptions = [
+ {
+ value: 'foo',
+ text: 'Foo',
+ },
+ ];
+ const optionalFieldOptions = [
+ {
+ value: 'bar',
+ text: 'Bar',
+ },
+ ];
+ const toggleTitleFieldHover = jest.fn();
+ const toggleSubtitleFieldHover = jest.fn();
+ const toggleDescriptionFieldHover = jest.fn();
+ const setTitleField = jest.fn();
+ const setSubtitleField = jest.fn();
+ const setDescriptionField = jest.fn();
+ const setUrlField = jest.fn();
+ const setColorField = jest.fn();
+
+ beforeEach(() => {
+ setMockActions({
+ toggleTitleFieldHover,
+ toggleSubtitleFieldHover,
+ toggleDescriptionFieldHover,
+ setTitleField,
+ setSubtitleField,
+ setDescriptionField,
+ setUrlField,
+ setColorField,
+ });
+ setMockValues({
+ searchResultConfig,
+ fieldOptions,
+ optionalFieldOptions,
+ });
+ });
+
+ it('renders', () => {
+ const wrapper = shallow();
+
+ expect(wrapper.find(ExampleSearchResultGroup)).toHaveLength(1);
+ expect(wrapper.find(ExampleStandoutResult)).toHaveLength(1);
+ });
+
+ it('calls setTitleField on change', () => {
+ const wrapper = shallow();
+ wrapper
+ .find('[data-test-subj="TitleFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.titleField } });
+
+ expect(setTitleField).toHaveBeenCalled();
+ });
+
+ it('calls setUrlField on change', () => {
+ const wrapper = shallow();
+ wrapper
+ .find('[data-test-subj="UrlFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.urlField } });
+
+ expect(setUrlField).toHaveBeenCalled();
+ });
+
+ it('calls setSubtitleField on change', () => {
+ const wrapper = shallow();
+ wrapper
+ .find('[data-test-subj="SubtitleFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.titleField } });
+
+ expect(setSubtitleField).toHaveBeenCalledWith(searchResultConfig.titleField);
+ });
+
+ it('calls setDescriptionField on change', () => {
+ const wrapper = shallow();
+ wrapper
+ .find('[data-test-subj="DescriptionFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.descriptionField } });
+
+ expect(setDescriptionField).toHaveBeenCalledWith(searchResultConfig.descriptionField);
+ });
+
+ it('handles blank fallbacks', () => {
+ setMockValues({
+ searchResultConfig: { detailFields: [] },
+ fieldOptions,
+ optionalFieldOptions,
+ });
+ const wrapper = shallow();
+ wrapper
+ .find('[data-test-subj="SubtitleFieldSelect"]')
+ .simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
+ wrapper
+ .find('[data-test-subj="DescriptionFieldSelect"]')
+ .simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
+
+ expect(wrapper.find('[data-test-subj="UrlFieldSelect"]').prop('value')).toEqual('');
+ expect(setSubtitleField).toHaveBeenCalledWith(null);
+ expect(setDescriptionField).toHaveBeenCalledWith(null);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx
index 96b7a6fbe14b5..c1a65d1c52b65 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx
@@ -59,8 +59,8 @@ export const SearchResults: React.FC = () => {
label="Title"
onMouseOver={toggleTitleFieldHover}
onMouseOut={toggleTitleFieldHover}
- onFocus={() => null} // FIXME
- onBlur={() => null} // FIXME
+ onFocus={toggleTitleFieldHover}
+ onBlur={toggleTitleFieldHover}
>
{
/>
- null} // FIXME
- onBlur={() => null} // FIXME
- />
+
null} // FIXME
- onBlur={() => null} // FIXME
+ onFocus={toggleSubtitleFieldHover}
+ onBlur={toggleSubtitleFieldHover}
>
{
helpText="This area is optional"
onMouseOver={toggleDescriptionFieldHover}
onMouseOut={toggleDescriptionFieldHover}
- onFocus={() => null} // FIXME
- onBlur={() => null} // FIXME
+ onFocus={toggleDescriptionFieldHover}
+ onBlur={toggleDescriptionFieldHover}
>
{
+ const result = { foo: 'bar' };
+ it('renders', () => {
+ const props = {
+ result,
+ subtitleField: 'foo',
+ subtitleFieldHover: false,
+ };
+ const wrapper = shallow();
+
+ expect(wrapper.find('[data-test-subj="SubtitleField"]')).toHaveLength(1);
+ });
+
+ it('shows fallback URL label when no override set', () => {
+ const props = {
+ result,
+ subtitleField: null,
+ subtitleFieldHover: false,
+ };
+ const wrapper = shallow();
+
+ expect(wrapper.find('[data-test-subj="DefaultSubtitleLabel"]')).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx
index e27052ddffc04..d2f26cd6726df 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx
@@ -22,6 +22,7 @@ export const SubtitleField: React.FC = ({
subtitleFieldHover,
}) => (
= ({
{subtitleField ? (
{result[subtitleField]}
) : (
-
Subtitle
+
+ Subtitle
+
)}
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
new file mode 100644
index 0000000000000..558fd7aa8c86a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.test.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { TitleField } from './title_field';
+
+describe('TitleField', () => {
+ const result = { foo: 'bar' };
+ it('renders', () => {
+ const props = {
+ result,
+ titleField: 'foo',
+ titleFieldHover: false,
+ };
+ const wrapper = shallow();
+
+ expect(wrapper.find('[data-test-subj="TitleField"]')).toHaveLength(1);
+ });
+
+ it('handles title when array', () => {
+ const props = {
+ result: { foo: ['baz', 'bar'] },
+ titleField: 'foo',
+ titleFieldHover: false,
+ };
+ const wrapper = shallow();
+
+ expect(wrapper.find('[data-test-subj="CustomTitleLabel"]').text()).toEqual('baz, bar');
+ });
+
+ it('shows fallback URL label when no override set', () => {
+ const props = {
+ result,
+ titleField: null,
+ titleFieldHover: false,
+ };
+ const wrapper = shallow();
+
+ expect(wrapper.find('[data-test-subj="DefaultTitleLabel"]')).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx
index a54c0977b464f..fa975c8b11ce0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx
@@ -21,14 +21,19 @@ export const TitleField: React.FC = ({ result, titleField, titl
const titleDisplay = Array.isArray(title) ? title.join(', ') : title;
return (
{titleField ? (
-
{titleDisplay}
+
+ {titleDisplay}
+
) : (
-
Title
+
+ Title
+
)}
);