Skip to content

Commit

Permalink
feat: 添加一些工具
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed May 27, 2020
1 parent e7d5d0a commit 8e9917a
Show file tree
Hide file tree
Showing 13 changed files with 3,509 additions and 777 deletions.
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/** @type import('haoma').JestConfig */
module.exports = require('haoma').getJestConfig()
module.exports = require('haoma').getJestConfig({
transformPackages: ['lodash-es'],
})
35 changes: 26 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
"author": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"generate-index": "vgis ./src/index.ts",
"generate-util": "ts-node -T -P ./tsconfig.scripts.json ./scripts/generateUtil.ts",
"test": "jest",
"test-coverage": "jest --coverage",
"test-coverage-open": "jest --coverage && open-cli ./coverage/lcov-report/index.html",
"test-update-snapshot": "jest --updateSnapshot"
},
"husky": {
"hooks": {
Expand All @@ -30,14 +35,26 @@
"prettier --write"
]
},
"dependencies": {
"@types/lodash-es": "^4.17.3",
"lodash-es": "^4.17.15"
},
"devDependencies": {
"codecov": "^3",
"eslint": "^6",
"haoma": "^1.13.0",
"husky": "^4",
"jest": "^25",
"lint-staged": "^10",
"prettier": "^2",
"typescript": "^3"
"@types/fs-extra": "^9.0.1",
"@types/prompts": "^2.0.8",
"codecov": "^3.7.0",
"eslint": "^6.8.0",
"execa": "^4.0.2",
"fs-extra": "^9.0.0",
"haoma": "^2.0.0-beta.1",
"husky": "^4.2.5",
"jest": "^26.0.1",
"lint-staged": "^10.2.6",
"open-cli": "^6.0.1",
"prettier": "^2.0.5",
"prompts": "^2.3.2",
"ts-node": "^8.10.1",
"typescript": "^3.9.3",
"vscode-generate-index-standalone": "^1.3.0"
}
}
3,881 changes: 3,117 additions & 764 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions scripts/generateUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import exec from 'execa'
import fs from 'fs-extra'
import prompts from 'prompts'
import { join } from 'path'

async function main(rootDir: string) {
const { name: util } = await prompts([
{
name: 'name',
type: 'text',
message: '请输入要创建的 util 名称',
},
])

const srcDir = join(rootDir, './src')
const utilsDir = join(srcDir, './utils')
const utilFile = join(utilsDir, `${util}.ts`)
const utilTestFile = join(utilsDir, `${util}.test.ts`)

const isClass = /^[A-Z]/.test(util)

console.log('开始写入文件...')

await Promise.all([
fs.writeFile(
utilFile,
isClass
? `
/**
* ${util}
*/
export class ${util} {
}
`
: `
/**
* ${util}
*
* @param value 值
* @returns 返回结果
*/
export function ${util}(value: any): number {
const res = 1
return res
}
`,
),
fs.writeFile(
utilTestFile,
`
import { ${util} } from './${util}'
describe(${util}.name, () => {
test('ok', () => {
expect(1).toBe(1)
})
})
`,
),
])

console.log('✔️ 写入文件成功')

await exec('npm', ['run', 'generate-index'], {
cwd: rootDir,
stdio: 'inherit',
})
}

main(join(__dirname, '..'))
10 changes: 10 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as lodash from 'lodash-es'
import * as vtils from './index'

describe('index', () => {
test('应该导出 lodash-es', () => {
expect([...Object.keys(vtils), 'default']).toIncludeAllMembers(
Object.keys(lodash),
)
})
})
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from 'lodash-es'

// @index(['./**/*.ts', '!./**/*.test.*'], f => `export * from '${f.path}'`)
export * from './utils/EventBus'
export * from './utils/isPossibleChineseMobilePhoneNumber'
export * from './utils/isUrl'
// @endindex
90 changes: 90 additions & 0 deletions src/utils/EventBus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { EventBus } from './EventBus'

type Events = {
enter: () => number
success: () => any
error: (payload: { message: string }) => any
}

describe(EventBus.name, () => {
test('可订阅发布', () => {
const bus = new EventBus<Events>()
const enterCallback = jest.fn()
bus.on('enter', enterCallback)
bus.emit('enter')
expect(enterCallback).toBeCalled().toBeCalledTimes(1)
})

test('可取消订阅', () => {
const bus = new EventBus<Events>()
const enterCallback = jest.fn()
const enterCallback2 = jest.fn()
const enterCallback3 = jest.fn()
bus.on('enter', enterCallback)
const offCallback2 = bus.on('enter', enterCallback2)
bus.on('enter', enterCallback3)
bus.emit('enter')
expect(enterCallback).toBeCalled().toBeCalledTimes(1)
expect(enterCallback2).toBeCalled().toBeCalledTimes(1)
expect(enterCallback3).toBeCalled().toBeCalledTimes(1)
offCallback2()
bus.emit('enter')
expect(enterCallback).toBeCalled().toBeCalledTimes(2)
expect(enterCallback2).toBeCalled().toBeCalledTimes(1)
expect(enterCallback3).toBeCalled().toBeCalledTimes(2)
bus.off('enter', enterCallback3)
bus.emit('enter')
expect(enterCallback).toBeCalled().toBeCalledTimes(3)
expect(enterCallback2).toBeCalled().toBeCalledTimes(1)
expect(enterCallback3).toBeCalled().toBeCalledTimes(2)
bus.off('enter')
bus.emit('enter')
expect(enterCallback).toBeCalled().toBeCalledTimes(3)
expect(enterCallback2).toBeCalled().toBeCalledTimes(1)
expect(enterCallback3).toBeCalled().toBeCalledTimes(2)
})

test('可订阅发布多次', () => {
const bus = new EventBus<Events>()
const enterCallback = jest.fn()
const enterCallback2 = jest.fn()
bus.on('enter', enterCallback)
bus.on('enter', enterCallback2)
bus.emit('enter')
bus.emit('enter')
bus.emit('enter')
expect(enterCallback).toBeCalled().toBeCalledTimes(3)
expect(enterCallback2).toBeCalled().toBeCalledTimes(3)
})

test('可传递参数', () => {
const bus = new EventBus<Events>()
const errorCallback = jest.fn()
bus.on('error', errorCallback)
bus.emit('error', { message: 'unexpected error' })
expect(errorCallback).toBeCalled().toBeCalledTimes(1).toBeCalledWith({
message: 'unexpected error',
})
})

test('可只订阅一次', () => {
const bus = new EventBus<Events>()
const successCallback = jest.fn()
bus.once('success', successCallback)
bus.emit('success')
bus.emit('success')
bus.emit('success')
bus.emit('success')
expect(successCallback).toBeCalled().toBeCalledTimes(1)
})

test('可获取订阅回调结果', () => {
const bus = new EventBus<Events>()
const enterCallback = jest.fn().mockImplementation(() => 1)
const enterCallback2 = jest.fn().mockImplementation(() => 2)
bus.on('enter', enterCallback)
bus.on('enter', enterCallback2)
const results = bus.emit('enter')
expect(results).toEqual([1, 2])
})
})
97 changes: 97 additions & 0 deletions src/utils/EventBus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* 事件巴士,管理事件的发布与订阅。
*
* ```
* const bus = new EventBus<{
* success: (payload: { message: string }) => any
* }>()
* bus.on('success', ({ message }) => console.log(message))
* bus.emit('success', { message: '提交成功' })
* // => 控制台输出: 提交成功
* ```
*
* @template TEvents 事件名称及其对应的回调描述
*/
export class EventBus<TEvents extends Record<string, (...args: any[]) => any>> {
/**
* 回调列表。
*/
#callbacks: {
[Key in keyof TEvents]: Array<TEvents[Key]>
} = Object.create(null)

/**
* 订阅事件。
*
* @param eventName 事件名称
* @param callback 事件触发回调
* @returns 返回取消订阅的函数
*/
on<TName extends keyof TEvents>(
eventName: TName,
callback: TEvents[TName],
): () => any {
if (!this.#callbacks[eventName]) {
this.#callbacks[eventName] = []
}
const index = this.#callbacks[eventName].indexOf(callback)
if (index === -1) {
this.#callbacks[eventName].push(callback)
}
return () => this.off(eventName, callback)
}

/**
* 订阅事件,但只订阅一次即取消订阅。
*
* @param eventName 事件名称
* @param callback 事件触发回调
* @returns 返回取消订阅的函数
*/
once<TName extends keyof TEvents>(
eventName: TName,
callback: TEvents[TName],
): () => any {
const off = this.on(eventName, ((...args) => {
off()
callback(...args)
}) as TEvents[TName])
return off
}

/**
* 取消订阅事件,若没有指定回调,则取消所有回调。
*
* @param eventName 事件名称
* @param callback 事件触发回调
*/
off<TName extends keyof TEvents>(
eventName: TName,
callback?: TEvents[TName],
): void {
if (this.#callbacks[eventName] && callback) {
const index = this.#callbacks[eventName].indexOf(callback)
if (index > -1) {
this.#callbacks[eventName].splice(index, 1)
}
} else {
delete this.#callbacks[eventName]
}
}

/**
* 发布事件。
*
* @param eventName 事件名称
* @param args 传给事件回调的参数
* @returns 返回各事件回调的返回结果组成的数组
*/
emit<TName extends keyof TEvents>(
eventName: TName,
...args: Parameters<TEvents[TName]>
): Array<ReturnType<TEvents[TName]>> {
return (this.#callbacks[eventName] || []).map(callback => {
return callback(...args)
})
}
}
30 changes: 30 additions & 0 deletions src/utils/isPossibleChineseMobilePhoneNumber.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { isPossibleChineseMobilePhoneNumber } from './isPossibleChineseMobilePhoneNumber'

describe(isPossibleChineseMobilePhoneNumber.name, () => {
test('不可能是中国的手机号码', () => {
for (const value of [
'',
110,
120,
10086,
'180800300800',
12345678,
87654321,
]) {
expect(isPossibleChineseMobilePhoneNumber(value)).toBeFalse()
}
})

test('可能是中国的手机号码', () => {
for (const value of [
16080030080,
18087030088,
13907199856,
'13591512420',
19913769406,
18512345657,
]) {
expect(isPossibleChineseMobilePhoneNumber(value)).toBeTrue()
}
})
})
14 changes: 14 additions & 0 deletions src/utils/isPossibleChineseMobilePhoneNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 检测传入的值是否可能是中国的手机号码。
*
* ```
* isPossibleChineseMobilePhoneNumber('10086') // => false
* isPossibleChineseMobilePhoneNumber('18087030088') // => true
* ```
*
* @param value 要检测的值
* @returns 返回检测结果
*/
export function isPossibleChineseMobilePhoneNumber(value: string | number) {
return /^1[3-9][0-9]{9}$/.test(String(value))
}
Loading

0 comments on commit 8e9917a

Please sign in to comment.