Skip to content

Commit 46430ff

Browse files
authored
fix(file): handle multiple boolean on droparea (#858)
ref: MANAGER-15187 Signed-off-by: Maxime Bajeux <maxime.bajeux.ext@corp.ovh.com>
1 parent d5e240b commit 46430ff

File tree

5 files changed

+146
-7
lines changed

5 files changed

+146
-7
lines changed

packages/apps/workshop/stories/design-system/components/file/file-webcomponent.stories.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export const DragDropArea = forModule(moduleName).createElement(
9494
disabled="$ctrl.disabled"
9595
maxsize="150000"
9696
model="$ctrl.model"
97+
multiple
9798
droparea>
9899
</oui-file>`,
99100
{
@@ -106,13 +107,33 @@ export const DragDropArea = forModule(moduleName).createElement(
106107

107108
DragDropArea.storyName = 'Drag & Drop area';
108109

110+
export const DragDropAreaSingle = forModule(moduleName).createElement(
111+
() => compileTemplate(
112+
`
113+
<oui-file
114+
disabled="$ctrl.disabled"
115+
maxsize="150000"
116+
model="$ctrl.model"
117+
droparea>
118+
</oui-file>`,
119+
{
120+
$ctrl: {
121+
disabled: boolean('Disabled state', false),
122+
},
123+
},
124+
),
125+
);
126+
127+
DragDropAreaSingle.storyName = 'Drag & Drop area single file';
128+
109129
export const DragDropWithPreview = forModule(moduleName).createElement(
110130
() => compileTemplate(
111131
`
112132
<oui-file
113133
disabled="$ctrl.disabled"
114134
maxsize="150000"
115135
model="$ctrl.model"
136+
multiple
116137
droparea
117138
preview>
118139
</oui-file>`,

packages/components/file/src/js/file.controller.js

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default class {
2121
this.units = orderBy(this.$filter('orderBy')(ouiFileConfiguration.units, '-size'), 'size', 'desc');
2222
}
2323

24-
checkFileValidity(_file_) {
24+
checkFileValidity(_file_, errors) {
2525
const file = _file_;
2626

2727
if (file) {
@@ -58,9 +58,19 @@ export default class {
5858
if (file.errors.type) {
5959
this.form[this.name].$setValidity('type', false);
6060
}
61+
if (errors?.notSingle) {
62+
this.form[this.name].$setValidity('not-single', false);
63+
}
6164
this.form[this.name].$setDirty();
6265
}
6366

67+
if (!isEmpty(errors)) {
68+
file.errors = {
69+
...file.errors,
70+
...errors,
71+
};
72+
}
73+
6474
// Clean errors
6575
if (isEmpty(file.errors)) {
6676
delete file.errors;
@@ -108,12 +118,22 @@ export default class {
108118
this.model = [];
109119
}
110120

121+
const errors = {};
122+
if ((!this.multiple && this.droparea) && (files.length + this.model?.length || 0) > 1) {
123+
errors.notSingle = true;
124+
this.model.forEach((_item_) => {
125+
const item = _item_;
126+
if (!item.errors) item.errors = {};
127+
item.errors.notSingle = true;
128+
});
129+
}
130+
111131
if (angular.isArray(files)) {
112132
files.forEach((file) => {
113133
// Check for duplicate before adding
114134
if (!find(this.model, (item) => file.name === item.name)) {
115135
this.getFileInfos(file);
116-
this.checkFileValidity(file);
136+
this.checkFileValidity(file, errors);
117137
this.loadFilePreview(file);
118138
this.model.push(file);
119139
}
@@ -127,15 +147,35 @@ export default class {
127147
removeFile(file) {
128148
if (angular.isArray(this.model)) {
129149
remove(this.model, (item) => item === file);
130-
if (file.errors && this.form && this.form[this.name]) {
150+
if (file.errors) {
131151
let hasMaxsizeErrors = false;
132152
let hasTypeErrors = false;
133-
this.model.forEach((item) => {
153+
let hasNotSingleError = false;
154+
155+
if (!this.multiple && this.droparea) {
156+
hasNotSingleError = (this.model?.length || 0) > 1;
157+
}
158+
159+
this.model.forEach((_item_) => {
160+
const item = _item_;
134161
hasMaxsizeErrors = hasMaxsizeErrors || item.errors?.maxsize;
135162
hasTypeErrors = hasTypeErrors || item.errors?.type;
163+
if (hasNotSingleError) {
164+
if (!item.errors) item.errors = {};
165+
item.errors.notSingle = true;
166+
} else if (item.errors?.notSingle) {
167+
delete item.errors.notSingle;
168+
}
169+
if (isEmpty(item.errors)) {
170+
delete item.errors;
171+
}
136172
});
137-
this.form[this.name].$setValidity('maxsize', !hasMaxsizeErrors);
138-
this.form[this.name].$setValidity('type', !hasTypeErrors);
173+
174+
if (this.form && this.form[this.name]) {
175+
this.form[this.name].$setValidity('maxsize', !hasMaxsizeErrors);
176+
this.form[this.name].$setValidity('type', !hasTypeErrors);
177+
this.form[this.name].$setValidity('not-single', !hasNotSingleError);
178+
}
139179
}
140180
this.onRemove({ modelValue: this.model });
141181
}
@@ -149,6 +189,7 @@ export default class {
149189
if (this.form && this.form[this.name]) {
150190
this.form[this.name].$setValidity('maxsize', true);
151191
this.form[this.name].$setValidity('type', true);
192+
this.form[this.name].$setValidity('not-single', true);
152193
}
153194
}
154195

packages/components/file/src/js/file.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@
9494
<span class="oui-icon oui-icon-file" aria-hidden="true"></span>
9595
</div>
9696
<div class="oui-file-droparea__text" >
97-
<span ng-bind="::$ctrl.translations.dropArea"></span>
97+
<span ng-if="$ctrl.multiple" ng-bind="::$ctrl.translations.dropArea"></span>
98+
<span ng-if="!$ctrl.multiple" ng-bind="::$ctrl.translations.dropAreaSingle"></span>
9899
<button class="oui-link"
99100
type="button"
100101
ng-bind="::$ctrl.translations.dropAreaSelector"
@@ -144,6 +145,10 @@
144145
ng-if="file.errors && file.errors.maxsize"
145146
ng-bind="::$ctrl.translations.maxsizeError">
146147
</span>
148+
<span class="oui-file-attachments__error"
149+
ng-if="file.errors && file.errors.notSingle"
150+
ng-bind="::$ctrl.translations.notSingleError">
151+
</span>
147152
</span>
148153
<button class="oui-file-attachments__remove oui-icon oui-icon-close"
149154
type="button"

packages/components/file/src/js/file.provider.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ export default class {
66
this.translations = {
77
attachmentsHeading: 'Attachment(s)',
88
dropArea: 'Attach document(s) by drap and drop or',
9+
dropAreaSingle: 'Attach a document by drap and drop or',
910
dropAreaSelector: 'select a file',
1011
filePlaceholder: 'Select a file...',
1112
fileSelector: 'Select file',
1213
filesSelector: 'Select file(s)...',
1314
maxsizeError: 'This file exceeds the size limit',
15+
notSingleError: 'You can only add one file',
1416
removeFile: 'Remove file from selector',
1517
};
1618

packages/components/file/src/js/file.spec.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import find from 'lodash/find';
2+
import isEmpty from 'lodash/isEmpty';
23

34
describe('ouiFile', () => {
45
let $timeout;
@@ -17,13 +18,20 @@ describe('ouiFile', () => {
1718
type: 'image/png',
1819
};
1920

21+
const mockFile2 = {
22+
name: 'test2.png',
23+
size: 150000,
24+
type: 'image/png',
25+
};
26+
2027
const invalidMockFile = {
2128
name: 'test_invalid.png',
2229
size: 500000,
2330
type: 'image/jpeg',
2431
};
2532

2633
const mockFiles = [mockFile];
34+
const mockMultipleFiles = [mockFile, mockFile2];
2735

2836
beforeEach(angular.mock.module('oui.file'));
2937
beforeEach(angular.mock.module('oui.file.configuration'));
@@ -192,6 +200,68 @@ describe('ouiFile', () => {
192200
});
193201
});
194202

203+
describe('Drop area without multiple', () => {
204+
let element;
205+
let controller;
206+
let inputFile;
207+
let selector;
208+
let attachments;
209+
210+
beforeEach(() => {
211+
element = TestUtils.compileTemplate('<oui-file model="$ctrl.model" droparea></oui-file>');
212+
controller = element.controller('ouiFile');
213+
214+
$timeout.flush();
215+
216+
inputFile = getInputFile(element);
217+
selector = getDropArea(element);
218+
attachments = getAttachments(element);
219+
});
220+
221+
it('should have input file multiple', () => {
222+
expect(inputFile.multiple).toBeTruthy();
223+
});
224+
225+
it('should show drop area and attachments', () => {
226+
expect(selector).toBeTruthy();
227+
expect(attachments).toBeTruthy();
228+
});
229+
230+
it('should update classname with drag & drop events', () => {
231+
controller.fileDroparea.triggerHandler('dragenter');
232+
expect(controller.fileDroparea.hasClass('oui-file-droparea_dragover')).toBeTruthy();
233+
234+
controller.fileDroparea.triggerHandler('dragleave');
235+
expect(controller.fileDroparea.hasClass('oui-file-droparea_dragover')).toBeFalsy();
236+
});
237+
238+
it('should add files when dropped', () => {
239+
controller.fileDroparea.triggerHandler({
240+
type: 'drop',
241+
dataTransfer: {
242+
files: mockMultipleFiles,
243+
},
244+
});
245+
});
246+
247+
it('should have error because multiple files have been loaded', () => {
248+
controller.addFiles(mockMultipleFiles);
249+
expect(controller.model.reduce(
250+
(acc, item) => acc && !isEmpty(item.errors),
251+
true,
252+
)).toBeTruthy();
253+
});
254+
255+
it('should have not error because we remove one of the two files loaded', () => {
256+
controller.addFiles(mockMultipleFiles);
257+
controller.removeFile(mockFile);
258+
expect(controller.model.reduce(
259+
(acc, item) => acc && !isEmpty(item.errors),
260+
true,
261+
)).toBeFalsy();
262+
});
263+
});
264+
195265
describe('File management', () => {
196266
let element;
197267
let controller;

0 commit comments

Comments
 (0)