@yaohaixiao/renames.js 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.editorconfig +9 -0
  2. package/.husky/commit-msg +4 -0
  3. package/.husky/pre-commit +3 -0
  4. package/.prettierignore +28 -0
  5. package/.prettierrc.js +41 -0
  6. package/LICENSE +21 -0
  7. package/README.md +239 -0
  8. package/bin/renames.js +415 -0
  9. package/commitlint.config.js +42 -0
  10. package/config/default.config.json +20 -0
  11. package/eslint.config.js +196 -0
  12. package/index.js +14 -0
  13. package/jest.config.js +51 -0
  14. package/jsconfig.json +9 -0
  15. package/lib/batch-rename.js +190 -0
  16. package/lib/create-config.js +265 -0
  17. package/lib/generate-filename.js +182 -0
  18. package/lib/read-list.js +87 -0
  19. package/lib/utils/get-basename.js +18 -0
  20. package/lib/utils/get-extension.js +20 -0
  21. package/lib/utils/is-empty-object.js +23 -0
  22. package/lib/utils/is-file-exists.js +26 -0
  23. package/lib/utils/is-function.js +29 -0
  24. package/lib/utils/pad-start.js +31 -0
  25. package/lib/utils/pad-zero.js +24 -0
  26. package/lib/utils/read-dir.js +73 -0
  27. package/lib/utils/read-file.js +56 -0
  28. package/lib/utils/remove-file.js +60 -0
  29. package/lib/utils/rename.js +74 -0
  30. package/lib/utils/replace-index-chapter.js +53 -0
  31. package/lib/utils/show-warning-log.js +20 -0
  32. package/lib/utils/sort-files.js +157 -0
  33. package/lib/utils/strip-non-digit.js +16 -0
  34. package/lib/utils/terminal-link.js +16 -0
  35. package/lib/utils/to-index-chapter.js +19 -0
  36. package/lib/utils/write-file.js +35 -0
  37. package/package.json +114 -0
  38. package/tests/batch-rename.spec.js +123 -0
  39. package/tests/get-basename.spec.js +23 -0
  40. package/tests/get-extension.spec.js +24 -0
  41. package/tests/is-empty-object.spec.js +73 -0
  42. package/tests/is-file-exsits.spec.js +17 -0
  43. package/tests/is-function.spec.js +63 -0
  44. package/tests/pad-start.spec.js +27 -0
  45. package/tests/pad-zero.spec.js +15 -0
  46. package/tests/read-dir.spec.js +42 -0
  47. package/tests/read-file.spec.js +52 -0
  48. package/tests/read-list.spec.js +91 -0
  49. package/tests/rename.spec.js +50 -0
  50. package/tests/replace-index-chapter.spec.js +40 -0
  51. package/tests/sort-files.spec.js +221 -0
  52. package/tests/strip-non-digit.spec.js +15 -0
  53. package/tests/to-index-chapter.spec.js +31 -0
  54. package/tests/write-file.spec.js +34 -0
@@ -0,0 +1,157 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import isFunction from './is-function.js';
5
+ import getBasename from './get-basename.js';
6
+ import getExtension from './get-extension.js';
7
+
8
+ // 辅助函数:自定义的拼接完整路径
9
+ const resolve = (filename, basePath) =>
10
+ basePath ? path.resolve(basePath, filename) : filename;
11
+
12
+ const { lstatSync } = fs;
13
+
14
+ const sortByBirthtime = (files, order = 'asc', dirPath = '') =>
15
+ files.toSorted((prev, next) => {
16
+ const prevStats = lstatSync(resolve(prev, dirPath));
17
+ const nextStats = lstatSync(resolve(next, dirPath));
18
+
19
+ const prevTime = prevStats.birthtime.getTime();
20
+ const nextTime = nextStats.birthtime.getTime();
21
+
22
+ const isDesc = order === 'desc';
23
+
24
+ return isDesc ? nextTime - prevTime : prevTime - nextTime;
25
+ });
26
+
27
+ const sortByModifyTime = (files, order = 'asc', dirPath = '') =>
28
+ files.toSorted((prev, next) => {
29
+ const prevStats = lstatSync(resolve(prev, dirPath));
30
+ const nextStats = lstatSync(resolve(next, dirPath));
31
+
32
+ // 转换为时间戳(毫秒数)方便比较
33
+ const prevTime = prevStats.mtime.getTime();
34
+ const nextTime = nextStats.mtime.getTime();
35
+
36
+ const isDesc = order === 'desc';
37
+
38
+ return isDesc ? nextTime - prevTime : prevTime - nextTime;
39
+ });
40
+
41
+ const sortByName = (files, order = 'asc', sensitivity = 'base') =>
42
+ files.toSorted((prev, next) => {
43
+ // 获取基础名中
44
+ const prevBasename = getBasename(prev);
45
+ const nextBasename = getBasename(next);
46
+
47
+ // 获取基础名中的数值(纯数字的文件名)
48
+ const prevIndex = Number(prevBasename);
49
+ const nextIndex = Number(nextBasename);
50
+
51
+ const isAsc = order === 'asc';
52
+
53
+ // 纯数字的文件名,按从小到大的值正序排序
54
+ if (!Number.isNaN(prevIndex) && !Number.isNaN(nextIndex)) {
55
+ return isAsc ? prevIndex - nextIndex : nextIndex - prevIndex;
56
+ }
57
+
58
+ const result = prevBasename.localeCompare(
59
+ nextBasename.toLowerCase(),
60
+ undefined,
61
+ {
62
+ sensitivity,
63
+ },
64
+ );
65
+
66
+ return isAsc ? result : -result;
67
+ });
68
+
69
+ const sortBySize = (files, order = 'asc', dirPath = '') =>
70
+ files.toSorted((prev, next) => {
71
+ const prevStats = lstatSync(resolve(prev, dirPath));
72
+ const nextStats = lstatSync(resolve(next, dirPath));
73
+
74
+ const prevSize = prevStats.size;
75
+ const nextSize = nextStats.size;
76
+
77
+ return order === 'desc' ? nextSize - prevSize : prevSize - nextSize;
78
+ });
79
+
80
+ const sortByType = (files, order = 'asc', sensitivity = 'base') =>
81
+ files.toSorted((prev, next) => {
82
+ // 获取扩展名
83
+ const prevExtname = getExtension(prev).replace('.', '');
84
+ const nextExtname = getExtension(next).replace('.', '');
85
+
86
+ const result = prevExtname
87
+ .toLowerCase()
88
+ .localeCompare(nextExtname.toLowerCase(), undefined, {
89
+ sensitivity,
90
+ });
91
+
92
+ return order === 'asc' ? result : -result;
93
+ });
94
+
95
+ /**
96
+ * # 获取排序后的文件组数数据
97
+ *
98
+ * @function sortFiles
99
+ * @param {Array} files - 需要排序的文件组数数据
100
+ * @param {object | null} [options={}] - 可选,文件排序的配置信息. Default is `{}`
101
+ * @param {Function | string} [options.sortBy='name'] - 可选,用来排序的回调函数. Default is
102
+ * `'name'`
103
+ * @param {string} [options.order='asc'] - 可选,排序方式,desc - 倒序,asc - 升序. Default
104
+ * is `'asc'`
105
+ * @param {string} [options.sensitivity='base'] - 可选,文本敏感类型(可选值:'base',
106
+ * 'accent', 'case', 'variant'). Default is `'base'`
107
+ * @param {string} [options.dirPath=''] - 可选,排序文件所属的文件夹路径. Default is `''`
108
+ * @returns {Array} - 返回排序后的数组数据
109
+ */
110
+ const sortFiles = (files, options = {}) => {
111
+ const normalizedOptions =
112
+ typeof options === 'object' && options !== null ? { ...options } : {};
113
+ const defaultConfig = {
114
+ // 内置默认排序类型
115
+ sortBy: 'name',
116
+ // 默认降序
117
+ order: 'asc',
118
+ // 可选值:'base', 'accent', 'case', 'variant'
119
+ sensitivity: 'base',
120
+ // 默认空文件夹路径
121
+ dirPath: '',
122
+ };
123
+ const finalConfig = {
124
+ ...defaultConfig,
125
+ ...normalizedOptions,
126
+ };
127
+ const { sortBy, order, sensitivity, dirPath } = finalConfig;
128
+
129
+ // 自定义排序
130
+ if (isFunction(sortBy)) {
131
+ return sortBy(files);
132
+ }
133
+
134
+ // 内置排序
135
+ switch (sortBy) {
136
+ case 'birthtime': {
137
+ return sortByBirthtime(files, order, dirPath);
138
+ }
139
+ case 'modify-time': {
140
+ return sortByModifyTime(files, order, dirPath);
141
+ }
142
+ case 'name': {
143
+ return sortByName(files, order, sensitivity);
144
+ }
145
+ case 'size': {
146
+ return sortBySize(files, order, dirPath);
147
+ }
148
+ case 'type': {
149
+ return sortByType(files, order, sensitivity);
150
+ }
151
+ default: {
152
+ return files;
153
+ }
154
+ }
155
+ };
156
+
157
+ export default sortFiles;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * # 移除文本中所有非数值的文本
3
+ *
4
+ * @function stripNonDigit
5
+ * @param {string} str - 文件名的文本字符串
6
+ * @returns {string} - 返回移除非数值的字符串
7
+ */
8
+ const stripNonDigit = (str) => {
9
+ if (typeof str !== 'string') {
10
+ return '';
11
+ }
12
+
13
+ return str.replaceAll(/\D/g, '');
14
+ };
15
+
16
+ export default stripNonDigit;
@@ -0,0 +1,16 @@
1
+ import chalk from 'chalk';
2
+
3
+ /**
4
+ * # 创建命令行的链接文本
5
+ *
6
+ * @function terminalLink
7
+ * @param {string} text - 必须,链接的文本
8
+ * @param {string} url - 必须,链接的 URL 地址
9
+ * @returns {string} - 返回链接文本
10
+ */
11
+ const terminalLink = (text, url) =>
12
+ chalk.blueBright.underline(
13
+ `\u001B]8;;${url}\u001B\\${text}\u001B]8;;\u001B\\`,
14
+ );
15
+
16
+ export default terminalLink;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * # 生成索引章节名,返回格式:‘第01集:’
3
+ *
4
+ * @function toIndexChapter
5
+ * @param {string | number} index - 索引值字符串
6
+ * @param {boolean} [onlyIndex=false] - 可选,是否仅显示索引. Default is `false`
7
+ * @param {object} [options={}] - 可选,配置信息. Default is `{}`
8
+ * @param {string} [options.prefix='第'] - 可选,前缀文本. Default is `'第'`
9
+ * @param {string} [options.suffix='集'] - 可选,后缀文本. Default is `'集'`
10
+ * @param {string} [options.delimiter=':'] - 可选,分隔符. Default is `':'`
11
+ * @returns {string} - 返回索引章节名的字符串
12
+ */
13
+ const toIndexChapter = (index, onlyIndex = false, options = {}) => {
14
+ const { prefix = '第', suffix = '集', delimiter = ':' } = options;
15
+
16
+ return onlyIndex ? `${index}` : `${prefix}${index}${suffix}${delimiter}`;
17
+ };
18
+
19
+ export default toIndexChapter;
@@ -0,0 +1,35 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import isFileExists from './is-file-exists.js';
5
+ import isFunction from './is-function.js';
6
+
7
+ /**
8
+ * # 向文件中(同步)写入数据
9
+ *
10
+ * @function writeFile
11
+ * @param {string} filePath - 文件路径
12
+ * @param {string} content - 文本内容
13
+ * @param {Function} [afterWrite=null] - 可选,写入数据完成后的回调函数. Default is `null`
14
+ * @returns {void}
15
+ */
16
+ const writeFile = (filePath, content, afterWrite = null) => {
17
+ const { resolve, dirname } = path;
18
+ // 提取文件的上级目录路径
19
+ const dirPath = dirname(filePath);
20
+
21
+ // 递归创建上级目录(不存在则创建,存在则不报错)
22
+ if (!isFileExists(dirPath)) {
23
+ fs.mkdirSync(dirPath, { recursive: true });
24
+ }
25
+
26
+ fs.writeFileSync(resolve(filePath), content, {
27
+ encoding: 'utf8',
28
+ });
29
+
30
+ if (isFunction(afterWrite)) {
31
+ afterWrite(content);
32
+ }
33
+ };
34
+
35
+ export default writeFile;
package/package.json ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "name": "@yaohaixiao/renames.js",
3
+ "version": "0.0.1",
4
+ "description": "renames.js - 基于 Node 的批量文件名重命名 cli 工具库。",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "renames": "bin/renames.js"
8
+ },
9
+ "scripts": {
10
+ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
11
+ "format": "npm run format:es && npm run format:prettier",
12
+ "format:es": "eslint . --fix",
13
+ "format:prettier": "npm run format:prettier:raw -- --write",
14
+ "format:prettier:raw": "prettier \"**/*.{{m,c,}{j,t}s{x,},md{x,},json,y{a,}ml}\" --ignore-path .gitignore",
15
+ "lint": "npm run lint:es && npm run lint:prettier",
16
+ "lint:es": "eslint .",
17
+ "lint:prettier": "npm run format:prettier:raw -- --check",
18
+ "test": "npm run format && npm run test:unit",
19
+ "test:unit": "jest --config jest.config.js",
20
+ "prepare": "husky install"
21
+ },
22
+ "keywords": [
23
+ "javascript",
24
+ "node",
25
+ "cli",
26
+ "rename",
27
+ "renames.js"
28
+ ],
29
+ "author": "乘风巨浪",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/yaohaixiao/renames.js.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/yaohaixiao/renames.js/issues"
37
+ },
38
+ "homepage": "https://github.com/yaohaixiao/renames.js#readme",
39
+ "dependencies": {
40
+ "@inquirer/prompts": "^8.1.0",
41
+ "chalk": "^5.6.2",
42
+ "cli-highlight": "^2.1.11",
43
+ "commander": "^14.0.2",
44
+ "prettier": "^3.7.4"
45
+ },
46
+ "devDependencies": {
47
+ "@babel/core": "^7.28.5",
48
+ "@babel/preset-env": "^7.28.5",
49
+ "@commitlint/cli": "^20.3.1",
50
+ "@commitlint/config-conventional": "^20.3.1",
51
+ "@eslint/compat": "^2.0.1",
52
+ "@eslint/js": "^9.39.2",
53
+ "@imgix/js-core": "^3.8.0",
54
+ "babel-jest": "^30.2.0",
55
+ "babel-plugin-transform-import-meta": "^2.3.3",
56
+ "commitlint": "^20.3.1",
57
+ "conventional-changelog-cli": "^5.0.0",
58
+ "eslint": "^9.39.2",
59
+ "eslint-config-prettier": "^10.1.8",
60
+ "eslint-import-resolver-jest": "^3.0.2",
61
+ "eslint-plugin-jest": "^29.12.1",
62
+ "eslint-plugin-jsdoc": "^62.0.0",
63
+ "eslint-plugin-n": "^17.23.1",
64
+ "eslint-plugin-prettier": "^5.5.4",
65
+ "eslint-plugin-unicorn": "^62.0.0",
66
+ "globals": "^17.0.0",
67
+ "husky": "^9.1.7",
68
+ "jest": "^30.2.0",
69
+ "jest-html-reporter": "^4.3.0",
70
+ "prettier-plugin-jsdoc": "^1.8.0"
71
+ },
72
+ "type": "module",
73
+ "engines": {
74
+ "node": ">=20.18.1"
75
+ },
76
+ "babel": {
77
+ "presets": [
78
+ [
79
+ "@babel/preset-env",
80
+ {
81
+ "targets": {
82
+ "node": "current"
83
+ }
84
+ }
85
+ ]
86
+ ],
87
+ "plugins": [
88
+ [
89
+ "babel-plugin-transform-import-meta",
90
+ {
91
+ "module": "ES6"
92
+ }
93
+ ]
94
+ ]
95
+ },
96
+ "prettier": {
97
+ "plugins": [
98
+ "./node_modules/prettier-plugin-jsdoc/dist/index.js"
99
+ ],
100
+ "proseWrap": "always",
101
+ "singleQuote": true,
102
+ "tabWidth": 2
103
+ },
104
+ "gitHooks": {
105
+ "pre-commit": "lint-staged",
106
+ "commit-msg": "commitlint"
107
+ },
108
+ "lint-staged": {
109
+ "utils/*.js": [
110
+ "npm run format:es",
111
+ "npm run format:prettier"
112
+ ]
113
+ }
114
+ }
@@ -0,0 +1,123 @@
1
+ import path from 'node:path';
2
+
3
+ import batchRename from '@/lib/batch-rename.js';
4
+ import isFileExists from '@/lib/utils/is-file-exists.js';
5
+ import removeFile from '@/lib/utils/remove-file.js';
6
+ import writeFile from '@/lib/utils/write-file.js';
7
+
8
+ const { resolve } = path;
9
+ const { stringify } = JSON;
10
+
11
+ const DEFAULT_CONFIG = {
12
+ namesList: '',
13
+ prefix: '',
14
+ suffix: '',
15
+ connector: '',
16
+ autoIndex: false,
17
+ startIndex: 0,
18
+ indexPadZero: true,
19
+ indexPrefix: '第',
20
+ indexSuffix: '集',
21
+ delimiter: ':',
22
+ ignoreQuantityDiscrepancies: false,
23
+ force: false,
24
+ extname: '',
25
+ sortBy: 'name',
26
+ order: 'desc',
27
+ };
28
+
29
+ const CONTENTS = [
30
+ '神秘的龙珠出现!悟空变成了小孩',
31
+ '我才是主角!小芳踏上宇宙飞行的旅程',
32
+ '超级抢钱!商人的行星伊美加',
33
+ '悟空成了通缉犯',
34
+ '快看那个强者!保镖莱德奇',
35
+ '好痛啊!当牙医的悟空',
36
+ ];
37
+
38
+ const NOT_EXISTS_PATH = './not-exists.json';
39
+ const CURRENT_PATH = process.cwd();
40
+ const TEMPLATE_DIR = './tests/batch';
41
+ const ABSOLUTE_PATH = resolve(CURRENT_PATH, TEMPLATE_DIR);
42
+
43
+ describe('batchRename() 方法:', () => {
44
+ beforeEach(() => {
45
+ for (const content of CONTENTS) {
46
+ writeFile(
47
+ resolve(CURRENT_PATH, `${TEMPLATE_DIR}/${content}.txt`),
48
+ content,
49
+ );
50
+ }
51
+ });
52
+
53
+ afterEach(() => {
54
+ // 删除临时文件,确保单测代码干净
55
+ removeFile(resolve(CURRENT_PATH, TEMPLATE_DIR));
56
+ });
57
+
58
+ it(`batchRename('${NOT_EXISTS_PATH}'), 返回:false`, () => {
59
+ expect(batchRename(NOT_EXISTS_PATH)).toBe(false);
60
+ });
61
+
62
+ it(`batchRename('${ABSOLUTE_PATH}', ${stringify(DEFAULT_CONFIG)}), 返回:true,检测文件'${TEMPLATE_DIR}/${CONTENTS[0]}.txt是否存在,返回:true'`, () => {
63
+ const result = batchRename(ABSOLUTE_PATH, DEFAULT_CONFIG);
64
+ expect(result).toBe(true);
65
+
66
+ const TEMP_FILE = `${TEMPLATE_DIR}/${CONTENTS[0]}.txt`;
67
+ expect(isFileExists(resolve(CURRENT_PATH, TEMP_FILE))).toBe(true);
68
+ });
69
+
70
+ const AUTO_CONFIG = {
71
+ ...DEFAULT_CONFIG,
72
+ autoIndex: true,
73
+ namesList: [
74
+ '龙珠-神秘的龙珠出现!悟空变成了小孩',
75
+ '龙珠-我才是主角!小芳踏上宇宙飞行的旅程',
76
+ '龙珠-超级抢钱!商人的行星伊美加',
77
+ '龙珠-悟空成了通缉犯',
78
+ '龙珠-快看那个强者!保镖莱德奇',
79
+ '龙珠-好痛啊!当牙医的悟空',
80
+ ],
81
+ };
82
+
83
+ it(`batchRename('${ABSOLUTE_PATH}', ${stringify(AUTO_CONFIG)}), 返回:true,并检测重命名后的"第1集:龙珠-神秘的龙珠出现!悟空变成了小孩.txt"是否存在,返回:true`, () => {
84
+ const result = batchRename(ABSOLUTE_PATH, AUTO_CONFIG);
85
+ expect(result).toBe(true);
86
+
87
+ const TEMP_FILE = `${TEMPLATE_DIR}/第1集:龙珠-神秘的龙珠出现!悟空变成了小孩.txt`;
88
+ expect(isFileExists(resolve(CURRENT_PATH, TEMP_FILE))).toBe(true);
89
+ });
90
+
91
+ const AUTO_DIFF_NAMES_CONFIG = {
92
+ ...DEFAULT_CONFIG,
93
+ autoIndex: true,
94
+ namesList: [
95
+ '龙珠-我才是主角!小芳踏上宇宙飞行的旅程',
96
+ '龙珠-超级抢钱!商人的行星伊美加',
97
+ '龙珠-悟空成了通缉犯',
98
+ '龙珠-快看那个强者!保镖莱德奇',
99
+ '龙珠-好痛啊!当牙医的悟空',
100
+ ],
101
+ };
102
+
103
+ it(`batchRename('${ABSOLUTE_PATH}', ${stringify(AUTO_DIFF_NAMES_CONFIG)}), 返回:true,并检测重命名后的"第6集:超级抢钱!商人的行星伊美加.txt"是否存在,返回:true`, () => {
104
+ const result = batchRename(ABSOLUTE_PATH, AUTO_DIFF_NAMES_CONFIG);
105
+ expect(result).toBe(true);
106
+
107
+ const TEMP_FILE = `${TEMPLATE_DIR}/第6集:超级抢钱!商人的行星伊美加.txt`;
108
+ expect(isFileExists(resolve(CURRENT_PATH, TEMP_FILE))).toBe(true);
109
+ });
110
+
111
+ const AUTO_NAMES_CONFIG = {
112
+ ...DEFAULT_CONFIG,
113
+ autoIndex: true,
114
+ };
115
+
116
+ it(`batchRename('${ABSOLUTE_PATH}', ${stringify(AUTO_NAMES_CONFIG)}), 返回:true,并检测重命名后的"第1集:悟空成了通缉犯.txt"是否存在,返回:true`, () => {
117
+ const result = batchRename(ABSOLUTE_PATH, AUTO_NAMES_CONFIG);
118
+ expect(result).toBe(true);
119
+
120
+ const TEMP_FILE = `${TEMPLATE_DIR}/第1集:悟空成了通缉犯.txt`;
121
+ expect(isFileExists(resolve(CURRENT_PATH, TEMP_FILE))).toBe(true);
122
+ });
123
+ });
@@ -0,0 +1,23 @@
1
+ import getBasename from '@/lib/utils/get-basename.js';
2
+
3
+ const DEFAULT_CONFIG_PATH = './config/default.config.json';
4
+ const GIT_IGNORE_PATH = '.gitignore';
5
+ const ESLINT_CONFIG_PATH = './eslint.config.js';
6
+
7
+ describe('getBasename() 方法:', () => {
8
+ it(`getBasename('${DEFAULT_CONFIG_PATH}'), 返回:'default.config'`, () => {
9
+ expect(getBasename(DEFAULT_CONFIG_PATH)).toEqual('default.config');
10
+ });
11
+
12
+ it(`getBasename('${ESLINT_CONFIG_PATH}'), 返回:'eslint.config'`, () => {
13
+ expect(getBasename(ESLINT_CONFIG_PATH)).toEqual('eslint.config');
14
+ });
15
+
16
+ it(`getBasename('${GIT_IGNORE_PATH}'), 返回:''`, () => {
17
+ expect(getBasename(GIT_IGNORE_PATH)).toEqual('');
18
+ });
19
+
20
+ it(`getBasename('Express in Action'), 返回:''`, () => {
21
+ expect(getBasename('Express in Action')).toEqual('');
22
+ });
23
+ });
@@ -0,0 +1,24 @@
1
+ import getExtension from '@/lib/utils/get-extension.js';
2
+
3
+ const DEFAULT_CONFIG_PATH = './config/default.config.json';
4
+ const GIT_IGNORE_PATH = '.gitignore';
5
+ const ESLINT_CONFIG_PATH = './eslint.config.js';
6
+ const CHAPTER_NAME = '超级抢钱!商人的行星伊美加';
7
+
8
+ describe('getExtension() 方法:', () => {
9
+ it(`getExtension('${DEFAULT_CONFIG_PATH}'), 返回:'.json'`, () => {
10
+ expect(getExtension(DEFAULT_CONFIG_PATH)).toEqual('.json');
11
+ });
12
+
13
+ it(`getExtension('${GIT_IGNORE_PATH}'), 返回:'.gitignore'`, () => {
14
+ expect(getExtension(`${GIT_IGNORE_PATH}`)).toEqual('.gitignore');
15
+ });
16
+
17
+ it(`getExtension('${ESLINT_CONFIG_PATH}'), 返回:'.js'`, () => {
18
+ expect(getExtension(ESLINT_CONFIG_PATH)).toEqual('.js');
19
+ });
20
+
21
+ it(`getExtension('${CHAPTER_NAME}'), 返回:'${CHAPTER_NAME}'`, () => {
22
+ expect(getExtension(CHAPTER_NAME)).toEqual(`${CHAPTER_NAME}`);
23
+ });
24
+ });
@@ -0,0 +1,73 @@
1
+ import isEmptyObject from '@/lib/utils/is-empty-object.js';
2
+
3
+ function Person(name) {
4
+ this.name = name;
5
+ }
6
+
7
+ describe('isEmptyObject() 方法:', () => {
8
+ it(`isEmptyObject({}), 返回:true`, () => {
9
+ expect(isEmptyObject({})).toBe(true);
10
+ });
11
+
12
+ it(`isEmptyObject([]), 返回:true`, () => {
13
+ expect(isEmptyObject([])).toBe(true);
14
+ });
15
+
16
+ it(`isEmptyObject(new Object()), 返回:true`, () => {
17
+ const o = new Object();
18
+ expect(isEmptyObject(o)).toBe(true);
19
+ });
20
+
21
+ it(`isEmptyObject(new Array()), 返回:true`, () => {
22
+ const o = new Array();
23
+ expect(isEmptyObject(o)).toBe(true);
24
+ });
25
+
26
+ it(`isEmptyObject(new RegExp('')), 返回:true`, () => {
27
+ const o = new RegExp('');
28
+ expect(isEmptyObject(o)).toBe(true);
29
+ });
30
+
31
+ it(`isEmptyObject(new Date('2023-06-29')), 返回:true`, () => {
32
+ const o = new Date('2023-06-29');
33
+ expect(isEmptyObject(o)).toBe(true);
34
+ });
35
+
36
+ it(`isEmptyObject(new RegExp('s')), 返回:true`, () => {
37
+ const o = new RegExp('s');
38
+ expect(isEmptyObject(o)).toBe(true);
39
+ });
40
+
41
+ it(`isEmptyObject(null), 返回:false`, () => {
42
+ expect(isEmptyObject(null)).toBe(false);
43
+ });
44
+
45
+ it(`isEmptyObject(['']), 返回:false`, () => {
46
+ expect(isEmptyObject([''])).toBe(false);
47
+ });
48
+
49
+ it(`isEmptyObject(new Function()), 返回:false`, () => {
50
+ const o = new Function();
51
+ expect(isEmptyObject(o)).toBe(false);
52
+ });
53
+
54
+ it(`isEmptyObject({ name: 'types.js' }), 返回:false`, () => {
55
+ const o = { name: 'types.js' };
56
+ expect(isEmptyObject(o)).toBe(false);
57
+ });
58
+
59
+ it(`isEmptyObject(new Person('Robert')), 返回:false`, () => {
60
+ const o = new Person('Robert');
61
+ Object.prototype.hasOwnProperty = null;
62
+ expect(isEmptyObject(o)).toBe(false);
63
+ });
64
+
65
+ it(`isEmptyObject(Person), 返回:false`, () => {
66
+ expect(isEmptyObject(Person)).toBe(false);
67
+ });
68
+
69
+ it(`isEmptyObject(String()), 返回:false`, () => {
70
+ const o = String();
71
+ expect(isEmptyObject(o)).toBe(false);
72
+ });
73
+ });
@@ -0,0 +1,17 @@
1
+ import isFileExists from '@/lib/utils/is-file-exists.js';
2
+
3
+ const DEFAULT_CONFIG_PATH = './config/default.config.json';
4
+
5
+ describe('isFileExists() 方法:', () => {
6
+ it(`isFileExists('${DEFAULT_CONFIG_PATH}'), 返回:true`, () => {
7
+ expect(isFileExists(DEFAULT_CONFIG_PATH)).toBe(true);
8
+ });
9
+
10
+ it(`isFileExists('${DEFAULT_CONFIG_PATH},${process.cwd()}'), 返回:true`, () => {
11
+ expect(isFileExists(DEFAULT_CONFIG_PATH, process.cwd())).toBe(true);
12
+ });
13
+
14
+ it(`isFileExists(), 返回:false`, () => {
15
+ expect(isFileExists()).toBe(false);
16
+ });
17
+ });
@@ -0,0 +1,63 @@
1
+ import isFunction from '@/lib/utils/is-function.js';
2
+
3
+ function emptyFunction() {}
4
+ const emptyArrowFunction = () => {};
5
+
6
+ describe('isFunction() 方法:', () => {
7
+ it('isFunction(function emptyFunction(){}),返回:true', () => {
8
+ expect(isFunction(emptyFunction)).toBe(true);
9
+ });
10
+
11
+ it('isFunction(() => {}),返回:true', () => {
12
+ expect(isFunction(emptyArrowFunction)).toBe(true);
13
+ });
14
+
15
+ it('isFunction(new Function()),返回:true', () => {
16
+ const emptyInstance = new Function();
17
+ expect(isFunction(emptyInstance)).toBe(true);
18
+ });
19
+
20
+ it('isFunction(parseInt),返回:true', () => {
21
+ expect(isFunction(Number.parseInt)).toBe(true);
22
+ });
23
+
24
+ it('isFunction(Array),返回:true', () => {
25
+ expect(isFunction(Array)).toBe(true);
26
+ });
27
+
28
+ it('isFunction(Boolean),返回:true', () => {
29
+ expect(isFunction(Boolean)).toBe(true);
30
+ });
31
+
32
+ it('isFunction(Date),返回:true', () => {
33
+ expect(isFunction(Date)).toBe(true);
34
+ });
35
+
36
+ it('isFunction(Number),返回:true', () => {
37
+ expect(isFunction(Number)).toBe(true);
38
+ });
39
+
40
+ it('isFunction(Object),返回:true', () => {
41
+ expect(isFunction(Object)).toBe(true);
42
+ });
43
+
44
+ it('isFunction(RegExp),返回:true', () => {
45
+ expect(isFunction(RegExp)).toBe(true);
46
+ });
47
+
48
+ it('isFunction(String),返回:true', () => {
49
+ expect(isFunction(String)).toBe(true);
50
+ });
51
+
52
+ it('isFunction(Math),返回:false', () => {
53
+ expect(isFunction(Math)).toBe(false);
54
+ });
55
+
56
+ it('isFunction(console),返回:false', () => {
57
+ expect(isFunction(console)).toBe(false);
58
+ });
59
+
60
+ it('isFunction(null),返回:false', () => {
61
+ expect(isFunction(null)).toBe(false);
62
+ });
63
+ });