1668 lines
31 KiB
Go
1668 lines
31 KiB
Go
|
package goja
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func TestGoReflectGet(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
o.X + o.Y;
|
||
|
`
|
||
|
type O struct {
|
||
|
X int
|
||
|
Y string
|
||
|
}
|
||
|
r := New()
|
||
|
o := O{X: 4, Y: "2"}
|
||
|
r.Set("o", o)
|
||
|
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if s, ok := v.(String); ok {
|
||
|
if s.String() != "42" {
|
||
|
t.Fatalf("Unexpected string: %s", s)
|
||
|
}
|
||
|
} else {
|
||
|
t.Fatalf("Unexpected type: %s", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectSet(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
o.X++;
|
||
|
o.Y += "P";
|
||
|
`
|
||
|
type O struct {
|
||
|
X int
|
||
|
Y string
|
||
|
}
|
||
|
r := New()
|
||
|
o := O{X: 4, Y: "2"}
|
||
|
r.Set("o", &o)
|
||
|
|
||
|
_, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if o.X != 5 {
|
||
|
t.Fatalf("Unexpected X: %d", o.X)
|
||
|
}
|
||
|
|
||
|
if o.Y != "2P" {
|
||
|
t.Fatalf("Unexpected Y: %s", o.Y)
|
||
|
}
|
||
|
|
||
|
r.Set("o", o)
|
||
|
_, err = r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if res, ok := r.Get("o").Export().(O); ok {
|
||
|
if res.X != 6 {
|
||
|
t.Fatalf("Unexpected res.X: %d", res.X)
|
||
|
}
|
||
|
|
||
|
if res.Y != "2PP" {
|
||
|
t.Fatalf("Unexpected res.Y: %s", res.Y)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectEnumerate(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var hasX = false;
|
||
|
var hasY = false;
|
||
|
for (var key in o) {
|
||
|
switch (key) {
|
||
|
case "X":
|
||
|
if (hasX) {
|
||
|
throw "Already have X";
|
||
|
}
|
||
|
hasX = true;
|
||
|
break;
|
||
|
case "Y":
|
||
|
if (hasY) {
|
||
|
throw "Already have Y";
|
||
|
}
|
||
|
hasY = true;
|
||
|
break;
|
||
|
default:
|
||
|
throw "Unexpected property: " + key;
|
||
|
}
|
||
|
}
|
||
|
hasX && hasY;
|
||
|
`
|
||
|
|
||
|
type S struct {
|
||
|
X, Y int
|
||
|
}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", S{X: 40, Y: 2})
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Expected true, got %v", v)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func TestGoReflectCustomIntUnbox(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
i + 2;
|
||
|
`
|
||
|
|
||
|
type CustomInt int
|
||
|
var i CustomInt = 40
|
||
|
|
||
|
r := New()
|
||
|
r.Set("i", i)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(intToValue(42)) {
|
||
|
t.Fatalf("Expected int 42, got %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectPreserveCustomType(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
i;
|
||
|
`
|
||
|
|
||
|
type CustomInt int
|
||
|
var i CustomInt = 42
|
||
|
|
||
|
r := New()
|
||
|
r.Set("i", i)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
ve := v.Export()
|
||
|
|
||
|
if ii, ok := ve.(CustomInt); ok {
|
||
|
if ii != i {
|
||
|
t.Fatalf("Wrong value: %v", ii)
|
||
|
}
|
||
|
} else {
|
||
|
t.Fatalf("Wrong type: %v", ve)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectCustomIntValueOf(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
if (i instanceof Number) {
|
||
|
i.valueOf();
|
||
|
} else {
|
||
|
throw new Error("Value is not a number");
|
||
|
}
|
||
|
`
|
||
|
|
||
|
type CustomInt int
|
||
|
var i CustomInt = 42
|
||
|
|
||
|
r := New()
|
||
|
r.Set("i", i)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(intToValue(42)) {
|
||
|
t.Fatalf("Expected int 42, got %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectEqual(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
x === y;
|
||
|
`
|
||
|
|
||
|
type CustomInt int
|
||
|
var x CustomInt = 42
|
||
|
var y CustomInt = 42
|
||
|
|
||
|
r := New()
|
||
|
r.Set("x", x)
|
||
|
r.Set("y", y)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Expected true, got %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type testGoReflectMethod_O struct {
|
||
|
field string
|
||
|
Test string
|
||
|
}
|
||
|
|
||
|
func (o testGoReflectMethod_O) Method(s string) string {
|
||
|
return o.field + s
|
||
|
}
|
||
|
|
||
|
func TestGoReflectMethod(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
o.Method(" 123")
|
||
|
`
|
||
|
|
||
|
o := testGoReflectMethod_O{
|
||
|
field: "test",
|
||
|
}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", &o)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(asciiString("test 123")) {
|
||
|
t.Fatalf("Expected 'test 123', got %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (o *testGoReflectMethod_O) Set(s string) {
|
||
|
o.field = s
|
||
|
}
|
||
|
|
||
|
func (o *testGoReflectMethod_O) Get() string {
|
||
|
return o.field
|
||
|
}
|
||
|
|
||
|
func TestGoReflectMethodPtr(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
o.Set("42")
|
||
|
o.Get()
|
||
|
`
|
||
|
|
||
|
o := testGoReflectMethod_O{
|
||
|
field: "test",
|
||
|
}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", &o)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(asciiString("42")) {
|
||
|
t.Fatalf("Expected '42', got %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (b *testBoolS) Method() bool {
|
||
|
return bool(*b)
|
||
|
}
|
||
|
|
||
|
func TestGoReflectPtrMethodOnNonPtrValue(t *testing.T) {
|
||
|
var o testGoReflectMethod_O
|
||
|
o.Get()
|
||
|
vm := New()
|
||
|
vm.Set("o", o)
|
||
|
_, err := vm.RunString(`o.Get()`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
_, err = vm.RunString(`o.Method()`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
var b testBoolS
|
||
|
vm.Set("b", b)
|
||
|
_, err = vm.RunString(`b.Method()`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectStructField(t *testing.T) {
|
||
|
type S struct {
|
||
|
F testGoReflectMethod_O
|
||
|
B testBoolS
|
||
|
}
|
||
|
var s S
|
||
|
vm := New()
|
||
|
vm.Set("s", &s)
|
||
|
|
||
|
const SCRIPT = `
|
||
|
s.F.Set("Test");
|
||
|
assert.sameValue(s.F.Method(""), "Test", "1");
|
||
|
|
||
|
s.B = true;
|
||
|
assert.sameValue(s.B.Method(), true, "2");
|
||
|
|
||
|
assert.sameValue(s.B.toString(), "B", "3");
|
||
|
`
|
||
|
|
||
|
vm.testScriptWithTestLib(SCRIPT, _undefined, t)
|
||
|
}
|
||
|
|
||
|
func TestGoReflectProp(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var d1 = Object.getOwnPropertyDescriptor(o, "Get");
|
||
|
var d2 = Object.getOwnPropertyDescriptor(o, "Test");
|
||
|
!d1.writable && !d1.configurable && d2.writable && !d2.configurable;
|
||
|
`
|
||
|
|
||
|
o := testGoReflectMethod_O{
|
||
|
field: "test",
|
||
|
}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", &o)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Expected true, got %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectRedefineFieldSuccess(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
Object.defineProperty(o, "Test", {value: "AAA"}) === o;
|
||
|
`
|
||
|
|
||
|
o := testGoReflectMethod_O{}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", &o)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Expected true, got %v", v)
|
||
|
}
|
||
|
|
||
|
if o.Test != "AAA" {
|
||
|
t.Fatalf("Expected 'AAA', got '%s'", o.Test)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func TestGoReflectRedefineFieldNonWritable(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var thrown = false;
|
||
|
try {
|
||
|
Object.defineProperty(o, "Test", {value: "AAA", writable: false});
|
||
|
} catch (e) {
|
||
|
if (e instanceof TypeError) {
|
||
|
thrown = true;
|
||
|
} else {
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
thrown;
|
||
|
`
|
||
|
|
||
|
o := testGoReflectMethod_O{Test: "Test"}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", &o)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Expected true, got %v", v)
|
||
|
}
|
||
|
|
||
|
if o.Test != "Test" {
|
||
|
t.Fatalf("Expected 'Test', got: '%s'", o.Test)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectRedefineFieldConfigurable(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var thrown = false;
|
||
|
try {
|
||
|
Object.defineProperty(o, "Test", {value: "AAA", configurable: true});
|
||
|
} catch (e) {
|
||
|
if (e instanceof TypeError) {
|
||
|
thrown = true;
|
||
|
} else {
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
thrown;
|
||
|
`
|
||
|
|
||
|
o := testGoReflectMethod_O{Test: "Test"}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", &o)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Expected true, got %v", v)
|
||
|
}
|
||
|
|
||
|
if o.Test != "Test" {
|
||
|
t.Fatalf("Expected 'Test', got: '%s'", o.Test)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectRedefineMethod(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var thrown = false;
|
||
|
try {
|
||
|
Object.defineProperty(o, "Method", {value: "AAA", configurable: true});
|
||
|
} catch (e) {
|
||
|
if (e instanceof TypeError) {
|
||
|
thrown = true;
|
||
|
} else {
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
thrown;
|
||
|
`
|
||
|
|
||
|
o := testGoReflectMethod_O{Test: "Test"}
|
||
|
|
||
|
r := New()
|
||
|
r.Set("o", &o)
|
||
|
v, err := r.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Expected true, got %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectEmbeddedStruct(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
if (o.ParentField2 !== "ParentField2") {
|
||
|
throw new Error("ParentField2 = " + o.ParentField2);
|
||
|
}
|
||
|
|
||
|
if (o.Parent.ParentField2 !== 2) {
|
||
|
throw new Error("o.Parent.ParentField2 = " + o.Parent.ParentField2);
|
||
|
}
|
||
|
|
||
|
if (o.ParentField1 !== 1) {
|
||
|
throw new Error("o.ParentField1 = " + o.ParentField1);
|
||
|
|
||
|
}
|
||
|
|
||
|
if (o.ChildField !== 3) {
|
||
|
throw new Error("o.ChildField = " + o.ChildField);
|
||
|
}
|
||
|
|
||
|
var keys = {};
|
||
|
for (var k in o) {
|
||
|
if (keys[k]) {
|
||
|
throw new Error("Duplicate key: " + k);
|
||
|
}
|
||
|
keys[k] = true;
|
||
|
}
|
||
|
|
||
|
var expectedKeys = ["ParentField2", "ParentField1", "Parent", "ChildField"];
|
||
|
for (var i in expectedKeys) {
|
||
|
if (!keys[expectedKeys[i]]) {
|
||
|
throw new Error("Missing key in enumeration: " + expectedKeys[i]);
|
||
|
}
|
||
|
delete keys[expectedKeys[i]];
|
||
|
}
|
||
|
|
||
|
var remainingKeys = Object.keys(keys);
|
||
|
if (remainingKeys.length > 0) {
|
||
|
throw new Error("Unexpected keys: " + remainingKeys);
|
||
|
}
|
||
|
|
||
|
o.ParentField2 = "ParentField22";
|
||
|
o.Parent.ParentField2 = 22;
|
||
|
o.ParentField1 = 11;
|
||
|
o.ChildField = 33;
|
||
|
`
|
||
|
|
||
|
type Parent struct {
|
||
|
ParentField1 int
|
||
|
ParentField2 int
|
||
|
}
|
||
|
|
||
|
type Child struct {
|
||
|
ParentField2 string
|
||
|
Parent
|
||
|
ChildField int
|
||
|
}
|
||
|
|
||
|
vm := New()
|
||
|
o := Child{
|
||
|
Parent: Parent{
|
||
|
ParentField1: 1,
|
||
|
ParentField2: 2,
|
||
|
},
|
||
|
ParentField2: "ParentField2",
|
||
|
ChildField: 3,
|
||
|
}
|
||
|
vm.Set("o", &o)
|
||
|
|
||
|
_, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if o.ParentField2 != "ParentField22" {
|
||
|
t.Fatalf("ParentField2 = %q", o.ParentField2)
|
||
|
}
|
||
|
|
||
|
if o.Parent.ParentField2 != 22 {
|
||
|
t.Fatalf("Parent.ParentField2 = %d", o.Parent.ParentField2)
|
||
|
}
|
||
|
|
||
|
if o.ParentField1 != 11 {
|
||
|
t.Fatalf("ParentField1 = %d", o.ParentField1)
|
||
|
}
|
||
|
|
||
|
if o.ChildField != 33 {
|
||
|
t.Fatalf("ChildField = %d", o.ChildField)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type jsonTagNamer struct{}
|
||
|
|
||
|
func (jsonTagNamer) FieldName(_ reflect.Type, field reflect.StructField) string {
|
||
|
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
||
|
return jsonTag
|
||
|
}
|
||
|
return field.Name
|
||
|
}
|
||
|
|
||
|
func (jsonTagNamer) MethodName(_ reflect.Type, method reflect.Method) string {
|
||
|
return method.Name
|
||
|
}
|
||
|
|
||
|
func TestGoReflectCustomNaming(t *testing.T) {
|
||
|
|
||
|
type testStructWithJsonTags struct {
|
||
|
A string `json:"b"` // <-- script sees field "A" as property "b"
|
||
|
}
|
||
|
|
||
|
o := &testStructWithJsonTags{"Hello world"}
|
||
|
r := New()
|
||
|
r.SetFieldNameMapper(&jsonTagNamer{})
|
||
|
r.Set("fn", func() *testStructWithJsonTags { return o })
|
||
|
|
||
|
t.Run("get property", func(t *testing.T) {
|
||
|
v, err := r.RunString(`fn().b`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if !v.StrictEquals(newStringValue(o.A)) {
|
||
|
t.Fatalf("Expected %q, got %v", o.A, v)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("set property", func(t *testing.T) {
|
||
|
_, err := r.RunString(`fn().b = "Hello universe"`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if o.A != "Hello universe" {
|
||
|
t.Fatalf("Expected \"Hello universe\", got %q", o.A)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("enumerate properties", func(t *testing.T) {
|
||
|
v, err := r.RunString(`Object.keys(fn())`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if !reflect.DeepEqual(v.Export(), []interface{}{"b"}) {
|
||
|
t.Fatalf("Expected [\"b\"], got %v", v.Export())
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestGoReflectCustomObjNaming(t *testing.T) {
|
||
|
|
||
|
type testStructWithJsonTags struct {
|
||
|
A string `json:"b"` // <-- script sees field "A" as property "b"
|
||
|
}
|
||
|
|
||
|
r := New()
|
||
|
r.SetFieldNameMapper(&jsonTagNamer{})
|
||
|
|
||
|
t.Run("Set object in slice", func(t *testing.T) {
|
||
|
testSlice := &[]testStructWithJsonTags{{"Hello world"}}
|
||
|
r.Set("testslice", testSlice)
|
||
|
_, err := r.RunString(`testslice[0] = {b:"setted"}`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if (*testSlice)[0].A != "setted" {
|
||
|
t.Fatalf("Expected \"setted\", got %q", (*testSlice)[0])
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("Set object in map", func(t *testing.T) {
|
||
|
testMap := map[string]testStructWithJsonTags{"key": {"Hello world"}}
|
||
|
r.Set("testmap", testMap)
|
||
|
_, err := r.RunString(`testmap["key"] = {b:"setted"}`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if testMap["key"].A != "setted" {
|
||
|
t.Fatalf("Expected \"setted\", got %q", testMap["key"])
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("Add object to map", func(t *testing.T) {
|
||
|
testMap := map[string]testStructWithJsonTags{}
|
||
|
r.Set("testmap", testMap)
|
||
|
_, err := r.RunString(`testmap["newkey"] = {b:"setted"}`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if testMap["newkey"].A != "setted" {
|
||
|
t.Fatalf("Expected \"setted\", got %q", testMap["newkey"])
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type fieldNameMapper1 struct{}
|
||
|
|
||
|
func (fieldNameMapper1) FieldName(_ reflect.Type, f reflect.StructField) string {
|
||
|
return strings.ToLower(f.Name)
|
||
|
}
|
||
|
|
||
|
func (fieldNameMapper1) MethodName(_ reflect.Type, m reflect.Method) string {
|
||
|
return m.Name
|
||
|
}
|
||
|
|
||
|
func TestNonStructAnonFields(t *testing.T) {
|
||
|
type Test1 struct {
|
||
|
M bool
|
||
|
}
|
||
|
type test3 []int
|
||
|
type Test4 []int
|
||
|
type Test2 struct {
|
||
|
test3
|
||
|
Test4
|
||
|
*Test1
|
||
|
}
|
||
|
|
||
|
const SCRIPT = `
|
||
|
JSON.stringify(a);
|
||
|
a.m && a.test3 === undefined && a.test4.length === 2
|
||
|
`
|
||
|
vm := New()
|
||
|
vm.SetFieldNameMapper(fieldNameMapper1{})
|
||
|
vm.Set("a", &Test2{Test1: &Test1{M: true}, Test4: []int{1, 2}, test3: nil})
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if !v.StrictEquals(valueTrue) {
|
||
|
t.Fatalf("Unexepected result: %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestStructNonAddressable(t *testing.T) {
|
||
|
type S struct {
|
||
|
Field int
|
||
|
}
|
||
|
|
||
|
const SCRIPT = `
|
||
|
"use strict";
|
||
|
|
||
|
if (!Object.getOwnPropertyDescriptor(s, "Field").writable) {
|
||
|
throw new Error("s.Field is non-writable");
|
||
|
}
|
||
|
|
||
|
if (!Object.getOwnPropertyDescriptor(s1, "Field").writable) {
|
||
|
throw new Error("s1.Field is non-writable");
|
||
|
}
|
||
|
|
||
|
s1.Field = 42;
|
||
|
s.Field = 43;
|
||
|
s;
|
||
|
`
|
||
|
|
||
|
var s S
|
||
|
vm := New()
|
||
|
vm.Set("s", s)
|
||
|
vm.Set("s1", &s)
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
exp := v.Export()
|
||
|
if s1, ok := exp.(S); ok {
|
||
|
if s1.Field != 43 {
|
||
|
t.Fatal(s1)
|
||
|
}
|
||
|
} else {
|
||
|
t.Fatalf("Wrong type: %T", exp)
|
||
|
}
|
||
|
if s.Field != 42 {
|
||
|
t.Fatalf("Unexpected s.Field value: %d", s.Field)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type testFieldMapper struct {
|
||
|
}
|
||
|
|
||
|
func (testFieldMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
|
||
|
if tag := f.Tag.Get("js"); tag != "" {
|
||
|
if tag == "-" {
|
||
|
return ""
|
||
|
}
|
||
|
return tag
|
||
|
}
|
||
|
|
||
|
return f.Name
|
||
|
}
|
||
|
|
||
|
func (testFieldMapper) MethodName(_ reflect.Type, m reflect.Method) string {
|
||
|
return m.Name
|
||
|
}
|
||
|
|
||
|
func TestHidingAnonField(t *testing.T) {
|
||
|
type InnerType struct {
|
||
|
AnotherField string
|
||
|
}
|
||
|
|
||
|
type OuterType struct {
|
||
|
InnerType `js:"-"`
|
||
|
SomeField string
|
||
|
}
|
||
|
|
||
|
const SCRIPT = `
|
||
|
var a = Object.getOwnPropertyNames(o);
|
||
|
if (a.length !== 2) {
|
||
|
throw new Error("unexpected length: " + a.length);
|
||
|
}
|
||
|
|
||
|
if (a.indexOf("SomeField") === -1) {
|
||
|
throw new Error("no SomeField");
|
||
|
}
|
||
|
|
||
|
if (a.indexOf("AnotherField") === -1) {
|
||
|
throw new Error("no SomeField");
|
||
|
}
|
||
|
`
|
||
|
|
||
|
var o OuterType
|
||
|
|
||
|
vm := New()
|
||
|
vm.SetFieldNameMapper(testFieldMapper{})
|
||
|
vm.Set("o", &o)
|
||
|
|
||
|
_, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestFieldOverriding(t *testing.T) {
|
||
|
type InnerType struct {
|
||
|
AnotherField string
|
||
|
AnotherField1 string
|
||
|
}
|
||
|
|
||
|
type OuterType struct {
|
||
|
InnerType `js:"-"`
|
||
|
SomeField string
|
||
|
AnotherField string `js:"-"`
|
||
|
AnotherField1 string
|
||
|
}
|
||
|
|
||
|
const SCRIPT = `
|
||
|
if (o.SomeField !== "SomeField") {
|
||
|
throw new Error("SomeField");
|
||
|
}
|
||
|
|
||
|
if (o.AnotherField !== "AnotherField inner") {
|
||
|
throw new Error("AnotherField");
|
||
|
}
|
||
|
|
||
|
if (o.AnotherField1 !== "AnotherField1 outer") {
|
||
|
throw new Error("AnotherField1");
|
||
|
}
|
||
|
|
||
|
if (o.InnerType) {
|
||
|
throw new Error("InnerType is present");
|
||
|
}
|
||
|
`
|
||
|
|
||
|
o := OuterType{
|
||
|
InnerType: InnerType{
|
||
|
AnotherField: "AnotherField inner",
|
||
|
AnotherField1: "AnotherField1 inner",
|
||
|
},
|
||
|
SomeField: "SomeField",
|
||
|
AnotherField: "AnotherField outer",
|
||
|
AnotherField1: "AnotherField1 outer",
|
||
|
}
|
||
|
|
||
|
vm := New()
|
||
|
vm.SetFieldNameMapper(testFieldMapper{})
|
||
|
vm.Set("o", &o)
|
||
|
|
||
|
_, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestDefinePropertyUnexportedJsName(t *testing.T) {
|
||
|
type T struct {
|
||
|
Field int
|
||
|
unexported int
|
||
|
}
|
||
|
|
||
|
vm := New()
|
||
|
vm.SetFieldNameMapper(fieldNameMapper1{})
|
||
|
vm.Set("f", &T{unexported: 0})
|
||
|
|
||
|
_, err := vm.RunString(`
|
||
|
"use strict";
|
||
|
Object.defineProperty(f, "field", {value: 42});
|
||
|
if (f.field !== 42) {
|
||
|
throw new Error("Unexpected value: " + f.field);
|
||
|
}
|
||
|
if (f.hasOwnProperty("unexported")) {
|
||
|
throw new Error("hasOwnProperty('unexported') is true");
|
||
|
}
|
||
|
var thrown;
|
||
|
try {
|
||
|
Object.defineProperty(f, "unexported", {value: 1});
|
||
|
} catch (e) {
|
||
|
thrown = e;
|
||
|
}
|
||
|
if (!(thrown instanceof TypeError)) {
|
||
|
throw new Error("Unexpected error: ", thrown);
|
||
|
}
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type fieldNameMapperToLower struct{}
|
||
|
|
||
|
func (fieldNameMapperToLower) FieldName(_ reflect.Type, f reflect.StructField) string {
|
||
|
return strings.ToLower(f.Name)
|
||
|
}
|
||
|
|
||
|
func (fieldNameMapperToLower) MethodName(_ reflect.Type, m reflect.Method) string {
|
||
|
return strings.ToLower(m.Name)
|
||
|
}
|
||
|
|
||
|
func TestHasOwnPropertyUnexportedJsName(t *testing.T) {
|
||
|
vm := New()
|
||
|
vm.SetFieldNameMapper(fieldNameMapperToLower{})
|
||
|
vm.Set("f", &testGoReflectMethod_O{})
|
||
|
|
||
|
_, err := vm.RunString(`
|
||
|
"use strict";
|
||
|
if (!f.hasOwnProperty("test")) {
|
||
|
throw new Error("hasOwnProperty('test') returned false");
|
||
|
}
|
||
|
if (!f.hasOwnProperty("method")) {
|
||
|
throw new Error("hasOwnProperty('method') returned false");
|
||
|
}
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkGoReflectGet(b *testing.B) {
|
||
|
type parent struct {
|
||
|
field, Test1, Test2, Test3, Test4, Test5, Test string
|
||
|
}
|
||
|
|
||
|
type child struct {
|
||
|
parent
|
||
|
Test6 string
|
||
|
}
|
||
|
|
||
|
b.StopTimer()
|
||
|
vm := New()
|
||
|
|
||
|
b.StartTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
v := vm.ToValue(child{parent: parent{Test: "Test", field: ""}}).(*Object)
|
||
|
v.Get("Test")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNestedStructSet(t *testing.T) {
|
||
|
type B struct {
|
||
|
Field int
|
||
|
}
|
||
|
type A struct {
|
||
|
B B
|
||
|
}
|
||
|
|
||
|
const SCRIPT = `
|
||
|
'use strict';
|
||
|
a.B.Field++;
|
||
|
if (a1.B.Field != 1) {
|
||
|
throw new Error("a1.B.Field = " + a1.B.Field);
|
||
|
}
|
||
|
var d = Object.getOwnPropertyDescriptor(a1.B, "Field");
|
||
|
if (!d.writable) {
|
||
|
throw new Error("a1.B is not writable");
|
||
|
}
|
||
|
a1.B.Field = 42;
|
||
|
a1;
|
||
|
`
|
||
|
a := A{
|
||
|
B: B{
|
||
|
Field: 1,
|
||
|
},
|
||
|
}
|
||
|
vm := New()
|
||
|
vm.Set("a", &a)
|
||
|
vm.Set("a1", a)
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
exp := v.Export()
|
||
|
if v, ok := exp.(A); ok {
|
||
|
if v.B.Field != 42 {
|
||
|
t.Fatal(v)
|
||
|
}
|
||
|
} else {
|
||
|
t.Fatalf("Wrong type: %T", exp)
|
||
|
}
|
||
|
|
||
|
if v := a.B.Field; v != 2 {
|
||
|
t.Fatalf("Unexpected a.B.Field: %d", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestStructNonAddressableAnonStruct(t *testing.T) {
|
||
|
|
||
|
type C struct {
|
||
|
Z int64
|
||
|
X string
|
||
|
}
|
||
|
|
||
|
type B struct {
|
||
|
C
|
||
|
Y string
|
||
|
}
|
||
|
|
||
|
type A struct {
|
||
|
B B
|
||
|
}
|
||
|
|
||
|
a := A{
|
||
|
B: B{
|
||
|
C: C{
|
||
|
Z: 1,
|
||
|
X: "X2",
|
||
|
},
|
||
|
Y: "Y3",
|
||
|
},
|
||
|
}
|
||
|
const SCRIPT = `
|
||
|
"use strict";
|
||
|
var s = JSON.stringify(a);
|
||
|
s;
|
||
|
`
|
||
|
|
||
|
vm := New()
|
||
|
vm.Set("a", &a)
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
expected := `{"B":{"C":{"Z":1,"X":"X2"},"Z":1,"X":"X2","Y":"Y3"}}`
|
||
|
if expected != v.String() {
|
||
|
t.Fatalf("Expected '%s', got '%s'", expected, v.String())
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func TestTagFieldNameMapperInvalidId(t *testing.T) {
|
||
|
vm := New()
|
||
|
vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
|
||
|
type S struct {
|
||
|
Field int `json:"-"`
|
||
|
}
|
||
|
vm.Set("s", S{Field: 42})
|
||
|
res, err := vm.RunString(`s.hasOwnProperty("field") || s.hasOwnProperty("Field")`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if res != valueFalse {
|
||
|
t.Fatalf("Unexpected result: %v", res)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestPrimitivePtr(t *testing.T) {
|
||
|
vm := New()
|
||
|
s := "test"
|
||
|
vm.Set("s", &s)
|
||
|
res, err := vm.RunString(`s instanceof String && s == "test"`) // note non-strict equality
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if v := res.ToBoolean(); !v {
|
||
|
t.Fatalf("value: %#v", res)
|
||
|
}
|
||
|
s = "test1"
|
||
|
res, err = vm.RunString(`s == "test1"`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if v := res.ToBoolean(); !v {
|
||
|
t.Fatalf("value: %#v", res)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestStringer(t *testing.T) {
|
||
|
vm := New()
|
||
|
vm.Set("e", errors.New("test"))
|
||
|
res, err := vm.RunString("e.toString()")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if v := res.Export(); v != "test" {
|
||
|
t.Fatalf("v: %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ExampleTagFieldNameMapper() {
|
||
|
vm := New()
|
||
|
vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
|
||
|
type S struct {
|
||
|
Field int `json:"field"`
|
||
|
}
|
||
|
vm.Set("s", S{Field: 42})
|
||
|
res, _ := vm.RunString(`s.field`)
|
||
|
fmt.Println(res.Export())
|
||
|
// Output: 42
|
||
|
}
|
||
|
|
||
|
func ExampleUncapFieldNameMapper() {
|
||
|
vm := New()
|
||
|
s := testGoReflectMethod_O{
|
||
|
Test: "passed",
|
||
|
}
|
||
|
vm.SetFieldNameMapper(UncapFieldNameMapper())
|
||
|
vm.Set("s", s)
|
||
|
res, _ := vm.RunString(`s.test + " and " + s.method("passed too")`)
|
||
|
fmt.Println(res.Export())
|
||
|
// Output: passed and passed too
|
||
|
}
|
||
|
|
||
|
func TestGoReflectWithProto(t *testing.T) {
|
||
|
type S struct {
|
||
|
Field int
|
||
|
}
|
||
|
var s S
|
||
|
vm := New()
|
||
|
vm.Set("s", &s)
|
||
|
vm.testScriptWithTestLib(`
|
||
|
(function() {
|
||
|
'use strict';
|
||
|
var proto = {
|
||
|
Field: "protoField",
|
||
|
test: 42
|
||
|
};
|
||
|
var test1Holder;
|
||
|
Object.defineProperty(proto, "test1", {
|
||
|
set: function(v) {
|
||
|
test1Holder = v;
|
||
|
},
|
||
|
get: function() {
|
||
|
return test1Holder;
|
||
|
}
|
||
|
});
|
||
|
Object.setPrototypeOf(s, proto);
|
||
|
assert.sameValue(s.Field, 0, "s.Field");
|
||
|
s.Field = 2;
|
||
|
assert.sameValue(s.Field, 2, "s.Field");
|
||
|
assert.sameValue(s.test, 42, "s.test");
|
||
|
assert.throws(TypeError, function() {
|
||
|
Object.defineProperty(s, "test", {value: 43});
|
||
|
});
|
||
|
test1Holder = 1;
|
||
|
assert.sameValue(s.test1, 1, "s.test1");
|
||
|
s.test1 = 2;
|
||
|
assert.sameValue(test1Holder, 2, "test1Holder");
|
||
|
})();
|
||
|
`, _undefined, t)
|
||
|
}
|
||
|
|
||
|
func TestGoReflectSymbols(t *testing.T) {
|
||
|
type S struct {
|
||
|
Field int
|
||
|
}
|
||
|
var s S
|
||
|
vm := New()
|
||
|
vm.Set("s", &s)
|
||
|
_, err := vm.RunString(`
|
||
|
'use strict';
|
||
|
var sym = Symbol(66);
|
||
|
s[sym] = "Test";
|
||
|
if (s[sym] !== "Test") {
|
||
|
throw new Error("s[sym]=" + s[sym]);
|
||
|
}
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectSymbolEqualityQuirk(t *testing.T) {
|
||
|
type Field struct {
|
||
|
}
|
||
|
type S struct {
|
||
|
Field *Field
|
||
|
}
|
||
|
var s = S{
|
||
|
Field: &Field{},
|
||
|
}
|
||
|
vm := New()
|
||
|
vm.Set("s", &s)
|
||
|
res, err := vm.RunString(`
|
||
|
var sym = Symbol(66);
|
||
|
var field1 = s.Field;
|
||
|
field1[sym] = true;
|
||
|
var field2 = s.Field;
|
||
|
// Because a wrapper is created every time the property is accessed
|
||
|
// field1 and field2 will be different instances of the wrapper.
|
||
|
// Symbol properties only exist in the wrapper, they cannot be placed into the original Go value,
|
||
|
// hence the following:
|
||
|
field1 === field2 && field1[sym] === true && field2[sym] === undefined;
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if res != valueTrue {
|
||
|
t.Fatal(res)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoObj__Proto__(t *testing.T) {
|
||
|
type S struct {
|
||
|
Field int
|
||
|
}
|
||
|
vm := New()
|
||
|
vm.Set("s", S{})
|
||
|
vm.Set("m", map[string]interface{}{})
|
||
|
vm.Set("mr", map[int]string{})
|
||
|
vm.Set("a", []interface{}{})
|
||
|
vm.Set("ar", []string{})
|
||
|
_, err := vm.RunString(`
|
||
|
function f(s, expectedCtor, prefix) {
|
||
|
if (s.__proto__ !== expectedCtor.prototype) {
|
||
|
throw new Error(prefix + ": __proto__: " + s.__proto__);
|
||
|
}
|
||
|
s.__proto__ = null;
|
||
|
if (s.__proto__ !== undefined) { // as there is no longer a prototype, there is no longer the __proto__ property
|
||
|
throw new Error(prefix + ": __proto__ is not undefined: " + s.__proto__);
|
||
|
}
|
||
|
var proto = Object.getPrototypeOf(s);
|
||
|
if (proto !== null) {
|
||
|
throw new Error(prefix + ": proto is not null: " + proto);
|
||
|
}
|
||
|
}
|
||
|
f(s, Object, "struct");
|
||
|
f(m, Object, "simple map");
|
||
|
f(mr, Object, "reflect map");
|
||
|
f(a, Array, "slice");
|
||
|
f(ar, Array, "reflect slice");
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectUnicodeProps(t *testing.T) {
|
||
|
type S struct {
|
||
|
Тест string
|
||
|
}
|
||
|
vm := New()
|
||
|
var s S
|
||
|
vm.Set("s", &s)
|
||
|
_, err := vm.RunString(`
|
||
|
if (!s.hasOwnProperty("Тест")) {
|
||
|
throw new Error("hasOwnProperty");
|
||
|
}
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectPreserveType(t *testing.T) {
|
||
|
vm := New()
|
||
|
var expect = time.Duration(math.MaxInt64)
|
||
|
vm.Set(`make`, func() time.Duration {
|
||
|
return expect
|
||
|
})
|
||
|
vm.Set(`handle`, func(d time.Duration) {
|
||
|
if d.String() != expect.String() {
|
||
|
t.Fatal(`expect`, expect, `, but get`, d)
|
||
|
}
|
||
|
})
|
||
|
_, e := vm.RunString(`
|
||
|
var d=make()
|
||
|
handle(d)
|
||
|
`)
|
||
|
if e != nil {
|
||
|
t.Fatal(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectCopyOnWrite(t *testing.T) {
|
||
|
type Inner struct {
|
||
|
Field int
|
||
|
}
|
||
|
type S struct {
|
||
|
I Inner
|
||
|
}
|
||
|
var s S
|
||
|
s.I.Field = 1
|
||
|
|
||
|
vm := New()
|
||
|
vm.Set("s", &s)
|
||
|
_, err := vm.RunString(`
|
||
|
if (s.I.Field !== 1) {
|
||
|
throw new Error("s.I.Field: " + s.I.Field);
|
||
|
}
|
||
|
|
||
|
let tmp = s.I; // tmp becomes a reference to s.I
|
||
|
if (tmp.Field !== 1) {
|
||
|
throw new Error("tmp.Field: " + tmp.Field);
|
||
|
}
|
||
|
|
||
|
s.I.Field = 2;
|
||
|
if (s.I.Field !== 2) {
|
||
|
throw new Error("s.I.Field (1): " + s.I.Field);
|
||
|
}
|
||
|
if (tmp.Field !== 2) {
|
||
|
throw new Error("tmp.Field (1): " + tmp.Field);
|
||
|
}
|
||
|
|
||
|
s.I = {Field: 3}; // at this point tmp is changed to a copy
|
||
|
if (s.I.Field !== 3) {
|
||
|
throw new Error("s.I.Field (2): " + s.I.Field);
|
||
|
}
|
||
|
if (tmp.Field !== 2) {
|
||
|
throw new Error("tmp.Field (2): " + tmp.Field);
|
||
|
}
|
||
|
`)
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestReflectSetReflectValue(t *testing.T) {
|
||
|
o := []testGoReflectMethod_O{{}}
|
||
|
vm := New()
|
||
|
vm.Set("o", o)
|
||
|
_, err := vm.RunString(`
|
||
|
const t = o[0];
|
||
|
t.Set("a");
|
||
|
o[0] = {};
|
||
|
o[0].Set("b");
|
||
|
if (t.Get() !== "a") {
|
||
|
throw new Error();
|
||
|
}
|
||
|
`)
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestReflectOverwriteReflectMap(t *testing.T) {
|
||
|
vm := New()
|
||
|
type S struct {
|
||
|
M map[int]interface{}
|
||
|
}
|
||
|
var s S
|
||
|
s.M = map[int]interface{}{
|
||
|
0: true,
|
||
|
}
|
||
|
vm.Set("s", &s)
|
||
|
_, err := vm.RunString(`
|
||
|
s.M = {1: false};
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if _, exists := s.M[0]; exists {
|
||
|
t.Fatal(s)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type testBoolS bool
|
||
|
|
||
|
func (testBoolS) String() string {
|
||
|
return "B"
|
||
|
}
|
||
|
|
||
|
type testIntS int
|
||
|
|
||
|
func (testIntS) String() string {
|
||
|
return "I"
|
||
|
}
|
||
|
|
||
|
type testStringS string
|
||
|
|
||
|
func (testStringS) String() string {
|
||
|
return "S"
|
||
|
}
|
||
|
|
||
|
func TestGoReflectToPrimitive(t *testing.T) {
|
||
|
vm := New()
|
||
|
|
||
|
f := func(expr string, expected Value, t *testing.T) {
|
||
|
v, err := vm.RunString(expr)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if IsNaN(expected) {
|
||
|
if IsNaN(v) {
|
||
|
return
|
||
|
}
|
||
|
} else {
|
||
|
if v.StrictEquals(expected) {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
t.Fatalf("%s: expected: %v, actual: %v", expr, expected, v)
|
||
|
}
|
||
|
|
||
|
t.Run("Not Stringers", func(t *testing.T) {
|
||
|
type Bool bool
|
||
|
var b Bool = true
|
||
|
|
||
|
t.Run("Bool", func(t *testing.T) {
|
||
|
vm.Set("b", b)
|
||
|
f("+b", intToValue(1), t)
|
||
|
f("`${b}`", asciiString("true"), t)
|
||
|
f("b.toString()", asciiString("true"), t)
|
||
|
f("b.valueOf()", valueTrue, t)
|
||
|
})
|
||
|
|
||
|
t.Run("*Bool", func(t *testing.T) {
|
||
|
vm.Set("b", &b)
|
||
|
f("+b", intToValue(1), t)
|
||
|
f("`${b}`", asciiString("true"), t)
|
||
|
f("b.toString()", asciiString("true"), t)
|
||
|
f("b.valueOf()", valueTrue, t)
|
||
|
})
|
||
|
|
||
|
type Int int
|
||
|
var i Int = 1
|
||
|
|
||
|
t.Run("Int", func(t *testing.T) {
|
||
|
vm.Set("i", i)
|
||
|
f("+i", intToValue(1), t)
|
||
|
f("`${i}`", asciiString("1"), t)
|
||
|
f("i.toString()", asciiString("1"), t)
|
||
|
f("i.valueOf()", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
t.Run("*Int", func(t *testing.T) {
|
||
|
vm.Set("i", &i)
|
||
|
f("+i", intToValue(1), t)
|
||
|
f("`${i}`", asciiString("1"), t)
|
||
|
f("i.toString()", asciiString("1"), t)
|
||
|
f("i.valueOf()", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
type Uint uint
|
||
|
var ui Uint = 1
|
||
|
|
||
|
t.Run("Uint", func(t *testing.T) {
|
||
|
vm.Set("ui", ui)
|
||
|
f("+ui", intToValue(1), t)
|
||
|
f("`${ui}`", asciiString("1"), t)
|
||
|
f("ui.toString()", asciiString("1"), t)
|
||
|
f("ui.valueOf()", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
t.Run("*Uint", func(t *testing.T) {
|
||
|
vm.Set("ui", &i)
|
||
|
f("+ui", intToValue(1), t)
|
||
|
f("`${ui}`", asciiString("1"), t)
|
||
|
f("ui.toString()", asciiString("1"), t)
|
||
|
f("ui.valueOf()", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
type Float float64
|
||
|
var fl Float = 1.1
|
||
|
|
||
|
t.Run("Float", func(t *testing.T) {
|
||
|
vm.Set("fl", fl)
|
||
|
f("+fl", floatToValue(1.1), t)
|
||
|
f("`${fl}`", asciiString("1.1"), t)
|
||
|
f("fl.toString()", asciiString("1.1"), t)
|
||
|
f("fl.valueOf()", floatToValue(1.1), t)
|
||
|
})
|
||
|
|
||
|
t.Run("*Float", func(t *testing.T) {
|
||
|
vm.Set("fl", &fl)
|
||
|
f("+fl", floatToValue(1.1), t)
|
||
|
f("`${fl}`", asciiString("1.1"), t)
|
||
|
f("fl.toString()", asciiString("1.1"), t)
|
||
|
f("fl.valueOf()", floatToValue(1.1), t)
|
||
|
})
|
||
|
|
||
|
fl = Float(math.Inf(1))
|
||
|
t.Run("FloatInf", func(t *testing.T) {
|
||
|
vm.Set("fl", fl)
|
||
|
f("+fl", _positiveInf, t)
|
||
|
f("fl.toString()", asciiString("Infinity"), t)
|
||
|
})
|
||
|
|
||
|
type Empty struct{}
|
||
|
|
||
|
var e Empty
|
||
|
t.Run("Empty", func(t *testing.T) {
|
||
|
vm.Set("e", &e)
|
||
|
f("+e", _NaN, t)
|
||
|
f("`${e}`", asciiString("[object Object]"), t)
|
||
|
f("e.toString()", asciiString("[object Object]"), t)
|
||
|
f("e.valueOf()", vm.ToValue(&e), t)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.Run("Stringers", func(t *testing.T) {
|
||
|
var b testBoolS = true
|
||
|
t.Run("Bool", func(t *testing.T) {
|
||
|
vm.Set("b", b)
|
||
|
f("`${b}`", asciiString("B"), t)
|
||
|
f("b.toString()", asciiString("B"), t)
|
||
|
f("b.valueOf()", valueTrue, t)
|
||
|
f("+b", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
t.Run("*Bool", func(t *testing.T) {
|
||
|
vm.Set("b", &b)
|
||
|
f("`${b}`", asciiString("B"), t)
|
||
|
f("b.toString()", asciiString("B"), t)
|
||
|
f("b.valueOf()", valueTrue, t)
|
||
|
f("+b", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
var i testIntS = 1
|
||
|
t.Run("Int", func(t *testing.T) {
|
||
|
vm.Set("i", i)
|
||
|
f("`${i}`", asciiString("I"), t)
|
||
|
f("i.toString()", asciiString("I"), t)
|
||
|
f("i.valueOf()", intToValue(1), t)
|
||
|
f("+i", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
t.Run("*Int", func(t *testing.T) {
|
||
|
vm.Set("i", &i)
|
||
|
f("`${i}`", asciiString("I"), t)
|
||
|
f("i.toString()", asciiString("I"), t)
|
||
|
f("i.valueOf()", intToValue(1), t)
|
||
|
f("+i", intToValue(1), t)
|
||
|
})
|
||
|
|
||
|
var s testStringS
|
||
|
t.Run("String", func(t *testing.T) {
|
||
|
vm.Set("s", s)
|
||
|
f("`${s}`", asciiString("S"), t)
|
||
|
f("s.toString()", asciiString("S"), t)
|
||
|
f("s.valueOf()", asciiString("S"), t)
|
||
|
f("+s", _NaN, t)
|
||
|
})
|
||
|
|
||
|
t.Run("*String", func(t *testing.T) {
|
||
|
vm.Set("s", &s)
|
||
|
f("`${s}`", asciiString("S"), t)
|
||
|
f("s.toString()", asciiString("S"), t)
|
||
|
f("s.valueOf()", asciiString("S"), t)
|
||
|
f("+s", _NaN, t)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type testGoReflectFuncRt struct {
|
||
|
}
|
||
|
|
||
|
func (*testGoReflectFuncRt) M(call FunctionCall, r *Runtime) Value {
|
||
|
if r == nil {
|
||
|
panic(typeError("Runtime is nil"))
|
||
|
}
|
||
|
return call.Argument(0)
|
||
|
}
|
||
|
|
||
|
func (*testGoReflectFuncRt) C(call ConstructorCall, r *Runtime) *Object {
|
||
|
if r == nil {
|
||
|
panic(typeError("Runtime is nil in constructor"))
|
||
|
}
|
||
|
call.This.Set("r", call.Argument(0))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func TestGoReflectFuncWithRuntime(t *testing.T) {
|
||
|
vm := New()
|
||
|
var s testGoReflectFuncRt
|
||
|
vm.Set("s", &s)
|
||
|
res, err := vm.RunString("s.M(true)")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if res != valueTrue {
|
||
|
t.Fatal(res)
|
||
|
}
|
||
|
|
||
|
res, err = vm.RunString("new s.C(true).r")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if res != valueTrue {
|
||
|
t.Fatal(res)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectDefaultToString(t *testing.T) {
|
||
|
var s testStringS
|
||
|
vm := New()
|
||
|
v := vm.ToValue(s).(*Object)
|
||
|
v.Delete("toString")
|
||
|
v.Delete("valueOf")
|
||
|
vm.Set("s", v)
|
||
|
_, err := vm.RunString(`
|
||
|
class S {
|
||
|
toString() {
|
||
|
return "X";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (s.toString() !== "S") {
|
||
|
throw new Error(s.toString());
|
||
|
}
|
||
|
if (("" + s) !== "S") {
|
||
|
throw new Error("" + s);
|
||
|
}
|
||
|
|
||
|
Object.setPrototypeOf(s, S.prototype);
|
||
|
if (s.toString() !== "X") {
|
||
|
throw new Error(s.toString());
|
||
|
}
|
||
|
if (("" + s) !== "X") {
|
||
|
throw new Error("" + s);
|
||
|
}
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGoReflectUnexportedEmbedStruct(t *testing.T) {
|
||
|
type privateEmbed struct {
|
||
|
A string
|
||
|
}
|
||
|
type PublicEmbed struct {
|
||
|
B string
|
||
|
}
|
||
|
type privateNested struct {
|
||
|
C string
|
||
|
}
|
||
|
type PublicNested struct {
|
||
|
D string
|
||
|
}
|
||
|
type Foo struct {
|
||
|
privateEmbed
|
||
|
PublicEmbed
|
||
|
|
||
|
privateNested privateNested
|
||
|
PublicNested PublicNested
|
||
|
|
||
|
e string
|
||
|
F string
|
||
|
}
|
||
|
|
||
|
vm := New()
|
||
|
vm.Set("foo", Foo{
|
||
|
privateEmbed: privateEmbed{A: "testA"},
|
||
|
PublicEmbed: PublicEmbed{B: "testB"},
|
||
|
privateNested: privateNested{C: "testC"},
|
||
|
PublicNested: PublicNested{D: "testD"},
|
||
|
e: "testE",
|
||
|
F: "testF",
|
||
|
})
|
||
|
|
||
|
scenarios := []struct {
|
||
|
expr string
|
||
|
expected string
|
||
|
}{
|
||
|
{"foo.privateEmbed", "undefined"},
|
||
|
{"foo.A", "testA"},
|
||
|
// ---
|
||
|
{"foo.PublicEmbed", "[object Object]"},
|
||
|
{"foo.B", "testB"},
|
||
|
{"foo.PublicEmbed.B", "testB"},
|
||
|
// ---
|
||
|
{"foo.privateNested", "undefined"},
|
||
|
{"foo.C", "undefined"},
|
||
|
// ---
|
||
|
{"foo.PublicNested", "[object Object]"},
|
||
|
{"foo.D", "undefined"},
|
||
|
{"foo.PublicNested.D", "testD"},
|
||
|
// ---
|
||
|
{"foo.e", "undefined"},
|
||
|
{"foo.F", "testF"},
|
||
|
}
|
||
|
|
||
|
for _, s := range scenarios {
|
||
|
t.Run(s.expr, func(t *testing.T) {
|
||
|
v, err := vm.RunString(s.expr)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
vStr := v.String()
|
||
|
|
||
|
if vStr != s.expected {
|
||
|
t.Fatalf("Expected %q, got %q", s.expected, vStr)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|