Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying the type of params #108

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions lib/__tests__/custom-params.factory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Factory, DeepPartial, HookFn } from 'fishery';

class DateTime {
private constructor(public date: Date) {}

toISO(): string {
return this.date.toISOString();
}

static fromISO(iso: string): DateTime {
return new DateTime(new Date(iso));
}
}

type User = {
id: string;
createdAt: DateTime;
};

type UserWithOptionalCreatedAt = DeepPartial<User> & {
createdAt?: DateTime;
};

const userFactory = Factory.define<User, {}, User, UserWithOptionalCreatedAt>(
({ params }) => {
const created =
params.createdAt ?? DateTime.fromISO(new Date().toISOString());
return {
id: '3',
createdAt: created,
};
},
);

describe('factory.build', () => {
it('builds the object without params', () => {
const user = userFactory.build();
expect(user.id).not.toBeNull();
expect(user.createdAt).not.toBeNull();
});

it('builds the object with a custom params', () => {
const date = DateTime.fromISO('2022');
const user = userFactory.build({ createdAt: date });
expect(user.id).not.toBeNull();
expect(user.createdAt.toISO()).toEqual(date.toISO());
});
});
8 changes: 4 additions & 4 deletions lib/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
} from './types';
import { merge, mergeCustomizer } from './merge';

export class FactoryBuilder<T, I, C> {
export class FactoryBuilder<T, I, C, P> {
constructor(
private generator: GeneratorFn<T, I, C>,
private generator: GeneratorFn<T, I, C, P>,
private sequence: number,
private params: DeepPartial<T>,
private params: P,
private transientParams: Partial<I>,
private associations: Partial<T>,
private afterBuilds: HookFn<T>[],
Expand All @@ -21,7 +21,7 @@ export class FactoryBuilder<T, I, C> {
) {}

build() {
const generatorOptions: GeneratorFnOptions<T, I, C> = {
const generatorOptions: GeneratorFnOptions<T, I, C, P> = {
sequence: this.sequence,
afterBuild: this.setAfterBuild,
afterCreate: this.setAfterCreate,
Expand Down
37 changes: 15 additions & 22 deletions lib/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import { merge, mergeCustomizer } from './merge';

const SEQUENCE_START_VALUE = 1;

export class Factory<T, I = any, C = T> {
export class Factory<T, I = any, C = T, P = DeepPartial<T>> {
// id is an object so it is shared between extended factories
private id: { value: number } = { value: SEQUENCE_START_VALUE };

private _afterBuilds: HookFn<T>[] = [];
private _afterCreates: AfterCreateFn<C>[] = [];
private _onCreate?: OnCreateFn<T, C>;
private _associations?: Partial<T>;
private _params?: DeepPartial<T>;
private _params?: P;
private _transient?: Partial<I>;

constructor(
private readonly generator: (opts: GeneratorFnOptions<T, I, C>) => T,
private readonly generator: (opts: GeneratorFnOptions<T, I, C, P>) => T,
) {}

/**
Expand All @@ -34,9 +34,9 @@ export class Factory<T, I = any, C = T> {
* @template C The class of the factory object being created.
* @param generator - your factory function
*/
static define<T, I = any, C = T, F = Factory<T, I, C>>(
this: new (generator: GeneratorFn<T, I, C>) => F,
generator: GeneratorFn<T, I, C>,
static define<T, I = any, C = T, P = DeepPartial<T>, F = Factory<T, I, C, P>>(
this: new (generator: GeneratorFn<T, I, C, P>) => F,
generator: GeneratorFn<T, I, C, P>,
): F {
return new this(generator);
}
Expand All @@ -46,15 +46,11 @@ export class Factory<T, I = any, C = T> {
* @param params
* @param options
*/
build(params?: DeepPartial<T>, options: BuildOptions<T, I> = {}): T {
build(params?: P, options: BuildOptions<T, I> = {}): T {
return this.builder(params, options).build();
}

buildList(
number: number,
params?: DeepPartial<T>,
options: BuildOptions<T, I> = {},
): T[] {
buildList(number: number, params?: P, options: BuildOptions<T, I> = {}): T[] {
let list: T[] = [];
for (let i = 0; i < number; i++) {
list.push(this.build(params, options));
Expand All @@ -68,16 +64,13 @@ export class Factory<T, I = any, C = T> {
* @param params
* @param options
*/
async create(
params?: DeepPartial<T>,
options: BuildOptions<T, I> = {},
): Promise<C> {
async create(params?: P, options: BuildOptions<T, I> = {}): Promise<C> {
return this.builder(params, options).create();
}

async createList(
number: number,
params?: DeepPartial<T>,
params?: P,
options: BuildOptions<T, I> = {},
): Promise<C[]> {
let list: Promise<C>[] = [];
Expand Down Expand Up @@ -141,7 +134,7 @@ export class Factory<T, I = any, C = T> {
* @param params
* @returns a new factory
*/
params(params: DeepPartial<T>): this {
params(params: P): this {
const factory = this.clone();
factory._params = merge({}, this._params, params, mergeCustomizer);
return factory;
Expand All @@ -165,9 +158,9 @@ export class Factory<T, I = any, C = T> {
this.id.value = SEQUENCE_START_VALUE;
}

protected clone<F extends Factory<T, I, C>>(this: F): F {
protected clone<F extends Factory<T, I, C, P>>(this: F): F {
const copy = new (this.constructor as {
new (generator: GeneratorFn<T, I, C>): F;
new (generator: GeneratorFn<T, I, C, P>): F;
})(this.generator);
Object.assign(copy, this);
copy._afterCreates = [...this._afterCreates];
Expand All @@ -179,8 +172,8 @@ export class Factory<T, I = any, C = T> {
return this.id.value++;
}

protected builder(params?: DeepPartial<T>, options: BuildOptions<T, I> = {}) {
return new FactoryBuilder<T, I, C>(
protected builder(params?: P, options: BuildOptions<T, I> = {}) {
return new FactoryBuilder<T, I, C, P>(
this.generator,
this.sequence(),
merge({}, this._params, params, mergeCustomizer),
Expand Down
8 changes: 5 additions & 3 deletions lib/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { DeepPartial } from './deepPartial';

export type GeneratorFnOptions<T, I, C> = {
export type GeneratorFnOptions<T, I, C, P> = {
sequence: number;
afterBuild: (fn: HookFn<T>) => any;
afterCreate: (fn: AfterCreateFn<C>) => any;
onCreate: (fn: OnCreateFn<T, C>) => any;
params: DeepPartial<T>;
params: P;
associations: Partial<T>;
transientParams: Partial<I>;
};
export type GeneratorFn<T, I, C> = (opts: GeneratorFnOptions<T, I, C>) => T;
export type GeneratorFn<T, I, C, P> = (
opts: GeneratorFnOptions<T, I, C, P>,
) => T;
export type HookFn<T> = (object: T) => any;
export type OnCreateFn<T, C = T> = (object: T) => C | Promise<C>;
export type AfterCreateFn<C> = (object: C) => C | Promise<C>;
Expand Down