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:
- For the "not" container
- For the "strictly.object" combination
- 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.