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:
@@ -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;
|
||||
|
||||
111
libs/angular/src/platform/utils/safe-provider.type.spec.ts
Normal file
111
libs/angular/src/platform/utils/safe-provider.type.spec.ts
Normal 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,
|
||||
});
|
||||
Reference in New Issue
Block a user