Skip to content

Commit 083710d

Browse files
committed
feat(class-mock): add faker all api to decorator
1 parent 342a123 commit 083710d

File tree

10 files changed

+416
-252
lines changed

10 files changed

+416
-252
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
"jsdelivr",
139139
"jsfiddle",
140140
"loglevel",
141+
"mersenne",
141142
"messageoption",
142143
"Metadatas",
143144
"Metas",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const fakeProps = [
2+
'mersenne',
3+
'random',
4+
'helpers',
5+
'datatype',
6+
'address',
7+
'animal',
8+
'commerce',
9+
'company',
10+
'database',
11+
'date',
12+
'finance',
13+
'git',
14+
'hacker',
15+
'image',
16+
'internet',
17+
'lorem',
18+
'music',
19+
'name',
20+
'phone',
21+
'system',
22+
'time',
23+
'vehicle',
24+
'word'
25+
] as const
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {mergeConfig, getTarget} from '@/utils/common'
3+
import {MetadataStorage} from '@/utils/meta-storage'
4+
import {
5+
MockPropertyDecoratorConfig,
6+
MockPropertyMetadata,
7+
MockPropertyDecorator,
8+
ArrayConfig,
9+
MockPropertyDecoratorProps
10+
} from '@/utils/types-helper'
11+
12+
export function ConfigDecorator<T extends MockPropertyDecoratorConfig = MockPropertyDecoratorConfig>(
13+
config: T = <T>{}
14+
): MockPropertyDecorator<T> {
15+
const createConfigDecorator = (config: T = <T>{}) => {
16+
const decorator = (target: any, propertyKey: string | symbol) => {
17+
const propertyName = propertyKey as string
18+
const _target = getTarget(target)
19+
const preMetadata = MetadataStorage.instance.findMockMetadata(_target, propertyName)
20+
const metadata: MockPropertyMetadata = mergeConfig(preMetadata, {
21+
target: _target,
22+
propertyName,
23+
...config
24+
})
25+
26+
MetadataStorage.instance.addMockMetadata(metadata)
27+
}
28+
29+
const createMergeConfigDecorator = (newConfig: T = <T>{}) => createConfigDecorator(mergeConfig(config, newConfig))
30+
31+
const decoratorProto: MockPropertyDecoratorProps<T> = {
32+
config: (_config: T = <T>{}) => createMergeConfigDecorator(_config),
33+
isPartial: () => createMergeConfigDecorator(<T>{partial: 'partial'}),
34+
isInclude: () => createMergeConfigDecorator(<T>{partial: 'include'}),
35+
isExclude: () => createMergeConfigDecorator(<T>{partial: 'exclude'}),
36+
isAlwaysRandom: () => createMergeConfigDecorator(<T>{alwaysRandom: true}),
37+
isNotAlwaysRandom: () => createMergeConfigDecorator(<T>{alwaysRandom: false}),
38+
isArray: (arrayConfig?: ArrayConfig) => createMergeConfigDecorator(<T>{array: true, ...arrayConfig}),
39+
isNotArray: () => createMergeConfigDecorator(<T>{array: false}),
40+
groups: (groups: string[]) => createMergeConfigDecorator(<T>{groups})
41+
}
42+
43+
Object.assign(decorator, decoratorProto)
44+
45+
return decorator as MockPropertyDecorator<T>
46+
}
47+
48+
return createConfigDecorator(config)
49+
}
Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,52 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import {MetadataStorage} from './../meta-storage'
3-
import {FakerPropertyDecoratorConfig, FakerPropertyMetadata, Fn, MockPropertyDecorator} from './../utils/types-helper'
2+
import {fakeProps} from '@/constants/faker.constants'
3+
import {Fn, MockPropertyDecorator, MockPropertyDecoratorConfig} from '@/utils/types-helper'
4+
import faker, {Faker} from '@faker-js/faker'
5+
import {MockDecorator} from './mock.decorator'
46

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

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

18-
decorator.config =
19-
(config: FakerPropertyDecoratorConfig = {}) =>
20-
(target: any, propertyKey: string) =>
21-
decorator(target, propertyKey, config)
15+
// return's type
16+
type MockProxy = {
17+
[key in KeyFns]: (
18+
...params: MockFnParameters<MockFaker[T][key]>
19+
) => MockPropertyDecorator<MockPropertyDecoratorConfig>
20+
}
2221

23-
return decorator as MockPropertyDecorator<FakerPropertyDecoratorConfig>
22+
return new Proxy(faker[fakeKey], {
23+
get(target, targetKey: string) {
24+
const mockFn = mockFaker[fakeKey][targetKey as KeyFns] as unknown as Fn
25+
return (...params: any[]) => MockDecorator(mockFn, ...params)
26+
}
27+
}) as unknown as MockProxy
2428
}
29+
30+
export const Mersenne = createFakeProxy('mersenne')
31+
export const Random = createFakeProxy('random')
32+
export const Helpers = createFakeProxy('helpers')
33+
export const Datatype = createFakeProxy('datatype')
34+
export const Address = createFakeProxy('address')
35+
export const Animal = createFakeProxy('animal')
36+
export const Commerce = createFakeProxy('commerce')
37+
export const Company = createFakeProxy('company')
38+
export const Database = createFakeProxy('database')
39+
export const Date = createFakeProxy('date')
40+
export const Finance = createFakeProxy('finance')
41+
export const Git = createFakeProxy('git')
42+
export const Hacker = createFakeProxy('hacker')
43+
export const Image = createFakeProxy('image')
44+
export const Internet = createFakeProxy('internet')
45+
export const Lorem = createFakeProxy('lorem')
46+
export const Music = createFakeProxy('music')
47+
export const Name = createFakeProxy('name')
48+
export const Phone = createFakeProxy('phone')
49+
export const System = createFakeProxy('system')
50+
export const Time = createFakeProxy('time')
51+
export const Vehicle = createFakeProxy('vehicle')
52+
export const Word = createFakeProxy('word')
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {MockPropertyMetadata, Fn, MockPropertyDecorator, MockPropertyDecoratorConfig} from '@/utils/types-helper'
3+
import {ConfigDecorator} from './config.decorator'
4+
5+
export function MockDecorator<T extends Fn>(
6+
mockFn: T,
7+
...mockParams: Parameters<T>
8+
): MockPropertyDecorator<MockPropertyDecoratorConfig> {
9+
return ConfigDecorator<MockPropertyDecoratorConfig>({
10+
mockFn,
11+
mockParams
12+
} as MockPropertyMetadata<T>)
13+
}

packages/class-mock/src/index.ts

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,39 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import 'reflect-metadata'
33
import faker from '@faker-js/faker'
4-
import {MetadataStorage} from './meta-storage'
5-
import {FakerDecorator} from './decorators/faker.decorator'
6-
import {valueIsFakerMeta} from './utils/common'
4+
import {Random, Name, Phone, Address} from './decorators/faker.decorator'
5+
import {createMock} from './utils/create-mock'
6+
7+
faker.setLocale('zh_CN')
78

89
class User {
9-
@FakerDecorator(faker.name.firstName, 'male').config({
10-
partial: true
11-
})
10+
@Name.firstName()
1211
name!: string
1312

14-
@FakerDecorator(faker.random.number, {min: 0, max: 100}).config({
15-
alwaysRandom: true
16-
})
13+
@Random.number({min: 0, max: 100})
1714
age!: number
1815
}
1916

2017
class Student extends User {
21-
@FakerDecorator(faker.random.words, 5).config({
22-
array: true,
23-
length: 3
24-
})
25-
favorites!: string[]
26-
}
18+
@Address.streetAddress().isArray({length: 3})
19+
address!: string[]
2720

28-
function createMock(Entity: any) {
29-
const metas = MetadataStorage.instance.getClassMetadatas(Entity)
30-
const entity = new Entity()
31-
metas.map(meta => {
32-
const {array = false, length, min, max, propertyName} = meta
33-
if (valueIsFakerMeta(meta)) {
34-
const {fakerFn, fakerParams} = meta
35-
let propertyValue: any
36-
if (!array) {
37-
propertyValue = fakerFn(...fakerParams)
38-
} else {
39-
const arrayLength =
40-
(length ?? min ?? max ?? undefined) === undefined
41-
? 10
42-
: length ?? faker.random.number({min: min ?? 0, max: max ?? 100})
43-
propertyValue = Array.from({length: arrayLength}, () => fakerFn(...fakerParams))
44-
}
45-
entity[propertyName] = propertyValue
46-
}
47-
})
48-
return entity
21+
@Phone.phoneNumber('188########')
22+
tel!: number
4923
}
5024

5125
const mockStudent = createMock(Student)
5226

5327
console.log('mockStudent: ', mockStudent)
28+
29+
// 比如提供一个 webpack 插件或者 vite 插件,配置一下,axios 或 fetch 访问该 url 就可以自动响应 mock 数据了
30+
// const serverPluginConfig = [
31+
// {
32+
// url: '/api/users',
33+
// res: () => createMock(Student, {array: true})
34+
// },
35+
// {
36+
// url: '/api/user/:id',
37+
// res: () => createMock(Student, {array: false})
38+
// }
39+
// ]
Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import {FakerPropertyMetadata} from './types-helper'
2+
import {ArrayConfig, BasePropertyConfig, MetadataTarget} from './types-helper'
33

4-
export function valueIsFakerMeta(value: any): value is FakerPropertyMetadata {
5-
return value && typeof value.fakerFn === 'function'
4+
export function getTarget(target: any): MetadataTarget {
5+
return target instanceof Function ? target : target.constructor
6+
}
7+
8+
export function randomNumber(min: number, max: number): number {
9+
if (min === max) return min
10+
return Math.floor(Math.random() * (max - min + 1)) + min
11+
}
12+
13+
export interface getGenerateArrayLengthOptions extends ArrayConfig {
14+
defaultLength?: number
15+
defaultMax?: number
16+
defaultMin?: number
17+
}
18+
19+
export function getGenerateArrayLength(options: getGenerateArrayLengthOptions): number {
20+
const {length, min, max, defaultLength = 10, defaultMax = 50, defaultMin = 0} = options
21+
const arrayLength =
22+
(length ?? min ?? max ?? undefined) === undefined
23+
? defaultLength
24+
: length ?? randomNumber(min ?? defaultMin, max ?? defaultMax)
25+
return arrayLength
26+
}
27+
28+
export function mergeConfig<Config extends BasePropertyConfig>(
29+
oldMeta: Config | undefined,
30+
newMeta: Config | undefined
31+
) {
32+
return {
33+
...(oldMeta || {}),
34+
...(newMeta || {})
35+
} as Config
636
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {getGenerateArrayLength} from './common'
3+
import {MetadataStorage} from './meta-storage'
4+
5+
export function createMock(Entity: any) {
6+
const metas = MetadataStorage.instance.getClassMetadatas(Entity)
7+
const entity = new Entity()
8+
metas.map(meta => {
9+
const {array = false, length, min, max, propertyName, mockFn, mockParams = []} = meta
10+
if (typeof mockFn === 'function') {
11+
let propertyValue: unknown
12+
if (!array) {
13+
propertyValue = mockFn(...mockParams)
14+
} else {
15+
const arrayLength = getGenerateArrayLength({length, min, max})
16+
propertyValue = Array.from({length: arrayLength}, () => mockFn(...mockParams))
17+
}
18+
entity[propertyName] = propertyValue
19+
}
20+
})
21+
return entity
22+
}

0 commit comments

Comments
 (0)