Skip to content
This repository has been archived by the owner on Dec 7, 2021. It is now read-only.

Commit

Permalink
fix: Fix display of tag color picker (#782)
Browse files Browse the repository at this point in the history
Resolves issue of tag color picker not being shown on alt-click or color-click + edit button. Also adds several tests for increased test coverage of tagInput.tsx
  • Loading branch information
tbarlow12 authored Apr 26, 2019
1 parent 4431557 commit ee6cc77
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 27 deletions.
28 changes: 15 additions & 13 deletions src/react/components/common/colorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,22 @@ export class ColorPicker extends React.Component<IColorPickerProps> {

private GithubPicker = () => {
return (
<GithubPicker
color={{hex: this.props.color}}
onChangeComplete={this.onChange}
colors={this.props.colors}
width={160}
styles={{
default: {
card: {
background: this.pickerBackground,
<div className="color-picker">
<GithubPicker
color={{hex: this.props.color}}
onChangeComplete={this.onChange}
colors={this.props.colors}
width={160}
styles={{
default: {
card: {
background: this.pickerBackground,
},
},
},
}}
triangle={"hide"}
/>
}}
triangle={"hide"}
/>
</div>
);
}

Expand Down
148 changes: 146 additions & 2 deletions src/react/components/common/tagInput/tagInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TagInput, ITagInputProps, ITagInputState } from "./tagInput";
import MockFactory from "../../../../common/mockFactory";
import { ITag } from "../../../../models/applicationState";
import TagInputItem, { ITagInputItemProps } from "./tagInputItem";
import { ColorPicker } from "../colorPicker";

describe("Tag Input Component", () => {

Expand Down Expand Up @@ -40,6 +41,31 @@ describe("Tag Input Component", () => {
expect(props.onCtrlTagClick).not.toBeCalled();
});

it("Edits tag name when alt clicked", () => {
const props = createProps();
const wrapper = createComponent(props);
wrapper.find("div.tag-name-container").first().simulate("click", { altKey: true } );
expect(wrapper.state().editingTag).toEqual(props.tags[0]);
expect(wrapper.exists("input.tag-name-editor")).toBe(true);
});

it("Edits tag color when alt clicked", () => {
const props = createProps();
const wrapper = createComponent(props);
expect(wrapper.state().clickedColor).toBe(false);
expect(wrapper.exists("div.color-picker")).toBe(false);
wrapper.find("div.tag-color").first().simulate("click", { altKey: true } );
expect(wrapper.state().clickedColor).toBe(true);
expect(wrapper.state().showColorPicker).toBe(true);
expect(wrapper.state().editingTag).toEqual(props.tags[0]);
expect(wrapper.exists("div.color-picker")).toBe(true);
// Get color picker and call onEditColor function
const picker = wrapper.find(ColorPicker).instance() as ColorPicker;
picker.props.onEditColor("#000000");
expect(props.onChange).toBeCalled();
expect(true).toBeTruthy();
});

it("Calls onClick handler when clicking text", () => {
const props: ITagInputProps = createProps();
const wrapper = createComponent(props);
Expand Down Expand Up @@ -101,6 +127,34 @@ describe("Tag Input Component", () => {
expect(wrapper.state().searchTags).toBe(true);
});

it("Add tag box closed with escape key", () => {
const wrapper = createComponent();
expect(wrapper.exists(".tag-input-box")).toBe(false);
expect(wrapper.state().addTags).toBeFalsy();
wrapper.find("div.tag-input-toolbar-item.plus").simulate("click");
expect(wrapper.exists(".tag-input-box")).toBe(true);
expect(wrapper.state().addTags).toBe(true);

wrapper.find(".tag-input-box").simulate("keydown", { key: "Escape" });
expect(wrapper.exists(".tag-input-box")).toBe(false);
expect(wrapper.state().addTags).toBe(false);
});

it("Tag search box closed with escape key", async () => {
const wrapper = createComponent();
expect(wrapper.exists(".tag-search-box")).toBe(false);
expect(wrapper.state().searchTags).toBeFalsy();
wrapper.find("div.tag-input-toolbar-item.search").simulate("click");
expect(wrapper.exists(".tag-search-box")).toBe(true);
expect(wrapper.state().searchTags).toBe(true);

wrapper.find(".tag-search-box").simulate("keydown", { key: "Escape" });
await MockFactory.flushUi();
expect(wrapper.state().searchTags).toBe(false);

expect(wrapper.exists(".tag-search-box")).toBe(false);
});

it("Tag can be locked from toolbar", () => {
const tags = MockFactory.createTestTags();
const props = createProps(tags);
Expand All @@ -110,7 +164,7 @@ describe("Tag Input Component", () => {
expect(props.onLockedTagsChange).toBeCalledWith([tags[0].name]);
});

it("Tag can be edited from toolbar", () => {
it("Tag name can be edited from toolbar", () => {
const tags = MockFactory.createTestTags();
const props = createProps(tags);
const wrapper = createComponent(props);
Expand All @@ -120,6 +174,25 @@ describe("Tag Input Component", () => {
expect(wrapper.exists("input.tag-name-editor")).toBe(true);
});

it("Tag color can be edited from toolbar", () => {
const tags = MockFactory.createTestTags();
const props = createProps(tags);
const wrapper = createComponent(props);
expect(wrapper.state().clickedColor).toBe(false);
expect(wrapper.exists("div.color-picker")).toBe(false);
wrapper.find("div.tag-color").first().simulate("click");
expect(wrapper.state().clickedColor).toBe(true);
wrapper.find("div.tag-input-toolbar-item.edit").simulate("click");
expect(wrapper.state().showColorPicker).toBe(true);
expect(wrapper.state().editingTag).toEqual(tags[0]);
expect(wrapper.exists("div.color-picker")).toBe(true);
// Get color picker and call onEditColor function
const picker = wrapper.find(ColorPicker).instance() as ColorPicker;
picker.props.onEditColor("#000000");
expect(props.onChange).toBeCalled();
expect(true).toBeTruthy();
});

it("Tag can be moved up from toolbar", () => {
const tags = MockFactory.createTestTags();
const lastTag = tags[tags.length - 1];
Expand Down Expand Up @@ -169,6 +242,16 @@ describe("Tag Input Component", () => {
expect(props.onChange).not.toBeCalled();
});

it("Does not try to add tag with same name as existing tag", () => {
const props: ITagInputProps = {
...createProps(),
showTagInputBox: true,
};
const wrapper = createComponent(props);
wrapper.find(".tag-input-box").simulate("keydown", { key: "Enter", target: { value: props.tags[0].name } });
expect(props.onChange).not.toBeCalled();
});

it("Selects a tag", () => {
const tags = MockFactory.createTestTags();
const onChange = jest.fn();
Expand Down Expand Up @@ -230,6 +313,54 @@ describe("Tag Input Component", () => {
expect(onChange).toBeCalledWith(expectedTags);
});

it("Does not edit tag name with empty string", () => {
const tags = MockFactory.createTestTags();
const onChange = jest.fn();
const onTagNameChange = jest.fn();
const props = {
...createProps(tags, onChange),
onTagNameChange,
};
const wrapper = createComponent(props);
wrapper.find(".tag-content").first().simulate("click");
wrapper.find("i.tag-input-toolbar-icon.fas.fa-edit").simulate("click");
wrapper.find("input.tag-name-editor").simulate("keydown", { key: "Enter", target: { value: "" } });
expect(wrapper.state().tags).toEqual(tags);
expect(onChange).not.toBeCalled();
});

it("Does not call onChange when edited tag name is the same", () => {
const tags = MockFactory.createTestTags();
const onChange = jest.fn();
const onTagNameChange = jest.fn();
const props = {
...createProps(tags, onChange),
onTagNameChange,
};
const wrapper = createComponent(props);
wrapper.find(".tag-content").first().simulate("click");
wrapper.find("i.tag-input-toolbar-icon.fas.fa-edit").simulate("click");
wrapper.find("input.tag-name-editor").simulate("keydown", { key: "Enter", target: { value: tags[0].name } });
expect(wrapper.state().tags).toEqual(tags);
expect(onChange).not.toBeCalled();
});

it("Does not change tag name to name of other existing tag", () => {
const tags = MockFactory.createTestTags();
const onChange = jest.fn();
const onTagNameChange = jest.fn();
const props = {
...createProps(tags, onChange),
onTagNameChange,
};
const wrapper = createComponent(props);
wrapper.find(".tag-content").first().simulate("click");
wrapper.find("i.tag-input-toolbar-icon.fas.fa-edit").simulate("click");
wrapper.find("input.tag-name-editor").simulate("keydown", { key: "Enter", target: { value: tags[1].name } });
expect(wrapper.state().tags).toEqual(tags);
expect(onChange).not.toBeCalled();
});

it("Reorders a tag", () => {
const tags = MockFactory.createTestTags();
const onChange = jest.fn();
Expand All @@ -247,7 +378,20 @@ describe("Tag Input Component", () => {
expect(wrapper.state().tags.indexOf(firstTag)).toEqual(0);
});

it("set's applied tags when selected regions are available", () => {
it("Searches for a tag", () => {
const props: ITagInputProps = {
...createProps(),
showSearchBox: true,
};
const wrapper = createComponent(props);
expect(wrapper.find(".tag-item-block").length).toBeGreaterThan(1);
wrapper.find(".tag-search-box").simulate("change", { target: { value: "1" } });
expect(wrapper.state().searchQuery).toEqual("1");
expect(wrapper.find(".tag-item-block")).toHaveLength(1);
expect(wrapper.find(".tag-name-body").first().text()).toEqual("Tag 1");
});

it("sets applied tags when selected regions are available", () => {
const tags = MockFactory.createTestTags();
const onChange = jest.fn();
const props = createProps(tags, onChange);
Expand Down
19 changes: 7 additions & 12 deletions src/react/components/common/tagInput/tagInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
portalElement: defaultDOMNode(),
};

private tagItemRefs: Map<string, RefObject<TagInputItem>> = new Map<string, RefObject<TagInputItem>>();
private tagItemRefs: Map<string, TagInputItem> = new Map<string, TagInputItem>();
private portalDiv = document.createElement("div");

public render() {
Expand All @@ -98,6 +98,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
this.state.searchTags &&
<div className="tag-input-text-input-row search-input">
<input
className="tag-search-box"
type="text"
onKeyDown={this.onSearchKeyDown}
onChange={(e) => this.setState({ searchQuery: e.target.value })}
Expand Down Expand Up @@ -155,22 +156,16 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
}

private getTagNode = (tag: ITag): Element => {
if (!tag) {
return defaultDOMNode();
}

const itemRef = this.tagItemRefs.get(tag.name);
return (itemRef ? ReactDOM.findDOMNode(itemRef.current) : defaultDOMNode()) as Element;
const itemRef = tag ? this.tagItemRefs.get(tag.name) : null;
return (itemRef ? ReactDOM.findDOMNode(itemRef) : defaultDOMNode()) as Element;
}

private onEditTag = (tag: ITag) => {
if (!tag) {
return;
}
const { editingTag } = this.state;
const newEditingTag = (editingTag && editingTag.name === tag.name) ? null : tag;
this.setState({
editingTag: newEditingTag,
editingTagNode: this.getTagNode(newEditingTag),
});
if (this.state.clickedColor) {
this.setState({
Expand Down Expand Up @@ -222,7 +217,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
}

private updateTag = (tag: ITag, newTag: ITag) => {
if (tag === newTag) {
if (tag.name === newTag.name && tag.color === newTag.color) {
return;
}
if (!newTag.name.length) {
Expand Down Expand Up @@ -317,7 +312,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
/>);
}

private setTagItemRef = (item, tag) => {
private setTagItemRef = (item: TagInputItem, tag: ITag) => {
this.tagItemRefs.set(tag.name, item);
return item;
}
Expand Down

0 comments on commit ee6cc77

Please sign in to comment.