Coder Social home page Coder Social logo

simonegianni / tsmatchers Goto Github PK

View Code? Open in Web Editor NEW
4.0 4.0 0.0 832 KB

A set of typesafe typescript matchers inspired by Hamcrest matchers for junit, to be used for easier and more descriptive APIs and test assertions.

License: Apache License 2.0

TypeScript 99.83% JavaScript 0.17%

tsmatchers's People

Contributors

dependabot[bot] avatar simonegianni avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

tsmatchers's Issues

Improve on array matching

Add methods like:

  1. Multiple matchers, each matching a specific item .. in simplest case is.array.matching(1,2,3)
  2. Implement strictly, it will require all elements to match with no additional elements before or after
  3. Chain multiple array method, as if they were in an either(...).and(

Point 3 could be extended to all other matchers, by always returning an "and" matcher factory, allowing syntaxes like is.array.withLength(5).containing(1) as well as is.number.greaterThan(3).lessThan(5) etc..

Better coordination between array and object matching

Currently object.matching will propagate to sub objects, even passing the strictly flag. This however does not happen with array.matching .. it does not propagate to other arrays, nor to objects.

It would be nice if both components would propagate to the other, so that one can write is.array.matching([{a:1}]) instead of is.array.matching([is.object.matching({a:1})]).

Moreover, equality in objects and arrays is given by strictly matching, given that if a user wants to check for instance equality he can use is.exactly. So, current implementation of array and object equality, including signature, could be migrated to object and array strictly matching.

`check` goes by default into the promise overload

When writing something like the following:

function getSomething<T>() :T {...};

// ...

check(getSomething(), is.object.matching({...}));

Typescript will infer this signature:

(alias) check<unknown>(obj: Promise<unknown>, matcher: unknown): Promise<unknown> (+4 overloads)
import check

Because it will for some reason infer:

(alias) getSomething<Promise<never>>(): Promise<never>

This is a problem cause it triggers eslint rule about promises having to be awaited, and each check in the tests gets the warning even when it is not actually dealing with a promise but merely with an inferred type.

This could happen because of the order of the check overloads, having the promise based ones before the others.

Check:

  1. Why such ordering is necessary, eventually if inverted what would stop working properly (I suspect await check(async () => ..., is.throwing(...))
  2. If it's possible to use a negated promise type to force typescript to use another overload unless the method is really really returning a promise

Improve object.matching error report

Currently it is almost unreadable in a vscode error report.

It should look something like this:

{
  a: 1, // OK, expecting a number
  b: 5, // ----> ERROR: was expecting a string
  c: { // OK, expecting an object 
    d:1, 
    e:4,
  },
  f: { // ----> ERROR
    g: 3, // OK
    h: 7, // ----> ERROR: was expecting 9
  }
}

Containers do not compose correctly on and.not.string

Containers are meant to group and wrap matchers.

More specifically, in the simplest case, a container is just a collection of matchers, plus an optional basic function, as in:

is.string(); // Basic function, resolves to aString matcher
is.string.containing("ciao"); // As collection of matchers, resolves to stringContains

Note that containers are also nested, is in the example above is itself a container.

In the most complex case, a container is a wrapper for another container, transforming the matcher, for example:

is.not.string(); // not is a wrapper around the entire "is" container, and transforms the "aString" matcher into not(aString)

This second case is used:

  1. For the "not" container
  2. For the "strictly.object" combination
  3. For the ".and." and ".or." constructs

The problem arises when multiple of the above constructs are used, for example in the following case:

assert("And combined", "ma", is.string().and.not.string.containing("pippo"));

This should obviously pass, but instead it fails because it completely misses the "not". What actually happens is that the ".and." wrapper end up "overwriting" the "not" one.

This is because of how the Containers are combined.

Given that we are dealing with javascript getters here, we can't wrap the results as if it was written in a composing style:

and(is.string(), not(string.containing("pippo"))

Instead, when "and" is called, it returns a new wrapping container, and when "not" is called another one, which is not correctly composed with the previous one.

The naive solution of always composing the containers while wrapping them:

receiveWrappedSub(name: string, sub: ContainerObj, register: boolean) {
  if (this[name]) return;
  let wrapFn = this._wrapFn;
  if (sub._wraps) {
    // Compose them
    wrapFn = (m) => this._wrapFn(sub._wrapFn(m));
  }
  var wrapper = sub.createWrapper(wrapFn, register);
  this.registerSub(name, wrapper, register);
}

ends up composing the "not" in two places in this simpler cases:

assert("And combined", 4.5, is.lessThan(5).and.not.greaterThan(4.7)); 
assert("And combined", "ma", is.string().and.not.number()); 

So the issue here is that when using two wrapping containers ending up calling a matcher inside a simple container there is a problem in executing all the wrapped functions, because the last simple container receives only one of the wrapper functions, while instead in the other case the two manage to delegate one to the other.

I think the whole container objects part should be rewritten, as it looks messy and undocumented.

We could consider using other techniques, such as saving a state between getters and resetting it on the root container (when is is used) and storing all the wrapping functions in this state, or returning proxies or similar.

Offer polluting import

With current TS versions, polluting the test global namespace is not an issue anymore because Facebook did it in Jest so now it's totally ok.

Offer an import that pollutes the global with all the checkers and all the functions and everything.

Object matching, accepting matcher<any> hinders type safety

It is sometimes wanted to write an object match as follows:

let a = {a:1};

// Undefineds
check(a, is.object.matching({a:1, b:undefined}));
check(a, is.object.matching({a:1, b:is.undefined}));

// Other 
check(a, is.object.matching({a:1, b:is.falsey}));
check(a, is.object.matching({a:1, b:is.not.number}));

To support the first two cases, we added [index:string]: undefined|Matcher<undefined>|MatcherFactory<undefined>|never. This keeps type safety because the following would give compile error:

check(a, is.object.matching({a:1, b: is.number}));
check(a, is.object.matching({a:1, b: "ciao"}));

However, to support the other two cases, we had to add also Matcher<any>|MatcherFactory<any>, which kills the type safety completely.

We should remove those, and accept only strictly undefined, an the user can still disable type check or use other constructs:

// Disable typecheck on second check
check(a, is.object.matching({a:1}).and.object.matching<any>({b:is.not.number}));

// Use withkeys
check(a, is.object.matching({a:1}).and.not.object.withKeys("b"));

Extend fluent syntax to other cases

Since now either is gone, and .and. and .or. as a nice syntax, with better composition support from #27 we may extend such syntax to other cases:

check(x, is.array.containing(is.greaterThan(11)));
check(x, is.array.containing.greaterThan(11));
check(x, is.array.eachItem(is.greaterThan(9)));
check(x, is.array.eachItem.greaterThan(9));

More in general, whenever a matcher function is accepting another matcher, it could return instead a container wrapping the IsInterface (or a subset of it, we could create an IsNumberInterface for specific numbering cases for example).

Type safety should also be considered, in the above examples:

const x :string[] = [];
check(x, is.array.eachItem(is.greaterThan(9))); // This will give error
check(x, is.array.eachItem.greaterThan(9)); // This should also

Make the matcher interface empty

After each matcher exposes continuation methods like .and. and .or., having in the matcher the methods matches and describe is rather unpleasant.

To remove them there are two strategies:

  1. Declare a new "external matcher" interface, use that in all user facing functions
  2. Move those methods to be abstract into BaseMatcher, use BaseMatcher wherever is needed inside

The first solution is cleaner but probably also harder.

Support for date comparison

Dates have odd behaviours in JS. Specifically, they can be used with <, >, <= and >= operators, cause they get in that case automatically converted to a simple timestamp. However, that does not apply to ==, which consider two identical dates actually different cause they are two instances, even if the === operator should be the correct one in that case.

TsMatchers should:

  1. Support the simple is.equal for dates, that works around this problem and compares actual timestamps.
  2. Still support strict equality in is.strictly.equal and in is.exactly
  3. Offer an is.date object allowing for some methods similar to number ones (before, after, close to with a selection of year, month, day, hour etc..)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.