Skip to content

Commit 1673066

Browse files
authored
Merge pull request #23 from saseungmin/write-tags-submit
[Feature] Tags on study group opening creation page
2 parents b0d0ac2 + d2062f7 commit 1673066

File tree

9 files changed

+365
-3
lines changed

9 files changed

+365
-3
lines changed

src/App.test.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ describe('App', () => {
2424
useSelector.mockImplementation((selector) => selector({
2525
groups: STUDY_GROUPS,
2626
group: STUDY_GROUP,
27+
writeField: {
28+
tags: [],
29+
},
2730
}));
2831
});
2932

src/components/write/TagsForm.jsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React, { useEffect, useState } from 'react';
2+
3+
import styled from '@emotion/styled';
4+
5+
const TagsFormWrapper = styled.div``;
6+
7+
const TagsForm = ({ onChange, tags }) => {
8+
const [tag, setTag] = useState('');
9+
const [inputTags, setInputTags] = useState([]);
10+
11+
const validateInput = (value) => {
12+
if (!value) {
13+
return;
14+
}
15+
16+
if (inputTags.includes(value)) {
17+
return;
18+
}
19+
20+
const resultTags = [...inputTags, value];
21+
22+
setInputTags(resultTags);
23+
onChange(resultTags);
24+
};
25+
26+
const handleChange = (e) => {
27+
setTag(e.target.value);
28+
};
29+
30+
const handleRemove = (removeTag) => {
31+
const removeTags = inputTags.filter((value) => value !== removeTag);
32+
33+
setInputTags(removeTags);
34+
onChange(removeTags);
35+
};
36+
37+
const handleSubmit = () => {
38+
validateInput(tag.trim());
39+
setTag('');
40+
};
41+
42+
useEffect(() => {
43+
setInputTags(tags);
44+
}, [tags]);
45+
46+
return (
47+
<TagsFormWrapper>
48+
<h4>태그</h4>
49+
<input
50+
type="text"
51+
placeholder="태그를 입력하세요"
52+
value={tag}
53+
onChange={handleChange}
54+
onKeyPress={handleSubmit}
55+
/>
56+
<div>
57+
{inputTags.map((inputTag) => (
58+
<span
59+
key={inputTag}
60+
onClick={() => handleRemove(inputTag)}
61+
onKeyPress={() => {}}
62+
role="button"
63+
tabIndex="-1"
64+
>
65+
{`#${inputTag}`}
66+
</span>
67+
))}
68+
</div>
69+
</TagsFormWrapper>
70+
);
71+
};
72+
73+
export default TagsForm;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React from 'react';
2+
3+
import { fireEvent, render } from '@testing-library/react';
4+
5+
import TagsForm from './TagsForm';
6+
7+
describe('TagsForm', () => {
8+
const handleChange = jest.fn();
9+
10+
beforeEach(() => {
11+
handleChange.mockClear();
12+
});
13+
14+
const renderTagsTagsForm = (tags) => render((
15+
<TagsForm
16+
tags={tags}
17+
onChange={handleChange}
18+
/>
19+
));
20+
21+
describe('render Tag Form Container contents text', () => {
22+
it('renders tag form text', () => {
23+
const { getByPlaceholderText, container } = renderTagsTagsForm([]);
24+
25+
expect(getByPlaceholderText('태그를 입력하세요')).not.toBeNull();
26+
expect(container).toHaveTextContent('태그');
27+
});
28+
});
29+
30+
describe('Check input tag validate', () => {
31+
context('with tag input value', () => {
32+
const tags = ['JavaScript', 'React'];
33+
it('listens write field change events', () => {
34+
const { getByPlaceholderText } = renderTagsTagsForm(['CSS']);
35+
36+
const input = getByPlaceholderText('태그를 입력하세요');
37+
38+
tags.forEach((tag) => {
39+
fireEvent.change(input, { target: { value: tag } });
40+
41+
fireEvent.keyPress(input, { key: 'Enter', code: 13, charCode: 13 });
42+
43+
expect(input).toHaveValue('');
44+
});
45+
expect(handleChange).toBeCalledTimes(2);
46+
});
47+
});
48+
49+
context('without tag input value', () => {
50+
const tags = [];
51+
it("doesn't listens write field change events", () => {
52+
const { getByPlaceholderText } = renderTagsTagsForm(tags);
53+
54+
const input = getByPlaceholderText('태그를 입력하세요');
55+
56+
fireEvent.change(input, { target: { value: '' } });
57+
58+
fireEvent.keyPress(input, { key: 'Enter', code: 13, charCode: 13 });
59+
60+
expect(handleChange).not.toBeCalled();
61+
});
62+
});
63+
64+
describe('It is not executed because the current tag value is included.', () => {
65+
const tags = ['JavaScript', 'React'];
66+
it('listens write field change events', () => {
67+
const { getByPlaceholderText } = renderTagsTagsForm(tags);
68+
69+
const input = getByPlaceholderText('태그를 입력하세요');
70+
71+
tags.forEach((tag) => {
72+
fireEvent.change(input, { target: { value: tag } });
73+
74+
fireEvent.keyPress(input, { key: 'Enter', code: 13, charCode: 13 });
75+
76+
expect(input).toHaveValue('');
77+
78+
expect(handleChange).not.toBeCalled();
79+
});
80+
});
81+
});
82+
});
83+
84+
context('with tags', () => {
85+
const tags = ['JavaScript', 'React'];
86+
87+
it('renders Tags are below the input window', () => {
88+
const { container } = renderTagsTagsForm(tags);
89+
90+
expect(container).toHaveTextContent('#JavaScript');
91+
expect(container).toHaveTextContent('#React');
92+
});
93+
94+
it('Click event remove tag', () => {
95+
const { getByText, container } = renderTagsTagsForm(tags);
96+
97+
tags.forEach((tag) => {
98+
fireEvent.click(getByText(`#${tag}`));
99+
100+
expect(container).not.toHaveTextContent(`#${tag}`);
101+
});
102+
103+
expect(handleChange).toBeCalledTimes(2);
104+
});
105+
});
106+
107+
context('without tags', () => {
108+
const tags = [];
109+
110+
it('renders Tags are below the input window', () => {
111+
const { container } = renderTagsTagsForm(tags);
112+
113+
expect(container).not.toHaveTextContent('#JavaScript');
114+
expect(container).not.toHaveTextContent('#React');
115+
});
116+
});
117+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
3+
import { useDispatch, useSelector } from 'react-redux';
4+
5+
import { get } from '../../util/utils';
6+
import TagsForm from '../../components/write/TagsForm';
7+
import { changeWriteField } from '../../reducers/slice';
8+
9+
const TagsFormContainer = () => {
10+
const dispatch = useDispatch();
11+
12+
const { tags } = useSelector(get('writeField'));
13+
14+
const onChangeTags = (nextTags) => {
15+
dispatch(
16+
changeWriteField({
17+
name: 'tags',
18+
value: nextTags,
19+
}),
20+
);
21+
};
22+
23+
return (
24+
<TagsForm onChange={onChangeTags} tags={tags} />
25+
);
26+
};
27+
28+
export default TagsFormContainer;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
3+
import { fireEvent, render } from '@testing-library/react';
4+
5+
import { useDispatch, useSelector } from 'react-redux';
6+
7+
import TagsFormContainer from './TagsFormContainer';
8+
9+
describe('TagsFormContainer', () => {
10+
const dispatch = jest.fn();
11+
12+
beforeEach(() => {
13+
dispatch.mockClear();
14+
15+
useDispatch.mockImplementation(() => dispatch);
16+
17+
useSelector.mockImplementation((state) => state({
18+
writeField: {
19+
tags: [],
20+
},
21+
}));
22+
});
23+
24+
const renderTagsFormContainer = () => render((
25+
<TagsFormContainer />
26+
));
27+
28+
describe('render Tag Form Container contents text', () => {
29+
it('renders tag form text', () => {
30+
const { getByPlaceholderText, container } = renderTagsFormContainer();
31+
32+
expect(getByPlaceholderText('태그를 입력하세요')).not.toBeNull();
33+
expect(container).toHaveTextContent('태그');
34+
});
35+
});
36+
37+
describe('calls dispatch tags change action', () => {
38+
const tags = ['JavaScript', 'React'];
39+
it('change tags', () => {
40+
const { getByPlaceholderText } = renderTagsFormContainer();
41+
42+
const input = getByPlaceholderText('태그를 입력하세요');
43+
44+
tags.forEach((tag) => {
45+
fireEvent.change(input, { target: { value: tag } });
46+
47+
fireEvent.keyPress(input, { key: 'Enter', code: 13, charCode: 13 });
48+
49+
expect(input).toHaveValue('');
50+
});
51+
expect(dispatch).toBeCalledWith({
52+
type: 'application/changeWriteField',
53+
payload: { name: 'tags', value: tags },
54+
});
55+
});
56+
});
57+
});

src/pages/WritePage.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22

3+
import TagFormContainer from '../containers/write/TagsFormContainer';
4+
35
import Responsive from '../styles/Responsive';
46

57
const IntroducePage = () => (
@@ -9,11 +11,17 @@ const IntroducePage = () => (
911
<input type="text" placeholder="제목을 입력하세요" />
1012
</div>
1113
<div>
12-
<textarea rows="10" cols="100" placeholder="내용" />
14+
<span>모집 마감 날짜</span>
15+
<input type="date" />
1316
</div>
1417
<div>
15-
<input type="text" placeholder="태그를 입력하세요" />
18+
<span>참여 인원 수</span>
19+
<input type="number" />
20+
</div>
21+
<div>
22+
<textarea rows="10" cols="100" placeholder="내용" />
1623
</div>
24+
<TagFormContainer />
1725
<div>
1826
<button type="button">저장</button>
1927
</div>

src/pages/WritePage.test.jsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
import React from 'react';
22

3+
import { useDispatch, useSelector } from 'react-redux';
4+
35
import { render } from '@testing-library/react';
46

57
import WritePage from './WritePage';
68

79
describe('WritePage', () => {
10+
const dispatch = jest.fn();
11+
12+
beforeEach(() => {
13+
dispatch.mockClear();
14+
15+
useDispatch.mockImplementation(() => dispatch);
16+
17+
useSelector.mockImplementation((state) => state({
18+
writeField: {
19+
tags: [],
20+
},
21+
}));
22+
});
23+
824
const renderWritePage = () => render((
925
<WritePage />
1026
));
@@ -21,8 +37,14 @@ describe('WritePage', () => {
2137

2238
expect(getByPlaceholderText('제목을 입력하세요')).not.toBeNull();
2339
expect(getByPlaceholderText('내용')).not.toBeNull();
24-
expect(getByPlaceholderText('태그를 입력하세요')).not.toBeNull();
2540
expect(getByText('저장')).not.toBeNull();
2641
});
42+
43+
it('renders tag form text', () => {
44+
const { getByPlaceholderText, container } = renderWritePage();
45+
46+
expect(getByPlaceholderText('태그를 입력하세요')).not.toBeNull();
47+
expect(container).toHaveTextContent('태그');
48+
});
2749
});
2850
});

0 commit comments

Comments
 (0)