From 68c9437de0e2cdc2ce4449428e7f3363c55cec92 Mon Sep 17 00:00:00 2001 From: aminzer Date: Mon, 10 Jul 2023 18:34:44 +0300 Subject: [PATCH] refactor code structure; add tests --- .../compareDirectories.test.ts | 16 ++-- .../compareDirectories.ts} | 14 +-- .../getMirroredFsEntry.ts} | 10 ++- src/compareDirectories/index.ts | 1 + .../validateArgs.ts} | 10 ++- src/compare_directories/index.ts | 1 - src/index.ts | 4 +- src/iterateDirectoryChildren/index.ts | 1 + .../iterateDirectoryChildren.test.ts | 8 +- .../iterateDirectoryChildren.ts | 13 +++ .../iterateDirectoryChildrenRecursive.ts} | 12 +-- .../types.ts | 0 src/iterateDirectoryChildren/validateArgs.ts | 11 +++ src/iterate_directory_children/index.ts | 1 - .../iterate_directory_children.ts | 11 --- .../validate_args.ts | 9 -- .../models/FsEntry.test.ts | 4 +- src/models/{fs_entry.ts => FsEntry.ts} | 4 +- src/models/index.ts | 2 +- src/utils/array.ts | 10 --- src/utils/fs.ts | 22 ----- .../utils/fs/areFileContentsEqual.test.ts | 8 +- src/utils/fs/areFileContentsEqual.ts | 11 +++ src/utils/fs/index.ts | 2 + .../utils/fs/isDirExist.test.ts | 4 +- src/utils/fs/isDirExist.ts | 12 +++ src/utils/iteration/index.ts | 2 + src/utils/iteration/iterateInSeries.test.ts | 90 +++++++++++++++++++ src/utils/iteration/iterateInSeries.ts | 11 +++ src/utils/type.ts | 11 --- src/utils/typeChecks/index.ts | 4 + src/utils/typeChecks/isBoolean.test.ts | 75 ++++++++++++++++ src/utils/typeChecks/isBoolean.ts | 3 + src/utils/typeChecks/isFunction.test.ts | 75 ++++++++++++++++ src/utils/typeChecks/isFunction.ts | 3 + src/utils/typeChecks/isNil.test.ts | 75 ++++++++++++++++ src/utils/typeChecks/isNil.ts | 3 + src/utils/typeChecks/isString.test.ts | 75 ++++++++++++++++ src/utils/typeChecks/isString.ts | 3 + src/utils/validations/index.ts | 3 + .../validations/validateBooleanArg.test.ts | 47 ++++++++++ src/utils/validations/validateBooleanArg.ts | 9 ++ .../validations/validateDirPathArg.test.ts | 47 ++++++++++ src/utils/validations/validateDirPathArg.ts | 10 +++ .../validations/validateFunctionArg.test.ts | 84 +++++++++++++++++ src/utils/validations/validateFunctionArg.ts | 17 ++++ src/validations/index.ts | 3 - src/validations/validate_boolean_arg.ts | 7 -- src/validations/validate_dir_path_arg.ts | 7 -- src/validations/validate_function_arg.ts | 19 ---- .../utils/array/iterate_in_series.test.ts | 61 ------------- tsconfig.build.json | 4 +- tsconfig.json | 3 +- 53 files changed, 742 insertions(+), 210 deletions(-) rename test/cases/compare_directories.test.ts => src/compareDirectories/compareDirectories.test.ts (94%) rename src/{compare_directories/compare_directories.ts => compareDirectories/compareDirectories.ts} (89%) rename src/{compare_directories/get_mirrored_fs_entry.ts => compareDirectories/getMirroredFsEntry.ts} (82%) create mode 100644 src/compareDirectories/index.ts rename src/{compare_directories/validate_args.ts => compareDirectories/validateArgs.ts} (86%) delete mode 100644 src/compare_directories/index.ts create mode 100644 src/iterateDirectoryChildren/index.ts rename test/cases/iterate_directory_children.test.ts => src/iterateDirectoryChildren/iterateDirectoryChildren.test.ts (93%) create mode 100644 src/iterateDirectoryChildren/iterateDirectoryChildren.ts rename src/{iterate_directory_children/iterate_directory_children_recursive.ts => iterateDirectoryChildren/iterateDirectoryChildrenRecursive.ts} (81%) rename src/{iterate_directory_children => iterateDirectoryChildren}/types.ts (100%) create mode 100644 src/iterateDirectoryChildren/validateArgs.ts delete mode 100644 src/iterate_directory_children/index.ts delete mode 100644 src/iterate_directory_children/iterate_directory_children.ts delete mode 100644 src/iterate_directory_children/validate_args.ts rename test/cases/models/fs_entry.test.ts => src/models/FsEntry.test.ts (93%) rename src/models/{fs_entry.ts => FsEntry.ts} (94%) delete mode 100644 src/utils/array.ts delete mode 100644 src/utils/fs.ts rename test/cases/utils/fs/are_file_contents_equal.test.ts => src/utils/fs/areFileContentsEqual.test.ts (72%) create mode 100644 src/utils/fs/areFileContentsEqual.ts create mode 100644 src/utils/fs/index.ts rename test/cases/utils/fs/dir_exists.test.ts => src/utils/fs/isDirExist.test.ts (88%) create mode 100644 src/utils/fs/isDirExist.ts create mode 100644 src/utils/iteration/index.ts create mode 100644 src/utils/iteration/iterateInSeries.test.ts create mode 100644 src/utils/iteration/iterateInSeries.ts delete mode 100644 src/utils/type.ts create mode 100644 src/utils/typeChecks/index.ts create mode 100644 src/utils/typeChecks/isBoolean.test.ts create mode 100644 src/utils/typeChecks/isBoolean.ts create mode 100644 src/utils/typeChecks/isFunction.test.ts create mode 100644 src/utils/typeChecks/isFunction.ts create mode 100644 src/utils/typeChecks/isNil.test.ts create mode 100644 src/utils/typeChecks/isNil.ts create mode 100644 src/utils/typeChecks/isString.test.ts create mode 100644 src/utils/typeChecks/isString.ts create mode 100644 src/utils/validations/index.ts create mode 100644 src/utils/validations/validateBooleanArg.test.ts create mode 100644 src/utils/validations/validateBooleanArg.ts create mode 100644 src/utils/validations/validateDirPathArg.test.ts create mode 100644 src/utils/validations/validateDirPathArg.ts create mode 100644 src/utils/validations/validateFunctionArg.test.ts create mode 100644 src/utils/validations/validateFunctionArg.ts delete mode 100644 src/validations/index.ts delete mode 100644 src/validations/validate_boolean_arg.ts delete mode 100644 src/validations/validate_dir_path_arg.ts delete mode 100644 src/validations/validate_function_arg.ts delete mode 100644 test/cases/utils/array/iterate_in_series.test.ts diff --git a/test/cases/compare_directories.test.ts b/src/compareDirectories/compareDirectories.test.ts similarity index 94% rename from test/cases/compare_directories.test.ts rename to src/compareDirectories/compareDirectories.test.ts index 130652e..f2249d2 100644 --- a/test/cases/compare_directories.test.ts +++ b/src/compareDirectories/compareDirectories.test.ts @@ -1,8 +1,8 @@ import * as path from 'path'; -import { compareDirectories } from '../../dist'; -import { FsEntry } from '../../dist/models'; -import expectedSourceEntries from '../resources/common/expected_source_fs_entries'; -import expectedTargetEntries from '../resources/common/expected_target_fs_entries'; +import expectedSourceEntries from '../../test/resources/common/expected_source_fs_entries'; +import expectedTargetEntries from '../../test/resources/common/expected_target_fs_entries'; +import { FsEntry } from '../models'; +import compareDirectories from './compareDirectories'; function expectEqualEntries(fsEntries, expectedFsEntries) { expect(fsEntries.every((fsEntry) => fsEntry instanceof FsEntry)).toBe(true); @@ -56,8 +56,8 @@ describe('compareDirectories', () => { }); describe('when both paths corresponds to directories', () => { - const sourceDirPath = path.join(__dirname, '../resources/common/source'); - const targetDirPath = path.join(__dirname, '../resources/common/target'); + const sourceDirPath = path.join(__dirname, '../../test/resources/common/source'); + const targetDirPath = path.join(__dirname, '../../test/resources/common/target'); const expectedSourceOnlyEntries = expectedSourceEntries.filter(({ name }) => name.includes('added')); const expectedDifferentSourceEntries = expectedSourceEntries.filter(({ name }) => name.includes('modified')); @@ -190,8 +190,8 @@ describe('compareDirectories', () => { describe('when entries have same name but different types (file or directory)', () => { const commonName = 'file_or_dir.txt'; - const sourceDirPathForCurrentCase = path.join(__dirname, '../resources/file_and_dir_equal_name/source'); - const targetDirPathForCurrentCase = path.join(__dirname, '../resources/file_and_dir_equal_name/target'); + const sourceDirPathForCurrentCase = path.join(__dirname, '../../test/resources/file_and_dir_equal_name/source'); + const targetDirPathForCurrentCase = path.join(__dirname, '../../test/resources/file_and_dir_equal_name/target'); it('differs these entries', async () => { const sourceOnlyEntries = []; diff --git a/src/compare_directories/compare_directories.ts b/src/compareDirectories/compareDirectories.ts similarity index 89% rename from src/compare_directories/compare_directories.ts rename to src/compareDirectories/compareDirectories.ts index 44d63d0..9b3cb57 100644 --- a/src/compare_directories/compare_directories.ts +++ b/src/compareDirectories/compareDirectories.ts @@ -1,10 +1,10 @@ -import iterateDirectoryChildren from '../iterate_directory_children'; +import iterateDirectoryChildren from '../iterateDirectoryChildren'; import { FsEntry } from '../models'; import { areFileContentsEqual } from '../utils/fs'; -import getMirroredFsEntry from './get_mirrored_fs_entry'; -import validateArgs from './validate_args'; +import getMirroredFsEntry from './getMirroredFsEntry'; +import validateArgs from './validateArgs'; -export default async function compareDirectories( +const compareDirectories = async ( sourceDirPath: string, targetDirPath: string, { @@ -22,7 +22,7 @@ export default async function compareDirectories( skipContentComparison?: boolean, skipExcessNestedIterations?: boolean, } = {}, -) { +): Promise => { await validateArgs( sourceDirPath, targetDirPath, @@ -88,4 +88,6 @@ export default async function compareDirectories( } } }); -} +}; + +export default compareDirectories; diff --git a/src/compare_directories/get_mirrored_fs_entry.ts b/src/compareDirectories/getMirroredFsEntry.ts similarity index 82% rename from src/compare_directories/get_mirrored_fs_entry.ts rename to src/compareDirectories/getMirroredFsEntry.ts index d1c5393..27c135d 100644 --- a/src/compare_directories/get_mirrored_fs_entry.ts +++ b/src/compareDirectories/getMirroredFsEntry.ts @@ -1,11 +1,11 @@ import * as path from 'path'; +import { stat } from 'fs/promises'; import { FsEntry } from '../models'; -import { stat } from '../utils/fs'; -export default async function getMirroredFsEntry( +const getMirroredFsEntry = async ( fsEntry: FsEntry, mirrorRootPath: string, -): Promise { +): Promise => { const mirroredFsEntryPath = path.join(mirrorRootPath, fsEntry.relativePath); let mirroredFsEntryStats; @@ -26,4 +26,6 @@ export default async function getMirroredFsEntry( isFile: mirroredFsEntryStats.isFile(), size: mirroredFsEntryStats.size, }); -} +}; + +export default getMirroredFsEntry; diff --git a/src/compareDirectories/index.ts b/src/compareDirectories/index.ts new file mode 100644 index 0000000..d8aaf92 --- /dev/null +++ b/src/compareDirectories/index.ts @@ -0,0 +1 @@ +export { default } from './compareDirectories'; diff --git a/src/compare_directories/validate_args.ts b/src/compareDirectories/validateArgs.ts similarity index 86% rename from src/compare_directories/validate_args.ts rename to src/compareDirectories/validateArgs.ts index fae1a04..fd89b56 100644 --- a/src/compare_directories/validate_args.ts +++ b/src/compareDirectories/validateArgs.ts @@ -1,6 +1,6 @@ -import { validateBooleanArg, validateDirPathArg, validateFunctionArg } from '../validations'; +import { validateBooleanArg, validateDirPathArg, validateFunctionArg } from '../utils/validations'; -export default async function validateArgs( +const validateArgs = async ( sourceDirPath: any, targetDirPath: any, onEachEntry: any, @@ -9,7 +9,7 @@ export default async function validateArgs( onDifferentEntries: any, skipContentComparison: any, skipExcessNestedIterations: any, -) { +): Promise => { await validateDirPathArg(sourceDirPath, 'Source directory'); await validateDirPathArg(targetDirPath, 'Target directory'); @@ -20,4 +20,6 @@ export default async function validateArgs( validateBooleanArg(skipContentComparison, 'skipContentComparison'); validateBooleanArg(skipExcessNestedIterations, 'skipExcessNestedIterations'); -} +}; + +export default validateArgs; diff --git a/src/compare_directories/index.ts b/src/compare_directories/index.ts deleted file mode 100644 index 8405f43..0000000 --- a/src/compare_directories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './compare_directories'; diff --git a/src/index.ts b/src/index.ts index 3127a3c..618944c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,3 @@ -export { default as iterateDirectoryChildren } from './iterate_directory_children'; -export { default as compareDirectories } from './compare_directories'; +export { default as iterateDirectoryChildren } from './iterateDirectoryChildren'; +export { default as compareDirectories } from './compareDirectories'; export { FsEntry } from './models'; diff --git a/src/iterateDirectoryChildren/index.ts b/src/iterateDirectoryChildren/index.ts new file mode 100644 index 0000000..f6f2ae3 --- /dev/null +++ b/src/iterateDirectoryChildren/index.ts @@ -0,0 +1 @@ +export { default } from './iterateDirectoryChildren'; diff --git a/test/cases/iterate_directory_children.test.ts b/src/iterateDirectoryChildren/iterateDirectoryChildren.test.ts similarity index 93% rename from test/cases/iterate_directory_children.test.ts rename to src/iterateDirectoryChildren/iterateDirectoryChildren.test.ts index 2bf1bb0..9669600 100644 --- a/test/cases/iterate_directory_children.test.ts +++ b/src/iterateDirectoryChildren/iterateDirectoryChildren.test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; -import { iterateDirectoryChildren } from '../../dist'; -import { FsEntry } from '../../dist/models'; -import expectedFsEntries from '../resources/common/expected_source_fs_entries'; +import { FsEntry } from '../models'; +import iterateDirectoryChildren from './iterateDirectoryChildren'; +import expectedFsEntries from '../../test/resources/common/expected_source_fs_entries'; const callback = () => { }; @@ -45,7 +45,7 @@ describe('iterateDirectoryChildren', () => { }); describe('when path corresponds to directory', () => { - const dirPath = path.join(__dirname, '../resources/common/source'); + const dirPath = path.join(__dirname, '../../test/resources/common/source'); describe('when callback is executed without rejections for each element', () => { let fsEntries; diff --git a/src/iterateDirectoryChildren/iterateDirectoryChildren.ts b/src/iterateDirectoryChildren/iterateDirectoryChildren.ts new file mode 100644 index 0000000..d894787 --- /dev/null +++ b/src/iterateDirectoryChildren/iterateDirectoryChildren.ts @@ -0,0 +1,13 @@ +import iterateDirectoryChildrenRecursive from './iterateDirectoryChildrenRecursive'; +import { OnEachChild } from './types'; +import validateArgs from './validateArgs'; + +const iterateDirectoryChildren = async ( + dirPath: string, + onEachChild: OnEachChild, +): Promise => { + await validateArgs(dirPath, onEachChild); + await iterateDirectoryChildrenRecursive(dirPath, null, onEachChild); +}; + +export default iterateDirectoryChildren; diff --git a/src/iterate_directory_children/iterate_directory_children_recursive.ts b/src/iterateDirectoryChildren/iterateDirectoryChildrenRecursive.ts similarity index 81% rename from src/iterate_directory_children/iterate_directory_children_recursive.ts rename to src/iterateDirectoryChildren/iterateDirectoryChildrenRecursive.ts index 4acd502..8b0aa9e 100644 --- a/src/iterate_directory_children/iterate_directory_children_recursive.ts +++ b/src/iterateDirectoryChildren/iterateDirectoryChildrenRecursive.ts @@ -1,14 +1,14 @@ import * as path from 'path'; +import { stat, readdir } from 'fs/promises'; import { FsEntry } from '../models'; -import { iterateInSeries } from '../utils/array'; -import { stat, readdir } from '../utils/fs'; +import { iterateInSeries } from '../utils/iteration'; import { OnEachChild } from './types'; -export default async function iterateDirectoryChildrenRecursive( +const iterateDirectoryChildrenRecursive = async ( absoluteDirPath: string, relativeDirPath: string, onEachChild: OnEachChild, -) { +): Promise => { const entries = await readdir(absoluteDirPath); await iterateInSeries(entries, async (name: string) => { @@ -38,4 +38,6 @@ export default async function iterateDirectoryChildrenRecursive( await iterateDirectoryChildrenRecursive(absolutePath, relativePath, onEachChild); } }); -} +}; + +export default iterateDirectoryChildrenRecursive; diff --git a/src/iterate_directory_children/types.ts b/src/iterateDirectoryChildren/types.ts similarity index 100% rename from src/iterate_directory_children/types.ts rename to src/iterateDirectoryChildren/types.ts diff --git a/src/iterateDirectoryChildren/validateArgs.ts b/src/iterateDirectoryChildren/validateArgs.ts new file mode 100644 index 0000000..8cd96b6 --- /dev/null +++ b/src/iterateDirectoryChildren/validateArgs.ts @@ -0,0 +1,11 @@ +import { validateDirPathArg, validateFunctionArg } from '../utils/validations'; + +const validateArgs = async (dirPath: unknown, onEachChild: unknown): Promise => { + await validateDirPathArg(dirPath, 'Directory'); + + validateFunctionArg(onEachChild, 'onEachChild', { + isRequired: true, + }); +}; + +export default validateArgs; diff --git a/src/iterate_directory_children/index.ts b/src/iterate_directory_children/index.ts deleted file mode 100644 index 005436c..0000000 --- a/src/iterate_directory_children/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './iterate_directory_children'; diff --git a/src/iterate_directory_children/iterate_directory_children.ts b/src/iterate_directory_children/iterate_directory_children.ts deleted file mode 100644 index 3af1194..0000000 --- a/src/iterate_directory_children/iterate_directory_children.ts +++ /dev/null @@ -1,11 +0,0 @@ -import validateArgs from './validate_args'; -import iterateDirectoryChildrenRecursive from './iterate_directory_children_recursive'; -import { OnEachChild } from './types'; - -export default async function iterateDirectoryChildren( - dirPath: string, - onEachChild: OnEachChild, -): Promise { - await validateArgs(dirPath, onEachChild); - await iterateDirectoryChildrenRecursive(dirPath, null, onEachChild); -} diff --git a/src/iterate_directory_children/validate_args.ts b/src/iterate_directory_children/validate_args.ts deleted file mode 100644 index 5efda1e..0000000 --- a/src/iterate_directory_children/validate_args.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { validateDirPathArg, validateFunctionArg } from '../validations'; - -export default async function validateArgs(dirPath: any, onEachChild: any) { - await validateDirPathArg(dirPath, 'Directory'); - - validateFunctionArg(onEachChild, 'onEachChild', { - isRequired: true, - }); -} diff --git a/test/cases/models/fs_entry.test.ts b/src/models/FsEntry.test.ts similarity index 93% rename from test/cases/models/fs_entry.test.ts rename to src/models/FsEntry.test.ts index 5c569cd..d460f15 100644 --- a/test/cases/models/fs_entry.test.ts +++ b/src/models/FsEntry.test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; -import { FsEntry } from '../../../dist'; +import FsEntry from './FsEntry'; -describe('FsEntry', () => { +describe('models > FsEntry', () => { describe('constructor', () => { it('initializes instance with correct params', () => { const opts = { diff --git a/src/models/fs_entry.ts b/src/models/FsEntry.ts similarity index 94% rename from src/models/fs_entry.ts rename to src/models/FsEntry.ts index b249b4f..c36d1eb 100644 --- a/src/models/fs_entry.ts +++ b/src/models/FsEntry.ts @@ -1,4 +1,4 @@ -export default class FsEntry { +class FsEntry { name: string; absolutePath: string; @@ -37,3 +37,5 @@ export default class FsEntry { this.isFile = !isDirectory; } } + +export default FsEntry; diff --git a/src/models/index.ts b/src/models/index.ts index 07ca58c..b435c10 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,2 +1,2 @@ // eslint-disable-next-line import/prefer-default-export -export { default as FsEntry } from './fs_entry'; +export { default as FsEntry } from './FsEntry'; diff --git a/src/utils/array.ts b/src/utils/array.ts deleted file mode 100644 index f5ed44d..0000000 --- a/src/utils/array.ts +++ /dev/null @@ -1,10 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export async function iterateInSeries( - array: Type[], - callback: (element: Type, index?: number) => void | Promise, -): Promise { - for (let index = 0; index < array.length; index += 1) { - // eslint-disable-next-line no-await-in-loop - await callback(array[index], index); - } -} diff --git a/src/utils/fs.ts b/src/utils/fs.ts deleted file mode 100644 index ef984a1..0000000 --- a/src/utils/fs.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as fs from 'fs'; -import * as util from 'util'; -import * as streamEqual from 'stream-equal'; - -export const readdir = util.promisify(fs.readdir); -export const stat = util.promisify(fs.stat); - -export async function isDirExist(path: string): Promise { - try { - const stats = await stat(path); - return stats.isDirectory(); - } catch (err) { - return false; - } -} - -export async function areFileContentsEqual(filePath1: string, filePath2: string): Promise { - const stream1 = fs.createReadStream(filePath1); - const stream2 = fs.createReadStream(filePath2); - - return streamEqual(stream1, stream2); -} diff --git a/test/cases/utils/fs/are_file_contents_equal.test.ts b/src/utils/fs/areFileContentsEqual.test.ts similarity index 72% rename from test/cases/utils/fs/are_file_contents_equal.test.ts rename to src/utils/fs/areFileContentsEqual.test.ts index e17954b..46645c5 100644 --- a/test/cases/utils/fs/are_file_contents_equal.test.ts +++ b/src/utils/fs/areFileContentsEqual.test.ts @@ -1,11 +1,9 @@ import * as path from 'path'; -import { areFileContentsEqual } from '../../../../dist/utils/fs'; +import areFileContentsEqual from './areFileContentsEqual'; -function getFilePath(fileName: string): string { - return path.join(__dirname, `../../../resources/common/${fileName}`); -} +const getFilePath = (fileName: string): string => path.join(__dirname, `../../../test/resources/common/${fileName}`); -describe('areFileContentsEqual', () => { +describe('utils > fs > areFileContentsEqual', () => { describe('when files have equal contents', () => { const filePath1 = getFilePath('source/file3.txt'); const filePath2 = getFilePath('target/file3.txt'); diff --git a/src/utils/fs/areFileContentsEqual.ts b/src/utils/fs/areFileContentsEqual.ts new file mode 100644 index 0000000..bc62c53 --- /dev/null +++ b/src/utils/fs/areFileContentsEqual.ts @@ -0,0 +1,11 @@ +import { createReadStream } from 'fs'; +import * as streamEqual from 'stream-equal'; + +const areFileContentsEqual = async (filePath1: string, filePath2: string): Promise => { + const stream1 = createReadStream(filePath1); + const stream2 = createReadStream(filePath2); + + return streamEqual(stream1, stream2); +}; + +export default areFileContentsEqual; diff --git a/src/utils/fs/index.ts b/src/utils/fs/index.ts new file mode 100644 index 0000000..33462fc --- /dev/null +++ b/src/utils/fs/index.ts @@ -0,0 +1,2 @@ +export { default as areFileContentsEqual } from './areFileContentsEqual'; +export { default as isDirExist } from './isDirExist'; diff --git a/test/cases/utils/fs/dir_exists.test.ts b/src/utils/fs/isDirExist.test.ts similarity index 88% rename from test/cases/utils/fs/dir_exists.test.ts rename to src/utils/fs/isDirExist.test.ts index 6d9e9ef..32db6c5 100644 --- a/test/cases/utils/fs/dir_exists.test.ts +++ b/src/utils/fs/isDirExist.test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; -import { isDirExist } from '../../../../dist/utils/fs'; +import isDirExist from './isDirExist'; -describe('dirExist', () => { +describe('utils > fs > dirExist', () => { describe('when path does not exist', () => { const invalidPath = path.join(__dirname, 'invalid/path'); diff --git a/src/utils/fs/isDirExist.ts b/src/utils/fs/isDirExist.ts new file mode 100644 index 0000000..c0bdd81 --- /dev/null +++ b/src/utils/fs/isDirExist.ts @@ -0,0 +1,12 @@ +import { stat } from 'fs/promises'; + +const isDirExist = async (path: string): Promise => { + try { + const stats = await stat(path); + return stats.isDirectory(); + } catch (err) { + return false; + } +}; + +export default isDirExist; diff --git a/src/utils/iteration/index.ts b/src/utils/iteration/index.ts new file mode 100644 index 0000000..4ce79e7 --- /dev/null +++ b/src/utils/iteration/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as iterateInSeries } from './iterateInSeries'; diff --git a/src/utils/iteration/iterateInSeries.test.ts b/src/utils/iteration/iterateInSeries.test.ts new file mode 100644 index 0000000..5b06430 --- /dev/null +++ b/src/utils/iteration/iterateInSeries.test.ts @@ -0,0 +1,90 @@ +import iterateInSeries from './iterateInSeries'; + +const wait = (delay: number): Promise => new Promise((resolve) => { + setTimeout(resolve, delay); +}); + +describe('utils > iteration > iterateInSeries', () => { + let sourceArray: string[] = []; + let logs: string[] = []; + + beforeEach(() => { + sourceArray = ['a', 'b', 'c']; + logs = []; + }); + + describe('when sync callback is passed', () => { + beforeEach(async () => { + await iterateInSeries(sourceArray, (element, index) => { + logs.push(`processed ${element}:${index}`); + }); + }); + + it('calls callback function for each array element in series', async () => { + expect(logs).toEqual([ + 'processed a:0', + 'processed b:1', + 'processed c:2', + ]); + }); + }); + + describe('when async callback is passed', () => { + beforeEach(async () => { + await iterateInSeries(sourceArray, async (element) => { + logs.push(`processing ${element}`); + + await wait(100); + + logs.push(`processed ${element}`); + }); + }); + + it('waits current element Promise resolving before calling callback on next element', async () => { + expect(logs).toEqual([ + 'processing a', + 'processed a', + 'processing b', + 'processed b', + 'processing c', + 'processed c', + ]); + }); + }); + + describe('when callback rejects with error for some element', () => { + let returnValue; + + beforeEach(async () => { + returnValue = iterateInSeries(['a', 'b', 'c'], async (element) => { + logs.push(`processing ${element}`); + + if (element === 'b') { + throw new Error(`Test error on element ${element}`); + } + + logs.push(`processed ${element}`); + }); + }); + + it('is rejected with corresponding error', async () => { + await expect(returnValue) + .rejects + .toThrow('Test error on element b'); + }); + + it("doesn't trigger callback after rejected element", async () => { + try { + await returnValue; + } catch (err) { + // ignored + } + + expect(logs).toEqual([ + 'processing a', + 'processed a', + 'processing b', + ]); + }); + }); +}); diff --git a/src/utils/iteration/iterateInSeries.ts b/src/utils/iteration/iterateInSeries.ts new file mode 100644 index 0000000..c2758e2 --- /dev/null +++ b/src/utils/iteration/iterateInSeries.ts @@ -0,0 +1,11 @@ +const iterateInSeries = async ( + array: T[], + callback: (element: T, index?: number) => void | Promise, +): Promise => { + for (let index = 0; index < array.length; index += 1) { + // eslint-disable-next-line no-await-in-loop + await callback(array[index], index); + } +}; + +export default iterateInSeries; diff --git a/src/utils/type.ts b/src/utils/type.ts deleted file mode 100644 index 73f3e2d..0000000 --- a/src/utils/type.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function isNullish(value: any): boolean { - return value === undefined || value === null; -} - -export function isFunction(value: any): boolean { - return typeof value === 'function'; -} - -export function isBoolean(value: any): boolean { - return typeof value === 'boolean'; -} diff --git a/src/utils/typeChecks/index.ts b/src/utils/typeChecks/index.ts new file mode 100644 index 0000000..86b6104 --- /dev/null +++ b/src/utils/typeChecks/index.ts @@ -0,0 +1,4 @@ +export { default as isBoolean } from './isBoolean'; +export { default as isFunction } from './isFunction'; +export { default as isNil } from './isNil'; +export { default as isString } from './isString'; diff --git a/src/utils/typeChecks/isBoolean.test.ts b/src/utils/typeChecks/isBoolean.test.ts new file mode 100644 index 0000000..447f79c --- /dev/null +++ b/src/utils/typeChecks/isBoolean.test.ts @@ -0,0 +1,75 @@ +import isBoolean from './isBoolean'; + +describe('utils > typeChecks > isBoolean', () => { + interface TestCase { + value: unknown; + expectedResult: boolean; + } + + const testCases: TestCase[] = [ + { + value: undefined, + expectedResult: false, + }, + { + value: null, + expectedResult: false, + }, + { + value: '', + expectedResult: false, + }, + { + value: 'a', + expectedResult: false, + }, + { + value: 0, + expectedResult: false, + }, + { + value: 1, + expectedResult: false, + }, + { + value: false, + expectedResult: true, + }, + { + value: true, + expectedResult: true, + }, + { + value: [], + expectedResult: false, + }, + { + value: [1], + expectedResult: false, + }, + { + value: {}, + expectedResult: false, + }, + { + value: { x: 1 }, + expectedResult: false, + }, + { + value: () => undefined, + expectedResult: false, + }, + { + value: () => 1, + expectedResult: false, + }, + ]; + + testCases.forEach(({ value, expectedResult }) => { + describe(`when input value is ${value}`, () => { + it(`returns ${expectedResult}`, () => { + expect(isBoolean(value)).toBe(expectedResult); + }); + }); + }); +}); diff --git a/src/utils/typeChecks/isBoolean.ts b/src/utils/typeChecks/isBoolean.ts new file mode 100644 index 0000000..40057ff --- /dev/null +++ b/src/utils/typeChecks/isBoolean.ts @@ -0,0 +1,3 @@ +const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean'; + +export default isBoolean; diff --git a/src/utils/typeChecks/isFunction.test.ts b/src/utils/typeChecks/isFunction.test.ts new file mode 100644 index 0000000..66efa08 --- /dev/null +++ b/src/utils/typeChecks/isFunction.test.ts @@ -0,0 +1,75 @@ +import isFunction from './isFunction'; + +describe('utils > typeChecks > isFunction', () => { + interface TestCase { + value: unknown; + expectedResult: boolean; + } + + const testCases: TestCase[] = [ + { + value: undefined, + expectedResult: false, + }, + { + value: null, + expectedResult: false, + }, + { + value: '', + expectedResult: false, + }, + { + value: 'a', + expectedResult: false, + }, + { + value: 0, + expectedResult: false, + }, + { + value: 1, + expectedResult: false, + }, + { + value: false, + expectedResult: false, + }, + { + value: true, + expectedResult: false, + }, + { + value: [], + expectedResult: false, + }, + { + value: [1], + expectedResult: false, + }, + { + value: {}, + expectedResult: false, + }, + { + value: { x: 1 }, + expectedResult: false, + }, + { + value: () => undefined, + expectedResult: true, + }, + { + value: () => 1, + expectedResult: true, + }, + ]; + + testCases.forEach(({ value, expectedResult }) => { + describe(`when input value is ${value}`, () => { + it(`returns ${expectedResult}`, () => { + expect(isFunction(value)).toBe(expectedResult); + }); + }); + }); +}); diff --git a/src/utils/typeChecks/isFunction.ts b/src/utils/typeChecks/isFunction.ts new file mode 100644 index 0000000..d0863c0 --- /dev/null +++ b/src/utils/typeChecks/isFunction.ts @@ -0,0 +1,3 @@ +const isFunction = (value: unknown): value is Function => typeof value === 'function'; + +export default isFunction; diff --git a/src/utils/typeChecks/isNil.test.ts b/src/utils/typeChecks/isNil.test.ts new file mode 100644 index 0000000..50affee --- /dev/null +++ b/src/utils/typeChecks/isNil.test.ts @@ -0,0 +1,75 @@ +import isNil from './isNil'; + +describe('utils > typeChecks > isNil', () => { + interface TestCase { + value: unknown; + expectedResult: boolean; + } + + const testCases: TestCase[] = [ + { + value: undefined, + expectedResult: true, + }, + { + value: null, + expectedResult: true, + }, + { + value: '', + expectedResult: false, + }, + { + value: 'a', + expectedResult: false, + }, + { + value: 0, + expectedResult: false, + }, + { + value: 1, + expectedResult: false, + }, + { + value: false, + expectedResult: false, + }, + { + value: true, + expectedResult: false, + }, + { + value: [], + expectedResult: false, + }, + { + value: [1], + expectedResult: false, + }, + { + value: {}, + expectedResult: false, + }, + { + value: { x: 1 }, + expectedResult: false, + }, + { + value: () => undefined, + expectedResult: false, + }, + { + value: () => 1, + expectedResult: false, + }, + ]; + + testCases.forEach(({ value, expectedResult }) => { + describe(`when input value is ${value}`, () => { + it(`returns ${expectedResult}`, () => { + expect(isNil(value)).toBe(expectedResult); + }); + }); + }); +}); diff --git a/src/utils/typeChecks/isNil.ts b/src/utils/typeChecks/isNil.ts new file mode 100644 index 0000000..5d12e97 --- /dev/null +++ b/src/utils/typeChecks/isNil.ts @@ -0,0 +1,3 @@ +const isNil = (value: unknown): value is undefined | null => value === undefined || value === null; + +export default isNil; diff --git a/src/utils/typeChecks/isString.test.ts b/src/utils/typeChecks/isString.test.ts new file mode 100644 index 0000000..91b7fcd --- /dev/null +++ b/src/utils/typeChecks/isString.test.ts @@ -0,0 +1,75 @@ +import isString from './isString'; + +describe('utils > typeChecks > isString', () => { + interface TestCase { + value: unknown; + expectedResult: boolean; + } + + const testCases: TestCase[] = [ + { + value: undefined, + expectedResult: false, + }, + { + value: null, + expectedResult: false, + }, + { + value: '', + expectedResult: true, + }, + { + value: 'a', + expectedResult: true, + }, + { + value: 0, + expectedResult: false, + }, + { + value: 1, + expectedResult: false, + }, + { + value: false, + expectedResult: false, + }, + { + value: true, + expectedResult: false, + }, + { + value: [], + expectedResult: false, + }, + { + value: [1], + expectedResult: false, + }, + { + value: {}, + expectedResult: false, + }, + { + value: { x: 1 }, + expectedResult: false, + }, + { + value: () => undefined, + expectedResult: false, + }, + { + value: () => 1, + expectedResult: false, + }, + ]; + + testCases.forEach(({ value, expectedResult }) => { + describe(`when input value is ${value}`, () => { + it(`returns ${expectedResult}`, () => { + expect(isString(value)).toBe(expectedResult); + }); + }); + }); +}); diff --git a/src/utils/typeChecks/isString.ts b/src/utils/typeChecks/isString.ts new file mode 100644 index 0000000..db69eb7 --- /dev/null +++ b/src/utils/typeChecks/isString.ts @@ -0,0 +1,3 @@ +const isString = (value: unknown): value is string => typeof value === 'string'; + +export default isString; diff --git a/src/utils/validations/index.ts b/src/utils/validations/index.ts new file mode 100644 index 0000000..db63dad --- /dev/null +++ b/src/utils/validations/index.ts @@ -0,0 +1,3 @@ +export { default as validateBooleanArg } from './validateBooleanArg'; +export { default as validateDirPathArg } from './validateDirPathArg'; +export { default as validateFunctionArg } from './validateFunctionArg'; diff --git a/src/utils/validations/validateBooleanArg.test.ts b/src/utils/validations/validateBooleanArg.test.ts new file mode 100644 index 0000000..0f5e684 --- /dev/null +++ b/src/utils/validations/validateBooleanArg.test.ts @@ -0,0 +1,47 @@ +import validateBooleanArg from './validateBooleanArg'; + +describe('utils > validations > validateBooleanArg', () => { + interface TestCase { + argument: unknown; + shouldThrow: boolean; + } + + const testCases: TestCase[] = [ + { + argument: undefined, + shouldThrow: true, + }, + { + argument: null, + shouldThrow: true, + }, + { + argument: true, + shouldThrow: false, + }, + { + argument: false, + shouldThrow: false, + }, + { + argument: 'false', + shouldThrow: true, + }, + ]; + + testCases.forEach(({ argument, shouldThrow }) => { + describe(`when input argument is ${argument}`, () => { + it(`should${shouldThrow ? '' : ' not'} throw error`, () => { + const callback = () => { + validateBooleanArg(argument, 'testArg'); + }; + + if (shouldThrow) { + expect(callback).toThrow('"testArg" is not a boolean'); + } else { + expect(callback).not.toThrow(); + } + }); + }); + }); +}); diff --git a/src/utils/validations/validateBooleanArg.ts b/src/utils/validations/validateBooleanArg.ts new file mode 100644 index 0000000..63da09e --- /dev/null +++ b/src/utils/validations/validateBooleanArg.ts @@ -0,0 +1,9 @@ +import { isBoolean } from '../typeChecks'; + +const validateBooleanArg = (arg: unknown, argName: string): void => { + if (!isBoolean(arg)) { + throw new Error(`"${argName}" is not a boolean`); + } +}; + +export default validateBooleanArg; diff --git a/src/utils/validations/validateDirPathArg.test.ts b/src/utils/validations/validateDirPathArg.test.ts new file mode 100644 index 0000000..2e994ce --- /dev/null +++ b/src/utils/validations/validateDirPathArg.test.ts @@ -0,0 +1,47 @@ +import validateDirPathArg from './validateDirPathArg'; + +describe('utils > validations > validateDirPathArg', () => { + interface TestCase { + argument: unknown; + shouldThrow: boolean; + } + + const testCases: TestCase[] = [ + { + argument: undefined, + shouldThrow: true, + }, + { + argument: null, + shouldThrow: true, + }, + { + argument: __dirname, + shouldThrow: false, + }, + { + argument: __filename, + shouldThrow: true, + }, + { + argument: `${__dirname}/non/existing/path`, + shouldThrow: true, + }, + ]; + + testCases.forEach(({ argument, shouldThrow }) => { + describe(`when input argument is ${argument}`, () => { + it(`should${shouldThrow ? '' : ' not'} throw error`, () => { + const callback = async () => { + await validateDirPathArg(argument, 'testArg'); + }; + + if (shouldThrow) { + expect(callback()).rejects.toThrow(`Directory testArg "${argument}" does not exist`); + } else { + expect(callback()).resolves.not.toThrow(); + } + }); + }); + }); +}); diff --git a/src/utils/validations/validateDirPathArg.ts b/src/utils/validations/validateDirPathArg.ts new file mode 100644 index 0000000..c5dedc2 --- /dev/null +++ b/src/utils/validations/validateDirPathArg.ts @@ -0,0 +1,10 @@ +import { isDirExist } from '../fs'; +import { isString } from '../typeChecks'; + +const validateDirPathArg = async (arg: unknown, argName: string): Promise => { + if (!isString(arg) || !await isDirExist(arg)) { + throw new Error(`Directory ${argName} "${arg}" does not exist`); + } +}; + +export default validateDirPathArg; diff --git a/src/utils/validations/validateFunctionArg.test.ts b/src/utils/validations/validateFunctionArg.test.ts new file mode 100644 index 0000000..42632aa --- /dev/null +++ b/src/utils/validations/validateFunctionArg.test.ts @@ -0,0 +1,84 @@ +import validateFunctionArg from './validateFunctionArg'; + +describe('utils > validations > validateFunctionArg', () => { + interface TestCase { + argument: unknown; + options?: { isRequired?: boolean }; + shouldThrow: boolean; + } + + const testCases: TestCase[] = [ + { + argument: undefined, + shouldThrow: false, + }, + { + argument: undefined, + options: { isRequired: false }, + shouldThrow: false, + }, + { + argument: undefined, + options: { isRequired: true }, + shouldThrow: true, + }, + { + argument: null, + shouldThrow: false, + }, + { + argument: null, + options: { isRequired: false }, + shouldThrow: false, + }, + { + argument: null, + options: { isRequired: true }, + shouldThrow: true, + }, + { + argument: '', + shouldThrow: true, + }, + { + argument: '', + options: { isRequired: false }, + shouldThrow: true, + }, + { + argument: '', + options: { isRequired: true }, + shouldThrow: true, + }, + { + argument: () => undefined, + shouldThrow: false, + }, + { + argument: () => undefined, + options: { isRequired: false }, + shouldThrow: false, + }, + { + argument: () => undefined, + options: { isRequired: true }, + shouldThrow: false, + }, + ]; + + testCases.forEach(({ argument, options, shouldThrow }) => { + describe(`when input argument is ${argument} and options are ${JSON.stringify(options)}`, () => { + it(`should${shouldThrow ? '' : ' not'} throw error`, () => { + const callback = () => { + validateFunctionArg(argument, 'testArg', options); + }; + + if (shouldThrow) { + expect(callback).toThrow('"testArg" is not a function'); + } else { + expect(callback).not.toThrow(); + } + }); + }); + }); +}); diff --git a/src/utils/validations/validateFunctionArg.ts b/src/utils/validations/validateFunctionArg.ts new file mode 100644 index 0000000..c68e726 --- /dev/null +++ b/src/utils/validations/validateFunctionArg.ts @@ -0,0 +1,17 @@ +import { isNil, isFunction } from '../typeChecks'; + +const validateFunctionArg = ( + arg: unknown, + argName: string, + { isRequired = false }: { isRequired?: boolean } = {}, +): void => { + if (isNil(arg) && !isRequired) { + return; + } + + if (!isFunction(arg)) { + throw new Error(`"${argName}" is not a function`); + } +}; + +export default validateFunctionArg; diff --git a/src/validations/index.ts b/src/validations/index.ts deleted file mode 100644 index 29b0c64..0000000 --- a/src/validations/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as validateBooleanArg } from './validate_boolean_arg'; -export { default as validateDirPathArg } from './validate_dir_path_arg'; -export { default as validateFunctionArg } from './validate_function_arg'; diff --git a/src/validations/validate_boolean_arg.ts b/src/validations/validate_boolean_arg.ts deleted file mode 100644 index 57bb06b..0000000 --- a/src/validations/validate_boolean_arg.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { isBoolean } from '../utils/type'; - -export default function validateBooleanArg(arg: any, argName: string) { - if (!isBoolean(arg)) { - throw new Error(`"${argName}" is not a boolean`); - } -} diff --git a/src/validations/validate_dir_path_arg.ts b/src/validations/validate_dir_path_arg.ts deleted file mode 100644 index 9f13e6f..0000000 --- a/src/validations/validate_dir_path_arg.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { isDirExist } from '../utils/fs'; - -export default async function validateDirPathArg(arg: any, argName: string) { - if (!await isDirExist(arg)) { - throw new Error(`${argName} "${arg}" does not exist`); - } -} diff --git a/src/validations/validate_function_arg.ts b/src/validations/validate_function_arg.ts deleted file mode 100644 index 6607bbe..0000000 --- a/src/validations/validate_function_arg.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { isNullish, isFunction } from '../utils/type'; - -export default function validateFunctionArg( - arg: any, - argName: string, - { - isRequired = false, - }: { - isRequired?: boolean, - } = {}, -) { - if (isNullish(arg) && !isRequired) { - return; - } - - if (!isFunction(arg)) { - throw new Error(`"${argName}" is not a function`); - } -} diff --git a/test/cases/utils/array/iterate_in_series.test.ts b/test/cases/utils/array/iterate_in_series.test.ts deleted file mode 100644 index 17e418c..0000000 --- a/test/cases/utils/array/iterate_in_series.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { iterateInSeries } from '../../../../dist/utils/array'; - -describe('iterateInSeries', () => { - describe('when callback is executed without rejections for each element', () => { - let buffer = ''; - let returnValue; - - beforeEach(async () => { - buffer = ''; - - returnValue = iterateInSeries(['a', 'b', 'c'], async (element: string, index: number) => { - buffer += `${element}${index}.`; - }); - }); - - it('is resolved to "undefined"', async () => { - await expect(returnValue) - .resolves - .toBe(undefined); - }); - - it('triggers callback in series for each array element', async () => { - await returnValue; - - expect(buffer).toBe('a0.b1.c2.'); - }); - }); - - describe('when callback rejects with error for some element', () => { - let buffer = ''; - let returnValue; - - beforeEach(async () => { - buffer = ''; - - returnValue = iterateInSeries(['a', 'b', 'c'], async (element: string, index: number) => { - if (element === 'b') { - throw new Error('Test error'); - } - - buffer += `${element}${index}.`; - }); - }); - - it('is rejected with corresponding error', async () => { - await expect(returnValue) - .rejects - .toThrow('Test error'); - }); - - it("doesn't trigger callback after rejected element", async () => { - try { - await returnValue; - } catch (err) { - // ignored - } - - expect(buffer).toBe('a0.'); - }); - }); -}); diff --git a/tsconfig.build.json b/tsconfig.build.json index 98182e1..798e7b4 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -6,7 +6,7 @@ "sourceMap": true, "noEmitOnError": true }, - "include": [ - "src/**/*.ts" + "exclude": [ + "src/**/*.test.ts" ] } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index ec66674..d7730c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ "module": "commonjs" }, "include": [ - "src/**/*.ts", - "test/**/*.ts" + "src/**/*.ts" ] } \ No newline at end of file