mirror of
https://github.com/MontFerret/ferret.git
synced 2025-07-03 00:46:51 +02:00
* Added support of pre-compiled eval expressions * Added unit tests for eval.Function * Added RemoteType and RemoteObjectType enums * Refactored function generation * Refactored Document and Element loading logic * Removed redundant fields from cdp.Page * Exposed eval.Runtime to external callers * Added new eval.RemoteValue interface
465 lines
12 KiB
Go
465 lines
12 KiB
Go
package eval
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mafredri/cdp/protocol/runtime"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
"github.com/MontFerret/ferret/pkg/drivers"
|
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
|
)
|
|
|
|
func TestFunction(t *testing.T) {
|
|
Convey("Function", t, func() {
|
|
Convey(".AsAsync", func() {
|
|
Convey("Should set async=true", func() {
|
|
f := F("return 'foo'").AsAsync()
|
|
args := f.eval(EmptyExecutionContextID)
|
|
|
|
So(*args.AwaitPromise, ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey(".AsSync", func() {
|
|
Convey("Should set async=false", func() {
|
|
f := F("return 'foo'").AsAsync()
|
|
args := f.eval(EmptyExecutionContextID)
|
|
|
|
So(*args.AwaitPromise, ShouldBeTrue)
|
|
|
|
args = f.AsSync().eval(EmptyExecutionContextID)
|
|
|
|
So(*args.AwaitPromise, ShouldBeFalse)
|
|
})
|
|
})
|
|
|
|
Convey(".AsNamed", func() {
|
|
Convey("When without args", func() {
|
|
Convey("Should generate a wrapper with a given function name", func() {
|
|
name := "getFoo"
|
|
exp := "return 'foo'"
|
|
f := F(exp).AsNamed(name)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
expected := "function " + name + "() {\n" + exp + "\n}"
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, expected)
|
|
})
|
|
})
|
|
|
|
Convey("When with args", func() {
|
|
Convey("When a declaration is an expression", func() {
|
|
Convey("Should generate a wrapper with a given function name", func() {
|
|
name := "getFoo"
|
|
exp := "return 'foo'"
|
|
f := F(exp).
|
|
AsNamed(name).
|
|
WithArg("bar").
|
|
WithArg(1)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
expected := "function " + name + "(arg1,arg2) {\n" + exp + "\n}"
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, expected)
|
|
})
|
|
})
|
|
|
|
Convey("When a declaration is an arrow function", func() {
|
|
Convey("Should generate a wrapper with a given function name", func() {
|
|
name := "getValue"
|
|
exp := "(el) => el.value"
|
|
f := F(exp).
|
|
AsNamed(name).
|
|
WithArgRef("my_element").
|
|
WithArg(1)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
expected := "function " + name + "() {\n" +
|
|
"const $exp = " + exp + ";\n" +
|
|
"return $exp.apply(this, arguments);\n" +
|
|
"}"
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, expected)
|
|
})
|
|
})
|
|
|
|
Convey("When a declaration is a plain function", func() {
|
|
Convey("Should generate a wrapper with a given function name", func() {
|
|
name := "getValue"
|
|
exp := "function getElementValue(el) => el.value"
|
|
f := F(exp).
|
|
AsNamed(name).
|
|
WithArgRef("my_element").
|
|
WithArg(1)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
expected := "function " + name + "() {\n" +
|
|
"const $exp = " + exp + ";\n" +
|
|
"return $exp.apply(this, arguments);\n" +
|
|
"}"
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, expected)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey(".AsAnonymous", func() {
|
|
Convey("When without args", func() {
|
|
Convey("Should generate an anonymous wrapper", func() {
|
|
name := ""
|
|
exp := "return 'foo'"
|
|
f := F(exp).AsNamed("getFoo").AsAnonymous()
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
expected := "function() {\n" + exp + "\n}"
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, expected)
|
|
})
|
|
})
|
|
|
|
Convey("When with args", func() {
|
|
Convey("When a declaration is an expression", func() {
|
|
Convey("Should generate an anonymous wrapper", func() {
|
|
name := ""
|
|
exp := "return 'foo'"
|
|
f := F(exp).
|
|
AsNamed("getFoo").
|
|
AsAnonymous().
|
|
WithArg("bar").
|
|
WithArg(1)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
expected := "function(arg1,arg2) {\n" + exp + "\n}"
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, expected)
|
|
})
|
|
})
|
|
|
|
Convey("When a declaration is an arrow function", func() {
|
|
Convey("Should NOT generate a wrapper", func() {
|
|
name := ""
|
|
exp := "(el) => el.value"
|
|
f := F(exp).
|
|
AsNamed("getValue").
|
|
AsAnonymous().
|
|
WithArgRef("my_element").
|
|
WithArg(1)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, exp)
|
|
})
|
|
})
|
|
|
|
Convey("When a declaration is a plain function", func() {
|
|
Convey("Should NOT generate a wrapper", func() {
|
|
name := ""
|
|
exp := "function(el) => el.value"
|
|
f := F(exp).
|
|
AsNamed("getValue").
|
|
AsAnonymous().
|
|
WithArgRef("my_element").
|
|
WithArg(1)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
So(call.FunctionDeclaration, ShouldEqual, exp)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey(".CallOn", func() {
|
|
Convey("It should use a given ownerID over ContextID", func() {
|
|
ownerID := runtime.RemoteObjectID("foo")
|
|
contextID := runtime.ExecutionContextID(42)
|
|
|
|
f := F("return 'foo'").CallOn(ownerID)
|
|
call := f.eval(contextID)
|
|
|
|
So(call.ExecutionContextID, ShouldBeNil)
|
|
So(call.ObjectID, ShouldNotBeNil)
|
|
So(*call.ObjectID, ShouldEqual, ownerID)
|
|
})
|
|
|
|
Convey("It should use a given ContextID when ownerID is empty or nil", func() {
|
|
ownerID := runtime.RemoteObjectID("")
|
|
contextID := runtime.ExecutionContextID(42)
|
|
|
|
f := F("return 'foo'").CallOn(ownerID)
|
|
call := f.eval(contextID)
|
|
|
|
So(call.ExecutionContextID, ShouldNotBeNil)
|
|
So(call.ObjectID, ShouldBeNil)
|
|
So(*call.ExecutionContextID, ShouldEqual, contextID)
|
|
})
|
|
})
|
|
|
|
Convey(".WithArgRef", func() {
|
|
Convey("Should add argument with a given RemoteObjectID", func() {
|
|
f := F("return 'foo'")
|
|
id1 := runtime.RemoteObjectID("foo")
|
|
id2 := runtime.RemoteObjectID("bar")
|
|
id3 := runtime.RemoteObjectID("baz")
|
|
|
|
f.WithArgRef(id1).WithArgRef(id2).WithArgRef(id3)
|
|
|
|
So(f.Length(), ShouldEqual, 3)
|
|
|
|
arg1 := f.args[0]
|
|
arg2 := f.args[1]
|
|
arg3 := f.args[2]
|
|
|
|
So(*arg1.ObjectID, ShouldEqual, id1)
|
|
So(arg1.Value, ShouldBeNil)
|
|
So(arg1.UnserializableValue, ShouldBeNil)
|
|
|
|
So(*arg2.ObjectID, ShouldEqual, id2)
|
|
So(arg2.Value, ShouldBeNil)
|
|
So(arg2.UnserializableValue, ShouldBeNil)
|
|
|
|
So(*arg3.ObjectID, ShouldEqual, id3)
|
|
So(arg3.Value, ShouldBeNil)
|
|
So(arg3.UnserializableValue, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey(".WithArgValue", func() {
|
|
Convey("Should add argument with a given Value", func() {
|
|
f := F("return 'foo'")
|
|
val1 := values.NewString("foo")
|
|
val2 := values.NewInt(1)
|
|
val3 := values.NewBoolean(true)
|
|
|
|
f.WithArgValue(val1).WithArgValue(val2).WithArgValue(val3)
|
|
|
|
So(f.Length(), ShouldEqual, 3)
|
|
|
|
arg1 := f.args[0]
|
|
arg2 := f.args[1]
|
|
arg3 := f.args[2]
|
|
|
|
So(arg1.ObjectID, ShouldBeNil)
|
|
So(arg1.Value, ShouldResemble, values.MustMarshal(val1))
|
|
So(arg1.UnserializableValue, ShouldBeNil)
|
|
|
|
So(arg2.ObjectID, ShouldBeNil)
|
|
So(arg2.Value, ShouldResemble, values.MustMarshal(val2))
|
|
So(arg2.UnserializableValue, ShouldBeNil)
|
|
|
|
So(arg3.ObjectID, ShouldBeNil)
|
|
So(arg3.Value, ShouldResemble, values.MustMarshal(val3))
|
|
So(arg3.UnserializableValue, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey(".WithArg", func() {
|
|
Convey("Should add argument with a given any type", func() {
|
|
f := F("return 'foo'")
|
|
val1 := "foo"
|
|
val2 := 1
|
|
val3 := true
|
|
|
|
f.WithArg(val1).WithArg(val2).WithArg(val3)
|
|
|
|
So(f.Length(), ShouldEqual, 3)
|
|
|
|
arg1 := f.args[0]
|
|
arg2 := f.args[1]
|
|
arg3 := f.args[2]
|
|
|
|
So(arg1.ObjectID, ShouldBeNil)
|
|
So(arg1.Value, ShouldResemble, values.MustMarshalAny(val1))
|
|
So(arg1.UnserializableValue, ShouldBeNil)
|
|
|
|
So(arg2.ObjectID, ShouldBeNil)
|
|
So(arg2.Value, ShouldResemble, values.MustMarshalAny(val2))
|
|
So(arg2.UnserializableValue, ShouldBeNil)
|
|
|
|
So(arg3.ObjectID, ShouldBeNil)
|
|
So(arg3.Value, ShouldResemble, values.MustMarshalAny(val3))
|
|
So(arg3.UnserializableValue, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey(".WithArgSelector", func() {
|
|
Convey("Should add argument with a given QuerySelector", func() {
|
|
f := F("return 'foo'")
|
|
val1 := drivers.NewCSSSelector(".foo-bar")
|
|
val2 := drivers.NewCSSSelector("#submit")
|
|
val3 := drivers.NewXPathSelector("//*[@id='q']")
|
|
|
|
f.WithArgSelector(val1).WithArgSelector(val2).WithArgSelector(val3)
|
|
|
|
So(f.Length(), ShouldEqual, 3)
|
|
|
|
arg1 := f.args[0]
|
|
arg2 := f.args[1]
|
|
arg3 := f.args[2]
|
|
|
|
So(arg1.ObjectID, ShouldBeNil)
|
|
So(arg1.Value, ShouldResemble, values.MustMarshalAny(val1.String()))
|
|
So(arg1.UnserializableValue, ShouldBeNil)
|
|
|
|
So(arg2.ObjectID, ShouldBeNil)
|
|
So(arg2.Value, ShouldResemble, values.MustMarshalAny(val2.String()))
|
|
So(arg2.UnserializableValue, ShouldBeNil)
|
|
|
|
So(arg3.ObjectID, ShouldBeNil)
|
|
So(arg3.Value, ShouldResemble, values.MustMarshalAny(val3.String()))
|
|
So(arg3.UnserializableValue, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey(".String", func() {
|
|
Convey("It should return a function expression", func() {
|
|
exp := "return 'foo'"
|
|
f := F(exp)
|
|
|
|
So(f.String(), ShouldEqual, exp)
|
|
})
|
|
})
|
|
|
|
Convey(".returnNothing", func() {
|
|
Convey("It should set return by value to false", func() {
|
|
f := F("return 'foo'").returnNothing()
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
So(*call.ReturnByValue, ShouldBeFalse)
|
|
})
|
|
})
|
|
|
|
Convey(".returnValue", func() {
|
|
Convey("It should set return by value to true", func() {
|
|
f := F("return 'foo'").returnValue()
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
So(*call.ReturnByValue, ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey(".returnRef", func() {
|
|
Convey("It should set return by value to false", func() {
|
|
f := F("return 'foo'").returnValue()
|
|
call := f.eval(EmptyExecutionContextID)
|
|
|
|
So(*call.ReturnByValue, ShouldBeTrue)
|
|
|
|
f.returnRef()
|
|
|
|
call = f.eval(EmptyExecutionContextID)
|
|
|
|
So(*call.ReturnByValue, ShouldBeFalse)
|
|
})
|
|
})
|
|
|
|
Convey(".compile", func() {
|
|
Convey("When Anonymous", func() {
|
|
Convey("When without args", func() {
|
|
Convey("Should generate an expression", func() {
|
|
name := ""
|
|
exp := "return 'foo'"
|
|
f := F(exp)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.compile(EmptyExecutionContextID)
|
|
|
|
expected := "const args = [];\n" +
|
|
"const " + compiledExpName + " = function() {\n" + exp + "\n};\n" +
|
|
compiledExpName + ".apply(this, args);\n"
|
|
|
|
So(call.Expression, ShouldEqual, expected)
|
|
})
|
|
|
|
Convey("When a function is given", func() {
|
|
Convey("Should generate an expression", func() {
|
|
name := ""
|
|
exp := "() => return 'foo'"
|
|
f := F(exp)
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.compile(EmptyExecutionContextID)
|
|
|
|
expected := "const args = [];\n" +
|
|
"const " + compiledExpName + " = " + exp + ";\n" +
|
|
compiledExpName + ".apply(this, args);\n"
|
|
|
|
So(call.Expression, ShouldEqual, expected)
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey("When with args", func() {
|
|
Convey("Should generate an expression", func() {
|
|
name := ""
|
|
exp := "return 'foo'"
|
|
f := F(exp).WithArg(1).WithArg("test").WithArg([]int{1, 2})
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.compile(EmptyExecutionContextID)
|
|
|
|
expected := "const args = [\n" +
|
|
"1,\n" +
|
|
"\"test\",\n" +
|
|
"[1,2],\n" +
|
|
"];\n" +
|
|
"const " + compiledExpName + " = function(arg1,arg2,arg3) {\n" + exp + "\n};\n" +
|
|
compiledExpName + ".apply(this, args);\n"
|
|
|
|
So(call.Expression, ShouldEqual, expected)
|
|
})
|
|
|
|
Convey("When a function is given", func() {
|
|
Convey("Should generate an expression", func() {
|
|
name := ""
|
|
exp := "() => return 'foo'"
|
|
f := F(exp).WithArg(1).WithArg("test").WithArg([]int{1, 2})
|
|
|
|
So(f.name, ShouldEqual, name)
|
|
|
|
call := f.compile(EmptyExecutionContextID)
|
|
|
|
expected := "const args = [\n" +
|
|
"1,\n" +
|
|
"\"test\",\n" +
|
|
"[1,2],\n" +
|
|
"];\n" +
|
|
"const " + compiledExpName + " = " + exp + ";\n" +
|
|
compiledExpName + ".apply(this, args);\n"
|
|
|
|
So(call.Expression, ShouldEqual, expected)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|