ts-spec icon indicating copy to clipboard operation
ts-spec copied to clipboard

feature request: assignability

Open mscharley opened this issue 2 years ago • 5 comments

I would like a way to write a test like the following:

interface Foo {
  foo: string | number;
}

test("{ foo: 10 } is assignable to Foo", (t) => t.assignable({ foo: 10 })<Foo>());

This seems to be subtly different to extends and includes in that extends tests that A is a strict superset of B instead of testing that A is sufficient to satisfy B.

mscharley avatar Mar 06 '23 00:03 mscharley

Hi,

Can you provide an example of a test that currently fails when you use t.extends or t.includes but you would want to see pass? Your example would pass with t.extends, so I am not sure what strictness check you would want to see disabled.

I am making assertions more and more strict with each version, so this isssue is welcome because I need to think about user friendly ways to opt-out.

The use case I want to optimise the library and provide the most sensible defaults for is checking that this crazy utility type you have written produces the type you intended to produce and exactly that, for all edge cases.

I am not a fan of adding assertions, especially if the difference between them is subtle, because it can easily get combinatorial and confusing. Currently I think the API is good: we have symetric tests with equal, asymetric tests with extends and includes and unary tests with any, unknwon, never, true and false.

I am more erring on the side of giving the existing assertions a bounded context via configuration. There is already a way to configure ts-spec globally with module augmentation and a Config interface (it's not in the doc yet, I mention it in this comment).

An improvement would be to make it possible to explicitly pass a config object to test so that different rulesets can be used alongside each other.

Maybe is it possible to come up with a ruleset that captures assignability. Something like:

const assignabilityConfig = {
   strictAnyNeverUnknown: false,
   strictOptionalProperties: false,
   strictReadonlyProperties: false,
   excessPropertyChecking: true // or false
};

const testAssignability = test.withConfig(assignabilityConfig);

testAssignability('The function F would accept T as an input', t =>
    t.extends<T, Parameters<F>[0]>()
)

geoffreytools avatar Mar 06 '23 12:03 geoffreytools

You know what, I'm not sure what the actual issue was. I just tried uncommenting it and it's magically working now. For reference sake, this is the test I had issues with though:

export interface TraceMetadata {
    traceId?: string | undefined;
    spanId?: string | undefined;
}

export type InternalQueueJob<T extends JSONObject> = T & {
  _newrelic?: TraceMetadata;
};

export type JSONScalar = null | undefined | string | number | boolean;
export type JSONArray = JSONValue[];
export type JSONObject = Record<string, JSONValue>;
export type JSONValue = JSONScalar | { [x: string]: JSONValue } | JSONValue[];

test("InternalQueueJob is a JSONObject", (t) => t.extends<InternalQueueJob<JSONObject>, JSONObject>());

It's possible there was a compilation error elsewhere that was messing up my original attempt.

mscharley avatar Mar 07 '23 23:03 mscharley

Be careful because { _newrelic?: TraceMetadata | undefined } only extends JSONObject if TraceMetadata is a type alias as per TS rules, because interfaces don't get an implicit index signature and JSONObject recursively has one. It's possible that you ran in a situation where this matters.

geoffreytools avatar Mar 08 '23 08:03 geoffreytools

That would go a long way to explaining some inconsistent behaviour I've been seeing lately. Thanks for the heads up.

mscharley avatar Mar 09 '23 08:03 mscharley

I don't remember what the current behaviour of ts-spec is in regard to interfaces vs objects but I corrected a few problems in the latest version, which is not yet public. I can't disambiguate an interface without converting it to an object, so the edge case of index signatures needs to be properly handled. I want to add more tests before publishing though.

geoffreytools avatar Mar 09 '23 08:03 geoffreytools