From d3aaa5bff7b868b742a3a66b7a97cb1048b13da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 17 May 2020 18:53:25 +0100 Subject: [PATCH] fix(ui): use custom inline edit component --- ui/package-lock.json | 9 -- ui/package.json | 1 - ui/src/Components/InlineEdit/index.js | 87 +++++++++++ ui/src/Components/InlineEdit/index.test.js | 141 ++++++++++++++++++ .../Labels/FilterInputLabel/index.js | 21 ++- .../Labels/FilterInputLabel/index.test.js | 4 +- 6 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 ui/src/Components/InlineEdit/index.js create mode 100644 ui/src/Components/InlineEdit/index.test.js diff --git a/ui/package-lock.json b/ui/package-lock.json index aadea0922..98ef3e08c 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -4,15 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@attently/riek": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@attently/riek/-/riek-2.0.1.tgz", - "integrity": "sha1-AFJ4WlurHKepkjbxNWJNOeZhkCQ=", - "requires": { - "debug": "^2.6.8", - "prop-types": "^15.5.10" - } - }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", diff --git a/ui/package.json b/ui/package.json index 0ab6335c9..5485a687d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -4,7 +4,6 @@ "license": "Apache-2.0", "private": true, "dependencies": { - "@attently/riek": "2.0.1", "@fortawesome/fontawesome-common-types": "0.2.28", "@fortawesome/fontawesome-svg-core": "1.2.28", "@fortawesome/free-regular-svg-icons": "5.13.0", diff --git a/ui/src/Components/InlineEdit/index.js b/ui/src/Components/InlineEdit/index.js new file mode 100644 index 000000000..9a01745b2 --- /dev/null +++ b/ui/src/Components/InlineEdit/index.js @@ -0,0 +1,87 @@ +import React, { useState, useRef, useEffect } from "react"; +import PropTypes from "prop-types"; + +import { useOnClickOutside } from "Hooks/useOnClickOutside"; + +const InlineEdit = ({ + className, + classNameEditing, + value, + onChange, + onEnterEditing, + onExitEditing, +}) => { + const ref = useRef(null); + const [editedValue, setEditedValue] = useState(null); + const [isEditing, setIsEditing] = useState(false); + + const startEditing = () => { + if (onEnterEditing) { + onEnterEditing(); + } + setIsEditing(true); + }; + + const doneEditing = () => { + setIsEditing(false); + setEditedValue(null); + if (onExitEditing) { + onExitEditing(); + } + }; + + const onInput = (event) => { + setEditedValue(event.target.value.trim()); + }; + + const onKeyDown = (event) => { + if (event.keyCode === 13) { + if (editedValue) { + onChange(editedValue); + } + doneEditing(); + } else if (event.keyCode === 27) { + doneEditing(); + } + }; + + useOnClickOutside(ref, doneEditing); + + useEffect(() => { + if (isEditing && ref.current) { + ref.current.focus(); + } + }, [isEditing, ref]); + + if (isEditing) { + const val = editedValue === null ? value : editedValue; + + return ( + + ); + } + + return ( + + {value} + + ); +}; +InlineEdit.propTypes = { + className: PropTypes.string, + classNameEditing: PropTypes.string, + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + onEnterEditing: PropTypes.func, + onExitEditing: PropTypes.func, +}; + +export { InlineEdit }; diff --git a/ui/src/Components/InlineEdit/index.test.js b/ui/src/Components/InlineEdit/index.test.js new file mode 100644 index 000000000..768bcb645 --- /dev/null +++ b/ui/src/Components/InlineEdit/index.test.js @@ -0,0 +1,141 @@ +import React from "react"; +import { act } from "react-dom/test-utils"; + +import { mount } from "enzyme"; + +import { InlineEdit } from "."; + +describe("", () => { + it("renders span by default", () => { + const tree = mount(); + expect(tree.html()).toBe('foo'); + }); + + it("renders input after click", () => { + const tree = mount(); + tree.simulate("click"); + expect(tree.html()).toBe(''); + }); + + it("edit mode start calls onEnterEditing", () => { + const onEnterEditing = jest.fn(); + const tree = mount( + + ); + + expect(onEnterEditing).not.toHaveBeenCalled(); + + tree.simulate("click"); + expect(tree.html()).toBe(''); + expect(onEnterEditing).toHaveBeenCalled(); + }); + + it("edit mode finish calls onExitEditing", () => { + const onExitEditing = jest.fn(); + const tree = mount( +
+ + +
+ ); + + expect(onExitEditing).not.toHaveBeenCalled(); + + tree.find("span").simulate("click"); + expect(onExitEditing).not.toHaveBeenCalled(); + + act(() => { + document.dispatchEvent( + new Event("mousedown", { target: tree.find("button").getDOMNode() }) + ); + }); + expect(tree.html()).not.toMatch(/ { + const tree = mount( +
+ + +
+ ); + + tree.find("span").simulate("click"); + expect(tree.html()).toMatch(/ { + document.dispatchEvent( + new Event("mousedown", { target: tree.find("button").getDOMNode() }) + ); + }); + expect(tree.html()).not.toMatch(/ { + const tree = mount(); + + tree.find("span").simulate("click"); + expect(tree.html()).toMatch(/'); + }); + + it("enter calls onChange if value was edited", () => { + const onChange = jest.fn(); + const tree = mount(); + + tree.find("span").simulate("click"); + expect(tree.html()).toMatch(/'); + + tree.simulate("keyDown", { keyCode: 13 }); + expect(onChange).toHaveBeenCalledWith("bar"); + }); + + it("enter doesn't call onChange if value was not edited", () => { + const onChange = jest.fn(); + const tree = mount(); + + tree.find("span").simulate("click"); + expect(tree.html()).toMatch(/ { + const onChange = jest.fn(); + const tree = mount(); + + tree.find("span").simulate("click"); + expect(tree.html()).toMatch(/ { + const onChange = jest.fn(); + const tree = mount(); + + tree.find("span").simulate("click"); + expect(tree.html()).toMatch(/ { - const onChange = ({ raw }) => { + const onChange = (val) => { // if filter is empty string then remove it - if (raw === "") { + if (val === "") { alertStore.filters.removeFilter(filter.raw); } // if not empty replace it - alertStore.filters.replaceFilter(filter.raw, raw); + alertStore.filters.replaceFilter(filter.raw, val); }; const cs = GetClassAndStyle( @@ -63,15 +62,13 @@ const FilterInputLabel = ({ alertStore, filter }) => { title="Click to edit this filter" className="components-filteredinputlabel-text flex-grow-1 flex-shrink-1 ml-1" > - { /> ); - const input = tree.find("RIEInput"); - input.props().change({ raw: newRaw }); + const input = tree.find("InlineEdit"); + input.props().onChange(newRaw); return tree; };