+
+
+
+
+ Hello World!
diff --git a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap
index 87e2784f989..19a670a600d 100644
--- a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap
+++ b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap
@@ -1,11 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`EuiInlineEditTitle props renders as title 1`] = `
+exports[`EuiInlineEditTitle renders 1`] = `
-
- hello world
-
+ Hello World!
+
+
+
+
+
+`;
+
+exports[`EuiInlineEditTitle title sizes renders size l 1`] = `
+
+
+
+
+
+
+ Hello World!
+
+
+
+
+
+`;
+
+exports[`EuiInlineEditTitle title sizes renders size m 1`] = `
+
+
+
+
+
+
+ Hello World!
+
+
+
+
+
+`;
+
+exports[`EuiInlineEditTitle title sizes renders size s 1`] = `
+
+
+
+
+
+
+ Hello World!
+
+
+
+
+
+`;
+
+exports[`EuiInlineEditTitle title sizes renders size xs 1`] = `
+
+
+
+
+
+
+ Hello World!
+
+
+
+
+
+`;
+
+exports[`EuiInlineEditTitle title sizes renders size xxs 1`] = `
+
+
+
+
+
+
+ Hello World!
+
+
+
+
+
+`;
+
+exports[`EuiInlineEditTitle title sizes renders size xxxs 1`] = `
+
+
+
+
+
+
+ Hello World!
+
diff --git a/src/components/inline_edit/inline_edit_form.test.tsx b/src/components/inline_edit/inline_edit_form.test.tsx
new file mode 100644
index 00000000000..1710f805942
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_form.test.tsx
@@ -0,0 +1,313 @@
+/*
+ * 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 React from 'react';
+import { render } from '../../test/rtl';
+import { requiredProps } from '../../test/required_props';
+import { fireEvent } from '@testing-library/dom';
+
+import {
+ EuiInlineEditForm,
+ EuiInlineEditFormProps,
+ SMALL_SIZE_FORM,
+ MEDIUM_SIZE_FORM,
+} from './inline_edit_form';
+
+describe('EuiInlineEditForm', () => {
+ const commonInlineEditFormProps: EuiInlineEditFormProps = {
+ ...requiredProps,
+ defaultValue: 'Hello World!',
+ inputAriaLabel: 'Edit inline',
+ sizes: MEDIUM_SIZE_FORM,
+ children: (readModeValue) => readModeValue,
+ };
+
+ describe('Read Mode', () => {
+ it('renders', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('renders readModeProps onto the button', () => {
+ const { container, getByTestSubject } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
+ });
+
+ it('renders small size', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+ });
+
+ describe('Edit Mode', () => {
+ it('renders', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('renders editModeProps.inputProps', () => {
+ const { container, getByTestSubject } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ expect(getByTestSubject('customInput')).toBeTruthy();
+ });
+
+ it('renders editModeProps.formRowProps', () => {
+ const { container, getByTestSubject } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ expect(getByTestSubject('customErrorText')).toBeTruthy();
+ });
+
+ it('renders save button and cancel button aria-labels', () => {
+ const { container, getByLabelText } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ expect(getByLabelText("Yes! Let's save.")).toBeTruthy();
+ expect(getByLabelText('Uh no. Do not save.')).toBeTruthy();
+ });
+
+ it('renders EuiSkeletonRectangles in place of editMode buttons when loading', () => {
+ const { container, queryByTestSubject } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+
+ expect(container.querySelectorAll('.euiSkeletonRectangle')).toHaveLength(
+ 2
+ );
+
+ expect(queryByTestSubject('euiInlineEditModeSaveButton')).toBeFalsy();
+ expect(queryByTestSubject('euiInlineEditModeCancelButton')).toBeFalsy();
+ });
+
+ it('disables the save button when input is invalid ', () => {
+ const { container, getByTestSubject } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+
+ expect(
+ getByTestSubject('euiInlineEditModeInput').hasAttribute('aria-invalid')
+ ).toBeTruthy();
+
+ expect(getByTestSubject('euiInlineEditModeSaveButton')).toBeDisabled();
+ expect(
+ getByTestSubject('euiInlineEditModeCancelButton')
+ ).not.toBeDisabled();
+ });
+
+ it('returns the latest value within EuiFieldText upon saving', () => {
+ const onSaveFunction = jest.fn();
+
+ const { getByTestSubject } = render(
+
+ );
+
+ fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
+ target: { value: 'New message!' },
+ });
+ fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
+
+ expect(onSaveFunction).toHaveBeenCalledWith('New message!');
+ });
+ });
+
+ describe('Toggling between readMode and editMode', () => {
+ it('clicking on the readModeButton takes us to editMode', () => {
+ const { getByTestSubject, queryByTestSubject } = render(
+
+ );
+
+ fireEvent.click(getByTestSubject('euiInlineReadModeButton'));
+ expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy();
+ expect(queryByTestSubject('euiInlineReadModeButton')).toBeFalsy();
+ });
+
+ it('saves text and returns to readMode', () => {
+ const { getByTestSubject, getByText } = render(
+
+ );
+
+ fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
+ target: { value: 'New message!' },
+ });
+ expect(
+ getByTestSubject('euiInlineEditModeInput').getAttribute('value')
+ ).toEqual('New message!');
+ fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
+
+ expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
+ expect(getByText('New message!')).toBeTruthy();
+ });
+
+ it('cancels text and returns to readMode', () => {
+ const onSave = jest.fn();
+
+ const { getByTestSubject, getByText } = render(
+
+ );
+
+ fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
+ target: { value: 'New message!' },
+ });
+ expect(
+ getByTestSubject('euiInlineEditModeInput').getAttribute('value')
+ ).toEqual('New message!');
+ fireEvent.click(getByTestSubject('euiInlineEditModeCancelButton'));
+
+ expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
+ expect(getByText('Hello World!')).toBeTruthy();
+ expect(onSave).not.toHaveBeenCalled();
+ });
+
+ describe('onConfirm behavior on save', () => {
+ it('returns to readMode with updated text when onConfirm returns true', () => {
+ const { getByTestSubject, getByText } = render(
+ true}
+ />
+ );
+
+ fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
+ target: { value: 'New message!' },
+ });
+ fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
+
+ expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
+ expect(getByText('New message!')).toBeTruthy();
+ });
+
+ it('stays in editMode when onConfirm returns false', () => {
+ const onSave = jest.fn();
+
+ const { getByTestSubject, queryByTestSubject } = render(
+ false}
+ />
+ );
+
+ fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
+ target: { value: 'New message!' },
+ });
+ fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
+
+ expect(queryByTestSubject('euiInlineReadModeButton')).toBeFalsy();
+ expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy();
+ expect(onSave).not.toHaveBeenCalled();
+ });
+
+ it('sends the editMode text to the onConfirm callback', () => {
+ const { getByText, getByTestSubject } = render(
+ {
+ return editModeValue === '' ? false : true;
+ }}
+ />
+ );
+
+ fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
+ target: { value: '' },
+ });
+ fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
+
+ expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy();
+
+ fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
+ target: { value: 'hey there' },
+ });
+ fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
+
+ expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
+ expect(getByText('hey there')).toBeTruthy();
+ });
+ });
+ });
+});
diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx
index f256b1dfa7e..23b68582707 100644
--- a/src/components/inline_edit/inline_edit_form.tsx
+++ b/src/components/inline_edit/inline_edit_form.tsx
@@ -6,7 +6,12 @@
* Side Public License, v 1.
*/
-import React, { ReactNode, FunctionComponent, useState } from 'react';
+import React, {
+ ReactNode,
+ FunctionComponent,
+ useState,
+ HTMLAttributes,
+} from 'react';
import classNames from 'classnames';
import { CommonProps } from '../common';
@@ -27,53 +32,56 @@ import { useEuiI18n } from '../i18n';
import { useGeneratedHtmlId } from '../../services/accessibility';
// Props shared between the internal form component as well as consumer-facing components
-export type EuiInlineEditCommonProps = CommonProps & {
- defaultValue: string;
- /**
- * Allow users to pass in a function that is called when the confirm button is clicked
- * The function should return a boolean flag that will determine if the value will be saved.
- * When the flag is true, the value will be saved. When the flag is false, the user will be
- * returned to editMode.
- */
- onConfirm?: () => boolean;
- /**
- * Form label that appears above the form control
- * This is required for accessibility because there is no visual label on the input
- */
- inputAriaLabel: string;
- /**
- * Aria-label for save button in editMode
- */
- saveButtonAriaLabel?: string;
- /**
- * Aria-label for cancel button in editMode
- */
- cancelButtonAriaLabel?: string;
- /**
- * Start in editMode
- */
- startWithEditOpen?: boolean;
- /**
- * Props that will be applied directly to the EuiEmptyButton displayed in readMode
- */
- readModeProps?: Omit;
- /**
- * Props that will be applied directly to the `EuiFormRow` and `EuiFieldText` input displayed in editMode
- */
- editModeProps?: {
- formRowProps?: Partial;
- inputProps?: Partial;
+export type EuiInlineEditCommonProps = HTMLAttributes &
+ CommonProps & {
+ defaultValue: string;
+ /**
+ * Callback that passes the updated value of the edited text when the save button is pressed,
+ * and the `onConfirm` callback (if passed) returns true
+ */
+ onSave?: (onSaveValue: string) => void;
+ /**
+ * Callback that fires when users click the save button, but before the text actually saves. Passes the current edited
+ * text value as an argument.
+ */
+ onConfirm?: (editModeValue: string) => boolean;
+ /**
+ * Form label that appears above the form control
+ * This is required for accessibility because there is no visual label on the input
+ */
+ inputAriaLabel: string;
+ /**
+ * Aria-label for save button in editMode
+ */
+ saveButtonAriaLabel?: string;
+ /**
+ * Aria-label for cancel button in editMode
+ */
+ cancelButtonAriaLabel?: string;
+ /**
+ * Start in editMode
+ */
+ startWithEditOpen?: boolean;
+ /**
+ * Props that will be applied directly to the EuiEmptyButton displayed in readMode
+ */
+ readModeProps?: Omit;
+ /**
+ * Props that will be applied directly to the `EuiFormRow` and `EuiFieldText` input displayed in editMode
+ */
+ editModeProps?: {
+ formRowProps?: Partial;
+ inputProps?: Partial;
+ };
+ /**
+ * Loading state when changes are saved in editMode
+ */
+ isLoading?: boolean;
+ /**
+ * Validation for the form control used to edit text in editMode
+ */
+ isInvalid?: boolean;
};
- /**
- * Loading state when changes are saved in editMode
- */
- isLoading?: boolean;
-
- /**
- * Validation for the form control used to edit text in editMode
- */
- isInvalid?: boolean;
-};
// Internal-only props, passed by the consumer-facing components
export type EuiInlineEditFormProps = EuiInlineEditCommonProps & {
@@ -115,8 +123,9 @@ export const EuiInlineEditForm: FunctionComponent = ({
startWithEditOpen,
readModeProps,
editModeProps,
- isLoading,
+ isLoading = false,
isInvalid,
+ onSave,
}) => {
const classes = classNames('euiInlineEdit', className);
@@ -147,15 +156,13 @@ export const EuiInlineEditForm: FunctionComponent = ({
};
const saveInlineEditValue = () => {
- if (editModeValue && onConfirm && !onConfirm()) {
- // If there is text, an onConfirm method is present, and it has returned false, cancel the action
+ if (onConfirm && !onConfirm(editModeValue)) {
+ // If an onConfirm method is present, and it has returned false, cancel the action
return;
- } else if (editModeValue) {
+ } else {
setReadModeValue(editModeValue);
setIsEditing(!isEditing);
- } else {
- // If there's no text, cancel the action, reset the input text, and return to readMode
- cancelInlineEdit();
+ onSave?.(editModeValue);
}
};
@@ -179,6 +186,7 @@ export const EuiInlineEditForm: FunctionComponent = ({
compressed={sizes.compressed}
isInvalid={isInvalid}
isLoading={isLoading}
+ data-test-subj="euiInlineEditModeInput"
{...editModeProps?.inputProps}
/>
@@ -201,6 +209,7 @@ export const EuiInlineEditForm: FunctionComponent = ({
size={sizes.buttonSize}
iconSize={sizes.iconSize}
disabled={isInvalid}
+ data-test-subj="euiInlineEditModeSaveButton"
/>
@@ -224,6 +233,7 @@ export const EuiInlineEditForm: FunctionComponent = ({
display="base"
size={sizes.buttonSize}
iconSize={sizes.iconSize}
+ data-test-subj="euiInlineEditModeCancelButton"
/>
@@ -244,6 +254,7 @@ export const EuiInlineEditForm: FunctionComponent = ({
onClick={() => {
setIsEditing(!isEditing);
}}
+ data-test-subj="euiInlineReadModeButton"
{...readModeProps}
>
{children(readModeValue)}
diff --git a/src/components/inline_edit/inline_edit_text.test.tsx b/src/components/inline_edit/inline_edit_text.test.tsx
index eb47d25e5b8..736f2b99e8c 100644
--- a/src/components/inline_edit/inline_edit_text.test.tsx
+++ b/src/components/inline_edit/inline_edit_text.test.tsx
@@ -10,20 +10,39 @@ import React from 'react';
import { render } from '../../test/rtl';
import { requiredProps } from '../../test/required_props';
-import { EuiInlineEditText } from './inline_edit_text';
+import { EuiInlineEditText, EuiInlineEditTextProps } from './inline_edit_text';
+import { TEXT_SIZES } from '../text/text';
describe('EuiInlineEditText', () => {
- describe('props', () => {
- test('renders as text', () => {
- const { container } = render(
-
- );
-
- expect(container.firstChild).toMatchSnapshot();
+ const inlineEditTextProps: EuiInlineEditTextProps = {
+ ...requiredProps,
+ inputAriaLabel: 'Edit text inline',
+ defaultValue: 'Hello World!',
+ };
+
+ it('renders', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ describe('text sizes', () => {
+ // Remove 'relative' from text sizes available for EuiInlineEditText
+ const availableTextSizes = TEXT_SIZES.filter((size) => size !== 'relative');
+
+ availableTextSizes.forEach((size: string) => {
+ test(`renders ${size}`, () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
});
});
diff --git a/src/components/inline_edit/inline_edit_text.tsx b/src/components/inline_edit/inline_edit_text.tsx
index 1719e369fdd..5a9451dba73 100644
--- a/src/components/inline_edit/inline_edit_text.tsx
+++ b/src/components/inline_edit/inline_edit_text.tsx
@@ -39,7 +39,7 @@ export const EuiInlineEditText: FunctionComponent = ({
startWithEditOpen,
readModeProps,
editModeProps,
- isLoading = false,
+ isLoading,
isInvalid = false,
...rest
}) => {
diff --git a/src/components/inline_edit/inline_edit_title.test.tsx b/src/components/inline_edit/inline_edit_title.test.tsx
index 9104347af45..84363d4fd11 100644
--- a/src/components/inline_edit/inline_edit_title.test.tsx
+++ b/src/components/inline_edit/inline_edit_title.test.tsx
@@ -10,21 +10,44 @@ import React from 'react';
import { render } from '../../test/rtl';
import { requiredProps } from '../../test/required_props';
-import { EuiInlineEditTitle } from './inline_edit_title';
+import {
+ EuiInlineEditTitle,
+ EuiInlineEditTitleProps,
+} from './inline_edit_title';
+import { TITLE_SIZES } from '../title/title';
describe('EuiInlineEditTitle', () => {
- describe('props', () => {
- test('renders as title', () => {
- const { container } = render(
-
- );
-
- expect(container.firstChild).toMatchSnapshot();
+ const inlineEditTitleProps: EuiInlineEditTitleProps = {
+ ...requiredProps,
+ inputAriaLabel: 'Edit title inline',
+ defaultValue: 'Hello World!',
+ heading: 'h1',
+ };
+
+ it('renders', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('renders the heading prop', () => {
+ const { container } = render(
+
+ );
+ expect(container.querySelector('h3')).toBeTruthy();
+ });
+
+ describe('title sizes', () => {
+ TITLE_SIZES.forEach((size) => {
+ it(`renders size ${size}`, () => {
+ const { container } = render(
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
});
});
diff --git a/src/components/inline_edit/inline_edit_title.tsx b/src/components/inline_edit/inline_edit_title.tsx
index a35ee2f8ef2..be663fa1c61 100644
--- a/src/components/inline_edit/inline_edit_title.tsx
+++ b/src/components/inline_edit/inline_edit_title.tsx
@@ -45,7 +45,7 @@ export const EuiInlineEditTitle: FunctionComponent = ({
startWithEditOpen = false,
readModeProps,
editModeProps,
- isLoading = false,
+ isLoading,
isInvalid = false,
...rest
}) => {