Browse Source

Implement tests

Add test for each exported function.
master
TheoryOfNekomata 3 years ago
parent
commit
81596e5e9c
14 changed files with 447 additions and 18 deletions
  1. +1
    -1
      README.md
  2. +7
    -3
      package.json
  3. +25
    -0
      src/calculateHash.test.ts
  4. +25
    -0
      src/dataUriToBlob.test.ts
  5. +13
    -3
      src/dataUriToBlob.ts
  6. +1
    -1
      src/formatFileSize.test.ts
  7. +3
    -4
      src/formatFileSize.ts
  8. +1
    -1
      src/formatRawFileSize.test.ts
  9. +3
    -5
      src/formatRawFileSize.ts
  10. +4
    -0
      src/index.ts
  11. +119
    -0
      src/isValidFileName.test.ts
  12. +65
    -0
      src/isValidFileName.ts
  13. +119
    -0
      src/isValidMimeType.test.ts
  14. +61
    -0
      src/isValidMimeType.ts

+ 1
- 1
README.md View File

@@ -1,4 +1,4 @@
# TSDX Bootstrap
# File Commons

Useful methods for file-related functions.



+ 7
- 3
package.json View File

@@ -1,5 +1,5 @@
{
"version": "0.1.0",
"version": "1.0.1",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
@@ -26,8 +26,12 @@
"pre-commit": "tsdx lint"
}
},
"name": "file-commons",
"author": "Allan Crisostomo",
"name": "@theoryofnekomata/file-commons",
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>",
"repository": {
"type": "git",
"url":"https://code.modal.sh/TheoryOfNekomata/file-commons.git"
},
"module": "dist/file-commons.esm.js",
"devDependencies": {
"@types/crypto-js": "^3.1.47",


+ 25
- 0
src/calculateHash.test.ts View File

@@ -0,0 +1,25 @@
import * as fc from 'fast-check'
import calculateHash from './calculateHash'

it('should exist', () => {
expect(calculateHash).toBeDefined()
})

it('should be a callable', () => {
expect(typeof calculateHash).toBe('function')
})

it('should accept a minimum of 1 argument', () => {
expect(calculateHash).toHaveLength(1)
})

it('should return a string', () => {
fc.assert(
fc.property(
fc.string(),
s => {
expect(typeof calculateHash(s)).toBe('string')
}
)
)
})

+ 25
- 0
src/dataUriToBlob.test.ts View File

@@ -0,0 +1,25 @@
import * as fc from 'fast-check'
import dataUriToBlob from './dataUriToBlob'

it('should exist', () => {
expect(dataUriToBlob).toBeDefined()
})

it('should be a callable', () => {
expect(typeof dataUriToBlob).toBe('function')
})

it('should accept 2 arguments', () => {
expect(dataUriToBlob).toHaveLength(2)
})

it('should throw an error for invalid parameters', () => {
fc.assert(
fc.property(
fc.anything().filter(s => typeof s !== 'string'),
s => {
expect(() => dataUriToBlob(s as string)).toThrow(TypeError)
}
)
)
})

+ 13
- 3
src/dataUriToBlob.ts View File

@@ -1,11 +1,19 @@
type DataUriToBlob = (dataUri: string, name?: string) => Blob

const DATA_URI_PREFIX = 'data:'
const DATA_TYPE_DELIMITER = ';'
const DATA_START = ','

const dataUriToBlob: DataUriToBlob = (dataUri, name) => {
if (typeof dataUri as unknown !== 'string') {
throw TypeError('Argument should be a string.')
}

const [encoding, base64] = dataUri
.slice('data:'.length)
.split(',')
.slice(DATA_URI_PREFIX.length)
.split(DATA_START)
const binary = atob(base64)
const [type,] = encoding.split(';')
const [type,] = encoding.split(DATA_TYPE_DELIMITER)
const ab = new ArrayBuffer(binary.length)
const ia = new Uint8Array(ab)
for (let i = 0; i < binary.length; i++) {
@@ -18,3 +26,5 @@ const dataUriToBlob: DataUriToBlob = (dataUri, name) => {
}

export default dataUriToBlob

// TODO make code portable to Node! Maybe return a buffer instead of blob?

+ 1
- 1
src/formatFileSize.test.ts View File

@@ -47,7 +47,7 @@ describe('on non-numeric arguments', () => {
fc.property(
fc.anything().filter(v => typeof v !== 'number'),
v => {
expect(() => formatFileSize(v)).toThrow(TypeError)
expect(() => formatFileSize(v as number)).toThrow(TypeError)
}
)
)


+ 3
- 4
src/formatFileSize.ts View File

@@ -1,13 +1,12 @@
import numeral from 'numeral'

type FormatFileSize = (maybeNumber: unknown) => string
type FormatFileSize = (n: number) => string

const formatFileSize: FormatFileSize = maybeNumber => {
if (typeof maybeNumber! !== 'number') {
const formatFileSize: FormatFileSize = n => {
if (typeof n as unknown !== 'number') {
throw TypeError('Argument should be a number.')
}

const n = maybeNumber as number
if (isNaN(n)) {
throw RangeError('Cannot format NaN.')
}


+ 1
- 1
src/formatRawFileSize.test.ts View File

@@ -18,7 +18,7 @@ it('should throw an error on non-numeric arguments', () => {
fc.property(
fc.anything().filter(v => typeof v !== 'number'),
v => {
expect(() => formatRawFileSize(v)).toThrow(TypeError)
expect(() => formatRawFileSize(v as number)).toThrow(TypeError)
}
)
)


+ 3
- 5
src/formatRawFileSize.ts View File

@@ -1,14 +1,12 @@
import numeral from 'numeral'

type FormatRawFileSize = (maybeNumber: unknown) => string
type FormatRawFileSize = (n: number) => string

const formatRawFileSize: FormatRawFileSize = maybeNumber => {
if (typeof maybeNumber! !== 'number') {
const formatRawFileSize: FormatRawFileSize = n => {
if (typeof n as unknown !== 'number') {
throw TypeError('Argument should be a number.')
}

const n = maybeNumber as number

if (isNaN(n)) {
throw RangeError('Cannot format NaN.')
}


+ 4
- 0
src/index.ts View File

@@ -2,10 +2,14 @@ import calculateHash from './calculateHash'
import dataUriToBlob from './dataUriToBlob'
import formatFileSize from './formatFileSize'
import formatRawFileSize from './formatRawFileSize'
import isValidFilename from './isValidFileName'
import isValidMimeType from './isValidMimeType'

export {
calculateHash,
dataUriToBlob,
formatFileSize,
formatRawFileSize,
isValidFilename,
isValidMimeType,
}

+ 119
- 0
src/isValidFileName.test.ts View File

@@ -0,0 +1,119 @@
import * as fc from 'fast-check'
import isValidFilename, { IsValidFilenameConfig } from './isValidFileName'

it('should exist', () => {
expect(isValidFilename).toBeDefined()
})

it('should be a callable', () => {
expect(typeof isValidFilename).toBe('function')
})

it('should accept a minimum of 1 argument', () => {
expect(isValidFilename).toHaveLength(1)
})

it('should throw an error given invalid param shapes', () => {
fc.assert(
fc.property(
fc.object().filter(o => !('validExtensions' in o)),
params => {
expect(() => isValidFilename(params as unknown as IsValidFilenameConfig)).toThrow(TypeError)
}
)
)
})

describe('on valid extensions', () => {
it('should throw an error given non-string and non-array values', () => {
fc.assert(
fc.property(
fc.record({
validExtensions: fc.anything().filter(e => !(typeof e === 'string' || Array.isArray(e)))
}),
params => {
expect(() => isValidFilename(params as unknown as IsValidFilenameConfig)).toThrow(TypeError)
}
)
)
})
it('should throw an error given empty arrays', () => {
expect(() => isValidFilename({ validExtensions: [] })).toThrow(RangeError)
})
it('should throw an error given non-string arrays', () => {
fc.assert(
fc.property(
fc.record({
validExtensions: fc.array(fc.anything().filter(e => typeof e !== 'string'), 1, 20),
}),
params => {
expect(() => isValidFilename(params as unknown as IsValidFilenameConfig)).toThrow(TypeError)
}
)
)
})
it('should return a main callable given strings', () => {
fc.assert(
fc.property(
fc.record<IsValidFilenameConfig>({
validExtensions: fc.string(),
}),
params => {
expect(typeof isValidFilename(params)).toBe('function')
}
)
)
})
it('should return a main callable given string arrays', () => {
fc.assert(
fc.property(
fc.record<IsValidFilenameConfig>({
validExtensions: fc.array(fc.string(), 1, 20),
}),
params => {
expect(typeof isValidFilename(params)).toBe('function')
}
)
)
})
})

describe('on main callable', () => {
it('should throw an error for non-string params', () => {
fc.assert(
fc.property(
fc.tuple(
fc.record<IsValidFilenameConfig>({
validExtensions: fc.oneof(
fc.string(),
fc.array(fc.string(), 1, 20),
),
}),
fc.anything().filter(v => typeof v !== 'string'),
),
([params, maybeFileName]) => {
expect(() => isValidFilename(params)(maybeFileName as string)).toThrow(TypeError)
}
)
)
})

it('should return a boolean for string params', () => {
fc.assert(
fc.property(
fc.tuple(
fc.record<IsValidFilenameConfig>({
validExtensions: fc.oneof(
fc.string(),
fc.array(fc.string(), 1, 20),
),
}),
fc.string(),
),
([params, maybeFileName]) => {
expect(typeof isValidFilename(params)(maybeFileName)).toBe('boolean')
}
)
)
})
})

+ 65
- 0
src/isValidFileName.ts View File

@@ -0,0 +1,65 @@
const EXTENSION_DELIMITER = '.'
const MULTIPLE_VALID_EXTENSION_DELIMITER = ' '

export type IsValidFilenameConfig = {
validExtensions: string | string[]
allowMultipleExtensions?: boolean
}

type IsValidFileName = (config: IsValidFilenameConfig) => (maybeFileName: string) => boolean

const isValidFileName: IsValidFileName = config => {
let validExtensionsArray: string[]
const maybeValidExtensions = config.validExtensions as unknown
if (typeof maybeValidExtensions === 'string') {
validExtensionsArray = (config.validExtensions as string)
.split(MULTIPLE_VALID_EXTENSION_DELIMITER)
.filter(p => p.trim().length > 0)
} else if (
Array.isArray(maybeValidExtensions)
&& (config.validExtensions as unknown[]).every(s => typeof s === 'string')
) {
if ((config.validExtensions as unknown[]).length < 1) {
throw RangeError('There must be at least 1 valid extension defined.')
}
validExtensionsArray = config.validExtensions as string[]
} else {
throw TypeError('Valid extensions should be a space-delimited string or a string array.')
}

// strip . in valid extensions for easier matching
const validExtensions = validExtensionsArray.map(e => (
e.startsWith(EXTENSION_DELIMITER)
? e.slice(EXTENSION_DELIMITER.length)
: e
))

return maybeFileName => {
if (typeof maybeFileName as unknown !== 'string') {
throw TypeError('Argument should be a string.')
}

const fileName = maybeFileName as string
const [, ...extensions] = fileName.split(EXTENSION_DELIMITER)

if (config.allowMultipleExtensions) {
return validExtensions.reduce<boolean>(
(isValid, validExtension) => (
isValid
|| extensions.includes(validExtension)
),
false
)
}

return validExtensions.reduce<boolean>(
(isValid, validExtension) => (
isValid
|| extensions.join(EXTENSION_DELIMITER).endsWith(validExtension)
),
false
)
}
}

export default isValidFileName

+ 119
- 0
src/isValidMimeType.test.ts View File

@@ -0,0 +1,119 @@
import * as fc from 'fast-check'
import isValidMimeType, { IsValidMimeTypeConfig } from './isValidMimeType'

it('should exist', () => {
expect(isValidMimeType).toBeDefined()
})

it('should be a callable', () => {
expect(typeof isValidMimeType).toBe('function')
})

it('should accept a minimum of 1 argument', () => {
expect(isValidMimeType).toHaveLength(1)
})

it('should throw an error given invalid param shapes', () => {
fc.assert(
fc.property(
fc.object().filter(o => !('validMimeTypes' in o)),
params => {
expect(() => isValidMimeType(params as unknown as IsValidMimeTypeConfig)).toThrow(TypeError)
}
)
)
})

describe('on valid MIME types', () => {
it('should throw an error given non-string and non-array values', () => {
fc.assert(
fc.property(
fc.record({
validMimeTypes: fc.anything().filter(e => !(typeof e === 'string' || Array.isArray(e)))
}),
params => {
expect(() => isValidMimeType(params as unknown as IsValidMimeTypeConfig)).toThrow(TypeError)
}
)
)
})
it('should throw an error given empty arrays', () => {
expect(() => isValidMimeType({ validMimeTypes: [] })).toThrow(RangeError)
})
it('should throw an error given non-string arrays', () => {
fc.assert(
fc.property(
fc.record({
validMimeTypes: fc.array(fc.anything().filter(e => typeof e !== 'string'), 1, 20),
}),
params => {
expect(() => isValidMimeType(params as unknown as IsValidMimeTypeConfig)).toThrow(TypeError)
}
)
)
})
it('should return a main callable given strings', () => {
fc.assert(
fc.property(
fc.record<IsValidMimeTypeConfig>({
validMimeTypes: fc.string(),
}),
params => {
expect(typeof isValidMimeType(params)).toBe('function')
}
)
)
})
it('should return a main callable given string arrays', () => {
fc.assert(
fc.property(
fc.record<IsValidMimeTypeConfig>({
validMimeTypes: fc.array(fc.string(), 1, 20),
}),
params => {
expect(typeof isValidMimeType(params)).toBe('function')
}
)
)
})
})

describe('on main callable', () => {
it('should throw an error for non-string params', () => {
fc.assert(
fc.property(
fc.tuple(
fc.record<IsValidMimeTypeConfig>({
validMimeTypes: fc.oneof(
fc.string(),
fc.array(fc.string(), 1, 20),
),
}),
fc.anything().filter(v => typeof v !== 'string'),
),
([params, maybeFileName]) => {
expect(() => isValidMimeType(params)(maybeFileName as string)).toThrow(TypeError)
}
)
)
})

it('should return a boolean for string params', () => {
fc.assert(
fc.property(
fc.tuple(
fc.record<IsValidMimeTypeConfig>({
validMimeTypes: fc.oneof(
fc.string(),
fc.array(fc.string(), 1, 20),
),
}),
fc.string(),
),
([params, maybeFileName]) => {
expect(typeof isValidMimeType(params)(maybeFileName)).toBe('boolean')
}
)
)
})
})

+ 61
- 0
src/isValidMimeType.ts View File

@@ -0,0 +1,61 @@
const MULTIPLE_VALID_MIME_TYPE_DELIMITER = ' '
const CATCH_ALL_MIME_TYPES = ['*', '*/*']
const MIME_TYPE_FORMAT_DELIMITER = '/'

// catch-all for format, e.g. "image/*" matches all MIME types starting with "image/"
const MIME_TYPE_FORMAT_CATCH_ALL = '*'

export type IsValidMimeTypeConfig = {
validMimeTypes: string | string[]
}

type IsValidMimeType = (config: IsValidMimeTypeConfig) => (mimeType: string) => boolean

const isValidMimeType: IsValidMimeType = config => {
let validMimeTypes: string[]
const maybeValidMimeTypes = config.validMimeTypes as unknown
if (typeof maybeValidMimeTypes === 'string') {
validMimeTypes = (config.validMimeTypes as string)
.split(MULTIPLE_VALID_MIME_TYPE_DELIMITER)
.filter(p => p.trim().length > 0)
} else if (
Array.isArray(maybeValidMimeTypes)
&& (config.validMimeTypes as unknown[]).every(s => typeof s === 'string')
) {
if ((config.validMimeTypes as unknown[]).length < 1) {
throw RangeError('There must be at least 1 valid extension defined.')
}
validMimeTypes = config.validMimeTypes as string[]
} else {
throw TypeError('Valid MimeTypes should be a space-delimited string or a string array.')
}

return mimeType => {
if (typeof mimeType as unknown !== 'string') {
throw TypeError('Argument should be a string.')
}

// short-circuit valid MIME types that are catch-all
if (validMimeTypes.some(a => CATCH_ALL_MIME_TYPES.includes(a))) {
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,
)
)
}
}

export default isValidMimeType

Loading…
Cancel
Save