react-dropzone-async-validation
Version:
Simple HTML5 drag-drop zone with React.js
568 lines (511 loc) • 16.2 kB
JavaScript
beforeEach(() => {
jest.resetModules();
});
describe("fileMatchSize()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return true if the file object doesn't have a {size} property", () => {
expect(utils.fileMatchSize({})).toEqual([true, null]);
expect(utils.fileMatchSize({ size: null })).toEqual([true, null]);
});
it("should return true if the minSize and maxSize were not provided", () => {
expect(utils.fileMatchSize({ size: 100 })).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, undefined, undefined)).toEqual([
true,
null,
]);
expect(utils.fileMatchSize({ size: 100 }, null, null)).toEqual([
true,
null,
]);
});
it("should return true if the file {size} is within the [minSize, maxSize] range", () => {
expect(utils.fileMatchSize({ size: 100 }, 10, 200)).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, 10, 99)).toEqual([
false,
{ code: "file-too-large", message: "File is larger than 99 bytes" },
]);
expect(utils.fileMatchSize({ size: 100 }, 101, 200)).toEqual([
false,
{ code: "file-too-small", message: "File is smaller than 101 bytes" },
]);
});
it("should return true if the file {size} is more than minSize", () => {
expect(utils.fileMatchSize({ size: 100 }, 100)).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, 101)).toEqual([
false,
{ code: "file-too-small", message: "File is smaller than 101 bytes" },
]);
});
it("should return true if the file {size} is less than maxSize", () => {
expect(utils.fileMatchSize({ size: 100 }, undefined, 100)).toEqual([
true,
null,
]);
expect(utils.fileMatchSize({ size: 100 }, null, 100)).toEqual([true, null]);
expect(utils.fileMatchSize({ size: 100 }, undefined, 99)).toEqual([
false,
{ code: "file-too-large", message: "File is larger than 99 bytes" },
]);
expect(utils.fileMatchSize({ size: 100 }, null, 99)).toEqual([
false,
{ code: "file-too-large", message: "File is larger than 99 bytes" },
]);
});
});
describe("isIeOrEdge", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return true for IE10", () => {
const userAgent =
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)";
expect(utils.isIeOrEdge(userAgent)).toBe(true);
});
it("should return true for IE11", () => {
const userAgent =
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko";
expect(utils.isIeOrEdge(userAgent)).toBe(true);
});
it("should return true for Edge", () => {
const userAgent =
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16258";
expect(utils.isIeOrEdge(userAgent)).toBe(true);
});
it("should return false for Chrome", () => {
const userAgent =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36";
expect(utils.isIeOrEdge(userAgent)).toBe(false);
});
});
describe("isKindFile()", () => {
it('should return true for DataTransferItem of kind "file"', async () => {
/**
* @constant
* @type {import('./index')}
*/
const utils = await import("./index");
expect(utils.isKindFile({ kind: "file" })).toBe(true);
expect(utils.isKindFile({ kind: "text/html" })).toBe(false);
expect(utils.isKindFile({})).toBe(false);
expect(utils.isKindFile(null)).toBe(false);
});
});
describe("isPropagationStopped()", () => {
const trueFn = jest.fn(() => true);
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return result of isPropagationStopped() if isPropagationStopped exists", () => {
expect(utils.isPropagationStopped({ isPropagationStopped: trueFn })).toBe(
true
);
});
it("should return value of cancelBubble if isPropagationStopped doesnt exist and cancelBubble exists", () => {
expect(utils.isPropagationStopped({ cancelBubble: true })).toBe(true);
});
it("should return false if isPropagationStopped and cancelBubble are missing", () => {
expect(utils.isPropagationStopped({})).toBe(false);
});
});
describe("isEvtWithFiles()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return true if some dragged types are files", () => {
expect(utils.isEvtWithFiles({ dataTransfer: { types: ["Files"] } })).toBe(
true
);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["application/x-moz-file"] },
})
).toBe(true);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["Files", "application/x-moz-file"] },
})
).toBe(true);
expect(
utils.isEvtWithFiles({ dataTransfer: { types: ["text/plain"] } })
).toBe(false);
expect(
utils.isEvtWithFiles({ dataTransfer: { types: ["text/html"] } })
).toBe(false);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["Files", "application/test"] },
})
).toBe(true);
expect(
utils.isEvtWithFiles({
dataTransfer: { types: ["application/x-moz-file", "application/test"] },
})
).toBe(true);
});
it("should return true if the event has a target with files", () => {
expect(utils.isEvtWithFiles({ target: { files: [] } })).toBe(true);
});
it("should return false otherwise", () => {
expect(utils.isEvtWithFiles({})).toBe(false);
});
});
describe("composeEventHandlers()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("returns a fn", () => {
const fn = utils.composeEventHandlers(() => {});
expect(typeof fn).toBe("function");
});
it("runs every passed fn in order", () => {
const fn1 = jest.fn();
const fn2 = jest.fn();
const fn = utils.composeEventHandlers(fn1, fn2);
const event = { type: "click" };
const data = { ping: true };
fn(event, data);
expect(fn1).toHaveBeenCalledWith(event, data);
expect(fn2).toHaveBeenCalledWith(event, data);
});
it("stops after first fn that calls stopPropagation()", () => {
const fn1 = jest.fn().mockImplementation((event) => {
Object.defineProperty(event, "cancelBubble", { value: true });
return event;
});
const fn2 = jest.fn();
const fn = utils.composeEventHandlers(fn1, fn2);
const event = new MouseEvent("click");
fn(event);
expect(fn1).toHaveBeenCalledWith(event);
expect(fn2).not.toHaveBeenCalled();
});
it("stops before first fn if bubble is already canceled", () => {
const fn1 = jest.fn();
const fn2 = jest.fn();
const fn = utils.composeEventHandlers(fn1, fn2);
const event = new MouseEvent("click");
Object.defineProperty(event, "cancelBubble", { value: true });
fn(event);
expect(fn1).not.toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
});
});
describe("fileAccepted()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("accepts bogus firefox file", () => {
const file = createFile("bogus.png", 100, "application/x-moz-file");
expect(utils.fileAccepted(file, ".pdf")).toEqual([true, null]);
});
it("accepts file when single accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, ".pdf")).toEqual([true, null]);
});
it("accepts file when multiple accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, [".pdf", ".png"])).toEqual([true, null]);
});
it("rejects file when single accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, ".png")).toEqual([
false,
{ code: "file-invalid-type", message: "File type must be .png" },
]);
});
it("rejects file when multiple accept criteria", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, [".gif", ".png"])).toEqual([
false,
{
code: "file-invalid-type",
message: "File type must be one of .gif, .png",
},
]);
});
it("rejects file when single accept criteria as array", () => {
const file = createFile("hamster.pdf", 100, "application/pdf");
expect(utils.fileAccepted(file, [".gif"])).toEqual([
false,
{ code: "file-invalid-type", message: "File type must be .gif" },
]);
});
});
describe("allFilesAccepted()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("rejects file when multiple accept criteria", async () => {
const files = [
createFile("hamster.pdf", 100, "application/pdf"),
createFile("fish.pdf", 100, "application/pdf"),
];
const images = [
createFile("cats.gif", 1234, "image/gif"),
createFile("dogs.gif", 2345, "image/jpeg"),
];
expect(await utils.allFilesAccepted({ files, multiple: true })).toEqual(
true
);
expect(
await utils.allFilesAccepted({ files, multiple: true, maxFiles: 10 })
).toEqual(true);
expect(
await utils.allFilesAccepted({ files, multiple: false, maxFiles: 10 })
).toEqual(false);
expect(
await utils.allFilesAccepted({
files,
multiple: true,
accept: "image/jpeg",
})
).toEqual(false);
expect(
await utils.allFilesAccepted({
files: images,
multiple: true,
accept: "image/*",
})
).toEqual(true);
expect(
await utils.allFilesAccepted({ files, multiple: true, minSize: 110 })
).toEqual(false);
expect(
await utils.allFilesAccepted({ files, multiple: true, maxSize: 99 })
).toEqual(false);
expect(
await utils.allFilesAccepted({ files, multiple: true, maxFiles: 1 })
).toEqual(false);
expect(
await utils.allFilesAccepted({
files,
validator: () => ({ code: "not-allowed", message: "Cannot do this!" }),
})
).toEqual(false);
});
});
describe("ErrorCode", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should exist and have known error code properties", () => {
expect(utils.ErrorCode.FileInvalidType).toEqual(utils.FILE_INVALID_TYPE);
expect(utils.ErrorCode.FileTooLarge).toEqual(utils.FILE_TOO_LARGE);
expect(utils.ErrorCode.FileTooSmall).toEqual(utils.FILE_TOO_SMALL);
expect(utils.ErrorCode.TooManyFiles).toEqual(utils.TOO_MANY_FILES);
});
});
describe("canUseFileSystemAccessAPI()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should return false if not", () => {
expect(utils.canUseFileSystemAccessAPI()).toBe(false);
});
it("should return true if yes", () => {
// TODO: If we use these in other tests, restore once test is done
window.showOpenFilePicker = jest.fn();
expect(utils.canUseFileSystemAccessAPI()).toBe(true);
});
});
describe("pickerOptionsFromAccept()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("converts the {accept} prop to file picker options", () => {
expect(
utils.pickerOptionsFromAccept({
"image/*": [".png", ".jpg"], // ok
"text/*": [".txt", ".pdf"], // ok
"audio/*": ["mp3"], // not ok
"*": [".p12"], // not ok
})
).toEqual([
{
description: "Files",
accept: {
"image/*": [".png", ".jpg"],
"text/*": [".txt", ".pdf"],
},
},
]);
});
});
describe("acceptPropAsAcceptAttr()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("converts {accept} to an array of strings", () => {
expect(
utils.acceptPropAsAcceptAttr({
"image/*": [".png", ".jpg"],
"text/*": [".txt", ".pdf"],
"audio/*": ["mp3"], // `mp3` not ok
"*": [".p12"], // `*` not ok
})
).toEqual("image/*,.png,.jpg,text/*,.txt,.pdf,audio/*,.p12");
});
});
describe("isMIMEType()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("checks that the value is a valid MIME type string", () => {
expect(utils.isMIMEType("text/html")).toBe(true);
expect(utils.isMIMEType("text/*")).toBe(true);
expect(utils.isMIMEType("image/*")).toBe(true);
expect(utils.isMIMEType("video/*")).toBe(true);
expect(utils.isMIMEType("audio/*")).toBe(true);
expect(utils.isMIMEType("test/*")).toBe(false);
expect(utils.isMIMEType("text")).toBe(false);
expect(utils.isMIMEType("")).toBe(false);
expect(utils.isMIMEType(undefined)).toBe(false);
});
});
describe("isExt()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("checks that the value is a valid file extension", () => {
expect(utils.isExt(".jpg")).toBe(true);
expect(utils.isExt("me.jpg")).toBe(true);
expect(utils.isExt("me.prev.png")).toBe(true);
expect(utils.isExt("")).toBe(false);
expect(utils.isExt("text")).toBe(false);
expect(utils.isExt(undefined)).toBe(false);
});
});
describe("isAbort()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should work as expected", () => {
expect(utils.isAbort(new DOMException())).toBe(false);
expect(utils.isAbort(new DOMException("some err"))).toBe(false);
expect(utils.isAbort(new DOMException("some err", "Noop"))).toBe(false);
expect(utils.isAbort(new DOMException("some err", "AbortError"))).toBe(
true
);
const err = new DOMException("some err");
const e = new Proxy(err, {
get(t, p) {
if (p === "code") {
return 20;
}
return t[p];
},
});
expect(utils.isAbort(e)).toBe(true);
});
});
describe("isSecurityError()", () => {
/**
* @constant
* @type {import('./index')}
*/
let utils;
beforeEach(async () => {
utils = await import("./index");
});
it("should work as expected", () => {
expect(utils.isSecurityError(new DOMException())).toBe(false);
expect(utils.isSecurityError(new DOMException("some err"))).toBe(false);
expect(utils.isSecurityError(new DOMException("some err", "Noop"))).toBe(
false
);
expect(
utils.isSecurityError(new DOMException("some err", "SecurityError"))
).toBe(true);
const err = new DOMException("some err");
const e = new Proxy(err, {
get(t, p) {
if (p === "code") {
return 18;
}
return t[p];
},
});
expect(utils.isSecurityError(e)).toBe(true);
});
});
function createFile(name, size, type) {
const file = new File([], name, { type });
Object.defineProperty(file, "size", {
get() {
return size;
},
});
return file;
}