Skip to content

Commit

Permalink
fix(file-uploader): mark files with invalid types/formats (#1231)
Browse files Browse the repository at this point in the history
Co-authored-by: trickstival <stival@ibm.com>
  • Loading branch information
trickstival and trickstival authored Aug 13, 2021
1 parent 7a16f4a commit dc3d8e1
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 24 deletions.
87 changes: 87 additions & 0 deletions packages/core/__tests__/cv-file-uploader.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { testComponent, awaitNextTick } from './_helpers';
import { CvFileUploader } from '@/components/cv-file-uploader';

const { shallowMount: shallow, trigger, setProps } = awaitNextTick;

describe('CvFileUploader', () => {
// ***************
// FUNCTIONAL TESTS
// ***************

it('should accept all file formats if no accept is specified', async () => {
const wrapper = await shallow(CvFileUploader);

const dropEvent = {
dataTransfer: {
types: ['Files'],
files: [{ name: 'foo.jpg', type: 'image/jpg' }],
},
type: 'drop',
preventDefault: jest.fn(),
};
wrapper.find('[data-file-drop-container]').trigger('drop', dropEvent);
expect(wrapper.emitted().change[0][0]).toEqual([
{
file: {
name: 'foo.jpg',
type: 'image/jpg',
},
invalidMessage: '',
invalidMessageTitle: '',
state: '',
},
]);
});

it('should emit when a file is dragged', async () => {
const propsData = { accept: '.jpg,.png' };
const wrapper = await shallow(CvFileUploader, { propsData });

const dropEvent = {
dataTransfer: {
types: ['Files'],
files: [{ name: 'foo.jpg', type: 'image/jpg' }],
},
type: 'drop',
preventDefault: jest.fn(),
};
wrapper.find('[data-file-drop-container]').trigger('drop', dropEvent);
expect(wrapper.emitted().change[0][0]).toEqual([
{
file: {
name: 'foo.jpg',
type: 'image/jpg',
},
invalidMessage: '',
invalidMessageTitle: '',
state: '',
},
]);
});

it('should not accept wrong file formats', async () => {
const propsData = { accept: '.jpg,.png' };
const wrapper = await shallow(CvFileUploader, { propsData });

const dropEvent = {
dataTransfer: {
types: ['Files'],
files: [{ name: 'foo.mp4', type: 'video/mp4' }],
},
type: 'drop',
preventDefault: jest.fn(),
};
wrapper.find('[data-file-drop-container]').trigger('drop', dropEvent);
expect(wrapper.emitted().change[0][0]).toEqual([
{
file: {
name: 'foo.mp4',
type: 'video/mp4',
},
invalidMessage: '"foo.mp4" does not have a valid file type.',
invalidMessageTitle: 'Invalid file type',
state: '',
},
]);
});
});
81 changes: 57 additions & 24 deletions packages/core/src/components/cv-file-uploader/cv-file-uploader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
v-if="kind !== 'button'"
v-bind="$attrs"
type="file"
:accept="accept"
:class="`${carbonPrefix}--file-input`"
:id="uid"
data-file-uploader
Expand All @@ -45,6 +46,7 @@
v-if="kind === 'button'"
v-bind="$attrs"
type="file"
:accept="accept"
:class="`${carbonPrefix}--file-input`"
:id="uid"
data-file-uploader
Expand Down Expand Up @@ -140,6 +142,7 @@ export default {
},
label: String,
helperText: String,
accept: String,
initialStateUploading: Boolean,
removable: Boolean,
buttonLabel: {
Expand Down Expand Up @@ -210,12 +213,19 @@ export default {
},
addFiles(files) {
for (const file of files) {
this.internalFiles.push({
const internalFile = {
state: this.initialStateUploading ? STATES.UPLOADING : STATES.NONE,
file,
invalidMessageTitle: '',
invalidMessage: '',
});
};
if (file.messageBundle) {
internalFile.invalidMessageTitle = file.messageBundle.invalidMessageTitle;
internalFile.invalidMessage = file.messageBundle.invalidMessage;
delete file.messageBundle;
}
this.internalFiles.push(internalFile);
}
this.$emit('change', this.internalFiles);
},
Expand All @@ -241,32 +251,55 @@ export default {
this.internalFiles[index].invalidMessage = message;
},
onDragEvent(evt) {
// NOTE: Validation of dragged files is not currently done.
// It may be possible to do this here or defer to the caller.
// It is certainly possible for the user to remove files after they are dropped.
if (Array.prototype.indexOf.call(evt.dataTransfer.types, 'Files') === -1) {
return;
}
if (Array.prototype.indexOf.call(evt.dataTransfer.types, 'Files') >= 0) {
if (evt.type === 'dragover') {
evt.preventDefault();
const dropEffect = 'copy';
if (Array.isArray(evt.dataTransfer.types)) {
try {
// IE11 throws a "permission denied" error accessing `.effectAllowed`
evt.dataTransfer.effectAllowed = dropEffect;
} catch (e) {
// ignore
}
if (evt.type === 'dragleave') {
this.allowDrop = false;
return;
}
if (evt.type === 'dragover') {
evt.preventDefault();
const dropEffect = 'copy';
if (Array.isArray(evt.dataTransfer.types)) {
try {
// IE11 throws a "permission denied" error accessing `.effectAllowed`
evt.dataTransfer.effectAllowed = dropEffect;
} catch (e) {
// ignore
}
evt.dataTransfer.dropEffect = dropEffect;
this.allowDrop = true;
}
if (evt.type === 'dragleave') {
this.allowDrop = false;
evt.dataTransfer.dropEffect = dropEffect;
this.allowDrop = true;
}
if (evt.type === 'drop') {
evt.preventDefault();
if (this.accept) {
this.markInvalidFileTypes(evt.dataTransfer.files);
}
if (evt.type === 'drop') {
evt.preventDefault();
this.addFiles(evt.dataTransfer.files);
this.allowDrop = false;
this.addFiles(evt.dataTransfer.files);
this.allowDrop = false;
}
},
markInvalidFileTypes(files) {
const isFileAllowed = file => {
const extension = '.' + file.name.split('.').pop();
const allowed = this.accept.split(',').map(x => x.trim());
if (allowed.includes('.jpg')) {
allowed.push('.jpeg');
}
return allowed.includes(file.type) || allowed.includes(extension);
};
for (const file of files) {
if (!isFileAllowed(file)) {
file.messageBundle = {
invalidMessageTitle: 'Invalid file type',
invalidMessage: `"${file.name}" does not have a valid file type.`,
};
}
}
},
Expand Down

0 comments on commit dc3d8e1

Please sign in to comment.