1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[AC-2356] Use safeProvider in web core services module (#8521)

* Also add tests
* Exclude type (compile-time) tests from jest config
This commit is contained in:
Thomas Rittson
2024-04-08 07:59:12 +10:00
committed by GitHub
parent 216bbdb44c
commit 26226c4090
4 changed files with 251 additions and 92 deletions

View File

@@ -85,9 +85,25 @@ type SafeConcreteProvider<
deps: D;
};
/**
* If useAngularDecorators: true is specified, do not require a deps array.
* This is a manual override for where @Injectable decorators are used
*/
type UseAngularDecorators<T extends { deps: any }> = Omit<T, "deps"> & {
useAngularDecorators: true;
};
/**
* Represents a type with a deps array that may optionally be overridden with useAngularDecorators
*/
type AllowAngularDecorators<T extends { deps: any }> = T | UseAngularDecorators<T>;
/**
* A factory function that creates a provider for the ngModule providers array.
* This guarantees type safety for your provider definition. It does nothing at runtime.
* This (almost) guarantees type safety for your provider definition. It does nothing at runtime.
* Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator,
* however this cannot be enforced by the type system and will not cause an error if the decorator is not used.
* @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] })
* @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.)
* @returns The exact same object without modification (pass-through).
*/
@@ -113,10 +129,10 @@ export const safeProvider = <
DConcrete extends MapParametersToDeps<ConstructorParameters<IConcrete>>,
>(
provider:
| SafeClassProvider<AClass, IClass, DClass>
| AllowAngularDecorators<SafeClassProvider<AClass, IClass, DClass>>
| SafeValueProvider<AValue, VValue>
| SafeFactoryProvider<AFactory, IFactory, DFactory>
| AllowAngularDecorators<SafeFactoryProvider<AFactory, IFactory, DFactory>>
| SafeExistingProvider<AExisting, IExisting>
| SafeConcreteProvider<IConcrete, DConcrete>
| AllowAngularDecorators<SafeConcreteProvider<IConcrete, DConcrete>>
| Constructor<unknown>,
): SafeProvider => provider as SafeProvider;

View File

@@ -0,0 +1,111 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// This rule bans @ts-expect-error comments without explanation. In this file, we use it to test our types, and
// explanation is provided in header comments before each test.
import { safeProvider } from "./safe-provider";
class FooFactory {
create() {
return "thing";
}
}
abstract class FooService {
createFoo: (str: string) => string;
}
class DefaultFooService implements FooService {
constructor(private factory: FooFactory) {}
createFoo(str: string) {
return str ?? this.factory.create();
}
}
class BarFactory {
create() {
return 5;
}
}
abstract class BarService {
createBar: (num: number) => number;
}
class DefaultBarService implements BarService {
constructor(private factory: BarFactory) {}
createBar(num: number) {
return num ?? this.factory.create();
}
}
abstract class FooBarService {}
class DefaultFooBarService {
constructor(
private fooFactory: FooFactory,
private barFactory: BarFactory,
) {}
}
// useClass happy path with deps
safeProvider({
provide: FooService,
useClass: DefaultFooService,
deps: [FooFactory],
});
// useClass happy path with useAngularDecorators
safeProvider({
provide: FooService,
useClass: DefaultFooService,
useAngularDecorators: true,
});
// useClass: expect error if implementation does not match abstraction
safeProvider({
provide: FooService,
// @ts-expect-error
useClass: DefaultBarService,
deps: [BarFactory],
});
// useClass: expect error if deps type does not match
safeProvider({
provide: FooService,
useClass: DefaultFooService,
// @ts-expect-error
deps: [BarFactory],
});
// useClass: expect error if not enough deps specified
safeProvider({
provide: FooService,
useClass: DefaultFooService,
// @ts-expect-error
deps: [],
});
// useClass: expect error if too many deps specified
safeProvider({
provide: FooService,
useClass: DefaultFooService,
// @ts-expect-error
deps: [FooFactory, BarFactory],
});
// useClass: expect error if deps are in the wrong order
safeProvider({
provide: FooBarService,
useClass: DefaultFooBarService,
// @ts-expect-error
deps: [BarFactory, FooFactory],
});
// useClass: expect error if no deps specified and not using Angular decorators
// @ts-expect-error
safeProvider({
provide: FooService,
useClass: DefaultFooService,
});