Skip to content

Commit

Permalink
test(types): add initial typesafety verification testing
Browse files Browse the repository at this point in the history
Using dtslint to ensure that typesafety guarentees do not have any unintentional changes or
regressions
  • Loading branch information
squirly committed Sep 30, 2018
1 parent 8810690 commit 3ced3c5
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 27 deletions.
62 changes: 38 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,60 +32,74 @@ npm install @squirly/di
import {Binding, Container, Injectable} from '@squirly/di';

const AuthenticationKey = Binding<string>('AuthenticationKey');
const ReversedKey = Binding<string>('ReversedKey');
interface Fetcher {
fetch: () => Promise<{data: string}>;
}
const Fetcher = Binding<Fetcher>('Fetcher');

@Injectable
class Client {
static Tag = Binding.Tag<Client>('Client');
static Inject = Injectable.Resolution(ReversedKey);
static Inject = Injectable.Resolution(Fetcher);

constructor(private readonly key: string) {}
constructor(private readonly fetcher: Fetcher) {}

getData(): string {
return `Calling API with '${this.key}'`;
getData(): Promise<string> {
return this.fetcher
.fetch()
.then(result => `Received result: '${result.data}'`);
}
}

async function fetcherFactory(c: Container<string>): Promise<Fetcher> {
const key = await c.resolve(AuthenticationKey);

return {
fetch: () =>
Promise.resolve({
data: `Called with AuthenticationKey "${key}"`,
}),
};
}

const container = Container.create()
.bindConstant(AuthenticationKey, 'my-secret-key')
.bindSingletonFactory(ReversedKey, async c =>
Array.from(await c.resolve(AuthenticationKey))
.reverse()
.join(''),
)
.bindSingletonFactory(Fetcher, fetcherFactory)
.bindService(Client, Client);

container.resolve(Client).then(client => {
client.getData(); // returns "Calling API with 'yek-terces-ym'"
});
const clientResult = container.resolve(Client).then(client => client.getData());

// Logs 'Received result: Called with AuthenticationKey "my-secret-key"';
clientResult.then(console.log);
```

### Module

Using the definitions above, a `Module` can be created.

```typescript
import {Binding, Container, Injectable} from '@squirly/di';
import {Module} from '@squirly/di';

const module = Module.create(
Container.create()
.bindConstant(AuthenticationKey, 'my-secret-key')
.bindSingletonFactory(ReversedKey, async c =>
Array.from(await c.resolve(AuthenticationKey))
.reverse()
.join(''),
),
).export(ReversedKey);
.bindSingletonFactory(Fetcher, fetcherFactory),
).export(Fetcher);

const container = Container.create()
const moduleContainer = Container.create()
.importModule(module)
.bindService(Client, Client);

container.resolve(Client).then(client => {
client.getData() // returns "Calling API with 'yek-terces-ym'"
moduleContainer.resolve(Client).then(client => {
client.getData(); // returns "Calling API with 'yek-terces-ym'"
});

container.resolve(AuthenticationKey); // Promise rejected with MissingDependencyError('Could not find dependency bound to AuthenticationKey.')
// $ExpectError
const resolution = moduleContainer.resolve(AuthenticationKey);
// Type 'string' is not assignable to 'Fetcher | Client'

// Logs "MissingDependencyError('Could not find dependency bound to AuthenticationKey.')"
clientResult.catch(console.log);
```

## Maintainers
Expand Down
110 changes: 110 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"types": "lib/index.d.ts",
"scripts": {
"check": "run-p commitlint lint build:check test",
"lint": "tslint --project .",
"lint:fix": "tslint --project . --fix",
"lint": "tslint --project . && (cd typeTests && tslint --project .)",
"lint:fix": "tslint --project . --fix && (cd typeTests && tslint --project . --fix)",
"test": "jest --coverage",
"test:watch": "jest --watch",
"prebuild": "rm -rf lib",
Expand All @@ -39,6 +39,7 @@
"@types/jest": "23.3.2",
"commitizen": "2.10.1",
"cz-conventional-changelog": "2.1.0",
"dtslint": "0.3.0",
"jest": "23.6.0",
"jest-junit": "5.2.0",
"npm-run-all": "4.1.3",
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"noImplicitReturns": true,
"experimentalDecorators": true,
"types": ["jest"]
}
},
"include": ["src/**/*"]
}
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": ["tslint:recommended", "tslint-config-prettier"],
"rulesDirectory": ["tslint-plugin-prettier"],
"rules": {
"file-name-casing": false,
"interface-name": false,
"max-classes-per-file": false,
"member-access": false,
Expand Down
28 changes: 28 additions & 0 deletions typeTests/Container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Binding, Container} from '@squirly/di';
import {C} from './Dependencies';
import {
Service1,
Service2,
Service3Invalid,
Service4Invalid,
Service5,
Service6Invalid,
Service7,
Service8Invalid,
} from './Injectable';

const binding = Binding<{}>('binding');

const container = Container.create()
.bindService(binding, Service1)
.bindService(Service2, Service2)
// TODO: $ExpectError
.bindService(C, Service3Invalid)
// TODO: $ExpectError
.bindService(binding, Service4Invalid)
.bindService(binding, Service5)
// TODO: $ExpectError
.bindService(binding, Service6Invalid)
.bindService(binding, Service7)
// TODO: $ExpectError
.bindService(binding, Service8Invalid);
31 changes: 31 additions & 0 deletions typeTests/Dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {Binding} from '@squirly/di';

// Bound types

export type A = string & {_: 'A'};
export type A0 = string & {_: 'A'; '1': 'A'};
export const A = Binding<A>('A');

export type B = string & {_: 'B'};
export const B = Binding<B>('B');

export type C = string & {_: 'C'};
export const C = Binding<B>('C');

// Injectable values

let a: A = 'a' as A;
let a0: A0 = 'a0' as A0;
let b: B = 'b' as B;
let c: C = 'c' as C;

// Assignability tests

a = a0;
a = b; // $ExpectError
a = c; // $ExpectError
a0 = a; // $ExpectError
b = a; // $ExpectError
b = c; // $ExpectError
c = a; // $ExpectError
c = b; // $ExpectError
Loading

0 comments on commit 3ced3c5

Please sign in to comment.