@kiva/kv-components 3.54.0 → 3.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [3.55.1](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.55.0...@kiva/kv-components@3.55.1) (2024-02-29)
7
+
8
+ **Note:** Version bump only for package @kiva/kv-components
9
+
10
+
11
+
12
+
13
+
14
+ # [3.55.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.54.0...@kiva/kv-components@3.55.0) (2024-02-29)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * input width ([2489339](https://github.com/kiva/kv-ui-elements/commit/2489339c622ac45739bcc1eefc0a69837f90b841))
20
+ * when to send hide event and test not recognizing vue3 ([c3941a7](https://github.com/kiva/kv-ui-elements/commit/c3941a7a73a439846e6abf16b6dd0b29fa0e36f7))
21
+
22
+
23
+ ### Features
24
+
25
+ * adding input for comment reply ([b18edf0](https://github.com/kiva/kv-ui-elements/commit/b18edf0ecd300f622379d160237fd750cf117fa1))
26
+ * update tests and remove handleClick for emits ([fc3c586](https://github.com/kiva/kv-ui-elements/commit/fc3c58667248fb4e44bb0a7558f6c88f97f299b1))
27
+
28
+
29
+
30
+
31
+
6
32
  # [3.54.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.53.0...@kiva/kv-components@3.54.0) (2024-02-28)
7
33
 
8
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kiva/kv-components",
3
- "version": "3.54.0",
3
+ "version": "3.55.1",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -75,5 +75,5 @@
75
75
  "optional": true
76
76
  }
77
77
  },
78
- "gitHead": "b29d4c7e40f229ac09c7f68c377741af7d068158"
78
+ "gitHead": "c8fdabba600d0e9b03143a90a2d8681abde09adb"
79
79
  }
@@ -16,26 +16,151 @@ const comments = {
16
16
  created_at: '2024-02-01T20:12:31.398932Z',
17
17
  updated_at: '2024-02-01T20:12:31.398932Z',
18
18
  id: 'dade3812-6aa0-4c32-90ea-2366dab178a1',
19
- user_id: '1de650a5-b12f-4d20-b46c-6e15b0722141',
20
19
  kind: 'comment',
21
20
  activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
22
21
  data: {
23
22
  text: 'another guest comment',
24
23
  },
24
+ user: {
25
+ data: {
26
+ publicLenderId: 'Nathan2352',
27
+ name: 'Nathan',
28
+ image: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg', // eslint-disable-line max-len
29
+ },
30
+ },
25
31
  parent: '',
26
- latest_children: {},
32
+ latest_children: {
33
+ like: [
34
+ {
35
+ created_at: '2024-02-28T20:04:21.445272Z',
36
+ updated_at: '2024-02-28T20:04:21.445272Z',
37
+ id: '290a6c07-143a-416e-b016-ac0d9ac8ad91',
38
+ user: {
39
+ created_at: '2024-02-28T19:09:54.093757Z',
40
+ updated_at: '2024-02-28T19:09:54.093757Z',
41
+ data: {
42
+ publicLenderId: 'Jess1234',
43
+ name: 'Jess',
44
+ image: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg', // eslint-disable-line max-len
45
+ },
46
+ },
47
+ kind: 'like',
48
+ activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
49
+ data: {},
50
+ parent: '6384df77-d7c4-4ea9-b1b9-ef1c76a054a2',
51
+ latest_children: {},
52
+ children_counts: {},
53
+ },
54
+ ],
55
+ comment: [
56
+ {
57
+ created_at: '2024-02-28T20:04:21.445272Z',
58
+ updated_at: '2024-02-28T20:04:21.445272Z',
59
+ id: '290a6c07-143a-416e-b016-ac0d9ac8ad92',
60
+ user: {
61
+ data: {
62
+ publicLenderId: 'Casey1234',
63
+ name: 'Casey',
64
+ image: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg', // eslint-disable-line max-len
65
+ },
66
+ },
67
+ kind: 'comment',
68
+ activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
69
+ data: {
70
+ text: 'child comment',
71
+ },
72
+ parent: '6384df77-d7c4-4ea9-b1b9-ef1c76a054a2',
73
+ latest_children: {
74
+ comment: [
75
+ {
76
+ created_at: '2024-02-28T20:04:21.445272Z',
77
+ updated_at: '2024-02-28T20:04:21.445272Z',
78
+ id: '290a6c07-143a-416e-b016-ac0d9ac8ad93',
79
+ user: {
80
+ data: {
81
+ publicLenderId: 'Sarah1234',
82
+ name: 'Sarah',
83
+ image: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg', // eslint-disable-line max-len
84
+ },
85
+ },
86
+ kind: 'comment',
87
+ activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
88
+ data: {
89
+ text: 'child comment reply',
90
+ },
91
+ parent: '6384df77-d7c4-4ea9-b1b9-ef1c76a054a2',
92
+ latest_children: {},
93
+ children_counts: {},
94
+ },
95
+ ],
96
+ },
97
+ children_counts: {},
98
+ },
99
+ {
100
+ created_at: '2024-02-28T20:04:21.445272Z',
101
+ updated_at: '2024-02-28T20:04:21.445272Z',
102
+ id: '290a6c07-143a-416e-b016-ac0d9ac8ad90',
103
+ user: {
104
+ data: {
105
+ publicLenderId: 'Sophie1234',
106
+ name: 'Sophie',
107
+ image: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg', // eslint-disable-line max-len
108
+ },
109
+ },
110
+ kind: 'comment',
111
+ activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
112
+ data: {
113
+ text: 'second child comment',
114
+ },
115
+ parent: '6384df77-d7c4-4ea9-b1b9-ef1c76a054a2',
116
+ latest_children: {
117
+ like: [
118
+ {
119
+ created_at: '2024-02-28T20:04:21.445272Z',
120
+ updated_at: '2024-02-28T20:04:21.445272Z',
121
+ id: '290a6c07-143a-416e-b016-ac0d9ac8ad99',
122
+ user: {
123
+ created_at: '2024-02-28T19:09:54.093757Z',
124
+ updated_at: '2024-02-28T19:09:54.093757Z',
125
+ data: {
126
+ publicLenderId: 'Jess1234',
127
+ name: 'Jess',
128
+ image: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg', // eslint-disable-line max-len
129
+ },
130
+ },
131
+ kind: 'like',
132
+ activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
133
+ data: {},
134
+ parent: '6384df77-d7c4-4ea9-b1b9-ef1c76a054a2',
135
+ latest_children: {},
136
+ children_counts: {},
137
+ },
138
+ ],
139
+ },
140
+ children_counts: {},
141
+ },
142
+ ],
143
+ },
27
144
  children_counts: {},
28
145
  },
29
146
  {
30
147
  created_at: '2024-02-01T20:11:23.931133Z',
31
148
  updated_at: '2024-02-01T20:11:23.931133Z',
32
149
  id: '3de615bb-0d48-4a69-9004-141ee26985eb',
33
- user_id: '1de650a5-b12f-4d20-b46c-6e15b0722141',
34
150
  kind: 'comment',
35
151
  activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
36
152
  data: {
37
153
  text: 'test as visitor',
38
154
  },
155
+ user: {
156
+ created_at: '2024-02-28T19:09:54.093757Z',
157
+ updated_at: '2024-02-28T19:09:54.093757Z',
158
+ data: {
159
+ publicLenderId: 'Lauren1234',
160
+ name: '',
161
+ image: '',
162
+ },
163
+ },
39
164
  parent: '',
40
165
  latest_children: {},
41
166
  children_counts: {},
@@ -45,17 +170,19 @@ const comments = {
45
170
  created_at: '2024-02-01T20:06:46.651764Z',
46
171
  updated_at: '2024-02-01T20:06:46.651764Z',
47
172
  id: 'e1db4420-159e-4ba2-aab9-704d1cc56dae',
48
- user_id: '123',
49
173
  user: {
50
- created_at: '2024-02-01T19:48:26.890734Z',
51
- updated_at: '2024-02-01T19:48:26.890734Z',
52
- id: '123',
53
- data: {},
174
+ created_at: '2024-02-28T19:09:54.093757Z',
175
+ updated_at: '2024-02-28T19:09:54.093757Z',
176
+ data: {
177
+ publicLenderId: 'Todd1234',
178
+ name: 'Todd',
179
+ image: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg', // eslint-disable-line max-len
180
+ },
54
181
  },
55
182
  kind: 'comment',
56
183
  activity_id: 'efb2dbfe-c12a-11ee-9fbc-065afce7d41d',
57
184
  data: {
58
- text: 'asd',
185
+ text: 'comment test forever!',
59
186
  },
60
187
  parent: '',
61
188
  latest_children: {},
@@ -1,6 +1,6 @@
1
1
  import { render } from '@testing-library/vue';
2
2
  import userEvent from '@testing-library/user-event';
3
- import CommentsAdd, { ADD_COMMENT_EVENT } from '../../../../vue/KvCommentsAdd.vue';
3
+ import CommentsAdd, { ADD_COMMENT_EVENT, HIDE_INPUT_EVENT } from '../../../../vue/KvCommentsAdd.vue';
4
4
 
5
5
  const renderCommentsAdd = (props = {}) => {
6
6
  return render(CommentsAdd, { props });
@@ -9,7 +9,7 @@ const renderCommentsAdd = (props = {}) => {
9
9
  describe('KvCommentsAdd', () => {
10
10
  it('should render defaults', () => {
11
11
  const { getByPlaceholderText, getByRole } = renderCommentsAdd();
12
- getByPlaceholderText('Add a comment to this loan...');
12
+ getByPlaceholderText('Add a comment...');
13
13
  getByRole('button', { name: 'Cancel' });
14
14
  getByRole('button', { name: 'Comment' });
15
15
  });
@@ -25,7 +25,7 @@ describe('KvCommentsAdd', () => {
25
25
 
26
26
  it('should enable comment button when text entered', async () => {
27
27
  const { getByPlaceholderText, getByRole } = renderCommentsAdd();
28
- const textInput = getByPlaceholderText('Add a comment to this loan...');
28
+ const textInput = getByPlaceholderText('Add a comment...');
29
29
  const commentButton = getByRole('button', { name: 'Comment' });
30
30
 
31
31
  expect(commentButton.disabled).toBeTruthy();
@@ -41,7 +41,7 @@ describe('KvCommentsAdd', () => {
41
41
 
42
42
  it('should clear text when cancel clicked', async () => {
43
43
  const { getByPlaceholderText, getByRole } = renderCommentsAdd();
44
- const textInput = getByPlaceholderText('Add a comment to this loan...');
44
+ const textInput = getByPlaceholderText('Add a comment...');
45
45
  const cancelButton = getByRole('button', { name: 'Cancel' });
46
46
 
47
47
  await userEvent.type(textInput, 'test');
@@ -52,7 +52,7 @@ describe('KvCommentsAdd', () => {
52
52
 
53
53
  it('should emit value when comment clicked', async () => {
54
54
  const { getByPlaceholderText, getByRole, emitted } = renderCommentsAdd();
55
- const textInput = getByPlaceholderText('Add a comment to this loan...');
55
+ const textInput = getByPlaceholderText('Add a comment...');
56
56
  const commentButton = getByRole('button', { name: 'Comment' });
57
57
  const TEST_INPUT = 'test test';
58
58
 
@@ -63,6 +63,18 @@ describe('KvCommentsAdd', () => {
63
63
  expect(emitted()[ADD_COMMENT_EVENT]).toEqual([[TEST_INPUT]]);
64
64
  });
65
65
 
66
+ it('should emit value when enter key pressed', async () => {
67
+ const { getByPlaceholderText, emitted } = renderCommentsAdd();
68
+ const textInput = getByPlaceholderText('Add a comment...');
69
+ const TEST_INPUT = 'test test';
70
+
71
+ await userEvent.type(textInput, TEST_INPUT);
72
+
73
+ userEvent.keyboard('{enter}');
74
+
75
+ expect(emitted()[ADD_COMMENT_EVENT]).toEqual([[TEST_INPUT]]);
76
+ });
77
+
66
78
  it('should not emit empty value when comment clicked', async () => {
67
79
  const { getByRole, emitted } = renderCommentsAdd();
68
80
  const commentButton = getByRole('button', { name: 'Comment' });
@@ -71,4 +83,26 @@ describe('KvCommentsAdd', () => {
71
83
 
72
84
  expect(emitted()[ADD_COMMENT_EVENT]).toBe(undefined);
73
85
  });
86
+
87
+ it('should emit close event when it replies a comment', async () => {
88
+ const { getByRole, emitted, getByPlaceholderText } = renderCommentsAdd({ isReply: true });
89
+ const textInput = getByPlaceholderText('Add a comment...');
90
+ const commentButton = getByRole('button', { name: 'Comment' });
91
+ const TEST_INPUT = 'test test';
92
+
93
+ await userEvent.type(textInput, TEST_INPUT);
94
+
95
+ await userEvent.click(commentButton);
96
+
97
+ expect(emitted()[HIDE_INPUT_EVENT]).toEqual([[]]);
98
+ });
99
+
100
+ it('should emit close event when it clicks cancel on reply', async () => {
101
+ const { getByRole, emitted } = renderCommentsAdd({ isReply: true });
102
+ const cancelButton = getByRole('button', { name: 'Cancel' });
103
+
104
+ await userEvent.click(cancelButton);
105
+
106
+ expect(emitted()[HIDE_INPUT_EVENT]).toEqual([[]]);
107
+ });
74
108
  });
@@ -1,6 +1,6 @@
1
1
  import { render } from '@testing-library/vue';
2
2
  import userEvent from '@testing-library/user-event';
3
- import Container from '../../../../vue/KvCommentsContainer.vue';
3
+ import Container, { ADD_REACTION_EVENT } from '../../../../vue/KvCommentsContainer.vue';
4
4
  import { ADD_COMMENT_ID, ADD_COMMENT_EVENT } from '../../../../vue/KvCommentsAdd.vue';
5
5
 
6
6
  const renderContainer = (props = {}) => {
@@ -15,7 +15,7 @@ describe('KvCommentsContainer', () => {
15
15
 
16
16
  it('should emit comment', async () => {
17
17
  const { getByPlaceholderText, getByRole, emitted } = renderContainer();
18
- const textInput = getByPlaceholderText('Add a comment to this loan...');
18
+ const textInput = getByPlaceholderText('Add a comment...');
19
19
  const commentButton = getByRole('button', { name: 'Comment' });
20
20
  const TEST_INPUT = 'test test';
21
21
 
@@ -23,6 +23,11 @@ describe('KvCommentsContainer', () => {
23
23
 
24
24
  await userEvent.click(commentButton);
25
25
 
26
- expect(emitted()[ADD_COMMENT_EVENT]).toEqual([[TEST_INPUT]]);
26
+ expect(emitted()[ADD_REACTION_EVENT]).toEqual([[{
27
+ id: null,
28
+ isChild: false,
29
+ reaction: ADD_COMMENT_EVENT,
30
+ value: TEST_INPUT,
31
+ }]]);
27
32
  });
28
33
  });
@@ -4,7 +4,7 @@ import KvCommentsHeartButton from '../../../../vue/KvCommentsHeartButton.vue';
4
4
 
5
5
  const CLICK_EVENT = 'click';
6
6
 
7
- describe('KvCommentsListItem', () => {
7
+ describe('KvCommentsHeartButton', () => {
8
8
  it('should render defaults', () => {
9
9
  const { getByRole } = render(KvCommentsHeartButton);
10
10
  const likeButton = getByRole('button', { name: 'Like' });
@@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
3
3
  import ListComponent from '../../../../vue/KvCommentsList.vue';
4
4
  import activityFeed from '../../../fixtures/mockFeedActivityData';
5
5
  import { LIKE_COMMENT_EVENT, REPLY_COMMENT_EVENT } from '../../../../vue/KvCommentsListItem.vue';
6
+ import { ADD_REACTION_EVENT } from '../../../../vue/KvCommentsContainer.vue';
6
7
 
7
8
  const renderList = (props = {}) => {
8
9
  return render(ListComponent, { props });
@@ -18,22 +19,44 @@ describe('KvCommentsList', () => {
18
19
  expect(container.querySelectorAll(`#${id}`).length).toBe(1);
19
20
  });
20
21
 
21
- it('should emit comment events', async () => {
22
+ it('should emit like reaction events', async () => {
22
23
  const { getAllByRole, emitted } = renderList({ comments });
23
- const replyButton = getAllByRole('button', { name: 'Reply' })[0];
24
24
  const likeButton = getAllByRole('button', { name: 'Like' })[0];
25
25
  const firstComment = comments.comment[0];
26
26
 
27
27
  const TEST_OBJ = {
28
- userId: firstComment.user_id,
28
+ id: firstComment.id,
29
+ isChild: true,
30
+ };
31
+
32
+ await userEvent.click(likeButton);
33
+ expect(emitted()[ADD_REACTION_EVENT]).toEqual([[{ ...TEST_OBJ, reaction: LIKE_COMMENT_EVENT, value: true }]]);
34
+ });
35
+
36
+ it('should emit reply reaction events', async () => {
37
+ const {
38
+ getAllByRole,
39
+ getByRole,
40
+ getByPlaceholderText,
41
+ emitted,
42
+ } = renderList({ comments });
43
+ const replyButton = getAllByRole('button', { name: 'Reply' })[0];
44
+ const firstComment = comments.comment[0];
45
+
46
+ const TEST_OBJ = {
29
47
  id: firstComment.id,
30
48
  isChild: true,
31
49
  };
32
50
 
33
51
  await userEvent.click(replyButton);
34
- expect(emitted()[REPLY_COMMENT_EVENT]).toEqual([[{ ...TEST_OBJ, reaction: REPLY_COMMENT_EVENT }]]);
52
+ const commentButton = getByRole('button', { name: 'Comment' });
35
53
 
36
- await userEvent.click(likeButton);
37
- expect(emitted()[LIKE_COMMENT_EVENT]).toEqual([[{ ...TEST_OBJ, reaction: LIKE_COMMENT_EVENT, value: true }]]);
54
+ const textInput = getByPlaceholderText('Add a comment...');
55
+ const TEST_INPUT = 'test test';
56
+
57
+ await userEvent.type(textInput, TEST_INPUT);
58
+
59
+ await userEvent.click(commentButton);
60
+ expect(emitted()[ADD_REACTION_EVENT]).toEqual([[{ ...TEST_OBJ, reaction: REPLY_COMMENT_EVENT, value: TEST_INPUT }]]);
38
61
  });
39
62
  });
@@ -1,21 +1,23 @@
1
1
  import { render } from '@testing-library/vue';
2
2
  import userEvent from '@testing-library/user-event';
3
- import KvCommentsListItem from '../../../../vue/KvCommentsListItem.vue';
3
+ import KvCommentsListItem, { LIKE_COMMENT_EVENT, REPLY_COMMENT_EVENT } from '../../../../vue/KvCommentsListItem.vue';
4
4
  import activityFeed from '../../../fixtures/mockFeedActivityData';
5
+ import { ADD_REACTION_EVENT } from '../../../../vue/KvCommentsContainer.vue';
5
6
 
6
7
  const comment = activityFeed.results[0].latest_reactions.comment[0];
7
8
 
8
- const handleClick = jest.fn();
9
-
10
9
  const renderComment = (props = {}) => {
11
10
  return render(KvCommentsListItem, { props });
12
11
  };
13
12
 
14
13
  describe('KvCommentsListItem', () => {
15
14
  it('should render defaults', () => {
16
- const { getByRole } = renderComment({ comment });
17
- getByRole('button', { name: 'Reply' });
18
- getByRole('button', { name: 'Like' });
15
+ const { getAllByRole } = renderComment({ comment });
16
+ const replyButton = getAllByRole('button', { name: 'Reply' })[0];
17
+ const likeButton = getAllByRole('button', { name: 'Like' })[0];
18
+
19
+ expect(likeButton).toBeDefined();
20
+ expect(replyButton).toBeDefined();
19
21
  });
20
22
 
21
23
  it('should render comment text', () => {
@@ -24,20 +26,50 @@ describe('KvCommentsListItem', () => {
24
26
  });
25
27
 
26
28
  it('should handle like button click', async () => {
27
- const { getByRole } = renderComment({ comment, handleClick });
28
- const likeButton = getByRole('button', { name: 'Like' });
29
+ const { getAllByRole, emitted } = renderComment({ comment });
30
+ const likeButton = getAllByRole('button', { name: 'Like' })[0];
29
31
 
30
32
  await userEvent.click(likeButton);
31
33
 
32
- expect(handleClick).toHaveBeenCalledTimes(1);
34
+ expect(emitted()[ADD_REACTION_EVENT]).toEqual([[{
35
+ id: comment.id,
36
+ isChild: true,
37
+ reaction: LIKE_COMMENT_EVENT,
38
+ value: true,
39
+ }]]);
33
40
  });
34
41
 
35
42
  it('should handle reply button click', async () => {
36
- const { getByRole } = renderComment({ comment, handleClick });
37
- const replyButton = getByRole('button', { name: 'Reply' });
43
+ const { getAllByRole, getByRole } = renderComment({ comment });
44
+ const replyButton = getAllByRole('button', { name: 'Reply' })[0];
38
45
 
39
46
  await userEvent.click(replyButton);
47
+ getByRole('button', { name: 'Comment' });
48
+ });
49
+
50
+ it('should emit reply reaction events', async () => {
51
+ const {
52
+ getAllByRole,
53
+ getByRole,
54
+ getByPlaceholderText,
55
+ emitted,
56
+ } = renderComment({ comment });
57
+ const replyButton = getAllByRole('button', { name: 'Reply' })[0];
58
+
59
+ const TEST_OBJ = {
60
+ id: comment.id,
61
+ isChild: true,
62
+ };
63
+
64
+ await userEvent.click(replyButton);
65
+ const commentButton = getByRole('button', { name: 'Comment' });
66
+
67
+ const textInput = getByPlaceholderText('Add a comment...');
68
+ const TEST_INPUT = 'test test';
69
+
70
+ await userEvent.type(textInput, TEST_INPUT);
40
71
 
41
- expect(handleClick).toHaveBeenCalledTimes(1);
72
+ await userEvent.click(commentButton);
73
+ expect(emitted()[ADD_REACTION_EVENT]).toEqual([[{ ...TEST_OBJ, reaction: REPLY_COMMENT_EVENT, value: TEST_INPUT }]]);
42
74
  });
43
75
  });
@@ -0,0 +1,23 @@
1
+ import { render } from '@testing-library/vue';
2
+ import userEvent from '@testing-library/user-event';
3
+ import KvCommentsReplyButton from '../../../../vue/KvCommentsReplyButton.vue';
4
+
5
+ const CLICK_EVENT = 'click';
6
+
7
+ describe('KvCommentsReplyButton', () => {
8
+ it('should render defaults', () => {
9
+ const { getByRole } = render(KvCommentsReplyButton);
10
+ const replyButton = getByRole('button', { name: 'Reply' });
11
+
12
+ expect(replyButton).toBeDefined();
13
+ });
14
+
15
+ it('should emit click event when clicked', async () => {
16
+ const { getByRole, emitted } = render(KvCommentsReplyButton);
17
+ const replyButton = getByRole('button', { name: 'Reply' });
18
+
19
+ await userEvent.click(replyButton);
20
+
21
+ expect(emitted()[CLICK_EVENT]).toEqual([[]]);
22
+ });
23
+ });
@@ -22,9 +22,11 @@
22
22
  </div>
23
23
  <kv-text-input
24
24
  :id="ADD_COMMENT_ID"
25
+ ref="input"
25
26
  v-model="addCommentValue"
26
- placeholder="Add a comment to this loan..."
27
+ placeholder="Add a comment..."
27
28
  class="data-hj-suppress tw-grow"
29
+ @keyup.enter="comment"
28
30
  />
29
31
  </div>
30
32
  <div class="tw-flex tw-py-0.5 tw-gap-0.5">
@@ -53,8 +55,10 @@ import KvTextInput from './KvTextInput.vue';
53
55
 
54
56
  export const ADD_COMMENT_ID = 'add-comment-value';
55
57
  export const ADD_COMMENT_EVENT = 'add-comment';
58
+ export const HIDE_INPUT_EVENT = 'hide-input';
56
59
 
57
60
  export default {
61
+ name: 'KvCommentsAdd',
58
62
  components: {
59
63
  KvButton,
60
64
  KvTextInput,
@@ -74,27 +78,45 @@ export default {
74
78
  type: String,
75
79
  default: '',
76
80
  },
81
+ /**
82
+ * Whether or not the comment is a reply
83
+ */
84
+ isReply: {
85
+ type: Boolean,
86
+ default: false,
87
+ },
77
88
  },
78
- emits: [ADD_COMMENT_EVENT],
79
- setup(_props, { emit }) {
89
+ emits: [ADD_COMMENT_EVENT, HIDE_INPUT_EVENT],
90
+ setup(props, { emit }) {
80
91
  const addCommentValue = ref('');
92
+ const input = ref(null);
81
93
 
82
94
  const commentButtonState = computed(() => (addCommentValue.value ? '' : 'disabled'));
83
95
 
84
96
  const cancel = () => {
85
97
  addCommentValue.value = '';
98
+ if (props.isReply) {
99
+ emit(HIDE_INPUT_EVENT);
100
+ }
86
101
  };
87
102
 
88
103
  const comment = () => {
89
104
  emit(ADD_COMMENT_EVENT, addCommentValue.value);
105
+ addCommentValue.value = '';
106
+ if (props.isReply) {
107
+ emit(HIDE_INPUT_EVENT);
108
+ }
90
109
  };
91
110
 
111
+ const focus = () => input.value.focus();
112
+
92
113
  return {
93
114
  ADD_COMMENT_ID,
94
115
  addCommentValue,
95
116
  commentButtonState,
96
117
  cancel,
97
118
  comment,
119
+ focus,
98
120
  };
99
121
  },
100
122
  };
@@ -3,13 +3,15 @@
3
3
  <kv-comments-add
4
4
  :user-image-url="userImageUrl"
5
5
  :user-display-name="userDisplayName"
6
- @add-comment="comment"
6
+ @add-comment="handleReaction"
7
7
  />
8
8
  <kv-comments-list
9
9
  v-if="comments"
10
+ :user-image-url="userImageUrl"
11
+ :user-display-name="userDisplayName"
12
+ :user-public-id="userPublicId"
10
13
  :comments="comments"
11
- @[REPLY_COMMENT_EVENT]="comment"
12
- @[LIKE_COMMENT_EVENT]="comment"
14
+ @add-reaction="handleReaction"
13
15
  />
14
16
  </div>
15
17
  </template>
@@ -17,11 +19,12 @@
17
19
  <script>
18
20
  import KvCommentsAdd from './KvCommentsAdd.vue';
19
21
  import KvCommentsList from './KvCommentsList.vue';
20
- import { REPLY_COMMENT_EVENT, LIKE_COMMENT_EVENT } from './KvCommentsListItem.vue';
21
22
 
22
23
  export const ADD_COMMENT_EVENT = 'add-comment';
24
+ export const ADD_REACTION_EVENT = 'add-reaction';
23
25
 
24
26
  export default {
27
+ name: 'KvCommentsContainer',
25
28
  components: {
26
29
  KvCommentsAdd,
27
30
  KvCommentsList,
@@ -41,6 +44,13 @@ export default {
41
44
  type: String,
42
45
  default: '',
43
46
  },
47
+ /**
48
+ * The ID for the user
49
+ */
50
+ userPublicId: {
51
+ type: String,
52
+ default: '',
53
+ },
44
54
  /**
45
55
  * Activity comments
46
56
  */
@@ -49,16 +59,24 @@ export default {
49
59
  default: () => {},
50
60
  },
51
61
  },
62
+ emits: [ADD_REACTION_EVENT],
52
63
  setup(_props, { emit }) {
53
- const comment = (commentValue) => {
54
- emit(ADD_COMMENT_EVENT, commentValue);
55
- emit(REPLY_COMMENT_EVENT, commentValue);
56
- emit(LIKE_COMMENT_EVENT, commentValue);
64
+ const handleReaction = (reactionPayload) => {
65
+ let payload;
66
+ if (reactionPayload.value === undefined) {
67
+ payload = {
68
+ reaction: ADD_COMMENT_EVENT,
69
+ id: null,
70
+ isChild: false,
71
+ value: reactionPayload,
72
+ };
73
+ } else {
74
+ payload = reactionPayload;
75
+ }
76
+ emit(ADD_REACTION_EVENT, payload);
57
77
  };
58
78
  return {
59
- comment,
60
- REPLY_COMMENT_EVENT,
61
- LIKE_COMMENT_EVENT,
79
+ handleReaction,
62
80
  };
63
81
  },
64
82
  };
@@ -1,7 +1,8 @@
1
1
  <template>
2
2
  <button
3
3
  aria-label="Like"
4
- @click="changeState"
4
+ class="tw-flex tw-flex-col tw-justify-center"
5
+ @click="$emit('click', !isLiked);"
5
6
  >
6
7
  <kv-material-icon
7
8
  :icon="icon"
@@ -39,7 +40,7 @@ export default {
39
40
  emits: [
40
41
  'click',
41
42
  ],
42
- setup(props, { emit }) {
43
+ setup(props) {
43
44
  const {
44
45
  isSmall,
45
46
  isLiked,
@@ -54,14 +55,9 @@ export default {
54
55
  return className;
55
56
  });
56
57
 
57
- const changeState = () => {
58
- emit('click', !isLiked.value);
59
- };
60
-
61
58
  return {
62
59
  icon,
63
60
  classState,
64
- changeState,
65
61
  };
66
62
  },
67
63
  };
@@ -6,19 +6,46 @@
6
6
  :key="comment.id"
7
7
  :nest-level="1"
8
8
  :comment="comment"
9
+ :user-image-url="userImageUrl"
10
+ :user-display-name="userDisplayName"
11
+ :user-public-id="userPublicId"
9
12
  :is-liked="comment.is_liked"
10
- :handle-click="handleClick"
13
+ class="tw-mb-2"
14
+ @add-reaction="handleReaction"
11
15
  />
12
16
  </div>
13
17
  </template>
14
18
 
15
19
  <script>
16
20
 
17
- import KvCommentsListItem, { REPLY_COMMENT_EVENT, LIKE_COMMENT_EVENT } from './KvCommentsListItem.vue';
21
+ import KvCommentsListItem from './KvCommentsListItem.vue';
22
+ import { ADD_REACTION_EVENT } from './KvCommentsContainer.vue';
18
23
 
19
24
  export default {
25
+ name: 'KvCommentsList',
20
26
  components: { KvCommentsListItem },
21
27
  props: {
28
+ /**
29
+ * The full URL for the user image
30
+ */
31
+ userImageUrl: {
32
+ type: String,
33
+ default: '',
34
+ },
35
+ /**
36
+ * The name to display for the user
37
+ */
38
+ userDisplayName: {
39
+ type: String,
40
+ default: '',
41
+ },
42
+ /**
43
+ * The ID for the user
44
+ */
45
+ userPublicId: {
46
+ type: String,
47
+ default: '',
48
+ },
22
49
  /**
23
50
  * Activity comments
24
51
  */
@@ -27,12 +54,15 @@ export default {
27
54
  default: () => {},
28
55
  },
29
56
  },
30
- methods: {
31
- handleClick(payload) {
32
- if ([REPLY_COMMENT_EVENT, LIKE_COMMENT_EVENT].includes(payload.reaction)) {
33
- this.$emit(payload.reaction, { ...payload });
34
- }
35
- },
57
+ emits: [ADD_REACTION_EVENT],
58
+ setup(_props, { emit }) {
59
+ const handleReaction = (payload) => {
60
+ emit(ADD_REACTION_EVENT, payload);
61
+ };
62
+
63
+ return {
64
+ handleReaction,
65
+ };
36
66
  },
37
67
  };
38
68
  </script>
@@ -1,54 +1,75 @@
1
1
  <template>
2
2
  <div>
3
- <div
4
- class="tw-flex tw-items-center tw-gap-1"
5
- >
6
- <img
7
- v-if="profileImage"
8
- class="
9
- data-hj-suppress
10
- tw-inline-block
11
- tw-w-3.5
12
- tw-h-3.5
13
- tw-rounded-full
14
- tw-overflow-hidden
15
- tw-object-fill
16
- "
17
- :src="profileImage"
18
- alt="picture"
19
- >
3
+ <div>
4
+ <div class="tw-flex tw-items-center tw-gap-1">
5
+ <img
6
+ v-if="authorImage"
7
+ class="
8
+ data-hj-suppress
9
+ tw-inline-block
10
+ tw-w-3.5
11
+ tw-h-3.5
12
+ tw-rounded-full
13
+ tw-overflow-hidden
14
+ tw-object-fill
15
+ "
16
+ :src="authorImage"
17
+ alt="picture"
18
+ >
19
+ <p
20
+ v-if="authorName"
21
+ class="tw-font-medium"
22
+ >
23
+ {{ authorName }}
24
+ </p>
25
+ </div>
20
26
  <p>
21
- {{ text }}
27
+ {{ commentText }}
22
28
  </p>
23
29
  </div>
24
- <div class="tw-flex tw-items-center tw-gap-x-0.5">
30
+ <div
31
+ v-if="nestLevel < 3"
32
+ class="tw-flex tw-items-center tw-gap-x-2"
33
+ >
25
34
  <kv-comments-heart-button
26
35
  :is-small="true"
27
36
  :is-liked="isLiked"
28
- @click="onClick(LIKE_COMMENT_EVENT, $event)"
37
+ @click="addReaction(LIKE_COMMENT_EVENT, $event)"
38
+ />
39
+ <kv-comments-reply-button
40
+ @click="replyClick"
29
41
  />
30
- <kv-button
31
- variant="ghost"
32
- class="tw-font-medium"
33
- @click="onClick(REPLY_COMMENT_EVENT)"
34
- >
35
- Reply
36
- </kv-button>
37
42
  </div>
38
43
  <div
39
- v-if="latestChildren"
44
+ v-if="showInput"
45
+ class="tw-w-full"
46
+ >
47
+ <kv-comments-add
48
+ ref="commentsAddRef"
49
+ :user-image-url="userImageUrl"
50
+ :user-display-name="userDisplayName"
51
+ :is-reply="true"
52
+ class="tw-ml-3"
53
+ @add-comment="addReaction(REPLY_COMMENT_EVENT, $event)"
54
+ @hide-input="hideInput"
55
+ />
56
+ </div>
57
+ <div
58
+ v-if="childComments"
40
59
  class="tw-my-1"
41
60
  >
42
61
  <p
43
- v-for="nested_comment in latestChildren"
62
+ v-for="nested_comment in childComments"
44
63
  :key="nested_comment.id"
45
64
  class="tw-ml-3"
46
65
  >
47
66
  <kv-comments-list-item
67
+ :user-image-url="userImageUrl"
68
+ :user-display-name="userDisplayName"
69
+ :user-public-id="userPublicId"
48
70
  :comment="nested_comment"
49
- :is-liked="nested_comment.is_liked"
50
71
  :nest-level="nestLevel + 1"
51
- :handle-click="handleClick"
72
+ @add-reaction="$emit(ADD_REACTION_EVENT, $event);"
52
73
  />
53
74
  </p>
54
75
  </div>
@@ -56,8 +77,16 @@
56
77
  </template>
57
78
 
58
79
  <script>
59
- import KvButton from './KvButton.vue';
80
+ import {
81
+ ref,
82
+ nextTick,
83
+ computed,
84
+ toRefs,
85
+ } from 'vue-demi';
86
+ import KvCommentsReplyButton from './KvCommentsReplyButton.vue';
60
87
  import KvCommentsHeartButton from './KvCommentsHeartButton.vue';
88
+ import KvCommentsAdd from './KvCommentsAdd.vue';
89
+ import { ADD_REACTION_EVENT } from './KvCommentsContainer.vue';
61
90
 
62
91
  export const REPLY_COMMENT_EVENT = 'reply-comment';
63
92
  export const LIKE_COMMENT_EVENT = 'like-comment';
@@ -65,10 +94,32 @@ export const LIKE_COMMENT_EVENT = 'like-comment';
65
94
  export default {
66
95
  name: 'KvCommentsListItem',
67
96
  components: {
68
- KvButton,
97
+ KvCommentsReplyButton,
69
98
  KvCommentsHeartButton,
99
+ KvCommentsAdd,
70
100
  },
71
101
  props: {
102
+ /**
103
+ * The full URL for the user image
104
+ */
105
+ userImageUrl: {
106
+ type: String,
107
+ default: '',
108
+ },
109
+ /**
110
+ * The name to display for the user
111
+ */
112
+ userDisplayName: {
113
+ type: String,
114
+ default: '',
115
+ },
116
+ /**
117
+ * The ID for the user
118
+ */
119
+ userPublicId: {
120
+ type: String,
121
+ default: '',
122
+ },
72
123
  /**
73
124
  * Activity comment
74
125
  */
@@ -83,51 +134,65 @@ export default {
83
134
  type: Number,
84
135
  default: 0,
85
136
  },
86
- /**
87
- * Comment is liked by current user
88
- */
89
- isLiked: {
90
- type: Boolean,
91
- default: false,
92
- },
93
- /**
94
- * The function to call when a reaction is clicked
95
- */
96
- handleClick: {
97
- type: Function,
98
- default: () => ({}),
99
- },
100
137
  },
101
- setup(props) {
102
- const onClick = (reaction, value) => {
103
- props.handleClick({
138
+ emits: [
139
+ ADD_REACTION_EVENT,
140
+ ],
141
+ setup(props, { emit }) {
142
+ const {
143
+ comment,
144
+ userPublicId,
145
+ } = toRefs(props);
146
+
147
+ const showInput = ref(false);
148
+ const commentsAddRef = ref(null);
149
+
150
+ const commentText = computed(() => comment?.value?.data?.text ?? '');
151
+ const authorImage = computed(() => comment?.value?.user?.data?.image ?? '');
152
+ const authorName = computed(() => comment?.value?.user?.data?.name ?? '');
153
+ const childComments = computed(() => comment?.value?.latest_children?.comment ?? null);
154
+ const childLikes = computed(() => comment?.value?.latest_children?.like ?? []);
155
+ const likedObject = computed(() => childLikes.value.find((child) => child.user.data.publicLenderId === userPublicId.value)); // eslint-disable-line max-len
156
+ const isLiked = computed(() => likedObject.value !== undefined);
157
+
158
+ const replyClick = () => {
159
+ showInput.value = !showInput.value;
160
+ nextTick(() => {
161
+ if (showInput.value) commentsAddRef.value.$refs.input.focus();
162
+ });
163
+ };
164
+
165
+ const addReaction = (reaction, value) => {
166
+ const payload = {
104
167
  reaction,
105
- id: props.comment?.id ?? null,
106
- userId: props.comment?.user_id ?? null,
168
+ id: comment?.value?.id ?? null,
107
169
  isChild: true,
108
170
  value,
109
- });
171
+ };
172
+ if (reaction === LIKE_COMMENT_EVENT && !value) {
173
+ payload.id = likedObject.value.id;
174
+ }
175
+
176
+ emit(ADD_REACTION_EVENT, payload);
110
177
  };
111
178
 
179
+ const hideInput = () => { showInput.value = false; };
180
+
112
181
  return {
113
- onClick,
182
+ hideInput,
183
+ showInput,
184
+ commentsAddRef,
185
+ replyClick,
186
+ addReaction,
114
187
  REPLY_COMMENT_EVENT,
115
188
  LIKE_COMMENT_EVENT,
189
+ ADD_REACTION_EVENT,
190
+ commentText,
191
+ authorImage,
192
+ authorName,
193
+ childComments,
194
+ isLiked,
116
195
  };
117
196
  },
118
- computed: {
119
- text() {
120
- return this.comment?.data?.text ?? '';
121
- },
122
- userId() {
123
- return this.comment?.user_id ?? null;
124
- },
125
- latestChildren() {
126
- return this.comment?.latest_children ?? null;
127
- },
128
- profileImage() {
129
- return this.comment?.user_picture ?? '';
130
- },
131
- },
132
197
  };
133
198
  </script>
@@ -0,0 +1,34 @@
1
+ <!-- eslint-disable max-len -->
2
+ <template>
3
+ <button
4
+ aria-label="Reply"
5
+ class="tw-font-medium tw-flex tw-items-center tw-gap-x-0.5"
6
+ @click="$emit('click')"
7
+ >
8
+ <svg
9
+ width="14"
10
+ height="11"
11
+ viewBox="0 0 14 11"
12
+ fill="none"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ >
15
+ <path
16
+ d="M2.87502 5.12499L4.91669 7.16666C5.06946 7.31943 5.14585 7.49652 5.14585 7.69791C5.14585 7.8993 5.06946 8.0797 4.91669 8.23911C4.76391 8.38525 4.58683 8.45832 4.38544 8.45832C4.18405 8.45832 4.00696 8.38193 3.85419 8.22916L0.520854 4.89582C0.368076 4.7481 0.291687 4.57575 0.291687 4.37878C0.291687 4.18181 0.368076 4.00693 0.520854 3.85416L3.85419 0.520823C4.00696 0.368045 4.18521 0.291656 4.38892 0.291656C4.59261 0.291656 4.76853 0.368045 4.91669 0.520823C5.06946 0.668976 5.14585 0.8449 5.14585 1.04859C5.14585 1.2523 5.06946 1.43055 4.91669 1.58332L2.87502 3.62499H10C11.1067 3.62499 12.05 4.01499 12.83 4.79499C13.61 5.57499 14 6.51832 14 7.62499V9.87499C14 10.0875 13.9286 10.2656 13.7857 10.4094C13.6427 10.5531 13.4657 10.625 13.2544 10.625C13.0432 10.625 12.8646 10.5531 12.7188 10.4094C12.5729 10.2656 12.5 10.0875 12.5 9.87499V7.62499C12.5 6.93055 12.257 6.34027 11.7709 5.85416C11.2847 5.36805 10.6945 5.12499 10 5.12499H2.87502Z"
17
+ fill="#1C1B1F"
18
+ />
19
+ </svg>
20
+
21
+ <span>
22
+ Reply
23
+ </span>
24
+ </button>
25
+ </template>
26
+
27
+ <script>
28
+ export default {
29
+ name: 'KvCommentsReplyButton',
30
+ emits: [
31
+ 'click',
32
+ ],
33
+ };
34
+ </script>
@@ -1,5 +1,5 @@
1
1
  import KvCommentsContainer from '../KvCommentsContainer.vue';
2
- import comments from '../../tests/fixtures/mockFeedActivityData';
2
+ import activityFeed from '../../tests/fixtures/mockFeedActivityData';
3
3
 
4
4
  export default {
5
5
  title: 'KvCommentsContainer',
@@ -21,4 +21,15 @@ const story = (args) => {
21
21
  return template;
22
22
  };
23
23
 
24
+ const comments = activityFeed.results[0].latest_reactions;
25
+
24
26
  export const Default = story({ comments });
27
+
28
+ export const UserData = story(
29
+ {
30
+ comments,
31
+ userDisplayName: 'Jess',
32
+ userImageUrl: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg',
33
+ userPublicId: 'Jess1234',
34
+ },
35
+ );
@@ -22,3 +22,12 @@ const story = (args) => {
22
22
  const comments = activityFeed.results[0].latest_reactions;
23
23
 
24
24
  export const Default = story({ comments });
25
+
26
+ export const UserData = story(
27
+ {
28
+ comments,
29
+ userDisplayName: 'Jess',
30
+ userImageUrl: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg',
31
+ userPublicId: 'Jess1234',
32
+ },
33
+ );
@@ -19,6 +19,18 @@ const story = (args) => {
19
19
  return template;
20
20
  };
21
21
 
22
- const comment = activityFeed.results[0].latest_reactions.comment[0];
22
+ const comment = activityFeed.results[0].latest_reactions.comment[1];
23
+ const childComments = activityFeed.results[0].latest_reactions.comment[0];
23
24
 
24
25
  export const Default = story({ comment });
26
+
27
+ export const ChildComments = story({ comment: childComments });
28
+
29
+ export const UserData = story(
30
+ {
31
+ comment: childComments,
32
+ userDisplayName: 'Jess',
33
+ userImageUrl: 'https://www-0.development.kiva.org/img/s100/6b1a24092be3aaa22216874e644a4acf.jpg',
34
+ userPublicId: 'Jess1234',
35
+ },
36
+ );