Typescript: setWorldConstructor with anonymous function too restrictive #1968
Labels
📖 documentation
Improvements or additions to documentation
❓ question
Consider using support forums: https://cucumber.io/tools/cucumber-open/support
👓 What did you see?
Whilst its not included in the documentation,
setWorldConstructor
can take either a class name (which is a function), or an anonymous function. This later ability is used numerous times in the features to set custom values on the world instance, conveniently ignoring to assign the props to the World instance, as its not relevant in the context of the test case.For example:
When using a custom class extending
World
, one would pass theprops
argument tosuper
so that the initial props are initialised on the instance.When using an anonymous function,
this
is a plain object, andprops
is an object containing the initial properties (attach, log and parameters).Without any type annotation, Typescript prevents assignment to
this
because it is implicitlyany
(assumingnoImplicitAny
is used intsconfig
.Without a type annotation one gets no protection against typos.
To ensure that Typescript has the correct type for
this
, the convention in Typescript is to annotatethis
as the first parameter, and use theIWorld
type to infer the correct members. One can use interface merging to add any custom properties.The problem here is that one would need to individually apply the props members to
this
or they get lost. But we can't use simple assignment as theIWorld
interface declares them asreadonly
. We can assign custom properties aside from those.Whilst the types allow for assignment to members of
this.parameters
, this is alterating a by reference object which will affect later scenarios.The only solution is to use
Object.assign
to apply the defaultprops
and any custom values, eg:Firstly, this just discards the type safety acquired so far.
Secondly, because
Object.assign
is a shallow copy, it also means that if we wnat to inject something intoparameters
it will affect subsequent scenarios (although World is recreated, we are working on the original (by reference) copy of the world parameters held by Cucumber from the CLI. We can work around this by using spread to create a new parameters object to override the one passed inprops
(necessary asassign
doesn't merge), thus:✅ What did you expect to see?
I think this is somewhat semantic, as if one had sufficiently complex functionality one probably would use a class instead of an anonymous function, but this seems to highlight a possibly unintended side effect of how the object is created when an anonymous function is used, given the restrictive typing of World (which is probably actually correct elsewhere).
For the most part I wanted to document it in case anyone else came across the same problem, rather than to propose/ask for a fix.
A solution that I can see is that if the instance created by the anonymous function is a plain object it could be merged with an instance derived from World, but that might have its own complicated side effects.
📦 Which tool/library version are you using?
🔬 How could we reproduce it?
See above examples
📚 Any additional context?
In my use case I was passing in the name of a class from the world parameters, and wanted to convert it to the constructor within the world parameters. The initial mystery was why the props were not being passed into the World instance (because of not using the World constructor), and then how to make changes to them once the types have been added.
The following is equivilent to how I dealt with it
The text was updated successfully, but these errors were encountered: