Skip to content

Commit

Permalink
fix(InlineEditor): value not updated on escape when controlled (#4379)
Browse files Browse the repository at this point in the history
  • Loading branch information
MEsteves22 authored Oct 8, 2024
1 parent dde7995 commit 039e057
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 19 deletions.
32 changes: 21 additions & 11 deletions packages/core/src/InlineEditor/InlineEditor.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { Meta, StoryObj } from "@storybook/react";
import { Decorator, Meta, StoryObj } from "@storybook/react";
import {
HvContainer,
HvGrid,
Expand All @@ -15,6 +15,10 @@ const meta: Meta<HvInlineEditorProps> = {
};
export default meta;

const decorator: Decorator = (Story) => (
<div style={{ width: 300 }}>{Story()}</div>
);

const variants: HvTypographyVariants[] = [
"display",
"title1",
Expand All @@ -35,21 +39,27 @@ export const Main: StoryObj<typeof HvInlineEditor<typeof HvInput>> = {
argTypes: {
classes: { control: { disable: true } },
},
render: (args) => {
return (
<div style={{ width: 300 }}>
<HvInlineEditor {...args} />
</div>
);
},
decorators: [decorator],
render: (args) => <HvInlineEditor {...args} />,
};

export const Disabled: StoryObj<HvInlineEditorProps> = {
decorators: [decorator],
render: () => <HvInlineEditor disabled />,
};

export const Controlled: StoryObj<HvInlineEditorProps> = {
decorators: [decorator],
render: () => {
const [value, setValue] = useState("My value");

return (
<div style={{ width: 300 }}>
<HvInlineEditor disabled />
</div>
<HvInlineEditor
value={value}
onChange={(event, newValue) => setValue(newValue)}
onBlur={(event, newValue) => setValue(newValue)}
onKeyDown={(event, newValue) => setValue(newValue)}
/>
);
},
};
Expand Down
118 changes: 112 additions & 6 deletions packages/core/src/InlineEditor/InlineEditor.test.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,122 @@
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { useState } from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";

import { HvInlineEditor } from "./InlineEditor";
import { HvInlineEditor, HvInlineEditorProps } from "./InlineEditor";

const controlledValue = "My value";
const controlledLabel = "Label";
const Controlled = ({
onBlur,
onChange,
onKeyDown,
...others
}: Partial<HvInlineEditorProps>) => {
const [value, setValue] = useState(controlledValue);

return (
<HvInlineEditor
label={controlledLabel}
value={value}
onChange={(event, newValue) => {
setValue(newValue);
onChange?.(event, newValue);
}}
onBlur={(event, newValue) => {
setValue(newValue);
onBlur?.(event, newValue);
}}
onKeyDown={(event, newValue) => {
setValue(newValue);
onKeyDown?.(event, newValue);
}}
{...others}
/>
);
};

describe("InlineEditor", () => {
it("renders the component as expected", () => {
const value = "VALUE123";
const { getByText } = render(<HvInlineEditor defaultValue={value} />);

const container = getByText(value);
render(<HvInlineEditor defaultValue={value} />);

const container = screen.getByText(value);
expect(container).toBeInTheDocument();
expect(container).toBeVisible();
});

it("should trigger onKeyDown and show the previous value when pressing ESC (controlled)", async () => {
const user = userEvent.setup();
const mockFn = vi.fn();
render(<Controlled onKeyDown={mockFn} />);

const inputButton = screen.getByRole("button", { name: controlledValue });
await user.click(inputButton);

const input = screen.getByRole("textbox", { name: controlledLabel });
const type = "123";
await user.type(input, type);
await user.keyboard("{Escape}");

expect(mockFn).toHaveBeenCalledTimes(type.length + 1);
expect(
screen.getByRole("button", { name: controlledValue }),
).toBeInTheDocument();
expect(
screen.queryByRole("button", { name: type }),
).not.toBeInTheDocument();
});

it("should trigger onChange when typing content (controlled)", async () => {
const user = userEvent.setup();
const mockFn = vi.fn();
render(<Controlled onChange={mockFn} />);

const inputButton = screen.getByRole("button", { name: controlledValue });
await user.click(inputButton);

const input = screen.getByRole("textbox", { name: controlledLabel });
const type = "123";
await user.type(input, type);

expect(mockFn).toHaveBeenCalledTimes(type.length);
});

it("should trigger onBlur and show the new value when blurring the input with new content (controlled)", async () => {
const user = userEvent.setup();
const mockFn = vi.fn();
render(<Controlled onBlur={mockFn} />);

const inputButton = screen.getByRole("button", { name: controlledValue });
await user.click(inputButton);

const input = screen.getByRole("textbox", { name: controlledLabel });
const type = "123";
await user.type(input, type);
await user.tab();

expect(mockFn).toHaveBeenCalledOnce();
expect(
screen.getByRole("button", { name: controlledValue + type }),
).toBeInTheDocument();
});

it("should trigger onBlur and show the previous value when blurring the input with empty content (controlled)", async () => {
const user = userEvent.setup();
const mockFn = vi.fn();
render(<Controlled onBlur={mockFn} />);

const inputButton = screen.getByRole("button", { name: controlledValue });
await user.click(inputButton);

const input = screen.getByRole("textbox", { name: controlledLabel });
await user.clear(input);
await user.tab();

expect(mockFn).toHaveBeenCalledOnce();
expect(
screen.getByRole("button", { name: controlledValue }),
).toBeInTheDocument();
});
});
13 changes: 11 additions & 2 deletions packages/core/src/InlineEditor/InlineEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ export type HvInlineEditorProps<C extends React.ElementType = typeof HvInput> =
) => void;
/** Called when the input value changes. */
onChange?: (event: React.SyntheticEvent, value: string) => void;
/** Called when there's a keydown event on the input. */
onKeyDown?: (
event:
| React.KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>
| React.MouseEvent,
value: string,
) => void;
/** Props passed to the HvButton component */
buttonProps?: HvButtonProps;
/** Props passed to the HvTypography text component */
Expand Down Expand Up @@ -116,11 +123,13 @@ export const HvInlineEditor = fixedForwardRef(function HvInlineEditor<
};

const handleKeyDown: HvInputProps["onKeyDown"] = (event) => {
let newValue = value;
if (isKey(event, "Esc")) {
newValue = cachedValue;
setEditMode(false);
setValue(cachedValue);
setValue(newValue);
}
onKeyDown?.(event as any);
onKeyDown?.(event, newValue);
};

const handleChange: HvInputProps["onChange"] = (event, val) => {
Expand Down

0 comments on commit 039e057

Please sign in to comment.