Skip to content

Commit 0b4dc88

Browse files
authored
feat: improve description editing panel
Refs: CO-3420 (#596)
1 parent d6b360e commit 0b4dc88

4 files changed

Lines changed: 142 additions & 262 deletions

File tree

src/carbonio-files-ui-common/tests/utils.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ export async function renameNode(newName: string, user: UserEvent): Promise<void
359359
await screen.findByText(/\brename\b/i);
360360
await user.click(screen.getByText(/\brename\b/i));
361361
// fill new name in modal input field
362-
const inputField = await screen.findByRole('textbox');
362+
const inputField = await screen.findByRole('textbox', { name: /item name/i });
363363
act(() => {
364364
// run timers of modal
365365
vi.advanceTimersToNextTimer();

src/carbonio-files-ui-common/views/components/NodeDetails.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,7 @@ describe('Node Details', () => {
172172
screen.getByText(formatDate(node.created_at, undefined, DATE_TIME_FORMAT))
173173
).toBeVisible();
174174
expect(screen.queryByText('Last edit')).not.toBeInTheDocument();
175-
expect(screen.getByText('Description')).toBeInTheDocument();
176-
expect(screen.getByText('Click the edit button to add a description')).toBeInTheDocument();
175+
expect(screen.getByRole('textbox', { name: 'Description' })).toBeVisible();
177176
expect(screen.queryByText('Size')).not.toBeInTheDocument();
178177
expect(screen.queryByText('Downloads')).not.toBeInTheDocument();
179178
expect(screen.queryByText('Collaborators')).not.toBeInTheDocument();

src/carbonio-files-ui-common/views/components/NodeDetailsDescription.test.tsx

Lines changed: 74 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { faker } from '@faker-js/faker';
1010
import { waitFor } from '@testing-library/react';
1111

1212
import { NodeDetailsDescription } from './NodeDetailsDescription';
13-
import { ICON_REGEXP } from '../../constants/test';
1413
import { populateFile } from '../../mocks/mockUtils';
1514
import { generateError, setup, screen } from '../../tests/utils';
1615
import { Resolvers } from '../../types/graphql/resolvers-types';
@@ -19,7 +18,7 @@ import { canUpsertDescription } from '../../utils/ActionsFactory';
1918
import { mockErrorResolver, mockUpdateNode } from '../../utils/resolverMocks';
2019

2120
describe('NodeDetailsDescription component', () => {
22-
test('Missing description show missing description label', () => {
21+
it('should render description section', () => {
2322
const node = populateFile();
2423
node.permissions.can_write_file = true;
2524
node.description = '';
@@ -31,11 +30,12 @@ describe('NodeDetailsDescription component', () => {
3130
/>,
3231
{ mocks: {} }
3332
);
34-
expect(screen.getByText('Description')).toBeInTheDocument();
35-
expect(screen.getByText('Click the edit button to add a description')).toBeInTheDocument();
33+
34+
expect(screen.getAllByText('Description')[0]).toBeVisible();
35+
expect(screen.getByRole('textbox', { name: 'Description' })).toBeVisible();
3636
});
3737

38-
test('Missing description is not shown if description cannot be edited', () => {
38+
it('should render TextArea as readOnly when description cannot be edited', () => {
3939
const node = populateFile();
4040
node.permissions.can_write_file = false;
4141
node.description = '';
@@ -47,15 +47,13 @@ describe('NodeDetailsDescription component', () => {
4747
/>,
4848
{ mocks: {} }
4949
);
50-
expect(screen.getByText('Description')).toBeInTheDocument();
51-
expect(
52-
screen.queryByText('Click the edit button to add a description')
53-
).not.toBeInTheDocument();
50+
51+
expect(screen.getByRole('textbox')).toHaveAttribute('readonly');
5452
});
5553

56-
test('Edit icon disabled if can_write_file is false', () => {
54+
it('should render TextArea as editable when can_write_file is true', () => {
5755
const node = populateFile();
58-
node.permissions.can_write_file = false;
56+
node.permissions.can_write_file = true;
5957
setup(
6058
<NodeDetailsDescription
6159
id={node.id}
@@ -64,35 +62,50 @@ describe('NodeDetailsDescription component', () => {
6462
/>,
6563
{ mocks: {} }
6664
);
67-
expect(screen.getByText('Description')).toBeInTheDocument();
6865

69-
const editIcon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
70-
expect(editIcon).toBeVisible();
71-
expect(editIcon).toBeDisabled();
66+
expect(screen.getByRole('textbox')).not.toHaveAttribute('readonly');
7267
});
7368

74-
test('Edit icon not disabled if can_write_file is true', () => {
69+
it('should show Cancel and Save buttons only when description changes and hide them when restored to original', async () => {
7570
const node = populateFile();
7671
node.permissions.can_write_file = true;
77-
setup(
72+
const newDescription = 'newDescription';
73+
74+
const { user } = setup(
7875
<NodeDetailsDescription
7976
id={node.id}
8077
description={node.description}
8178
canUpsertDescription={canUpsertDescription({ nodes: [node] })}
8279
/>,
8380
{ mocks: {} }
8481
);
85-
expect(screen.getByText('Description')).toBeInTheDocument();
8682

87-
const editIcon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
88-
expect(editIcon).toBeVisible();
89-
expect(editIcon).toBeEnabled();
83+
expect(screen.queryByRole('button', { name: /cancel/i })).not.toBeInTheDocument();
84+
expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument();
85+
86+
const inputField = screen.getByRole('textbox');
87+
await user.clear(inputField);
88+
await user.type(inputField, newDescription);
89+
90+
const saveButton = screen.getByRole('button', { name: /save/i });
91+
expect(saveButton).toBeVisible();
92+
expect(saveButton).toBeEnabled();
93+
expect(screen.getByRole('button', { name: /cancel/i })).toBeVisible();
94+
95+
await user.clear(inputField);
96+
await user.type(inputField, node.description);
97+
98+
expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument();
99+
expect(screen.queryByRole('button', { name: /cancel/i })).not.toBeInTheDocument();
90100
});
91101

92-
test('save button is disabled when description is the same', async () => {
102+
it('should disable Save button when description has more than 1024 characters', async () => {
93103
const node = populateFile();
94104
node.permissions.can_write_file = true;
95105
const newDescription = 'newDescription';
106+
const moreThan1024Description = faker.string.sample(2000);
107+
108+
expect(moreThan1024Description.length).toBeGreaterThan(1024);
96109

97110
const { user } = setup(
98111
<NodeDetailsDescription
@@ -102,37 +115,24 @@ describe('NodeDetailsDescription component', () => {
102115
/>,
103116
{ mocks: {} }
104117
);
105-
expect(screen.getByText('Description')).toBeInTheDocument();
106-
expect(screen.getByText(node.description)).toBeInTheDocument();
107-
108-
const editIcon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
109-
expect(editIcon).toBeVisible();
110-
expect(editIcon).toBeEnabled();
111-
await user.click(editIcon);
112-
113-
const saveIcon = await screen.findByRoleWithIcon('button', { icon: ICON_REGEXP.save });
114-
expect(saveIcon).toBeVisible();
115-
expect(saveIcon).toBeDisabled();
116118

117119
const inputField = screen.getByRole('textbox');
118120
await user.clear(inputField);
119121
await user.type(inputField, newDescription);
120122

121-
expect(saveIcon).toBeEnabled();
123+
const saveButton = screen.getByRole('button', { name: /save/i });
124+
expect(saveButton).toBeEnabled();
122125

123126
await user.clear(inputField);
124-
await user.type(inputField, node.description);
127+
await user.paste(moreThan1024Description);
125128

126-
expect(saveIcon).toBeDisabled();
129+
expect(saveButton).toBeDisabled();
127130
});
128131

129-
test('save button is disabled when description has more than 4096 characters', async () => {
132+
it('should render "Maximum length allowed is 1024 characters" text when textarea is focused', async () => {
130133
const node = populateFile();
131134
node.permissions.can_write_file = true;
132-
const newDescription = 'newDescription';
133-
const moreThan4096Description = faker.string.sample(5000);
134-
135-
expect(moreThan4096Description.length).toBeGreaterThan(4096);
135+
node.description = '';
136136

137137
const { user } = setup(
138138
<NodeDetailsDescription
@@ -142,31 +142,25 @@ describe('NodeDetailsDescription component', () => {
142142
/>,
143143
{ mocks: {} }
144144
);
145-
expect(screen.getByText('Description')).toBeInTheDocument();
146-
expect(screen.getByText(node.description)).toBeInTheDocument();
147145

148-
const editIcon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
149-
expect(editIcon).toBeVisible();
150-
expect(editIcon).toBeEnabled();
151-
await user.click(editIcon);
146+
const inputField = screen.getByRole('textbox', { name: 'Description' });
152147

153-
const saveIcon = await screen.findByRoleWithIcon('button', { icon: ICON_REGEXP.save });
154-
expect(saveIcon).toBeVisible();
155-
expect(saveIcon).toBeDisabled();
148+
expect(
149+
screen.queryByText(/maximum length allowed is 1024 characters/i)
150+
).not.toBeInTheDocument();
156151

157-
const inputField = screen.getByRole('textbox');
158-
await user.clear(inputField);
159-
await user.type(inputField, newDescription);
152+
await user.click(inputField);
160153

161-
expect(saveIcon).toBeEnabled();
154+
expect(screen.getByText(/maximum length allowed is 1024 characters/i)).toBeVisible();
162155

163-
await user.clear(inputField);
164-
await user.paste(moreThan4096Description);
156+
await user.tab();
165157

166-
expect(saveIcon).toBeDisabled();
158+
expect(
159+
screen.queryByText(/maximum length allowed is 1024 characters/i)
160+
).not.toBeInTheDocument();
167161
});
168162

169-
test('close button do not save changes', async () => {
163+
it('should discard changes and restore original value when Cancel button is clicked', async () => {
170164
const node = populateFile();
171165
node.permissions.can_write_file = true;
172166
const newDescription = 'newDescription';
@@ -179,42 +173,25 @@ describe('NodeDetailsDescription component', () => {
179173
/>,
180174
{ mocks: {} }
181175
);
182-
expect(screen.getByText('Description')).toBeInTheDocument();
183-
expect(screen.getByText(node.description)).toBeInTheDocument();
184-
185-
let editIcon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
186-
expect(editIcon).toBeVisible();
187-
expect(editIcon).toBeEnabled();
188-
await user.click(editIcon);
189176

190-
const saveIcon = await screen.findByRoleWithIcon('button', { icon: ICON_REGEXP.save });
191-
expect(saveIcon).toBeVisible();
192-
expect(saveIcon).toBeDisabled();
177+
const inputField = screen.getByRole('textbox');
178+
expect(inputField).toHaveValue(node.description);
193179

194-
let inputField = screen.getByRole('textbox');
195180
await user.clear(inputField);
196181
await user.type(inputField, newDescription);
197182

198183
expect(inputField).toHaveValue(newDescription);
199184

200-
expect(saveIcon).toBeEnabled();
201-
202-
const closeICon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.close });
203-
expect(closeICon).toBeVisible();
204-
await user.click(closeICon);
205-
206-
editIcon = await screen.findByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
207-
expect(editIcon).toBeVisible();
208-
expect(screen.getByText(node.description)).toBeInTheDocument();
209-
210-
await user.click(editIcon);
211-
212-
inputField = screen.getByRole('textbox');
185+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
186+
await user.click(cancelButton);
213187

188+
// Value restored, buttons gone
214189
expect(inputField).toHaveValue(node.description);
190+
expect(screen.queryByRole('button', { name: /cancel/i })).not.toBeInTheDocument();
191+
expect(screen.queryByRole('button', { name: /save/i })).not.toBeInTheDocument();
215192
});
216193

217-
test('save button close editing mode and call mutation', async () => {
194+
it('should call mutation when Save button is clicked', async () => {
218195
const node = populateFile();
219196
node.permissions.can_write_file = true;
220197
const newDescription = 'newDescription';
@@ -238,38 +215,23 @@ describe('NodeDetailsDescription component', () => {
238215
/>,
239216
{ mocks }
240217
);
241-
expect(screen.getByText('Description')).toBeInTheDocument();
242-
expect(screen.getByText(node.description)).toBeInTheDocument();
243-
244-
let editIcon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
245-
expect(editIcon).toBeVisible();
246-
expect(editIcon).toBeEnabled();
247-
await user.click(editIcon);
248-
249-
const saveIcon = await screen.findByRoleWithIcon('button', { icon: ICON_REGEXP.save });
250-
expect(saveIcon).toBeVisible();
251-
expect(saveIcon).toBeDisabled();
252218

253219
const inputField = screen.getByRole('textbox');
254220
await user.clear(inputField);
255221
await user.type(inputField, newDescription);
256222

257223
expect(inputField).toHaveValue(newDescription);
258224

259-
expect(saveIcon).toBeEnabled();
225+
const saveButton = screen.getByRole('button', { name: /save/i });
226+
expect(saveButton).toBeEnabled();
260227

261-
await user.click(saveIcon);
262-
263-
editIcon = await screen.findByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
264-
expect(editIcon).toBeVisible();
265-
266-
expect(saveIcon).not.toBeVisible();
228+
await user.click(saveButton);
267229

268230
await waitFor(() => expect(mocks.Mutation.updateNode).toHaveBeenCalled());
269231
expect(mocks.Mutation.updateNode).toHaveBeenCalledTimes(1);
270232
});
271233

272-
test('if save operation throws an error, description input field is shown with last description typed', async () => {
234+
it('should keep description textarea visible with last typed value if save operation throws an error', async () => {
273235
const node = populateFile();
274236
node.permissions.can_write_file = true;
275237
const newDescription = 'newDescription';
@@ -286,28 +248,19 @@ describe('NodeDetailsDescription component', () => {
286248
/>,
287249
{ mocks }
288250
);
289-
expect(screen.getByText(/description/i)).toBeVisible();
290-
expect(screen.getByText(node.description)).toBeVisible();
291-
292-
const editIcon = screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.edit });
293-
expect(editIcon).toBeVisible();
294-
await user.click(editIcon);
295-
const saveIcon = await screen.findByRoleWithIcon('button', { icon: ICON_REGEXP.save });
296-
expect(saveIcon).toBeVisible();
297-
const inputField = screen.getByRole('textbox', {
298-
name: /maximum length allowed is 4096 characters/i
299-
});
251+
expect(screen.getAllByText('Description')[0]).toBeVisible();
252+
253+
const inputField = screen.getByRole('textbox');
300254
await user.clear(inputField);
301255
await user.type(inputField, newDescription);
302-
await user.click(saveIcon);
256+
257+
const saveButton = screen.getByRole('button', { name: /save/i });
258+
await user.click(saveButton);
259+
303260
await screen.findByText(/update description error/i);
304261

305-
expect(
306-
screen.getByRole('textbox', { name: /maximum length allowed is 4096 characters/i })
307-
).toBeVisible();
308-
expect(screen.getByRoleWithIcon('button', { icon: ICON_REGEXP.save })).toBeVisible();
309-
expect(
310-
screen.queryByRoleWithIcon('button', { icon: ICON_REGEXP.edit })
311-
).not.toBeInTheDocument();
262+
// TextArea still visible with the typed value
263+
expect(screen.getByRole('textbox')).toBeVisible();
264+
expect(screen.getByRole('textbox')).toHaveValue(newDescription);
312265
});
313266
});

0 commit comments

Comments
 (0)