Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 44 additions & 42 deletions src/components/inline_edit/inline_edit_form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import React from 'react';
import { render } from '../../test/rtl';
import { requiredProps } from '../../test/required_props';
import { fireEvent } from '@testing-library/dom';
import { fireEvent, act, waitFor } from '@testing-library/react';

import {
EuiInlineEditForm,
Expand Down Expand Up @@ -165,28 +165,12 @@ describe('EuiInlineEditForm', () => {
getByTestSubject('euiInlineEditModeCancelButton')
).not.toBeDisabled();
});

it('returns the latest value within EuiFieldText upon saving', () => {
const onSaveFunction = jest.fn();

const { getByTestSubject } = render(
<EuiInlineEditForm
{...commonInlineEditFormProps}
startWithEditOpen={true}
onSave={onSaveFunction}
/>
);

fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
target: { value: 'New message!' },
});
fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));

expect(onSaveFunction).toHaveBeenCalledWith('New message!');
});
});

describe('Toggling between readMode and editMode', () => {
const onSave = jest.fn();
beforeEach(() => onSave.mockReset());

it('clicking on the readModeButton takes us to editMode', () => {
const { getByTestSubject, queryByTestSubject } = render(
<EuiInlineEditForm {...commonInlineEditFormProps} />
Expand All @@ -202,6 +186,7 @@ describe('EuiInlineEditForm', () => {
<EuiInlineEditForm
{...commonInlineEditFormProps}
startWithEditOpen={true}
onSave={onSave}
/>
);

Expand All @@ -215,11 +200,10 @@ describe('EuiInlineEditForm', () => {

expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
expect(getByText('New message!')).toBeTruthy();
expect(onSave).toHaveBeenCalledWith('New message!');
});

it('cancels text and returns to readMode', () => {
const onSave = jest.fn();

const { getByTestSubject, getByText } = render(
<EuiInlineEditForm
{...commonInlineEditFormProps}
Expand All @@ -241,72 +225,90 @@ describe('EuiInlineEditForm', () => {
expect(onSave).not.toHaveBeenCalled();
});

describe('onConfirm behavior on save', () => {
it('returns to readMode with updated text when onConfirm returns true', () => {
describe('onSave validation', () => {
it('returns to readMode with updated text when onSave returns true', () => {
onSave.mockReturnValueOnce(true);

const { getByTestSubject, getByText } = render(
<EuiInlineEditForm
{...commonInlineEditFormProps}
startWithEditOpen={true}
onConfirm={() => true}
onSave={onSave}
/>
);

fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
target: { value: 'New message!' },
});
fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
act(() => {
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();
it('stays in editMode when onSave returns false', () => {
onSave.mockReturnValueOnce(false);

const { getByTestSubject, queryByTestSubject } = render(
<EuiInlineEditForm
{...commonInlineEditFormProps}
startWithEditOpen={true}
onSave={onSave}
onConfirm={() => false}
/>
);

fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
target: { value: 'New message!' },
});
fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
act(() => {
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(
it('handles async promises', async () => {
onSave.mockImplementation(
(value) =>
new Promise((resolve) => {
setTimeout(resolve, 100);
return !!value; // returns false if empty string, true if not
})
);

const { getByTestSubject, queryByTestSubject, getByText } = render(
<EuiInlineEditForm
{...commonInlineEditFormProps}
startWithEditOpen={true}
onConfirm={(editModeValue) => {
return editModeValue === '' ? false : true;
}}
onSave={onSave}
/>
);

// Should still be in edit mode after an empty string is submitted
fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
target: { value: '' },
});
fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));

await act(async () => {
fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
waitFor(() => setTimeout(() => {}, 100)); // Let the promise finish resolving
});
expect(queryByTestSubject('euiInlineReadModeButton')).toBeFalsy();
expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy();

// Should successfully save into read mode
fireEvent.change(getByTestSubject('euiInlineEditModeInput'), {
target: { value: 'hey there' },
});
fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));

expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
expect(getByText('hey there')).toBeTruthy();
await act(async () => {
fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton'));
});
waitFor(() => {
expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy();
expect(getByText('hey there')).toBeTruthy();
});
});
});
});
Expand Down
34 changes: 17 additions & 17 deletions src/components/inline_edit/inline_edit_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,14 @@ export type EuiInlineEditCommonProps = HTMLAttributes<HTMLDivElement> &
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
* Callback that fires when a user clicks the save button.
* Passes the current edited text value as an argument.
*
* To validate the value of the edited text, pass back a boolean flag.
* If `false`, EuiInlineEdit will remain in edit mode, where loading or invalid states can be set.
* If `true`, EuiInlineEdit will return to read mode.
*/
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;
onSave?: (value: string) => void | boolean | Promise<boolean | void>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌟 Combining these into one function does make a lot more sense here.

/**
* Form label that appears above the form control
* This is required for accessibility because there is no visual label on the input
Expand Down Expand Up @@ -116,7 +115,6 @@ export const EuiInlineEditForm: FunctionComponent<EuiInlineEditFormProps> = ({
children,
sizes,
defaultValue,
onConfirm,
inputAriaLabel,
saveButtonAriaLabel,
cancelButtonAriaLabel,
Expand Down Expand Up @@ -155,15 +153,17 @@ export const EuiInlineEditForm: FunctionComponent<EuiInlineEditFormProps> = ({
setIsEditing(!isEditing);
};

const saveInlineEditValue = () => {
if (onConfirm && !onConfirm(editModeValue)) {
// If an onConfirm method is present, and it has returned false, cancel the action
return;
} else {
setReadModeValue(editModeValue);
setIsEditing(!isEditing);
onSave?.(editModeValue);
const saveInlineEditValue = async () => {
// If an onSave callback is present, and returns false, stay in edit mode
if (onSave) {
const onSaveReturn = onSave(editModeValue);
const awaitedReturn =
onSaveReturn instanceof Promise ? await onSaveReturn : onSaveReturn;
if (awaitedReturn === false) return;
}

setReadModeValue(editModeValue);
setIsEditing(!isEditing);
};

const editModeForm = (
Expand Down
2 changes: 0 additions & 2 deletions src/components/inline_edit/inline_edit_text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export const EuiInlineEditText: FunctionComponent<EuiInlineEditTextProps> = ({
className,
size = 'm',
defaultValue,
onConfirm,
inputAriaLabel,
saveButtonAriaLabel,
cancelButtonAriaLabel,
Expand All @@ -55,7 +54,6 @@ export const EuiInlineEditText: FunctionComponent<EuiInlineEditTextProps> = ({
const formProps = {
sizes,
defaultValue,
onConfirm,
inputAriaLabel,
saveButtonAriaLabel,
cancelButtonAriaLabel,
Expand Down
2 changes: 0 additions & 2 deletions src/components/inline_edit/inline_edit_title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export const EuiInlineEditTitle: FunctionComponent<EuiInlineEditTitleProps> = ({
size = 'm',
heading,
defaultValue,
onConfirm,
inputAriaLabel,
saveButtonAriaLabel,
cancelButtonAriaLabel,
Expand All @@ -63,7 +62,6 @@ export const EuiInlineEditTitle: FunctionComponent<EuiInlineEditTitleProps> = ({
const formProps = {
sizes,
defaultValue,
onConfirm,
inputAriaLabel,
saveButtonAriaLabel,
cancelButtonAriaLabel,
Expand Down