Skip to content

Commit

Permalink
feat(class-mock): add faker all api to decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
2214962083 committed Apr 29, 2022
1 parent 342a123 commit 083710d
Show file tree
Hide file tree
Showing 10 changed files with 416 additions and 252 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"jsdelivr",
"jsfiddle",
"loglevel",
"mersenne",
"messageoption",
"Metadatas",
"Metas",
Expand Down
25 changes: 25 additions & 0 deletions packages/class-mock/src/constants/faker.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const fakeProps = [
'mersenne',
'random',
'helpers',
'datatype',
'address',
'animal',
'commerce',
'company',
'database',
'date',
'finance',
'git',
'hacker',
'image',
'internet',
'lorem',
'music',
'name',
'phone',
'system',
'time',
'vehicle',
'word'
] as const
49 changes: 49 additions & 0 deletions packages/class-mock/src/decorators/config.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {mergeConfig, getTarget} from '@/utils/common'
import {MetadataStorage} from '@/utils/meta-storage'
import {
MockPropertyDecoratorConfig,
MockPropertyMetadata,
MockPropertyDecorator,
ArrayConfig,
MockPropertyDecoratorProps
} from '@/utils/types-helper'

export function ConfigDecorator<T extends MockPropertyDecoratorConfig = MockPropertyDecoratorConfig>(
config: T = <T>{}
): MockPropertyDecorator<T> {
const createConfigDecorator = (config: T = <T>{}) => {
const decorator = (target: any, propertyKey: string | symbol) => {
const propertyName = propertyKey as string
const _target = getTarget(target)
const preMetadata = MetadataStorage.instance.findMockMetadata(_target, propertyName)
const metadata: MockPropertyMetadata = mergeConfig(preMetadata, {
target: _target,
propertyName,
...config
})

MetadataStorage.instance.addMockMetadata(metadata)
}

const createMergeConfigDecorator = (newConfig: T = <T>{}) => createConfigDecorator(mergeConfig(config, newConfig))

const decoratorProto: MockPropertyDecoratorProps<T> = {
config: (_config: T = <T>{}) => createMergeConfigDecorator(_config),
isPartial: () => createMergeConfigDecorator(<T>{partial: 'partial'}),
isInclude: () => createMergeConfigDecorator(<T>{partial: 'include'}),
isExclude: () => createMergeConfigDecorator(<T>{partial: 'exclude'}),
isAlwaysRandom: () => createMergeConfigDecorator(<T>{alwaysRandom: true}),
isNotAlwaysRandom: () => createMergeConfigDecorator(<T>{alwaysRandom: false}),
isArray: (arrayConfig?: ArrayConfig) => createMergeConfigDecorator(<T>{array: true, ...arrayConfig}),
isNotArray: () => createMergeConfigDecorator(<T>{array: false}),
groups: (groups: string[]) => createMergeConfigDecorator(<T>{groups})
}

Object.assign(decorator, decoratorProto)

return decorator as MockPropertyDecorator<T>
}

return createConfigDecorator(config)
}
64 changes: 46 additions & 18 deletions packages/class-mock/src/decorators/faker.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {MetadataStorage} from './../meta-storage'
import {FakerPropertyDecoratorConfig, FakerPropertyMetadata, Fn, MockPropertyDecorator} from './../utils/types-helper'
import {fakeProps} from '@/constants/faker.constants'
import {Fn, MockPropertyDecorator, MockPropertyDecoratorConfig} from '@/utils/types-helper'
import faker, {Faker} from '@faker-js/faker'
import {MockDecorator} from './mock.decorator'

export function FakerDecorator<T extends Fn>(fakerFn: T, ...fakerParams: Parameters<T>) {
function decorator(target: any, propertyKey: string, config: FakerPropertyDecoratorConfig = {}) {
const metadata: FakerPropertyMetadata<T> = {
target: target instanceof Function ? target : target.constructor,
propertyName: propertyKey,
fakerFn,
fakerParams,
...config
}
export type FakeProp = typeof fakeProps[number]
export type MockFaker = Pick<Faker, FakeProp>
const createFakeProxy = <T extends keyof MockFaker, KeyFns extends keyof MockFaker[T]>(fakeKey: T) => {
const mockFaker = faker as MockFaker

MetadataStorage.instance.addFakerMetadata(metadata)
}
// fix Parameter params only allow function
type MockFnParameters<T> = T extends (...args: infer P) => any ? P : never

decorator.config =
(config: FakerPropertyDecoratorConfig = {}) =>
(target: any, propertyKey: string) =>
decorator(target, propertyKey, config)
// return's type
type MockProxy = {
[key in KeyFns]: (
...params: MockFnParameters<MockFaker[T][key]>
) => MockPropertyDecorator<MockPropertyDecoratorConfig>
}

return decorator as MockPropertyDecorator<FakerPropertyDecoratorConfig>
return new Proxy(faker[fakeKey], {
get(target, targetKey: string) {
const mockFn = mockFaker[fakeKey][targetKey as KeyFns] as unknown as Fn
return (...params: any[]) => MockDecorator(mockFn, ...params)
}
}) as unknown as MockProxy
}

export const Mersenne = createFakeProxy('mersenne')
export const Random = createFakeProxy('random')
export const Helpers = createFakeProxy('helpers')
export const Datatype = createFakeProxy('datatype')
export const Address = createFakeProxy('address')
export const Animal = createFakeProxy('animal')
export const Commerce = createFakeProxy('commerce')
export const Company = createFakeProxy('company')
export const Database = createFakeProxy('database')
export const Date = createFakeProxy('date')
export const Finance = createFakeProxy('finance')
export const Git = createFakeProxy('git')
export const Hacker = createFakeProxy('hacker')
export const Image = createFakeProxy('image')
export const Internet = createFakeProxy('internet')
export const Lorem = createFakeProxy('lorem')
export const Music = createFakeProxy('music')
export const Name = createFakeProxy('name')
export const Phone = createFakeProxy('phone')
export const System = createFakeProxy('system')
export const Time = createFakeProxy('time')
export const Vehicle = createFakeProxy('vehicle')
export const Word = createFakeProxy('word')
13 changes: 13 additions & 0 deletions packages/class-mock/src/decorators/mock.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {MockPropertyMetadata, Fn, MockPropertyDecorator, MockPropertyDecoratorConfig} from '@/utils/types-helper'
import {ConfigDecorator} from './config.decorator'

export function MockDecorator<T extends Fn>(
mockFn: T,
...mockParams: Parameters<T>
): MockPropertyDecorator<MockPropertyDecoratorConfig> {
return ConfigDecorator<MockPropertyDecoratorConfig>({
mockFn,
mockParams
} as MockPropertyMetadata<T>)
}
58 changes: 22 additions & 36 deletions packages/class-mock/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,39 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import 'reflect-metadata'
import faker from '@faker-js/faker'
import {MetadataStorage} from './meta-storage'
import {FakerDecorator} from './decorators/faker.decorator'
import {valueIsFakerMeta} from './utils/common'
import {Random, Name, Phone, Address} from './decorators/faker.decorator'
import {createMock} from './utils/create-mock'

faker.setLocale('zh_CN')

class User {
@FakerDecorator(faker.name.firstName, 'male').config({
partial: true
})
@Name.firstName()
name!: string

@FakerDecorator(faker.random.number, {min: 0, max: 100}).config({
alwaysRandom: true
})
@Random.number({min: 0, max: 100})
age!: number
}

class Student extends User {
@FakerDecorator(faker.random.words, 5).config({
array: true,
length: 3
})
favorites!: string[]
}
@Address.streetAddress().isArray({length: 3})
address!: string[]

function createMock(Entity: any) {
const metas = MetadataStorage.instance.getClassMetadatas(Entity)
const entity = new Entity()
metas.map(meta => {
const {array = false, length, min, max, propertyName} = meta
if (valueIsFakerMeta(meta)) {
const {fakerFn, fakerParams} = meta
let propertyValue: any
if (!array) {
propertyValue = fakerFn(...fakerParams)
} else {
const arrayLength =
(length ?? min ?? max ?? undefined) === undefined
? 10
: length ?? faker.random.number({min: min ?? 0, max: max ?? 100})
propertyValue = Array.from({length: arrayLength}, () => fakerFn(...fakerParams))
}
entity[propertyName] = propertyValue
}
})
return entity
@Phone.phoneNumber('188########')
tel!: number
}

const mockStudent = createMock(Student)

console.log('mockStudent: ', mockStudent)

// 比如提供一个 webpack 插件或者 vite 插件,配置一下,axios 或 fetch 访问该 url 就可以自动响应 mock 数据了
// const serverPluginConfig = [
// {
// url: '/api/users',
// res: () => createMock(Student, {array: true})
// },
// {
// url: '/api/user/:id',
// res: () => createMock(Student, {array: false})
// }
// ]
36 changes: 33 additions & 3 deletions packages/class-mock/src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {FakerPropertyMetadata} from './types-helper'
import {ArrayConfig, BasePropertyConfig, MetadataTarget} from './types-helper'

export function valueIsFakerMeta(value: any): value is FakerPropertyMetadata {
return value && typeof value.fakerFn === 'function'
export function getTarget(target: any): MetadataTarget {
return target instanceof Function ? target : target.constructor
}

export function randomNumber(min: number, max: number): number {
if (min === max) return min
return Math.floor(Math.random() * (max - min + 1)) + min
}

export interface getGenerateArrayLengthOptions extends ArrayConfig {
defaultLength?: number
defaultMax?: number
defaultMin?: number
}

export function getGenerateArrayLength(options: getGenerateArrayLengthOptions): number {
const {length, min, max, defaultLength = 10, defaultMax = 50, defaultMin = 0} = options
const arrayLength =
(length ?? min ?? max ?? undefined) === undefined
? defaultLength
: length ?? randomNumber(min ?? defaultMin, max ?? defaultMax)
return arrayLength
}

export function mergeConfig<Config extends BasePropertyConfig>(
oldMeta: Config | undefined,
newMeta: Config | undefined
) {
return {
...(oldMeta || {}),
...(newMeta || {})
} as Config
}
22 changes: 22 additions & 0 deletions packages/class-mock/src/utils/create-mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {getGenerateArrayLength} from './common'
import {MetadataStorage} from './meta-storage'

export function createMock(Entity: any) {
const metas = MetadataStorage.instance.getClassMetadatas(Entity)
const entity = new Entity()
metas.map(meta => {
const {array = false, length, min, max, propertyName, mockFn, mockParams = []} = meta
if (typeof mockFn === 'function') {
let propertyValue: unknown
if (!array) {
propertyValue = mockFn(...mockParams)
} else {
const arrayLength = getGenerateArrayLength({length, min, max})
propertyValue = Array.from({length: arrayLength}, () => mockFn(...mockParams))
}
entity[propertyName] = propertyValue
}
})
return entity
}
Loading

0 comments on commit 083710d

Please sign in to comment.