Skip to content

Commit 342a123

Browse files
committed
feat(class-mock): implementing a minimum runnable instance
1 parent 7e60b89 commit 342a123

File tree

7 files changed

+285
-57
lines changed

7 files changed

+285
-57
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@
139139
"jsfiddle",
140140
"loglevel",
141141
"messageoption",
142+
"Metadatas",
143+
"Metas",
142144
"mockdate",
143145
"modaloption",
144146
"navigations",

packages/class-mock/src/constants/metakey.constants.ts

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {MetadataStorage} from './../meta-storage'
3+
import {FakerPropertyDecoratorConfig, FakerPropertyMetadata, Fn, MockPropertyDecorator} from './../utils/types-helper'
4+
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+
}
14+
15+
MetadataStorage.instance.addFakerMetadata(metadata)
16+
}
17+
18+
decorator.config =
19+
(config: FakerPropertyDecoratorConfig = {}) =>
20+
(target: any, propertyKey: string) =>
21+
decorator(target, propertyKey, config)
22+
23+
return decorator as MockPropertyDecorator<FakerPropertyDecoratorConfig>
24+
}

packages/class-mock/src/index.ts

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,53 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import 'reflect-metadata'
33
import faker from '@faker-js/faker'
4-
import {PropertyDecoratorConfig, ClassDecoratorConfig} from './utils/types-helper'
4+
import {MetadataStorage} from './meta-storage'
5+
import {FakerDecorator} from './decorators/faker.decorator'
6+
import {valueIsFakerMeta} from './utils/common'
57

6-
const METADATA_KEY_MOCK = 'mock'
7-
8-
function FakerDecorator<T extends (...args: any[]) => any>(
9-
fakerFn: T,
10-
...fakerParams: Parameters<T>
11-
): PropertyDecorator & {
12-
config(config?: PropertyDecoratorConfig): PropertyDecorator
13-
} {
14-
function decorator(target: Object, propertyKey: string | symbol, config: PropertyDecoratorConfig = {}) {
15-
const metadata: {
16-
fakerFn: T
17-
fakerParams: Parameters<T>
18-
config: PropertyDecoratorConfig
19-
} = {
20-
fakerFn,
21-
fakerParams,
22-
config
23-
}
24-
let classMetadata = Reflect.getOwnMetadata(METADATA_KEY_MOCK, target)
25-
if (!classMetadata) classMetadata = {}
26-
if (!classMetadata.property) classMetadata.property = {}
27-
classMetadata.property[propertyKey] = metadata
28-
Reflect.defineMetadata(METADATA_KEY_MOCK, classMetadata, target)
29-
Reflect.defineMetadata(propertyKey, metadata, target, propertyKey)
30-
}
31-
32-
decorator.config = (config: PropertyDecoratorConfig) => (target: Object, propertyKey: string | symbol) =>
33-
decorator(target, propertyKey, config)
8+
class User {
9+
@FakerDecorator(faker.name.firstName, 'male').config({
10+
partial: true
11+
})
12+
name!: string
3413

35-
return decorator
14+
@FakerDecorator(faker.random.number, {min: 0, max: 100}).config({
15+
alwaysRandom: true
16+
})
17+
age!: number
3618
}
3719

38-
function Mock(config: ClassDecoratorConfig = {}): ClassDecorator {
39-
function decorator<TFunction extends Function>(target: TFunction): TFunction | void {
40-
const metadata: {
41-
config: PropertyDecoratorConfig
42-
} = {
43-
config
44-
}
45-
Reflect.defineMetadata(METADATA_KEY_MOCK, metadata, target)
46-
}
47-
return decorator
20+
class Student extends User {
21+
@FakerDecorator(faker.random.words, 5).config({
22+
array: true,
23+
length: 3
24+
})
25+
favorites!: string[]
4826
}
4927

50-
interface MockClass<T = any> {
51-
new (...args: any[]): T
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
5249
}
5350

54-
function createMock<T = any>(target: MockClass<T>): T {
55-
const classMetadata = Reflect.getOwnMetadata(METADATA_KEY_MOCK, target)
56-
const metadataKeys = Reflect.getMetadataKeys(target)
51+
const mockStudent = createMock(Student)
5752

58-
return '' as any as T
59-
}
60-
61-
@Mock()
62-
class User {
63-
@FakerDecorator(faker.random.number, {min: 0, max: 100}).config({
64-
alwaysRandom: true
65-
})
66-
age!: number
67-
}
53+
console.log('mockStudent: ', mockStudent)
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import {
2+
BaseMetadata,
3+
EntityPropertyMetadata,
4+
FakerPropertyMetadata,
5+
TargetMap,
6+
MetadataTarget
7+
} from './utils/types-helper'
8+
9+
export class MetadataUtils<Meta extends BaseMetadata> {
10+
private _ancestorsMap = new Map<MetadataTarget, Function[]>()
11+
12+
/**
13+
* clear constructors map
14+
*/
15+
public clear(): void {
16+
this._ancestorsMap.clear()
17+
}
18+
19+
/**
20+
* save metadata to target map's property map
21+
* @param constructMap constructor map Map<constructor, Map<propertyName, metadata>>
22+
* @param metadata metadata
23+
*/
24+
public setMetadata<T extends Meta>(constructMap: TargetMap<T>, metadata: T): void {
25+
const {target, propertyName} = metadata
26+
27+
if (!constructMap.has(target)) {
28+
constructMap.set(target, new Map<string, T>())
29+
}
30+
constructMap.get(target)!.set(propertyName, metadata)
31+
}
32+
33+
/**
34+
* save metadatas to target map's property map
35+
* @param constructMap constructor map Map<constructor, Map<propertyName, metadata[]>>
36+
* @param metadata metadata
37+
*/
38+
public setMetadatas<T extends Meta>(constructMap: TargetMap<T[]>, metadata: T): void {
39+
const {target, propertyName} = metadata
40+
41+
if (!constructMap.has(target)) {
42+
constructMap.set(target, new Map<string, T[]>())
43+
}
44+
const propertyMap = constructMap.get(target)!
45+
const metadatas = propertyMap.get(propertyName) ?? []
46+
propertyMap.set(propertyName, [...metadatas, metadata])
47+
}
48+
49+
/**
50+
* Returns all the metadatas of the given target.(filter by propertyName if not undefined)
51+
* @param constructMap constructor map Map<constructor, Map<propertyName, metadata>>
52+
* @param target target constructor
53+
* @returns metadatas
54+
*/
55+
public getMetadatas<T extends Meta>(constructMap: TargetMap<T>, target: MetadataTarget): T[] {
56+
const ancestorsMetas: T[] = []
57+
const ancestors = [...this.getAncestors(target), target]
58+
59+
for (const ancestor of ancestors) {
60+
const ancestorMetas = Array.from(constructMap.get(ancestor)?.values() ?? []).filter(
61+
meta => meta.propertyName !== undefined
62+
)
63+
ancestorsMetas.push(...ancestorMetas)
64+
}
65+
return ancestorsMetas
66+
}
67+
68+
/**
69+
* .
70+
* @param constructMap constructor map Map<constructor, Map<propertyName, metadata>>
71+
* @param target target constructor
72+
* @param propertyName property name
73+
* @returns metadata or undefined
74+
*/
75+
public findMetadata<T extends Meta>(
76+
constructMap: TargetMap<T>,
77+
target: MetadataTarget,
78+
propertyName: string
79+
): T | undefined {
80+
const ancestors = [target, ...this.getAncestors(target)]
81+
82+
for (const ancestor of ancestors) {
83+
const ancestorPropMeta: T | undefined = constructMap.get(ancestor)?.get(propertyName)
84+
if (ancestorPropMeta) return ancestorPropMeta
85+
}
86+
return undefined
87+
}
88+
89+
/**
90+
* find metadatas from target and its ancestors
91+
* @param constructMap constructor map Map<constructor, Map<propertyName, metadata[]>>
92+
* @param target target constructor
93+
* @param propertyName property name
94+
* @returns metadatas
95+
*/
96+
public findMetadatas<T extends Meta>(
97+
constructMap: TargetMap<T[]>,
98+
target: MetadataTarget,
99+
propertyName: string
100+
): T[] {
101+
const targetPropMetas: T[] = constructMap.get(target)?.get(propertyName) ?? []
102+
const ancestorsPropMetas: T[] = []
103+
const ancestors = this.getAncestors(target)
104+
105+
for (const ancestor of ancestors) {
106+
const ancestorPropMetas = constructMap.get(ancestor)?.get(propertyName) ?? []
107+
ancestorsPropMetas.push(...ancestorPropMetas)
108+
}
109+
110+
return [...ancestorsPropMetas.reverse(), ...targetPropMetas.reverse()]
111+
}
112+
113+
/**
114+
* Returns all the parent's class constructor of target class
115+
* @param target target class
116+
* @returns all parent's constructor of target
117+
*/
118+
private getAncestors(target: MetadataTarget): Function[] {
119+
if (!target) return []
120+
if (!this._ancestorsMap.has(target)) {
121+
const ancestors: Function[] = []
122+
123+
for (
124+
let baseClass = Object.getPrototypeOf(target.prototype.constructor);
125+
typeof baseClass.prototype !== 'undefined';
126+
baseClass = Object.getPrototypeOf(baseClass.prototype.constructor)
127+
) {
128+
ancestors.push(baseClass)
129+
}
130+
this._ancestorsMap.set(target, ancestors)
131+
}
132+
return this._ancestorsMap.get(target) ?? []
133+
}
134+
}
135+
136+
/**
137+
* Storage all library metadata.
138+
*/
139+
export class MetadataStorage {
140+
static _instance: MetadataStorage
141+
static get instance(): MetadataStorage {
142+
if (!MetadataStorage._instance) {
143+
MetadataStorage._instance = new MetadataStorage()
144+
}
145+
return MetadataStorage._instance
146+
}
147+
148+
private _fakerMockConstructMap: TargetMap<FakerPropertyMetadata> = new Map()
149+
private _entityMockConstructMap: TargetMap<EntityPropertyMetadata> = new Map()
150+
private _metadataUtils = new MetadataUtils()
151+
152+
addFakerMetadata(metadata: FakerPropertyMetadata): void {
153+
this._metadataUtils.setMetadata(this._fakerMockConstructMap, metadata)
154+
}
155+
156+
addEntityMetadata(metadata: EntityPropertyMetadata): void {
157+
this._metadataUtils.setMetadata(this._entityMockConstructMap, metadata)
158+
}
159+
160+
findFakerMetadata(target: MetadataTarget, propertyName: string): FakerPropertyMetadata | undefined {
161+
return this._metadataUtils.findMetadata(this._fakerMockConstructMap, target, propertyName)
162+
}
163+
164+
findEntityMetadata(target: MetadataTarget, propertyName: string): EntityPropertyMetadata | undefined {
165+
return this._metadataUtils.findMetadata(this._entityMockConstructMap, target, propertyName)
166+
}
167+
168+
getClassMetadatas(target: Function): Array<FakerPropertyMetadata | EntityPropertyMetadata> {
169+
const fakerMetas = this._metadataUtils.getMetadatas(this._fakerMockConstructMap, target)
170+
const entityMetas = this._metadataUtils.getMetadatas(this._entityMockConstructMap, target)
171+
172+
return [...fakerMetas, ...entityMetas]
173+
}
174+
175+
clear(): void {
176+
this._fakerMockConstructMap.clear()
177+
this._entityMockConstructMap.clear()
178+
this._metadataUtils.clear()
179+
}
180+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {FakerPropertyMetadata} from './types-helper'
3+
4+
export function valueIsFakerMeta(value: any): value is FakerPropertyMetadata {
5+
return value && typeof value.fakerFn === 'function'
6+
}
Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,46 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
export type Nil = null | undefined
3+
export type Fn = (...args: any[]) => any
4+
export type MetadataTarget = Function
5+
export type PropertyMetadataMap<T> = Map<string, T>
6+
export type TargetMap<T> = Map<MetadataTarget, PropertyMetadataMap<T>>
27

3-
export interface PropertyDecoratorConfig {
8+
export interface BasePropertyConfig {
49
partial?: boolean | Nil
5-
alwaysRandom?: boolean | Nil
610
groups?: string[] | Nil
711
array?: boolean
812
length?: number | Nil
913
max?: number | Nil
1014
min?: number | Nil
1115
}
1216

17+
export interface FakerPropertyDecoratorConfig extends BasePropertyConfig {
18+
alwaysRandom?: boolean | Nil
19+
}
20+
21+
export type EntityPropertyConfig = BasePropertyConfig
22+
1323
export interface ClassDecoratorConfig {
1424
partial?: boolean | Nil
1525
alwaysRandom?: boolean | Nil
1626
}
27+
28+
export interface BaseMetadata {
29+
target: MetadataTarget
30+
propertyName: string
31+
}
32+
33+
export interface BasePropertyMetadata extends BaseMetadata, BasePropertyConfig {}
34+
35+
export interface FakerPropertyMetadata<T extends Fn = Fn> extends BasePropertyMetadata, FakerPropertyDecoratorConfig {
36+
fakerFn: T
37+
fakerParams: Parameters<T>
38+
}
39+
40+
export interface EntityPropertyMetadata extends BasePropertyMetadata, EntityPropertyConfig {
41+
entityFn: Fn
42+
}
43+
44+
export type MockPropertyDecorator<T extends BasePropertyConfig> = PropertyDecorator & {
45+
config(config?: T): PropertyDecorator
46+
}

0 commit comments

Comments
 (0)