如何根据条件 return 属性 类型?
How can I return a property type based on a condition?
在我的 jasmine-auto-spies project, I have the following lines -
export type Spy<T> = T & {
[k in keyof T]: AsyncSpyFunction;
}
export interface AsyncSpyFunction extends jasmine.Spy {
(...params: any[]): any;
and: AsyncSpyFunctionAnd
}
export interface AsyncSpyFunctionAnd extends jasmine.SpyAnd {
nextWith(value: any): void;
nextWithError(value: any): void;
resolveWith(value: any): void;
rejectWith(value: any): void;
}
我想实现的是把一个class的所有功能都变成间谍。
所以我可以做到:
let myServiceSpy: Spy<MyService> = createSpyFromClass(MyService);
并获得一个既是 class 实例 又是 间谍的对象。
所以当我调用其中一种方法时 -
myServiceSpy.doSomething();
我也可以这样设置 -
myServiceSpy.doSomething.and.returnValue('someVal');
但是...我遗漏了两件事:
1。忽略 getter 和 setter 函数的能力
在类型方面,我想忽略 getter 和 setter,因为它们很容易通过设置 属性 来模拟。
目前,如果我尝试设置一个具有 setter 函数的 属性,TypeScript 会报错
MyService{
set url(value:string){
this._url = value;
}
}
let myServiceSpy: Spy<MyService> = createSpyFromClass(MyService);
在我的测试中,我尝试覆盖它 -
myServiceSpy.url = 'test';
我收到这个错误 -
Type '"test"' is not assignable to type 'string & AsyncSpyFunction'.
所以我希望能够忽略类型定义本身中的 setters 和 getters。
2。 Return 基于原始 return 类型的不同间谍类型
当原始函数 return 是 Observable 时,我希望能够 return ObservableSpy。
同样适用于 Promise 和其他异步类型。
他们每个人都有自己独特的方法。
目前我只使用一个 catch all 类型,它具有所有可能实现的间谍方法,但我更喜欢更具体。
假设我有以下 class -
class MyService{
getUsers():Observable<User[]> {
return Observable.of(users);
}
getToken():Promise<Token> {
return Promise.resolve(token);
}
}
在我的测试中,我正在设置 -
myServiceSpy.getUsers.and.nextWith(fakeUsers);
myServiceSpy.getToken.and.resolveWith(fakeToken);
我希望方法 nextWith
仅出现在 return Observables
的方法中
并且 resovleWith
仅针对 return 承诺的方法出现。
是否可以通过某种方式进行配置?
随着 typescript 2.8 的发布,您可以使用映射的条件类型来做到这一点:
import { Observable } from "rxjs";
export type Spy<T> = { [k in keyof T]: AddSpyTypes<T[k]> };
export type AddSpyTypes<T> = T extends (...args: any[]) => any
? AddSpyByReturnTypes<T>
: T;
export interface PromiseSpy<T> {
resolveWith(value: T): void;
rejectWith(value: any): void;
}
export interface ObservableSpy<T> {
nextWith(value: T): void;
nextWithError(value: any): void;
}
export type AddSpyOnFunction<T extends (...args: any[]) => R, R> = T & {
and: jasmine.Spy;
};
export type AddSpyOnPromise<T extends Promise<any>> = T & {
and: PromiseSpy<Unpacked<T>>;
};
export type AddSpyOnObservable<T extends Observable<any>> = T & {
and: ObservableSpy<Unpacked<T>>;
};
// Wrap the return type of the given function type with the appropriate spy methods
export type AddSpyByReturnTypes<
TF extends (...args: any[]) => any
> = TF extends (...args: any[]) => infer TR // returnes a function
? TR extends (...args: any[]) => infer R2
? AddSpyOnFunction<TR, R2> // returnes a Promise
: TR extends Promise<any>
? AddSpyOnPromise<TR> // returnes an Observable
: TR extends Observable<any> ? AddSpyOnObservable<TR> : TF
: never;
//github.com/Microsoft/TypeScript/issues/21705#issue-294964744
export type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U> ? U : T extends Observable<infer U> ? U : T;
旧的和过时的答案:
* 这不是一个可行的解决方案,而是朝着这个方向迈出的一步 *
我的直接想法是向 TS 提供更多数据。
这远非完美,我希望它可以通过 TS
获得更多
另一个问题是,我们不能(?)真正推断出 resolveWith/rejectWith 的类型。
如果你提供P和PO接口的props类型,而不仅仅是props名称,我们可以使用它们。
// p is interface with the the promise props, PO is an interface with the observer props
export type Spy<T, P, PO> = T & {
[k in keyof P]: AsyncSpyFunctionAnd<P[k]>;
} & {
[k in keyof PO]: AsyncSpyFunctionAndObserver<PO[k]>;
}
export interface AsyncSpyFunction<T> extends jasmine.Spy {
(...params: any[]): any;
and: AsyncSpyFunctionAnd<T>;
}
export interface AsyncSpyFunctionAnd<T> extends jasmine.SpyAnd {
resolveWith(value: T): void;
rejectWith(value: any): void;
}
export interface AsyncSpyFunctionAndObserver<T> extends jasmine.SpyAnd {
nextWith(value: T): void;
nextWithError(value: any): void;
}
在打字稿操场上:link
在我的 jasmine-auto-spies project, I have the following lines -
export type Spy<T> = T & {
[k in keyof T]: AsyncSpyFunction;
}
export interface AsyncSpyFunction extends jasmine.Spy {
(...params: any[]): any;
and: AsyncSpyFunctionAnd
}
export interface AsyncSpyFunctionAnd extends jasmine.SpyAnd {
nextWith(value: any): void;
nextWithError(value: any): void;
resolveWith(value: any): void;
rejectWith(value: any): void;
}
我想实现的是把一个class的所有功能都变成间谍。
所以我可以做到:
let myServiceSpy: Spy<MyService> = createSpyFromClass(MyService);
并获得一个既是 class 实例 又是 间谍的对象。
所以当我调用其中一种方法时 -
myServiceSpy.doSomething();
我也可以这样设置 -
myServiceSpy.doSomething.and.returnValue('someVal');
但是...我遗漏了两件事:
1。忽略 getter 和 setter 函数的能力
在类型方面,我想忽略 getter 和 setter,因为它们很容易通过设置 属性 来模拟。
目前,如果我尝试设置一个具有 setter 函数的 属性,TypeScript 会报错
MyService{
set url(value:string){
this._url = value;
}
}
let myServiceSpy: Spy<MyService> = createSpyFromClass(MyService);
在我的测试中,我尝试覆盖它 -
myServiceSpy.url = 'test';
我收到这个错误 -
Type '"test"' is not assignable to type 'string & AsyncSpyFunction'.
所以我希望能够忽略类型定义本身中的 setters 和 getters。
2。 Return 基于原始 return 类型的不同间谍类型
当原始函数 return 是 Observable 时,我希望能够 return ObservableSpy。
同样适用于 Promise 和其他异步类型。
他们每个人都有自己独特的方法。
目前我只使用一个 catch all 类型,它具有所有可能实现的间谍方法,但我更喜欢更具体。
假设我有以下 class -
class MyService{
getUsers():Observable<User[]> {
return Observable.of(users);
}
getToken():Promise<Token> {
return Promise.resolve(token);
}
}
在我的测试中,我正在设置 -
myServiceSpy.getUsers.and.nextWith(fakeUsers);
myServiceSpy.getToken.and.resolveWith(fakeToken);
我希望方法 nextWith
仅出现在 return Observables
并且 resovleWith
仅针对 return 承诺的方法出现。
是否可以通过某种方式进行配置?
随着 typescript 2.8 的发布,您可以使用映射的条件类型来做到这一点:
import { Observable } from "rxjs";
export type Spy<T> = { [k in keyof T]: AddSpyTypes<T[k]> };
export type AddSpyTypes<T> = T extends (...args: any[]) => any
? AddSpyByReturnTypes<T>
: T;
export interface PromiseSpy<T> {
resolveWith(value: T): void;
rejectWith(value: any): void;
}
export interface ObservableSpy<T> {
nextWith(value: T): void;
nextWithError(value: any): void;
}
export type AddSpyOnFunction<T extends (...args: any[]) => R, R> = T & {
and: jasmine.Spy;
};
export type AddSpyOnPromise<T extends Promise<any>> = T & {
and: PromiseSpy<Unpacked<T>>;
};
export type AddSpyOnObservable<T extends Observable<any>> = T & {
and: ObservableSpy<Unpacked<T>>;
};
// Wrap the return type of the given function type with the appropriate spy methods
export type AddSpyByReturnTypes<
TF extends (...args: any[]) => any
> = TF extends (...args: any[]) => infer TR // returnes a function
? TR extends (...args: any[]) => infer R2
? AddSpyOnFunction<TR, R2> // returnes a Promise
: TR extends Promise<any>
? AddSpyOnPromise<TR> // returnes an Observable
: TR extends Observable<any> ? AddSpyOnObservable<TR> : TF
: never;
//github.com/Microsoft/TypeScript/issues/21705#issue-294964744
export type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U> ? U : T extends Observable<infer U> ? U : T;
旧的和过时的答案:
* 这不是一个可行的解决方案,而是朝着这个方向迈出的一步 *
我的直接想法是向 TS 提供更多数据。 这远非完美,我希望它可以通过 TS
获得更多另一个问题是,我们不能(?)真正推断出 resolveWith/rejectWith 的类型。
如果你提供P和PO接口的props类型,而不仅仅是props名称,我们可以使用它们。
// p is interface with the the promise props, PO is an interface with the observer props
export type Spy<T, P, PO> = T & {
[k in keyof P]: AsyncSpyFunctionAnd<P[k]>;
} & {
[k in keyof PO]: AsyncSpyFunctionAndObserver<PO[k]>;
}
export interface AsyncSpyFunction<T> extends jasmine.Spy {
(...params: any[]): any;
and: AsyncSpyFunctionAnd<T>;
}
export interface AsyncSpyFunctionAnd<T> extends jasmine.SpyAnd {
resolveWith(value: T): void;
rejectWith(value: any): void;
}
export interface AsyncSpyFunctionAndObserver<T> extends jasmine.SpyAnd {
nextWith(value: T): void;
nextWithError(value: any): void;
}
在打字稿操场上:link