Improve test cases. Also merged formatFileSize and formatRawFileSize through specifying a separate boolean argument to switch between modes. Add Node support for File and Blob (TODO test each implementation)master
@@ -3,3 +3,4 @@ | |||||
node_modules | node_modules | ||||
dist | dist | ||||
.idea/ | .idea/ | ||||
coverage/ |
@@ -1,5 +1,5 @@ | |||||
{ | { | ||||
"version": "1.0.1", | |||||
"version": "1.0.2", | |||||
"license": "MIT", | "license": "MIT", | ||||
"main": "dist/index.js", | "main": "dist/index.js", | ||||
"typings": "dist/index.d.ts", | "typings": "dist/index.d.ts", | ||||
@@ -19,7 +19,8 @@ | |||||
}, | }, | ||||
"peerDependencies": { | "peerDependencies": { | ||||
"crypto-js": "^4.0.0", | "crypto-js": "^4.0.0", | ||||
"numeral": "^2.0.6" | |||||
"numeral": "^2.0.6", | |||||
"node-blob": "^0.0.2" | |||||
}, | }, | ||||
"husky": { | "husky": { | ||||
"hooks": { | "hooks": { | ||||
@@ -30,20 +31,19 @@ | |||||
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>", | "author": "TheoryOfNekomata <allan.crisostomo@outlook.com>", | ||||
"repository": { | "repository": { | ||||
"type": "git", | "type": "git", | ||||
"url":"https://code.modal.sh/TheoryOfNekomata/file-commons.git" | |||||
"url": "https://code.modal.sh/TheoryOfNekomata/file-commons.git" | |||||
}, | }, | ||||
"module": "dist/file-commons.esm.js", | "module": "dist/file-commons.esm.js", | ||||
"devDependencies": { | "devDependencies": { | ||||
"@types/crypto-js": "^3.1.47", | "@types/crypto-js": "^3.1.47", | ||||
"@types/numeral": "^0.0.28", | "@types/numeral": "^0.0.28", | ||||
"crypto-js": "^4.0.0", | |||||
"fast-check": "^2.3.0", | "fast-check": "^2.3.0", | ||||
"husky": "^4.3.0", | "husky": "^4.3.0", | ||||
"numeral": "^2.0.6", | |||||
"tsdx": "^0.13.3", | "tsdx": "^0.13.3", | ||||
"tslib": "^2.0.1", | "tslib": "^2.0.1", | ||||
"typescript": "^4.0.2" | |||||
}, | |||||
"dependencies": { | |||||
"crypto-js": "^4.0.0", | |||||
"numeral": "^2.0.6" | |||||
"typescript": "^4.0.2", | |||||
"node-blob": "^0.0.2" | |||||
} | } | ||||
} | } |
@@ -15,11 +15,8 @@ it('should accept a minimum of 1 argument', () => { | |||||
it('should return a string', () => { | it('should return a string', () => { | ||||
fc.assert( | fc.assert( | ||||
fc.property( | |||||
fc.string(), | |||||
s => { | |||||
expect(typeof calculateHash(s)).toBe('string') | |||||
} | |||||
) | |||||
fc.property(fc.string(), s => { | |||||
expect(typeof calculateHash(s)).toBe('string') | |||||
}) | |||||
) | ) | ||||
}) | }) |
@@ -1,4 +1,4 @@ | |||||
import {LibWordArray} from 'crypto-js' | |||||
import { LibWordArray } from 'crypto-js' | |||||
import sha512 from 'crypto-js/sha512' | import sha512 from 'crypto-js/sha512' | ||||
import Hex from 'crypto-js/enc-hex' | import Hex from 'crypto-js/enc-hex' | ||||
@@ -1,30 +1,44 @@ | |||||
import NodeBlob from 'node-blob' | |||||
type DataUriToBlob = (dataUri: string, name?: string) => Blob | type DataUriToBlob = (dataUri: string, name?: string) => Blob | ||||
const DATA_URI_PREFIX = 'data:' | const DATA_URI_PREFIX = 'data:' | ||||
const DATA_TYPE_DELIMITER = ';' | const DATA_TYPE_DELIMITER = ';' | ||||
const DATA_START = ',' | const DATA_START = ',' | ||||
const dataUriToBlob: DataUriToBlob = (dataUri, name) => { | |||||
if (typeof dataUri as unknown !== 'string') { | |||||
interface NodeFileConfig { | |||||
lastModified: number | |||||
} | |||||
class NodeFile extends NodeBlob { | |||||
public readonly name: string | |||||
public readonly lastModified: number | |||||
constructor(blobParts: unknown[], name: string, config: Partial<NodeFileConfig>) { | |||||
super(blobParts, config) | |||||
this.name = name | |||||
this.lastModified = typeof config.lastModified! === 'number' ? config.lastModified : Date.now() | |||||
} | |||||
} | |||||
const dataUriToBlob: DataUriToBlob = (dataUri, name?) => { | |||||
if ((typeof dataUri as unknown) !== 'string') { | |||||
throw TypeError('Argument should be a string.') | throw TypeError('Argument should be a string.') | ||||
} | } | ||||
const [encoding, base64] = dataUri | |||||
.slice(DATA_URI_PREFIX.length) | |||||
.split(DATA_START) | |||||
const [encoding, base64] = dataUri.slice(DATA_URI_PREFIX.length).split(DATA_START) | |||||
const binary = atob(base64) | const binary = atob(base64) | ||||
const [type,] = encoding.split(DATA_TYPE_DELIMITER) | |||||
const [type] = encoding.split(DATA_TYPE_DELIMITER) | |||||
const ab = new ArrayBuffer(binary.length) | const ab = new ArrayBuffer(binary.length) | ||||
const ia = new Uint8Array(ab) | const ia = new Uint8Array(ab) | ||||
for (let i = 0; i < binary.length; i++) { | for (let i = 0; i < binary.length; i++) { | ||||
ia[i] = binary.charCodeAt(i) | ia[i] = binary.charCodeAt(i) | ||||
} | } | ||||
if (typeof name! === 'string') { | if (typeof name! === 'string') { | ||||
return new File([ab], name, { type, }) | |||||
const FileCtor = typeof window !== 'undefined' ? window.File : NodeFile | |||||
return new FileCtor([ab], name, { type }) | |||||
} | } | ||||
return new Blob([ab], { type, }) | |||||
const BlobCtor = typeof window !== 'undefined' ? window.Blob : NodeBlob | |||||
return new BlobCtor([ab], { type }) | |||||
} | } | ||||
export default dataUriToBlob | export default dataUriToBlob | ||||
// TODO make code portable to Node! Maybe return a buffer instead of blob? |
@@ -9,8 +9,8 @@ it('should be a function', () => { | |||||
expect(typeof formatFileSize).toBe('function') | expect(typeof formatFileSize).toBe('function') | ||||
}) | }) | ||||
it('should take 1 argument', () => { | |||||
expect(formatFileSize).toHaveLength(1) | |||||
it('should accept 2 arguments', () => { | |||||
expect(formatFileSize).toHaveLength(2) | |||||
}) | }) | ||||
describe('on numeric arguments', () => { | describe('on numeric arguments', () => { | ||||
@@ -53,3 +53,36 @@ describe('on non-numeric arguments', () => { | |||||
) | ) | ||||
}) | }) | ||||
}) | }) | ||||
describe('on raw', () => { | |||||
it('should throw an error on non-numeric arguments', () => { | |||||
fc.assert( | |||||
fc.property( | |||||
fc.anything().filter(v => typeof v !== 'number'), | |||||
v => { | |||||
expect(() => formatFileSize(v as number, true)).toThrow(TypeError) | |||||
} | |||||
) | |||||
) | |||||
}) | |||||
it('should throw an error on NaN', () => { | |||||
expect(() => formatFileSize(NaN, true)).toThrow(RangeError) | |||||
}) | |||||
it('should return string on numeric values', () => { | |||||
fc.assert( | |||||
fc.property(fc.integer(), v => { | |||||
expect(typeof formatFileSize(v, true)).toBe('string') | |||||
}) | |||||
) | |||||
}) | |||||
it('should format numeric values', () => { | |||||
fc.assert( | |||||
fc.property(fc.integer(), v => { | |||||
expect(formatFileSize(v, true)).toMatch(/^-?\d[,\d]* .?B$/) | |||||
}) | |||||
) | |||||
}) | |||||
}) |
@@ -1,9 +1,9 @@ | |||||
import numeral from 'numeral' | import numeral from 'numeral' | ||||
type FormatFileSize = (n: number) => string | |||||
type FormatFileSize = (n: number, raw?: boolean) => string | |||||
const formatFileSize: FormatFileSize = n => { | |||||
if (typeof n as unknown !== 'number') { | |||||
const formatFileSize: FormatFileSize = (n, raw = false) => { | |||||
if ((typeof n as unknown) !== 'number') { | |||||
throw TypeError('Argument should be a number.') | throw TypeError('Argument should be a number.') | ||||
} | } | ||||
@@ -11,8 +11,14 @@ const formatFileSize: FormatFileSize = n => { | |||||
throw RangeError('Cannot format NaN.') | throw RangeError('Cannot format NaN.') | ||||
} | } | ||||
const base = numeral(Math.abs(n)).format(Math.abs(n) < 1000 ? '0 b' : '0.00 b') | |||||
if (raw) { | |||||
const absValue = Math.abs(n) | |||||
const base = numeral(absValue < 1000 ? absValue : 999).format('0 b') | |||||
const suffix = base.slice(base.indexOf(' ') + ' '.length) | |||||
return `${n < 0 ? '-' : ''}${numeral(absValue).format('0,0')} ${suffix}` | |||||
} | |||||
const base = numeral(Math.abs(n)).format(Math.abs(n) < 1000 ? '0 b' : '0.00 b') | |||||
return `${n < 0 ? '-' : ''}${base}` | return `${n < 0 ? '-' : ''}${base}` | ||||
} | } | ||||
@@ -1,51 +0,0 @@ | |||||
import * as fc from 'fast-check' | |||||
import formatRawFileSize from './formatRawFileSize' | |||||
it('should exist', () => { | |||||
expect(formatRawFileSize).toBeDefined() | |||||
}) | |||||
it('should be a function', () => { | |||||
expect(typeof formatRawFileSize).toBe('function') | |||||
}) | |||||
it('should take 1 argument', () => { | |||||
expect(formatRawFileSize).toHaveLength(1) | |||||
}) | |||||
it('should throw an error on non-numeric arguments', () => { | |||||
fc.assert( | |||||
fc.property( | |||||
fc.anything().filter(v => typeof v !== 'number'), | |||||
v => { | |||||
expect(() => formatRawFileSize(v as number)).toThrow(TypeError) | |||||
} | |||||
) | |||||
) | |||||
}) | |||||
it('should throw an error on NaN', () => { | |||||
expect(() => formatRawFileSize(NaN)).toThrow(RangeError) | |||||
}) | |||||
it('should return string on numeric values', () => { | |||||
fc.assert( | |||||
fc.property( | |||||
fc.integer(), | |||||
v => { | |||||
expect(typeof formatRawFileSize(v)).toBe('string') | |||||
} | |||||
) | |||||
) | |||||
}) | |||||
it('should format numeric values', () => { | |||||
fc.assert( | |||||
fc.property( | |||||
fc.integer(), | |||||
v => { | |||||
expect(formatRawFileSize(v)).toMatch(/^-?\d[,\d]* .?B$/) | |||||
} | |||||
) | |||||
) | |||||
}) |
@@ -1,20 +0,0 @@ | |||||
import numeral from 'numeral' | |||||
type FormatRawFileSize = (n: number) => string | |||||
const formatRawFileSize: FormatRawFileSize = n => { | |||||
if (typeof n as unknown !== 'number') { | |||||
throw TypeError('Argument should be a number.') | |||||
} | |||||
if (isNaN(n)) { | |||||
throw RangeError('Cannot format NaN.') | |||||
} | |||||
const absValue = Math.abs(n) | |||||
const base = numeral(absValue < 1000 ? absValue : 999).format('0 b') | |||||
const suffix = base.slice(base.indexOf(' ') + ' '.length) | |||||
return `${n < 0 ? '-' : ''}${numeral(absValue).format('0,0')} ${suffix}` | |||||
} | |||||
export default formatRawFileSize |
@@ -0,0 +1 @@ | |||||
declare module 'node-blob' |
@@ -0,0 +1,31 @@ | |||||
import * as index from './index' | |||||
describe('calculateHash', () => { | |||||
it('should exist', () => { | |||||
expect(index.calculateHash).toBeDefined() | |||||
}) | |||||
}) | |||||
describe('dataUriToBlob', () => { | |||||
it('should exist', () => { | |||||
expect(index.dataUriToBlob).toBeDefined() | |||||
}) | |||||
}) | |||||
describe('formatFileSize', () => { | |||||
it('should exist', () => { | |||||
expect(index.formatFileSize).toBeDefined() | |||||
}) | |||||
}) | |||||
describe('isValidFileName', () => { | |||||
it('should exist', () => { | |||||
expect(index.isValidFileName).toBeDefined() | |||||
}) | |||||
}) | |||||
describe('isValidMimeType', () => { | |||||
it('should exist', () => { | |||||
expect(index.isValidMimeType).toBeDefined() | |||||
}) | |||||
}) |
@@ -1,15 +1,7 @@ | |||||
import calculateHash from './calculateHash' | import calculateHash from './calculateHash' | ||||
import dataUriToBlob from './dataUriToBlob' | import dataUriToBlob from './dataUriToBlob' | ||||
import formatFileSize from './formatFileSize' | import formatFileSize from './formatFileSize' | ||||
import formatRawFileSize from './formatRawFileSize' | |||||
import isValidFilename from './isValidFileName' | |||||
import isValidFileName from './isValidFileName' | |||||
import isValidMimeType from './isValidMimeType' | import isValidMimeType from './isValidMimeType' | ||||
export { | |||||
calculateHash, | |||||
dataUriToBlob, | |||||
formatFileSize, | |||||
formatRawFileSize, | |||||
isValidFilename, | |||||
isValidMimeType, | |||||
} | |||||
export { calculateHash, dataUriToBlob, formatFileSize, isValidFileName, isValidMimeType } |
@@ -18,7 +18,7 @@ it('should throw an error given invalid param shapes', () => { | |||||
fc.property( | fc.property( | ||||
fc.object().filter(o => !('validExtensions' in o)), | fc.object().filter(o => !('validExtensions' in o)), | ||||
params => { | params => { | ||||
expect(() => isValidFilename(params as unknown as IsValidFilenameConfig)).toThrow(TypeError) | |||||
expect(() => isValidFilename((params as unknown) as IsValidFilenameConfig)).toThrow(TypeError) | |||||
} | } | ||||
) | ) | ||||
) | ) | ||||
@@ -29,10 +29,10 @@ describe('on valid extensions', () => { | |||||
fc.assert( | fc.assert( | ||||
fc.property( | fc.property( | ||||
fc.record({ | fc.record({ | ||||
validExtensions: fc.anything().filter(e => !(typeof e === 'string' || Array.isArray(e))) | |||||
validExtensions: fc.anything().filter(e => !(typeof e === 'string' || Array.isArray(e))), | |||||
}), | }), | ||||
params => { | params => { | ||||
expect(() => isValidFilename(params as unknown as IsValidFilenameConfig)).toThrow(TypeError) | |||||
expect(() => isValidFilename((params as unknown) as IsValidFilenameConfig)).toThrow(TypeError) | |||||
} | } | ||||
) | ) | ||||
) | ) | ||||
@@ -44,10 +44,14 @@ describe('on valid extensions', () => { | |||||
fc.assert( | fc.assert( | ||||
fc.property( | fc.property( | ||||
fc.record({ | fc.record({ | ||||
validExtensions: fc.array(fc.anything().filter(e => typeof e !== 'string'), 1, 20), | |||||
validExtensions: fc.array( | |||||
fc.anything().filter(e => typeof e !== 'string'), | |||||
1, | |||||
20 | |||||
), | |||||
}), | }), | ||||
params => { | params => { | ||||
expect(() => isValidFilename(params as unknown as IsValidFilenameConfig)).toThrow(TypeError) | |||||
expect(() => isValidFilename((params as unknown) as IsValidFilenameConfig)).toThrow(TypeError) | |||||
} | } | ||||
) | ) | ||||
) | ) | ||||
@@ -84,12 +88,9 @@ describe('on main callable', () => { | |||||
fc.property( | fc.property( | ||||
fc.tuple( | fc.tuple( | ||||
fc.record<IsValidFilenameConfig>({ | fc.record<IsValidFilenameConfig>({ | ||||
validExtensions: fc.oneof( | |||||
fc.string(), | |||||
fc.array(fc.string(), 1, 20), | |||||
), | |||||
validExtensions: fc.oneof(fc.string(), fc.array(fc.string(), 1, 20)), | |||||
}), | }), | ||||
fc.anything().filter(v => typeof v !== 'string'), | |||||
fc.anything().filter(v => typeof v !== 'string') | |||||
), | ), | ||||
([params, maybeFileName]) => { | ([params, maybeFileName]) => { | ||||
expect(() => isValidFilename(params)(maybeFileName as string)).toThrow(TypeError) | expect(() => isValidFilename(params)(maybeFileName as string)).toThrow(TypeError) | ||||
@@ -103,12 +104,9 @@ describe('on main callable', () => { | |||||
fc.property( | fc.property( | ||||
fc.tuple( | fc.tuple( | ||||
fc.record<IsValidFilenameConfig>({ | fc.record<IsValidFilenameConfig>({ | ||||
validExtensions: fc.oneof( | |||||
fc.string(), | |||||
fc.array(fc.string(), 1, 20), | |||||
), | |||||
validExtensions: fc.oneof(fc.string(), fc.array(fc.string(), 1, 20)), | |||||
}), | }), | ||||
fc.string(), | |||||
fc.string() | |||||
), | ), | ||||
([params, maybeFileName]) => { | ([params, maybeFileName]) => { | ||||
expect(typeof isValidFilename(params)(maybeFileName)).toBe('boolean') | expect(typeof isValidFilename(params)(maybeFileName)).toBe('boolean') | ||||
@@ -16,8 +16,8 @@ const isValidFileName: IsValidFileName = config => { | |||||
.split(MULTIPLE_VALID_EXTENSION_DELIMITER) | .split(MULTIPLE_VALID_EXTENSION_DELIMITER) | ||||
.filter(p => p.trim().length > 0) | .filter(p => p.trim().length > 0) | ||||
} else if ( | } else if ( | ||||
Array.isArray(maybeValidExtensions) | |||||
&& (config.validExtensions as unknown[]).every(s => typeof s === 'string') | |||||
Array.isArray(maybeValidExtensions) && | |||||
(config.validExtensions as unknown[]).every(s => typeof s === 'string') | |||||
) { | ) { | ||||
if ((config.validExtensions as unknown[]).length < 1) { | if ((config.validExtensions as unknown[]).length < 1) { | ||||
throw RangeError('There must be at least 1 valid extension defined.') | throw RangeError('There must be at least 1 valid extension defined.') | ||||
@@ -28,14 +28,12 @@ const isValidFileName: IsValidFileName = config => { | |||||
} | } | ||||
// strip . in valid extensions for easier matching | // strip . in valid extensions for easier matching | ||||
const validExtensions = validExtensionsArray.map(e => ( | |||||
e.startsWith(EXTENSION_DELIMITER) | |||||
? e.slice(EXTENSION_DELIMITER.length) | |||||
: e | |||||
)) | |||||
const validExtensions = validExtensionsArray.map(e => | |||||
e.startsWith(EXTENSION_DELIMITER) ? e.slice(EXTENSION_DELIMITER.length) : e | |||||
) | |||||
return maybeFileName => { | return maybeFileName => { | ||||
if (typeof maybeFileName as unknown !== 'string') { | |||||
if ((typeof maybeFileName as unknown) !== 'string') { | |||||
throw TypeError('Argument should be a string.') | throw TypeError('Argument should be a string.') | ||||
} | } | ||||
@@ -44,19 +42,13 @@ const isValidFileName: IsValidFileName = config => { | |||||
if (config.allowMultipleExtensions) { | if (config.allowMultipleExtensions) { | ||||
return validExtensions.reduce<boolean>( | return validExtensions.reduce<boolean>( | ||||
(isValid, validExtension) => ( | |||||
isValid | |||||
|| extensions.includes(validExtension) | |||||
), | |||||
(isValid, validExtension) => isValid || extensions.includes(validExtension), | |||||
false | false | ||||
) | ) | ||||
} | } | ||||
return validExtensions.reduce<boolean>( | return validExtensions.reduce<boolean>( | ||||
(isValid, validExtension) => ( | |||||
isValid | |||||
|| extensions.join(EXTENSION_DELIMITER).endsWith(validExtension) | |||||
), | |||||
(isValid, validExtension) => isValid || extensions.join(EXTENSION_DELIMITER).endsWith(validExtension), | |||||
false | false | ||||
) | ) | ||||
} | } | ||||
@@ -18,7 +18,7 @@ it('should throw an error given invalid param shapes', () => { | |||||
fc.property( | fc.property( | ||||
fc.object().filter(o => !('validMimeTypes' in o)), | fc.object().filter(o => !('validMimeTypes' in o)), | ||||
params => { | params => { | ||||
expect(() => isValidMimeType(params as unknown as IsValidMimeTypeConfig)).toThrow(TypeError) | |||||
expect(() => isValidMimeType((params as unknown) as IsValidMimeTypeConfig)).toThrow(TypeError) | |||||
} | } | ||||
) | ) | ||||
) | ) | ||||
@@ -29,10 +29,10 @@ describe('on valid MIME types', () => { | |||||
fc.assert( | fc.assert( | ||||
fc.property( | fc.property( | ||||
fc.record({ | fc.record({ | ||||
validMimeTypes: fc.anything().filter(e => !(typeof e === 'string' || Array.isArray(e))) | |||||
validMimeTypes: fc.anything().filter(e => !(typeof e === 'string' || Array.isArray(e))), | |||||
}), | }), | ||||
params => { | params => { | ||||
expect(() => isValidMimeType(params as unknown as IsValidMimeTypeConfig)).toThrow(TypeError) | |||||
expect(() => isValidMimeType((params as unknown) as IsValidMimeTypeConfig)).toThrow(TypeError) | |||||
} | } | ||||
) | ) | ||||
) | ) | ||||
@@ -44,10 +44,14 @@ describe('on valid MIME types', () => { | |||||
fc.assert( | fc.assert( | ||||
fc.property( | fc.property( | ||||
fc.record({ | fc.record({ | ||||
validMimeTypes: fc.array(fc.anything().filter(e => typeof e !== 'string'), 1, 20), | |||||
validMimeTypes: fc.array( | |||||
fc.anything().filter(e => typeof e !== 'string'), | |||||
1, | |||||
20 | |||||
), | |||||
}), | }), | ||||
params => { | params => { | ||||
expect(() => isValidMimeType(params as unknown as IsValidMimeTypeConfig)).toThrow(TypeError) | |||||
expect(() => isValidMimeType((params as unknown) as IsValidMimeTypeConfig)).toThrow(TypeError) | |||||
} | } | ||||
) | ) | ||||
) | ) | ||||
@@ -84,12 +88,9 @@ describe('on main callable', () => { | |||||
fc.property( | fc.property( | ||||
fc.tuple( | fc.tuple( | ||||
fc.record<IsValidMimeTypeConfig>({ | fc.record<IsValidMimeTypeConfig>({ | ||||
validMimeTypes: fc.oneof( | |||||
fc.string(), | |||||
fc.array(fc.string(), 1, 20), | |||||
), | |||||
validMimeTypes: fc.oneof(fc.string(), fc.array(fc.string(), 1, 20)), | |||||
}), | }), | ||||
fc.anything().filter(v => typeof v !== 'string'), | |||||
fc.anything().filter(v => typeof v !== 'string') | |||||
), | ), | ||||
([params, maybeFileName]) => { | ([params, maybeFileName]) => { | ||||
expect(() => isValidMimeType(params)(maybeFileName as string)).toThrow(TypeError) | expect(() => isValidMimeType(params)(maybeFileName as string)).toThrow(TypeError) | ||||
@@ -103,12 +104,9 @@ describe('on main callable', () => { | |||||
fc.property( | fc.property( | ||||
fc.tuple( | fc.tuple( | ||||
fc.record<IsValidMimeTypeConfig>({ | fc.record<IsValidMimeTypeConfig>({ | ||||
validMimeTypes: fc.oneof( | |||||
fc.string(), | |||||
fc.array(fc.string(), 1, 20), | |||||
), | |||||
validMimeTypes: fc.oneof(fc.string(), fc.array(fc.string(), 1, 20)), | |||||
}), | }), | ||||
fc.string(), | |||||
fc.string() | |||||
), | ), | ||||
([params, maybeFileName]) => { | ([params, maybeFileName]) => { | ||||
expect(typeof isValidMimeType(params)(maybeFileName)).toBe('boolean') | expect(typeof isValidMimeType(params)(maybeFileName)).toBe('boolean') | ||||
@@ -116,4 +114,26 @@ describe('on main callable', () => { | |||||
) | ) | ||||
) | ) | ||||
}) | }) | ||||
it('should return true when catch-all MIME type is specified', () => { | |||||
fc.assert( | |||||
fc.property(fc.tuple(fc.array(fc.string(), 1, 20), fc.string()), ([validMimeTypes, maybeFileName]) => { | |||||
expect(isValidMimeType({ validMimeTypes: [...validMimeTypes, '*/*'] })(maybeFileName)).toBe(true) | |||||
}) | |||||
) | |||||
}) | |||||
it('should return true when catch-all for specified MIME type class is specified', () => { | |||||
fc.assert( | |||||
fc.property( | |||||
fc.tuple( | |||||
fc.string().filter(s => !s.includes('/')), | |||||
fc.string().filter(s => !s.includes('/')) | |||||
), | |||||
([mimeTypeClass, test]) => { | |||||
expect(isValidMimeType({ validMimeTypes: [`${mimeTypeClass}/*`] })(`${mimeTypeClass}/${test}`)).toBe(true) | |||||
} | |||||
) | |||||
) | |||||
}) | |||||
}) | }) |
@@ -19,8 +19,8 @@ const isValidMimeType: IsValidMimeType = config => { | |||||
.split(MULTIPLE_VALID_MIME_TYPE_DELIMITER) | .split(MULTIPLE_VALID_MIME_TYPE_DELIMITER) | ||||
.filter(p => p.trim().length > 0) | .filter(p => p.trim().length > 0) | ||||
} else if ( | } else if ( | ||||
Array.isArray(maybeValidMimeTypes) | |||||
&& (config.validMimeTypes as unknown[]).every(s => typeof s === 'string') | |||||
Array.isArray(maybeValidMimeTypes) && | |||||
(config.validMimeTypes as unknown[]).every(s => typeof s === 'string') | |||||
) { | ) { | ||||
if ((config.validMimeTypes as unknown[]).length < 1) { | if ((config.validMimeTypes as unknown[]).length < 1) { | ||||
throw RangeError('There must be at least 1 valid extension defined.') | throw RangeError('There must be at least 1 valid extension defined.') | ||||
@@ -31,7 +31,7 @@ const isValidMimeType: IsValidMimeType = config => { | |||||
} | } | ||||
return mimeType => { | return mimeType => { | ||||
if (typeof mimeType as unknown !== 'string') { | |||||
if ((typeof mimeType as unknown) !== 'string') { | |||||
throw TypeError('Argument should be a string.') | throw TypeError('Argument should be a string.') | ||||
} | } | ||||
@@ -40,21 +40,16 @@ const isValidMimeType: IsValidMimeType = config => { | |||||
return true | return true | ||||
} | } | ||||
return ( | |||||
validMimeTypes.reduce<boolean>( | |||||
(isValid, validMimeType) => { | |||||
const [type, format] = validMimeType.split(MIME_TYPE_FORMAT_DELIMITER) | |||||
// maybe short circuit matching format catch-all valid MIME types? | |||||
if (format === MIME_TYPE_FORMAT_CATCH_ALL) { | |||||
return isValid || mimeType.startsWith(type + MIME_TYPE_FORMAT_DELIMITER) | |||||
} | |||||
return isValid || mimeType === validMimeType | |||||
}, | |||||
false, | |||||
) | |||||
) | |||||
return validMimeTypes.reduce<boolean>((isValid, validMimeType) => { | |||||
const [type, format] = validMimeType.split(MIME_TYPE_FORMAT_DELIMITER) | |||||
// maybe short circuit matching format catch-all valid MIME types? | |||||
if (format === MIME_TYPE_FORMAT_CATCH_ALL) { | |||||
return isValid || mimeType.startsWith(type + MIME_TYPE_FORMAT_DELIMITER) | |||||
} | |||||
return isValid || mimeType === validMimeType | |||||
}, false) | |||||
} | } | ||||
} | } | ||||
@@ -4215,6 +4215,11 @@ no-case@^2.2.0: | |||||
dependencies: | dependencies: | ||||
lower-case "^1.1.1" | lower-case "^1.1.1" | ||||
node-blob@^0.0.2: | |||||
version "0.0.2" | |||||
resolved "https://kabahagi-922648072964.d.codeartifact.ap-southeast-1.amazonaws.com:443/npm/core/node-blob/-/node-blob-0.0.2.tgz#12abb5e722dc4bc396f85c2c2f0073e25eafc0fd" | |||||
integrity sha512-82wiGzMht96gPQDUYaZBdZEVvYD9aEhU6Bt9KLCr4rADZPRd7dQVY2Yj0ZG/1vp4DhVkL49nJT/M3CiMTAt3ag== | |||||
node-int64@^0.4.0: | node-int64@^0.4.0: | ||||
version "0.4.0" | version "0.4.0" | ||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" | resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" | ||||