-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
No type safety around test signatures #351
Comments
Hey, @Jameskmonger not sure we could get much of use at compile time for this but worth an investigation :) Maybe the better solution is pretest warning messages? |
What do you think of the suggestion above @Jameskmonger? |
I have found a solution to this @jamesrichford but it will need to go into |
See microsoft/TypeScript#1024 and http://stackoverflow.com/questions/43668884/get-signature-of-method-from-method-decorator Another solution would be to change signature to this: @TestCase([1, 2, null], "Some test case giving 1, 2, null") This would be much easier to do. |
@Jameskmonger looks good. What about for tests without test cases? |
@Test("Some test without arguments") |
@Jameskmonger I think you misunderstood. How would you make it type safe. We need to keep the description separate otherwise you'll have to repeat it for every test case or have inconsistent decorators. So given this, how will we say how many arguments the function should take if it doesn't have the |
Ah sorry yeah I understand. In hindsight I don't think we'll be able to get type safety around the Of course, we could always roll them into one and have a separate @TestDescription("Test without parameters")
@Test()
public shouldDoSomething() { }
@TestDescription("Test with parameters")
@Test(1, 3)
@Test(5, 10)
public shouldDoSomething(a: number, b: number) { } This way we can ensure that test parameters <-> signature parameters always match up |
Anyway, here is my implementation for interface TestCaseDecoratorImplementation<TProperty> {
(
target: object,
propertyKey: string,
descriptor?: TypedPropertyDescriptor<TProperty>
): void;
}
const TestCaseDecorator = <TArgs, TProperty>(args: TArgs): TestCaseDecoratorImplementation<TProperty> => {
return (
target: object,
propertyKey: string,
descriptor?: TypedPropertyDescriptor<TProperty>
) => { }
}
function TestCase<T1, T2>(args: [T1, T2]): TestCaseDecoratorImplementation<(arg1: T1, arg2: T2) => any>;
function TestCase<T1>(args: [T1]): TestCaseDecoratorImplementation<(arg1: T1) => any>;
function TestCase(args: Array<any>) {
return TestCaseDecorator(args);
}
class SomeClass {
@TestCase([ "cool value" ]) // ok
@TestCase([ 3 ]) // not ok
public stringTestCase(val: string) {}
@TestCase([ "ok", { val: 5 } ])
@TestCase([ false, false ]) // not ok
public twoValTestCase(first: any, second: { val: number }) {}
} |
By making function Test<T1, T2>(args: [T1, T2]): TestCaseDecoratorImplementation<(arg1: T1, arg2: T2) => any>;
function Test<T1>(args: [T1]): TestCaseDecoratorImplementation<(arg1: T1) => any>;
function Test(args?: Array<any>): TestCaseDecoratorImplementation<() => any> {
return TestCaseDecorator(args);
}
class SomeClass {
@Test([ "cool value" ]) // ok
@Test([ 3 ]) // not ok
public stringTestCase(val: string) {}
@Test([ "ok", { val: 5 } ])
@Test([false, false]) // not ok
@Test()
public twoValTestCase(first: any, second: { val: number }) { }
@Test(5, 10) // not ok
public noValTestCase() { }
} |
Probably even better might be to remove the array from the overloads and use the spread operator on the main function :) |
You can't type a spread operator from what I've seen @jamesrichford |
I'll show you what I mean either tomorrow or Monday as I'm away from my PC just now @Jameskmonger |
interface TestCaseDecoratorImplementation<TProperty> {
(
target: object,
propertyKey: string,
descriptor?: TypedPropertyDescriptor<TProperty>
): void;
}
const TestCaseDecorator = <TArgs, TProperty>(args: TArgs): TestCaseDecoratorImplementation<TProperty> => {
return (
target: object,
propertyKey: string,
descriptor?: TypedPropertyDescriptor<TProperty>
) => { }
}
function Test<T1, T2>(arg1: T1, arg2: T2): TestCaseDecoratorImplementation<(arg1: T1, arg2: T2) => any>;
function Test<T1>(arg1: T1): TestCaseDecoratorImplementation<(arg1: T1) => any>;
function Test(): TestCaseDecoratorImplementation<() => any>;
function Test(...args: Array<any>) {
return TestCaseDecorator(args);
}
class SomeClass {
@Test("cool value") // ok
@Test(3) // not ok
public stringTestCase(val: string) {}
@Test("ok", { val: 5 })
@Test(false, false) // not ok
public twoValTestCase(first: any, second: { val: number }) {}
@Test(5, 10) // ok?
public noValTestCase() { }
} this is roughly what I meant but interestingly looks like your suggestion for empty doesn't flag up an issue with the last scenario |
@Jameskmonger it looks like this declaration doesn't care about extra arguments which is causing the issue here because also the below is OK by the compiler @Test("cool value", 42) // ok?
public stringTestCase(val: string) {} |
That's expected behaviour @jamesrichford - the rest parameter allows for 0 children: function Foo(...args: Array<any>) {
return 0;
}
function Bar(args: Array<any>) {
return 1;
}
Foo(); // ok
Bar(); // Supplied parameters do not match any signature of call target. |
@Jameskmonger the problem isn't the 0 children thing it's the extra children that are allowed. This isn't actually to do with the spread operator as we can see this in two other scenarios function Test(): TestCaseDecoratorImplementation<() => any>;
function Test<T1>(args: [ T1 ]): TestCaseDecoratorImplementation<(arg1: T1) => any>;
function Test<T1, T2>(args: [ T1, T2 ]): TestCaseDecoratorImplementation<(arg1: T1, arg2: T2) => any>;
function Test<T1, T2>(args?: Array<any>) {
return TestCaseDecorator(arguments);
}
class SomeClass {
@Test([ "cool value" ]) // ok
@Test([ "cool value", 42 ]) // ok?
@Test([ 3 ]) // not ok
public stringTestCase(val: string) {}
@Test([ "ok", { val: 5 } ]) // ok
@Test([ false, false ]) // not ok
public twoValTestCase(first: any, second: { val: number }) {}
@Test([ 5 ]) // ok?
@Test() // ok
public noValTestCase() {
}
} function Test(): TestCaseDecoratorImplementation<() => any>;
function Test<T1>(arg1: T1): TestCaseDecoratorImplementation<(arg1: T1) => any>;
function Test<T1, T2>(args: T1, arg2: T2): TestCaseDecoratorImplementation<(arg1: T1, arg2: T2) => any>;
function Test<T1, T2>(arg1?: T1, arg2?: T2) {
return TestCaseDecorator(arguments);
}
class SomeClass {
@Test("cool value") // ok
@Test("cool value", 42) // ok?
@Test(3) // not ok
public stringTestCase(val: string) {}
@Test("ok", { val: 5 }) // ok
@Test(false, false) // not ok
public twoValTestCase(first: any, second: { val: number }) {}
@Test(5) // ok?
@Test() // ok
public noValTestCase() {
}
} Not had a chance to look at this a great deal but looks like on the face of it this may be a bug in TypeScript |
@Jameskmonger for clarity the issues are marked with the comment |
These compile where they shouldn't |
The below is something like what we need to do but not possible as things stand (see microsoft/TypeScript#2607) export function TestCase<Target extends { [PropertyKey in keyof Target]: Target[PropertyKey] },
PropertyKey extends keyof Target & string>(...testCaseArguments: Parameters<Target[PropertyKey]>) {
return (
target: Target,
propertyKey: PropertyKey,
descriptor?: TypedPropertyDescriptor<Target[PropertyKey]>
) => { |
Currently, these two compile fine:
It would be good to get compile-time safety around these.
The text was updated successfully, but these errors were encountered: