Up to Geb version 0.10 the example code below has worked just fine:
package whatever
import geb.Module
import geb.Page
import geb.spock.GebSpec
class ExampleSpec extends GebSpec {
def 'MODULE - Y U NO HAVE THE RIGHT CLASS?'() {
when:
ExamplePage page = to ExamplePage
then:
verifySomething(page.theModule)
}
boolean verifySomething(ExampleModule module) {
// ...
}
}
class ExamplePage extends Page {
static content = {
theModule { module ExampleModule }
}
}
class ExampleModule extends Module {
}
I wanted upgrade to the latest 0.13.1 but apparently the breaking (regressive I would say) change has been introduced which results with:
groovy.lang.MissingMethodException: No signature of method:
geb.navigator.NonEmptyNavigator.verifySomething() is applicable for
argument types: (geb.content.TemplateDerivedPageContent) values:
[whatever.ExamplePage -> theModule: whatever.ExampleModule]
I've noticed that the same happens but with different class since version 0.11, the exception message is as follows:
groovy.lang.MissingMethodException: No signature of method:
geb.navigator.NonEmptyNavigator.verifySomething() is applicable for
argument types: (geb.content.SimplePageContent) values: [theModule -
SimplePageContent (owner: whatever.ExamplePage, args: [], value:
null)]
Why module declared with a given, specific class looses it at runtime? How to prevent that?
Objects implementing Navigator interface (which includes classes extending from Module) and returned from content definitions are wrapped with TemplateDerivedPageContent objects which delegate to the underlying object but also allow to produce a meaningful path to the object for error reporting.
The wrapping of modules worked in older versions of Geb, then it got inadvertently removed and now it's back. Even though you can still call all the methods of the module when it's wrapped thanks to TemplateDerivedPageContent dynamically delegating to the underlying object you run into trouble in cases like yours - when you want to strongly type your code that uses modules. Therefore I'm still undecided what we should sacrifice here - better error reporting or ability to strongly type and this wrapping might be removed in a future version of Geb.
Luckily there is a workaround - if you want to strongly type code that consumes modules then use a getter instead of a content definition to declare them. In your case it would be:
class ExamplePage extends Page {
ExampleModule getTheModule() {
module ExampleModule
}
}
Related
I'm trying to build a sort of model Factory in Typescript.
I'm receiving a string parameter from an API call and I would like to istantiate a new object depending on the received value.
Here you can find a simple example of what I would like to accomplish:
/classes/ClassA.ts
export class ClassA {
doSomething() {
console.log("ClassA");
}
}
/classes/ClassB.ts
export class ClassB {
doSomething() {
console.log("ClassB");
}
}
/classes/index.ts
import { ClassA } from './ClassA';
import { ClassB } from './ClassB';
export { ClassA, ClassB }
Now, I would like to import all classes exported from index.ts (this file will be automatically updated when a new Class is being created) and run doSomething() on a class depending on a variable value:
/index.ts
import * as Classes from './classes';
const className: string = "ClassA";
new Classes[className]().doSomething()
In visualStudioCode I don't get any error, but at compile time I get:
error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'typeof import("/testApp/src/tmp/classes/index")'.
Even changing className to "any" gives the same result.
If I remove className type
const className = "ClassA";
it works without any issue but I cannot proceed in this direction because received value is "typed" as string.
I know that prepending istantiation code with
// #ts-ignore
It works but I would like to avoid this kind of "tricks"
So, what would it be the correct way to type className getting it's possible values from the imported ts?
Thanks
Micko
I am writing a node app in typescript. I have written a class decorator #myDecorator, and the purpose of #myDecorator is such that I need to keep track of all the classes to which it's applied.
My question: how do I make sure all of those decorated classes are loaded before making use of that behavior? Some example code will help to make this more concrete:
Declaration of the decorator in file myDecorator.ts:
type ConstructorType = { new (...args: any[]): any };
// keep track of decorated classes
const registeredClasses: Map<string, ConstructorType> = new Map();
// class decorator
export function myDecorator<T extends ConstructorType>(targetConstructor: T) {
// create the modified class
const newClass = class extends targetConstructor { /* ... */ }
// register the modified class
registeredClasses.set(targetConstructor.name, newClass);
// and return it
return newClass;
}
export function doSomethingWithMyDecoratedClasses() {
//... some behavior that relies on `registeredClasses`
}
Declaration of a decorated class in file someClass.ts
import {myDecorator} from 'myDecorator.ts'
#myDecorator
class SomeClass { /* ... */ }
Making use of doSomethingWithMyDecoratedClasses in anotherFile.ts:
import { doSomethingWithMyDecoratedClasses } from 'myDecorator.ts`
//...
doSomethingWithMyDecoratedClasses()
//...
The problem is that I need to make sure that SomeClass has been added to registeredClasses before I make this call to doSomethingWithMyDecoratedClasses. And, in fact, I've written a number of such classes in my app, and I need to make sure they are all registered.
My current best understanding is that I need to call import 'someClass.ts' in anotherFile.ts (and, in fact, import all files where decorated classes are declared), so really I need to import someClass1.ts, import someClass2.ts, ...
Is that the best/only approach? Is there a recommended pattern for doing so?
Most applications have an index file that is responsible for importing the top level things. If you import doSomethingWithMyDecoratedClasses there, you'll guarantee that everything else is imported first.
An alternative would be to not call it in the root level of a module, and instead wait for an event.
The Problem
I am importing a class in two related projects. It appears that the prototype chain of a class instance created in one project does not match the version of the class in the other project.
Specifically, instanceof returns false.
The Code
I have three npm packages / projects (names invented for simplicity):
classes (contains Payload)
processors (contains ProcessPayload); depends on classes
main (not actually a package); depends on classes and processors
The main project creates a Payload and passes it to a ProcessPayload object.
To be clear: these projects have three separate package.json and any associations between them are done via dependencies.
main/index.js
import { Payload } from 'classes'
import { PayloadProcessor } from 'processors'
const payload = new Payload()
const processor = new PayloadProcessor()
console.log(payload instanceof Payload) // true
processor.process(payload)
The ProcessPayload object imports Payload and uses it to validate the type of its parameters.
processors/PayloadProcessor.js
import { Payload } from 'classes'
class PayloadProcessor {
process = (obj) => {
console.log(obj instanceof Payload) // false
}
}
export default PayloadProcessor
The Question
What is going on with the prototype chains here. I plan to use duck typing and create an isPayload method to solve my problem, but are there really two versions of Payload floating around in my dependency stack? Is there any way to get instanceof to work as expected in this situation?
I have just come across an interesting gotcha where optional arguments on an interface and the implementing class can conflict.
I found this out the hard way (school boy error) whilst experimenting. You cannot spot it in the debugger and I assumed it was me messing up the dependency injection.
I'm guessing this is so an alternative interface can give a differing view on what default behaviour should be?
Is there a compiler warning or style cop rule to help point this out?
public interface MyInterface
{
MyStuff Get(bool eagerLoad = true); //this overrules the implementation.
}
public class MyClass : MyInterface
{
public MyStuff Get(bool eagerLoad = false) //will still be true
{
//stuff
}
}
Remember default arguments are a compile-time feature. The compiler picks up the default argument based on the static type of the reference in question and inserts the appropriate default argument. I.e. if you reference is of the interface type you get one behavior but if the reference is of the class type you get the other in your case.
I am in the process of porting my JS+requirejs code to typescript+requirejs.
One scenario I haven't found how to handle is circular dependencies.
Require.js returns undefined on modules that are also dependent on the current and to solve this problem you can do:
MyClass.js
define(["Modules/dataModel"], function(dataModel){
return function(){
dataModel = require("Modules/dataModel");
...
}
});
Now in typescript, I have:
MyClass.ts
import dataModel = require("Modules/dataModel");
class MyClass {
dataModel: any;
constructor(){
this.dataModel = require("Modules/dataModel"); // <- this kind of works but I lose typechecking
...
}
}
How to call require a second time and yet keep the type checking benefits of typescript? dataModel is a module { ... }
Specify the type using what you get from import i.e
import dataModelType = require("Modules/dataModel");
class MyClass {
dataModel: typeof dataModelType;