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

Commit ee6cc77

Browse files
authored
fix: Fix display of tag color picker (#782)
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
1 parent 4431557 commit ee6cc77

3 files changed

Lines changed: 168 additions & 27 deletions

File tree

src/react/components/common/colorPicker.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,22 @@ export class ColorPicker extends React.Component<IColorPickerProps> {
2525

2626
private GithubPicker = () => {
2727
return (
28-
<GithubPicker
29-
color={{hex: this.props.color}}
30-
onChangeComplete={this.onChange}
31-
colors={this.props.colors}
32-
width={160}
33-
styles={{
34-
default: {
35-
card: {
36-
background: this.pickerBackground,
28+
<div className="color-picker">
29+
<GithubPicker
30+
color={{hex: this.props.color}}
31+
onChangeComplete={this.onChange}
32+
colors={this.props.colors}
33+
width={160}
34+
styles={{
35+
default: {
36+
card: {
37+
background: this.pickerBackground,
38+
},
3739
},
38-
},
39-
}}
40-
triangle={"hide"}
41-
/>
40+
}}
41+
triangle={"hide"}
42+
/>
43+
</div>
4244
);
4345
}
4446

src/react/components/common/tagInput/tagInput.test.tsx

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { TagInput, ITagInputProps, ITagInputState } from "./tagInput";
44
import MockFactory from "../../../../common/mockFactory";
55
import { ITag } from "../../../../models/applicationState";
66
import TagInputItem, { ITagInputItemProps } from "./tagInputItem";
7+
import { ColorPicker } from "../colorPicker";
78

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

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

44+
it("Edits tag name when alt clicked", () => {
45+
const props = createProps();
46+
const wrapper = createComponent(props);
47+
wrapper.find("div.tag-name-container").first().simulate("click", { altKey: true } );
48+
expect(wrapper.state().editingTag).toEqual(props.tags[0]);
49+
expect(wrapper.exists("input.tag-name-editor")).toBe(true);
50+
});
51+
52+
it("Edits tag color when alt clicked", () => {
53+
const props = createProps();
54+
const wrapper = createComponent(props);
55+
expect(wrapper.state().clickedColor).toBe(false);
56+
expect(wrapper.exists("div.color-picker")).toBe(false);
57+
wrapper.find("div.tag-color").first().simulate("click", { altKey: true } );
58+
expect(wrapper.state().clickedColor).toBe(true);
59+
expect(wrapper.state().showColorPicker).toBe(true);
60+
expect(wrapper.state().editingTag).toEqual(props.tags[0]);
61+
expect(wrapper.exists("div.color-picker")).toBe(true);
62+
// Get color picker and call onEditColor function
63+
const picker = wrapper.find(ColorPicker).instance() as ColorPicker;
64+
picker.props.onEditColor("#000000");
65+
expect(props.onChange).toBeCalled();
66+
expect(true).toBeTruthy();
67+
});
68+
4369
it("Calls onClick handler when clicking text", () => {
4470
const props: ITagInputProps = createProps();
4571
const wrapper = createComponent(props);
@@ -101,6 +127,34 @@ describe("Tag Input Component", () => {
101127
expect(wrapper.state().searchTags).toBe(true);
102128
});
103129

130+
it("Add tag box closed with escape key", () => {
131+
const wrapper = createComponent();
132+
expect(wrapper.exists(".tag-input-box")).toBe(false);
133+
expect(wrapper.state().addTags).toBeFalsy();
134+
wrapper.find("div.tag-input-toolbar-item.plus").simulate("click");
135+
expect(wrapper.exists(".tag-input-box")).toBe(true);
136+
expect(wrapper.state().addTags).toBe(true);
137+
138+
wrapper.find(".tag-input-box").simulate("keydown", { key: "Escape" });
139+
expect(wrapper.exists(".tag-input-box")).toBe(false);
140+
expect(wrapper.state().addTags).toBe(false);
141+
});
142+
143+
it("Tag search box closed with escape key", async () => {
144+
const wrapper = createComponent();
145+
expect(wrapper.exists(".tag-search-box")).toBe(false);
146+
expect(wrapper.state().searchTags).toBeFalsy();
147+
wrapper.find("div.tag-input-toolbar-item.search").simulate("click");
148+
expect(wrapper.exists(".tag-search-box")).toBe(true);
149+
expect(wrapper.state().searchTags).toBe(true);
150+
151+
wrapper.find(".tag-search-box").simulate("keydown", { key: "Escape" });
152+
await MockFactory.flushUi();
153+
expect(wrapper.state().searchTags).toBe(false);
154+
155+
expect(wrapper.exists(".tag-search-box")).toBe(false);
156+
});
157+
104158
it("Tag can be locked from toolbar", () => {
105159
const tags = MockFactory.createTestTags();
106160
const props = createProps(tags);
@@ -110,7 +164,7 @@ describe("Tag Input Component", () => {
110164
expect(props.onLockedTagsChange).toBeCalledWith([tags[0].name]);
111165
});
112166

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

177+
it("Tag color can be edited from toolbar", () => {
178+
const tags = MockFactory.createTestTags();
179+
const props = createProps(tags);
180+
const wrapper = createComponent(props);
181+
expect(wrapper.state().clickedColor).toBe(false);
182+
expect(wrapper.exists("div.color-picker")).toBe(false);
183+
wrapper.find("div.tag-color").first().simulate("click");
184+
expect(wrapper.state().clickedColor).toBe(true);
185+
wrapper.find("div.tag-input-toolbar-item.edit").simulate("click");
186+
expect(wrapper.state().showColorPicker).toBe(true);
187+
expect(wrapper.state().editingTag).toEqual(tags[0]);
188+
expect(wrapper.exists("div.color-picker")).toBe(true);
189+
// Get color picker and call onEditColor function
190+
const picker = wrapper.find(ColorPicker).instance() as ColorPicker;
191+
picker.props.onEditColor("#000000");
192+
expect(props.onChange).toBeCalled();
193+
expect(true).toBeTruthy();
194+
});
195+
123196
it("Tag can be moved up from toolbar", () => {
124197
const tags = MockFactory.createTestTags();
125198
const lastTag = tags[tags.length - 1];
@@ -169,6 +242,16 @@ describe("Tag Input Component", () => {
169242
expect(props.onChange).not.toBeCalled();
170243
});
171244

245+
it("Does not try to add tag with same name as existing tag", () => {
246+
const props: ITagInputProps = {
247+
...createProps(),
248+
showTagInputBox: true,
249+
};
250+
const wrapper = createComponent(props);
251+
wrapper.find(".tag-input-box").simulate("keydown", { key: "Enter", target: { value: props.tags[0].name } });
252+
expect(props.onChange).not.toBeCalled();
253+
});
254+
172255
it("Selects a tag", () => {
173256
const tags = MockFactory.createTestTags();
174257
const onChange = jest.fn();
@@ -230,6 +313,54 @@ describe("Tag Input Component", () => {
230313
expect(onChange).toBeCalledWith(expectedTags);
231314
});
232315

316+
it("Does not edit tag name with empty string", () => {
317+
const tags = MockFactory.createTestTags();
318+
const onChange = jest.fn();
319+
const onTagNameChange = jest.fn();
320+
const props = {
321+
...createProps(tags, onChange),
322+
onTagNameChange,
323+
};
324+
const wrapper = createComponent(props);
325+
wrapper.find(".tag-content").first().simulate("click");
326+
wrapper.find("i.tag-input-toolbar-icon.fas.fa-edit").simulate("click");
327+
wrapper.find("input.tag-name-editor").simulate("keydown", { key: "Enter", target: { value: "" } });
328+
expect(wrapper.state().tags).toEqual(tags);
329+
expect(onChange).not.toBeCalled();
330+
});
331+
332+
it("Does not call onChange when edited tag name is the same", () => {
333+
const tags = MockFactory.createTestTags();
334+
const onChange = jest.fn();
335+
const onTagNameChange = jest.fn();
336+
const props = {
337+
...createProps(tags, onChange),
338+
onTagNameChange,
339+
};
340+
const wrapper = createComponent(props);
341+
wrapper.find(".tag-content").first().simulate("click");
342+
wrapper.find("i.tag-input-toolbar-icon.fas.fa-edit").simulate("click");
343+
wrapper.find("input.tag-name-editor").simulate("keydown", { key: "Enter", target: { value: tags[0].name } });
344+
expect(wrapper.state().tags).toEqual(tags);
345+
expect(onChange).not.toBeCalled();
346+
});
347+
348+
it("Does not change tag name to name of other existing tag", () => {
349+
const tags = MockFactory.createTestTags();
350+
const onChange = jest.fn();
351+
const onTagNameChange = jest.fn();
352+
const props = {
353+
...createProps(tags, onChange),
354+
onTagNameChange,
355+
};
356+
const wrapper = createComponent(props);
357+
wrapper.find(".tag-content").first().simulate("click");
358+
wrapper.find("i.tag-input-toolbar-icon.fas.fa-edit").simulate("click");
359+
wrapper.find("input.tag-name-editor").simulate("keydown", { key: "Enter", target: { value: tags[1].name } });
360+
expect(wrapper.state().tags).toEqual(tags);
361+
expect(onChange).not.toBeCalled();
362+
});
363+
233364
it("Reorders a tag", () => {
234365
const tags = MockFactory.createTestTags();
235366
const onChange = jest.fn();
@@ -247,7 +378,20 @@ describe("Tag Input Component", () => {
247378
expect(wrapper.state().tags.indexOf(firstTag)).toEqual(0);
248379
});
249380

250-
it("set's applied tags when selected regions are available", () => {
381+
it("Searches for a tag", () => {
382+
const props: ITagInputProps = {
383+
...createProps(),
384+
showSearchBox: true,
385+
};
386+
const wrapper = createComponent(props);
387+
expect(wrapper.find(".tag-item-block").length).toBeGreaterThan(1);
388+
wrapper.find(".tag-search-box").simulate("change", { target: { value: "1" } });
389+
expect(wrapper.state().searchQuery).toEqual("1");
390+
expect(wrapper.find(".tag-item-block")).toHaveLength(1);
391+
expect(wrapper.find(".tag-name-body").first().text()).toEqual("Tag 1");
392+
});
393+
394+
it("sets applied tags when selected regions are available", () => {
251395
const tags = MockFactory.createTestTags();
252396
const onChange = jest.fn();
253397
const props = createProps(tags, onChange);

src/react/components/common/tagInput/tagInput.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
7272
portalElement: defaultDOMNode(),
7373
};
7474

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

7878
public render() {
@@ -98,6 +98,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
9898
this.state.searchTags &&
9999
<div className="tag-input-text-input-row search-input">
100100
<input
101+
className="tag-search-box"
101102
type="text"
102103
onKeyDown={this.onSearchKeyDown}
103104
onChange={(e) => this.setState({ searchQuery: e.target.value })}
@@ -155,22 +156,16 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
155156
}
156157

157158
private getTagNode = (tag: ITag): Element => {
158-
if (!tag) {
159-
return defaultDOMNode();
160-
}
161-
162-
const itemRef = this.tagItemRefs.get(tag.name);
163-
return (itemRef ? ReactDOM.findDOMNode(itemRef.current) : defaultDOMNode()) as Element;
159+
const itemRef = tag ? this.tagItemRefs.get(tag.name) : null;
160+
return (itemRef ? ReactDOM.findDOMNode(itemRef) : defaultDOMNode()) as Element;
164161
}
165162

166163
private onEditTag = (tag: ITag) => {
167-
if (!tag) {
168-
return;
169-
}
170164
const { editingTag } = this.state;
171165
const newEditingTag = (editingTag && editingTag.name === tag.name) ? null : tag;
172166
this.setState({
173167
editingTag: newEditingTag,
168+
editingTagNode: this.getTagNode(newEditingTag),
174169
});
175170
if (this.state.clickedColor) {
176171
this.setState({
@@ -222,7 +217,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
222217
}
223218

224219
private updateTag = (tag: ITag, newTag: ITag) => {
225-
if (tag === newTag) {
220+
if (tag.name === newTag.name && tag.color === newTag.color) {
226221
return;
227222
}
228223
if (!newTag.name.length) {
@@ -317,7 +312,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
317312
/>);
318313
}
319314

320-
private setTagItemRef = (item, tag) => {
315+
private setTagItemRef = (item: TagInputItem, tag: ITag) => {
321316
this.tagItemRefs.set(tag.name, item);
322317
return item;
323318
}

0 commit comments

Comments
 (0)