diff --git a/.codacy.yml b/.codacy.yml index 3fc2a76..22fed79 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,6 +1,3 @@ exclude_paths: - - '.pipelines/**/*' - '.vscode/**/*' - - 'test/**/*' - - '*.min.js' - - '**/tests/**' \ No newline at end of file + - '**/test/**' \ No newline at end of file diff --git a/README.md b/README.md index b26b4fa..e32bd1c 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ ### Generate a fluent, typed object builder for any interface or type. -`fluent-builder` consumes a seeding schema, and generates a `mutator` with a signature identical to the type being built, but with `mutate` functions, to make iterative modifications to your object. +`fluent-builder` consumes a seeding schema, and generates a builder with a signature identical to the type being built, but with `mutate` functions, to make iterative modifications to your object. The builder contains two additional properties, `reset()` and `build()`. ```ts -createBuilder(schema).mutate(set => set.name('Bob').age(42)).instance(); +createBuilder(schema).name('Shirt').price(42).build(); ``` ## Why? @@ -45,13 +45,11 @@ interface Product { ```ts import {Schema} from '@develohpanda/fluent-builder'; -const buyMock = jest.fn(); - const schema: Schema = { name: () => 'Shirt', - price: () => 2), + price: () => 2, color: () => undefined, - buy: () => buyMock, + buy: () => jest.fn(), } ``` @@ -68,15 +66,10 @@ describe('suite', () => { beforeEach(() => builder.reset()); it('test', () => { - builder.mutate(set => - set - .price(4) - .buy(jest.fn(() => console.log('here lol 1234'))) - ); - - const instance = builder.instance(); + const mock = jest.fn(); + const instance = builder.price(4).buy(mock).build(); - // use instance + // use instance and mock }); }); ``` @@ -84,7 +77,7 @@ describe('suite', () => { The overhead of constructing a new builder can be avoided by using the `builder.reset()` method. This resets the mutated schema back to its original, and can be chained. ```ts -builder.reset().mutate(...).instance(); +builder.reset().price(5).build(); ``` ## Contributing diff --git a/package.json b/package.json index 6259729..12952f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@develohpanda/fluent-builder", - "version": "1.0.1", + "version": "2.0.0", "description": "A typed, fluent builder for creating objects in Typescript", "repository": "https://github.com/develohpanda/fluent-builder", "author": "Opender Singh ", diff --git a/src/index.ts b/src/index.ts index a297308..99b3777 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,47 +23,43 @@ type Mutator = { }; type Mutate = IsOptional extends true - ? (value?: T[K]) => Mutator - : (value: T[K]) => Mutator; + ? (value?: T[K]) => FluentBuilder + : (value: T[K]) => FluentBuilder; -export class FluentBuilder { - private readonly mutator: Mutator; - private readonly schema: Schema; - private internalSchema: InternalSchema; +interface Builder { + reset: () => FluentBuilder; + build: () => T; +} - public constructor(schema: Schema) { - this.schema = schema; - this.internalSchema = {...schema}; - const mutator: Partial> = {}; +export type FluentBuilder = Builder & Mutator; - for (const key in this.internalSchema) { - if (this.internalSchema.hasOwnProperty(key)) { - mutator[key] = ((v: T[typeof key]) => { - this.internalSchema[key] = () => v; +export const createBuilder = ( + schema: Schema +): FluentBuilder => { + const internalSchema: InternalSchema = {...schema}; + const mutator: Partial> = {}; - return this.mutator; - }) as Mutate; - } - } + for (const key in internalSchema) { + if (internalSchema.hasOwnProperty(key)) { + mutator[key] = ((v: T[typeof key]) => { + internalSchema[key] = () => v; - this.mutator = mutator as Mutator; + return mutator as FluentBuilder; + }) as Mutate; + } } - public mutate = (func: (mutate: Mutator) => void): FluentBuilder => { - func(this.mutator); - - return this; - }; - - public reset = (): FluentBuilder => { - this.internalSchema = {...this.schema}; + const builder = mutator as FluentBuilder; + builder.build = () => fromSchema(internalSchema); + builder.reset = () => { + for (const key in schema) { + if (schema.hasOwnProperty(key)) { + internalSchema[key] = schema[key]; + } + } - return this; + return builder; }; - public instance = (): T => fromSchema(this.internalSchema); -} - -export const createBuilder = ( - schema: Schema -): FluentBuilder => new FluentBuilder(schema); + return builder; +}; diff --git a/test/FluentBuilder.test.ts b/test/FluentBuilder.test.ts index da67809..d928f6f 100644 --- a/test/FluentBuilder.test.ts +++ b/test/FluentBuilder.test.ts @@ -46,14 +46,14 @@ describe('FluentBuilder', () => { beforeEach(() => jest.clearAllMocks()); it('should create initial instance from schema', () => { - const instance = createBuilder(schema).instance(); + const instance = createBuilder(schema).build(); expect(instance).toEqual(expectedInitial); }); it('should track complex properties by reference from schema initializer to instance', () => { const builder = createBuilder(schema); - const before = builder.instance(); + const before = builder.build(); expect(before.arr).toBe(arr); expect(before.obj).toBe(obj); @@ -61,14 +61,14 @@ describe('FluentBuilder', () => { arr.push(3); obj.valOpt = 2; - const after = builder.instance(); + const after = builder.build(); expect(after.arr).toBe(arr); expect(after.obj).toBe(obj); }); it('can track jest function calls on the instance', () => { - const instance = createBuilder(schema).instance(); + const instance = createBuilder(schema).build(); expect(instance.func).not.toHaveBeenCalled(); @@ -79,17 +79,17 @@ describe('FluentBuilder', () => { it('can track jest function calls between instances', () => { const builder = createBuilder(schema); - expect(builder.instance().func).not.toHaveBeenCalled(); - builder.instance().func(); - expect(builder.instance().func).toHaveBeenCalled(); + expect(builder.build().func).not.toHaveBeenCalled(); + builder.build().func(); + expect(builder.build().func).toHaveBeenCalled(); }); it('can track mutated function calls', () => { const mutatedFunc = jest.fn(); const instance = createBuilder(schema) - .mutate(s => s.func(mutatedFunc)) - .instance(); + .func(mutatedFunc) + .build(); expect(instance.func).not.toHaveBeenCalled(); mutatedFunc(); @@ -101,12 +101,13 @@ describe('FluentBuilder', () => { const builder = createBuilder(schema); const instance = builder - .mutate(set => set.numOpt(5).str('test')) - .instance(); + .numOpt(5) + .str('test') + .build(); expect(instance).not.toEqual(expectedInitial); - const resetInstance = builder.reset().instance(); + const resetInstance = builder.reset().build(); expect(resetInstance).toEqual(expectedInitial); }); @@ -119,26 +120,25 @@ describe('FluentBuilder', () => { const func = jest.fn(); const instance = builder - .mutate(set => - set - .numOpt(numOpt) - .str(str) - .func(func) - ) - .instance(); + .numOpt(numOpt) + .str(str) + .func(func) + .build(); expect(instance.numOpt).toEqual(numOpt); expect(instance.str).toEqual(str); expect(instance.func).toBe(func); - const resetInstance = builder.reset().instance(); + const resetInstance = builder.reset().build(); expect(resetInstance).toEqual(expectedInitial); numOpt = 3; str = 'test'; const rebuiltInstance = builder - .mutate(set => set.numOpt(numOpt).str(str)) - .instance(); + .numOpt(numOpt) + .str(str) + .build(); + expect(rebuiltInstance.numOpt).toEqual(numOpt); expect(rebuiltInstance.str).toEqual(str); expect(rebuiltInstance.func).toBe(expectedInitial.func); @@ -148,23 +148,21 @@ describe('FluentBuilder', () => { it('should define all mutator properties', () => { const builder = createBuilder(schema); - builder.mutate(set => { - for (const key in set) { - expect((set as any)[key]).toBeDefined(); - } - }); + for (const key in builder) { + expect((builder as any)[key]).toBeDefined(); + } }); it('should not update a previous instance if the builder is mutated afterards', () => { const builder = createBuilder(schema); - const before = builder.instance(); + const before = builder.build(); expect(before.num).toEqual(num); const updatedNum = num + 1; - builder.mutate(set => set.num(updatedNum)); + builder.num(updatedNum); - const after = builder.instance(); + const after = builder.build(); expect(before.num).toEqual(num); expect(after.num).toEqual(updatedNum); @@ -173,19 +171,19 @@ describe('FluentBuilder', () => { it('can mutate an optional property that was initialized as undefined', () => { const builder = createBuilder(schema); - expect(builder.instance().numOpt).toBeUndefined(); + expect(builder.build().numOpt).toBeUndefined(); const update = 1; - builder.mutate(set => set.numOpt(update)); + builder.numOpt(update); - expect(builder.instance().numOpt).toEqual(update); + expect(builder.build().numOpt).toEqual(update); }); it('should show mutation on instance after mutator function', () => { const builder = createBuilder(schema); const str = 'test'; - const instance = builder.mutate(set => set.str(str)).instance(); + const instance = builder.str(str).build(); expect(instance.str).toEqual(str); }); @@ -195,7 +193,7 @@ describe('FluentBuilder', () => { (input: any) => { const builder = createBuilder(schema); - const instance = builder.mutate(set => set.numOpt(input)).instance(); + const instance = builder.numOpt(input).build(); expect(instance.numOpt).toEqual(input); }