Skip to content

Commit 5568f8c

Browse files
committed
feat: initial release
1 parent 00df5e4 commit 5568f8c

24 files changed

+880
-14
lines changed

.releaserc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"plugins": [
3+
"@semantic-release/commit-analyzer",
4+
"@semantic-release/github",
5+
"@semantic-release/release-notes-generator",
6+
"@semantic-release/changelog",
7+
"@semantic-release/npm",
8+
"@semantic-release/git"
9+
]
10+
}

README.md

Lines changed: 134 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,151 @@
11
# @lleon/object-builders
22

3+
Library to easily create object builders, useful for testing or for generating seed data.
4+
5+
This library requires
6+
[ES Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
7+
to work. So be sure your browser or the node version you're using supports proxies.
8+
9+
- [Installation](#installation)
10+
- [fromInterface()](#frominterface--)
11+
- [fromClassObject()](#fromclassobject--)
12+
- [fromClassConstructor()](#fromclassconstructor--)
13+
- [fromFactory()](#fromfactory--)
14+
- [Development](#development)
15+
- [yarn build:commonjs](#yarn-build-commonjs)
16+
- [yarn build:commonjs](#yarn-build-commonjs-1)
17+
- [yarn build:esm](#yarn-build-esm)
18+
- [yarn clean](#yarn-clean)
19+
- [yarn test](#yarn-test)
20+
- [yarn cover](#yarn-cover)
21+
- [yarn lint](#yarn-lint)
22+
- [yarn lint:fix](#yarn-lint-fix)
23+
24+
## Installation
25+
26+
```sh
27+
yarn add @lleon/object-builders
28+
29+
# or using npm...
30+
npm install @lleon/object-builders
31+
```
32+
33+
The project use [husky](https://github.com/typicode/husky) and
34+
[lint-staged](https://github.com/okonet/lint-staged) for linting and fixing possible errors on
35+
source code before commit
36+
37+
### fromInterface()
38+
39+
Creates a new object builder typed from an interface
40+
41+
```ts
42+
import { fromInterface } from '@lleon/object-builders';
43+
44+
interface User {
45+
name: string;
46+
}
47+
48+
const user = fromInterface<User>()
49+
.name.set('John')
50+
.build();
51+
52+
console.log(user.name); // prints "John"
53+
```
54+
55+
### fromClassObject()
56+
57+
Creates a new object builder from the given class. The properties that can be set are the same
58+
properties as the class has.
59+
60+
When using this builder object is instantiated using `Object.create` so the function constructor is
61+
never called.
62+
63+
This is a good builder when using in conjunction with
64+
[class-transformer](https://www.npmjs.com/package/class-transformer) and
65+
[class-validators](https://www.npmjs.com/package/class-validator)
66+
67+
```ts
68+
import { fromClassObject } from '@lleon/object-builders';
69+
import { IsString } from 'class-validators';
70+
71+
class User {
72+
@IsString()
73+
name!: string;
74+
}
75+
76+
const user = fromClassObject(User)
77+
.name.set('John')
78+
.build();
79+
80+
console.log(user instanceof User); // prints `true`
81+
console.log(user.name); // prints "John"
82+
```
83+
84+
### fromClassConstructor()
85+
86+
Creates a new object builder from the given class. The class must receive an object as unique
87+
argument. In this case the object is instantiated using the `new` operator
88+
89+
```ts
90+
import { fromClassConstructor } from '@lleon/object-builders';
91+
92+
class User {
93+
givenName: string;
94+
familyName: string;
95+
96+
constructor({ firstName, lastName }: { firstName: string; lastName: string }) {
97+
this.givenName = firstName;
98+
this.familyName = lastName;
99+
}
100+
}
101+
102+
// the available builder properties are the properties that receives the constructor
103+
const user = fromClassConstructor(User)
104+
.firstName.set('John')
105+
.lastName.set('Doe')
106+
.build();
107+
108+
console.log(user instanceof User); // prints `true`
109+
console.log(user.givenName); // prints "John"
110+
console.log(user.familyName); // prints "Doe"
111+
```
112+
113+
### fromFactory()
114+
115+
Creates a new builder using the given factory function. Using a factory allow you to return any type
116+
you want not just objects
117+
118+
```ts
119+
import { fromFactory } from '@lleon/object-builders';
120+
121+
const factory = (properties: Partial<{ userName: string }>): string => {
122+
return properties.userName || '';
123+
};
124+
125+
const userName = fromFactory(factory)
126+
.userName.set('john.doe1')
127+
.build();
128+
129+
console.log(userName === 'john.doe1'); // prints `true`
130+
```
131+
3132
## Development
4133

5134
The project use [husky](https://github.com/typicode/husky) and
6135
[lint-staged](https://github.com/okonet/lint-staged) for linting and fixing possible errors on
7136
source code before commit
8137

9-
Git hooks scripts are installed after running `yarn` the first time
138+
### yarn build
139+
140+
Concurrently run `build:commonjs` and `build:esm`
10141

11142
### yarn build:commonjs
12143

13144
Compile typescript files from the `src` folder inside the `lib` folder
14145

15146
### yarn build:esm
16147

17-
Compile typescript files from the `src` folder inside the `esm` folder using es modules
18-
19-
### yarn build
20-
21-
Concurrently run both `build:commonjs` and `build:esm`
148+
Compile typescript files from the `src` folder inside the `esm` folder using ES modules
22149

23150
### yarn clean
24151

@@ -30,7 +157,7 @@ Remove the following directories/files
30157

31158
### yarn test
32159

33-
Run tests files inside the `tests` folder that matches the following patterns. Exit with code > 0 on
160+
Run tests files inside the `tests` and `src` folders that matches the following patterns. Exit with code > 0 on
34161
error
35162

36163
- **\*.test.ts**

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lleon/object-builders",
3-
"version": "0.0.0",
3+
"version": "0.0.0-development",
44
"description": "Utilities for building shaped objects and classes",
55
"main": "lib/index.js",
66
"module": "esm/index.js",
@@ -29,6 +29,8 @@
2929
},
3030
"dependencies": {},
3131
"devDependencies": {
32+
"@semantic-release/changelog": "^3.0.4",
33+
"@semantic-release/git": "^7.0.16",
3234
"@types/chai": "^4.2.3",
3335
"@types/mocha": "^5.2.7",
3436
"@typescript-eslint/eslint-plugin": "^2.3.1",
@@ -44,6 +46,7 @@
4446
"nyc": "^14.1.1",
4547
"prettier": "^1.18.2",
4648
"semantic-release": "^15.13.24",
49+
"ts-mockito": "^2.5.0",
4750
"ts-node": "^8.4.1",
4851
"typescript": "^3.6.3"
4952
},
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { expect } from 'chai';
2+
import { anyFunction, reset, spy, verify } from 'ts-mockito';
3+
import { fromClassConstructor } from './fromClassConstructor';
4+
import * as builder from './fromFactory';
5+
6+
describe('fromClassConstructor()', () => {
7+
let spiedBuilder: typeof builder;
8+
9+
beforeEach(() => (spiedBuilder = spy(builder)));
10+
afterEach(() => reset(spiedBuilder));
11+
12+
it('should call fromFactory()', () => {
13+
getTestBuilder();
14+
15+
verify(spiedBuilder.fromFactory(anyFunction())).once();
16+
});
17+
18+
describe('#get()', () => {
19+
it('should return a plain object with the set properties', () => {
20+
const value = 'foo';
21+
const test = getTestBuilder()
22+
.property.set(value)
23+
.get();
24+
25+
expect(test).to.not.be.instanceOf(Test);
26+
expect(test.property).to.be.equal(value);
27+
});
28+
});
29+
30+
describe('#build()', () => {
31+
it('should return a class instance with the set properties', () => {
32+
const value = 'foo';
33+
const test = getTestBuilder()
34+
.property.set(value)
35+
.build();
36+
37+
expect(test).to.be.instanceOf(Test);
38+
expect(test.testProperty).to.be.equal(value);
39+
});
40+
});
41+
});
42+
43+
class Test {
44+
testProperty?: string;
45+
46+
constructor(properties: { property?: string }) {
47+
this.testProperty = properties.property;
48+
}
49+
}
50+
51+
function getTestBuilder() {
52+
return fromClassConstructor(Test);
53+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Builder } from '../types/Builder';
2+
import { fromFactory } from './fromFactory';
3+
4+
export function fromClassConstructor<Instance extends object, Properties extends object>(classConstructor: {
5+
new (properties: Properties): Instance;
6+
}): Builder<Properties, Instance> {
7+
return fromFactory(properties => new classConstructor(properties as Properties));
8+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expect } from 'chai';
2+
import { anyFunction, reset, spy, verify } from 'ts-mockito';
3+
import { fromClassObject } from './fromClassObject';
4+
import * as builder from './fromFactory';
5+
6+
describe('fromClassObject()', () => {
7+
let spiedBuilder: typeof builder;
8+
9+
beforeEach(() => (spiedBuilder = spy(builder)));
10+
afterEach(() => reset(spiedBuilder));
11+
12+
it('should call fromFactory()', () => {
13+
getTestBuilder();
14+
15+
verify(spiedBuilder.fromFactory(anyFunction())).once();
16+
});
17+
18+
describe('#get()', () => {
19+
it('should return a plain object with the set properties', () => {
20+
const value = 'foo';
21+
const test = getTestBuilder()
22+
.testProperty.set(value)
23+
.get();
24+
25+
expect(test).to.not.be.instanceOf(Test);
26+
expect(test.testProperty).to.be.equal(value);
27+
});
28+
});
29+
30+
describe('#build()', () => {
31+
it('should return a class instance with the set properties', () => {
32+
const value = 'foo';
33+
const test = getTestBuilder()
34+
.testProperty.set(value)
35+
.build();
36+
37+
expect(test).to.be.instanceOf(Test);
38+
expect(test.testProperty).to.be.equal(value);
39+
});
40+
});
41+
});
42+
43+
class Test {
44+
testProperty?: string;
45+
}
46+
47+
function getTestBuilder() {
48+
return fromClassObject(Test);
49+
}

src/builders/fromClassObject.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Builder } from '../types/Builder';
2+
import { ClassProperties } from '../types/ClassProperties';
3+
import { fromFactory } from './fromFactory';
4+
5+
export function fromClassObject<T extends object>(classConstructor: { new (...args: any[]): T }): Builder<ClassProperties<T>, T> {
6+
return fromFactory(properties => {
7+
const instance = Object.create(classConstructor.prototype);
8+
9+
return Object.assign(instance, properties);
10+
});
11+
}

src/builders/fromFactory.spec.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect } from 'chai';
2+
import { deepEqual, reset, spy, verify } from 'ts-mockito';
3+
import * as builder from '../makeBuilder';
4+
import { fromFactory } from './fromFactory';
5+
6+
describe('fromFactory()', () => {
7+
let spiedBuilder: typeof builder;
8+
let spiedFactory: typeof factoryWrapper;
9+
10+
beforeEach(() => {
11+
spiedBuilder = spy(builder);
12+
spiedFactory = spy(factoryWrapper);
13+
});
14+
15+
afterEach(() => {
16+
reset(spiedBuilder);
17+
reset(spiedFactory);
18+
});
19+
20+
it('should call fromFactory()', () => {
21+
getTestBuilder();
22+
23+
verify(spiedBuilder.makeBuilder(factoryWrapper.factory)).once();
24+
});
25+
26+
describe('#get()', () => {
27+
it('should return a plain object with the set properties', () => {
28+
const value = 'foo';
29+
const test = getTestBuilder()
30+
.testInputProperty.set(value)
31+
.get();
32+
33+
expect(test).to.not.be.instanceOf(TestResult);
34+
expect(test.testInputProperty).to.be.equal(value);
35+
});
36+
});
37+
38+
describe('#build()', () => {
39+
it('should return a class instance with the set properties', () => {
40+
const value = 'foo';
41+
const builder = getTestBuilder().testInputProperty.set(value);
42+
const test = builder.build();
43+
44+
verify(spiedFactory.factory(deepEqual(builder.get()))).once();
45+
46+
expect(test).to.be.instanceOf(TestResult);
47+
expect(test.testResultProperty).to.be.equal(value);
48+
});
49+
});
50+
});
51+
52+
interface TestInput {
53+
testInputProperty: string;
54+
}
55+
56+
class TestResult {
57+
constructor(readonly testResultProperty?: string) {}
58+
}
59+
60+
const factoryWrapper = {
61+
factory(input: Partial<TestInput>): TestResult {
62+
return new TestResult(input.testInputProperty);
63+
}
64+
};
65+
66+
function getTestBuilder() {
67+
return fromFactory(factoryWrapper.factory);
68+
}

0 commit comments

Comments
 (0)