From ac0e6f8c2ce35ed25c020063570e04ffa0598736 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 12:35:32 +1300 Subject: [PATCH 1/8] Works first time every time????? --- src/index.ts | 64 ++++++++++++++++++-------------------- test/FluentBuilder.test.ts | 33 +++++++++----------- 2 files changed, 45 insertions(+), 52 deletions(-) diff --git a/src/index.ts b/src/index.ts index a297308..3abdab7 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]) => Builder + : (value: T[K]) => Builder; -export class FluentBuilder { - private readonly mutator: Mutator; - private readonly schema: Schema; - private internalSchema: InternalSchema; +interface Modifier { + reset: () => Builder; + instance: () => T; +} - public constructor(schema: Schema) { - this.schema = schema; - this.internalSchema = {...schema}; - const mutator: Partial> = {}; +type Builder = Mutator & Modifier; - 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 +): Builder => { + 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 Builder; + }) 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 Builder; + builder.instance = () => 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..0ebce77 100644 --- a/test/FluentBuilder.test.ts +++ b/test/FluentBuilder.test.ts @@ -88,7 +88,7 @@ describe('FluentBuilder', () => { const mutatedFunc = jest.fn(); const instance = createBuilder(schema) - .mutate(s => s.func(mutatedFunc)) + .func(mutatedFunc) .instance(); expect(instance.func).not.toHaveBeenCalled(); @@ -101,7 +101,8 @@ describe('FluentBuilder', () => { const builder = createBuilder(schema); const instance = builder - .mutate(set => set.numOpt(5).str('test')) + .numOpt(5) + .str('test') .instance(); expect(instance).not.toEqual(expectedInitial); @@ -119,12 +120,9 @@ describe('FluentBuilder', () => { const func = jest.fn(); const instance = builder - .mutate(set => - set - .numOpt(numOpt) - .str(str) - .func(func) - ) + .numOpt(numOpt) + .str(str) + .func(func) .instance(); expect(instance.numOpt).toEqual(numOpt); @@ -137,7 +135,8 @@ describe('FluentBuilder', () => { numOpt = 3; str = 'test'; const rebuiltInstance = builder - .mutate(set => set.numOpt(numOpt).str(str)) + .numOpt(numOpt) + .str(str) .instance(); expect(rebuiltInstance.numOpt).toEqual(numOpt); expect(rebuiltInstance.str).toEqual(str); @@ -148,11 +147,9 @@ 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', () => { @@ -162,7 +159,7 @@ describe('FluentBuilder', () => { expect(before.num).toEqual(num); const updatedNum = num + 1; - builder.mutate(set => set.num(updatedNum)); + builder.num(updatedNum); const after = builder.instance(); @@ -176,7 +173,7 @@ describe('FluentBuilder', () => { expect(builder.instance().numOpt).toBeUndefined(); const update = 1; - builder.mutate(set => set.numOpt(update)); + builder.numOpt(update); expect(builder.instance().numOpt).toEqual(update); }); @@ -185,7 +182,7 @@ describe('FluentBuilder', () => { const builder = createBuilder(schema); const str = 'test'; - const instance = builder.mutate(set => set.str(str)).instance(); + const instance = builder.str(str).instance(); expect(instance.str).toEqual(str); }); @@ -195,7 +192,7 @@ describe('FluentBuilder', () => { (input: any) => { const builder = createBuilder(schema); - const instance = builder.mutate(set => set.numOpt(input)).instance(); + const instance = builder.numOpt(input).instance(); expect(instance.numOpt).toEqual(input); } From 7fd357bfec879db96942ee110d0d0a02d1dc58e1 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 12:35:53 +1300 Subject: [PATCH 2/8] Version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ", From ee6fa13dd15ddf60ec7b2a682344bd3766d912e6 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 13:03:07 +1300 Subject: [PATCH 3/8] Update readme --- README.md | 17 ++++++----------- src/index.ts | 8 ++++---- test/FluentBuilder.test.ts | 38 +++++++++++++++++++------------------- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index b26b4fa..02bc263 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? @@ -68,15 +68,10 @@ describe('suite', () => { beforeEach(() => builder.reset()); it('test', () => { - builder.mutate(set => - set - .price(4) - .buy(jest.fn(() => console.log('here lol 1234'))) - ); + const mock = jest.fn(); + const instance = builder.price(4).buy(mock).build(); - const instance = builder.instance(); - - // use instance + // use instance and mock }); }); ``` @@ -84,7 +79,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/src/index.ts b/src/index.ts index 3abdab7..ce4171b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,12 +26,12 @@ type Mutate = IsOptional extends true ? (value?: T[K]) => Builder : (value: T[K]) => Builder; -interface Modifier { +interface Operator { reset: () => Builder; - instance: () => T; + build: () => T; } -type Builder = Mutator & Modifier; +type Builder = Mutator & Operator; export const createBuilder = ( schema: Schema @@ -50,7 +50,7 @@ export const createBuilder = ( } const builder = mutator as Builder; - builder.instance = () => fromSchema(internalSchema); + builder.build = () => fromSchema(internalSchema); builder.reset = () => { for (const key in schema) { if (schema.hasOwnProperty(key)) { diff --git a/test/FluentBuilder.test.ts b/test/FluentBuilder.test.ts index 0ebce77..d6a69ef 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,9 +79,9 @@ 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', () => { @@ -89,7 +89,7 @@ describe('FluentBuilder', () => { const instance = createBuilder(schema) .func(mutatedFunc) - .instance(); + .build(); expect(instance.func).not.toHaveBeenCalled(); mutatedFunc(); @@ -103,11 +103,11 @@ describe('FluentBuilder', () => { const instance = builder .numOpt(5) .str('test') - .instance(); + .build(); expect(instance).not.toEqual(expectedInitial); - const resetInstance = builder.reset().instance(); + const resetInstance = builder.reset().build(); expect(resetInstance).toEqual(expectedInitial); }); @@ -123,13 +123,13 @@ describe('FluentBuilder', () => { .numOpt(numOpt) .str(str) .func(func) - .instance(); + .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; @@ -137,7 +137,7 @@ describe('FluentBuilder', () => { const rebuiltInstance = builder .numOpt(numOpt) .str(str) - .instance(); + .build(); expect(rebuiltInstance.numOpt).toEqual(numOpt); expect(rebuiltInstance.str).toEqual(str); expect(rebuiltInstance.func).toBe(expectedInitial.func); @@ -154,14 +154,14 @@ describe('FluentBuilder', () => { 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.num(updatedNum); - const after = builder.instance(); + const after = builder.build(); expect(before.num).toEqual(num); expect(after.num).toEqual(updatedNum); @@ -170,19 +170,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.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.str(str).instance(); + const instance = builder.str(str).build(); expect(instance.str).toEqual(str); }); @@ -192,7 +192,7 @@ describe('FluentBuilder', () => { (input: any) => { const builder = createBuilder(schema); - const instance = builder.numOpt(input).instance(); + const instance = builder.numOpt(input).build(); expect(instance.numOpt).toEqual(input); } From 7ccd67811dcd0cc2d40bce77265ef90f6b65bbd8 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 13:21:53 +1300 Subject: [PATCH 4/8] Rename --- test/FluentBuilder.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/FluentBuilder.test.ts b/test/FluentBuilder.test.ts index d6a69ef..d928f6f 100644 --- a/test/FluentBuilder.test.ts +++ b/test/FluentBuilder.test.ts @@ -138,6 +138,7 @@ describe('FluentBuilder', () => { .numOpt(numOpt) .str(str) .build(); + expect(rebuiltInstance.numOpt).toEqual(numOpt); expect(rebuiltInstance.str).toEqual(str); expect(rebuiltInstance.func).toBe(expectedInitial.func); From b3a2f2622163a6b9d186e00c9cb60c704891a1ff Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 13:24:51 +1300 Subject: [PATCH 5/8] Update --- .codacy.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 From b211846b67c52f25efe40395c4295606b32d1e07 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 13:30:33 +1300 Subject: [PATCH 6/8] Update --- src/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index ce4171b..99b3777 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,19 +23,19 @@ type Mutator = { }; type Mutate = IsOptional extends true - ? (value?: T[K]) => Builder - : (value: T[K]) => Builder; + ? (value?: T[K]) => FluentBuilder + : (value: T[K]) => FluentBuilder; -interface Operator { - reset: () => Builder; +interface Builder { + reset: () => FluentBuilder; build: () => T; } -type Builder = Mutator & Operator; +export type FluentBuilder = Builder & Mutator; export const createBuilder = ( schema: Schema -): Builder => { +): FluentBuilder => { const internalSchema: InternalSchema = {...schema}; const mutator: Partial> = {}; @@ -44,12 +44,12 @@ export const createBuilder = ( mutator[key] = ((v: T[typeof key]) => { internalSchema[key] = () => v; - return mutator as Builder; + return mutator as FluentBuilder; }) as Mutate; } } - const builder = mutator as Builder; + const builder = mutator as FluentBuilder; builder.build = () => fromSchema(internalSchema); builder.reset = () => { for (const key in schema) { From 81305f6da6ddb81519e6397a3f32f0198e8f8d53 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 13:39:33 +1300 Subject: [PATCH 7/8] Update readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 02bc263..b665416 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,11 @@ interface Product { ```ts import {Schema} from '@develohpanda/fluent-builder'; -const buyMock = jest.fn(); - const schema: Schema = { name: () => 'Shirt', price: () => 2), color: () => undefined, - buy: () => buyMock, + buy: () => jest.fn(), } ``` From 4319bb29ac5c53dc95d1f9f0bb21332d24cf96a8 Mon Sep 17 00:00:00 2001 From: Opender Singh Date: Sat, 26 Oct 2019 13:40:37 +1300 Subject: [PATCH 8/8] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b665416..e32bd1c 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ import {Schema} from '@develohpanda/fluent-builder'; const schema: Schema = { name: () => 'Shirt', - price: () => 2), + price: () => 2, color: () => undefined, buy: () => jest.fn(), }