669 lines
12 KiB
Go
669 lines
12 KiB
Go
package goja
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestDefineProperty(t *testing.T) {
|
|
r := New()
|
|
o := r.NewObject()
|
|
|
|
err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int {
|
|
return 1
|
|
}), nil, FLAG_TRUE, FLAG_TRUE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = o.DefineAccessorProperty("accessor_rw",
|
|
r.ToValue(func(call FunctionCall) Value {
|
|
return o.Get("__hidden")
|
|
}),
|
|
r.ToValue(func(call FunctionCall) (ret Value) {
|
|
o.Set("__hidden", call.Argument(0))
|
|
return
|
|
}),
|
|
FLAG_TRUE, FLAG_TRUE)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if v := o.Get("accessor_ro"); v.ToInteger() != 1 {
|
|
t.Fatalf("Unexpected accessor value: %v", v)
|
|
}
|
|
|
|
err = o.Set("accessor_ro", r.ToValue(2))
|
|
if err == nil {
|
|
t.Fatal("Expected an error")
|
|
}
|
|
if ex, ok := err.(*Exception); ok {
|
|
if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" {
|
|
t.Fatalf("Unexpected error: '%s'", msg)
|
|
}
|
|
} else {
|
|
t.Fatalf("Unexected error type: %T", err)
|
|
}
|
|
|
|
err = o.Set("accessor_rw", 42)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if v := o.Get("accessor_rw"); v.ToInteger() != 42 {
|
|
t.Fatalf("Unexpected value: %v", v)
|
|
}
|
|
}
|
|
|
|
func TestPropertyOrder(t *testing.T) {
|
|
const SCRIPT = `
|
|
var o = {};
|
|
var sym1 = Symbol(1);
|
|
var sym2 = Symbol(2);
|
|
o[sym2] = 1;
|
|
o[4294967294] = 1;
|
|
o[2] = 1;
|
|
o[1] = 1;
|
|
o[0] = 1;
|
|
o["02"] = 1;
|
|
o[4294967295] = 1;
|
|
o["01"] = 1;
|
|
o["00"] = 1;
|
|
o[sym1] = 1;
|
|
var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1];
|
|
var actual = Reflect.ownKeys(o);
|
|
if (actual.length !== expected.length) {
|
|
throw new Error("Unexpected length: "+actual.length);
|
|
}
|
|
for (var i = 0; i < actual.length; i++) {
|
|
if (actual[i] !== expected[i]) {
|
|
throw new Error("Unexpected list: " + actual);
|
|
}
|
|
}
|
|
`
|
|
|
|
testScript(SCRIPT, _undefined, t)
|
|
}
|
|
|
|
func TestDefinePropertiesSymbol(t *testing.T) {
|
|
const SCRIPT = `
|
|
var desc = {};
|
|
desc[Symbol.toStringTag] = {value: "Test"};
|
|
var o = {};
|
|
Object.defineProperties(o, desc);
|
|
o[Symbol.toStringTag] === "Test";
|
|
`
|
|
|
|
testScript(SCRIPT, valueTrue, t)
|
|
}
|
|
|
|
func TestObjectShorthandProperties(t *testing.T) {
|
|
const SCRIPT = `
|
|
var b = 1;
|
|
var a = {b, get() {return "c"}};
|
|
|
|
assert.sameValue(a.b, b, "#1");
|
|
assert.sameValue(a.get(), "c", "#2");
|
|
|
|
var obj = {
|
|
w\u0069th() { return 42; }
|
|
};
|
|
|
|
assert.sameValue(obj['with'](), 42, 'property exists');
|
|
`
|
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
|
}
|
|
|
|
func TestObjectAssign(t *testing.T) {
|
|
const SCRIPT = `
|
|
assert.sameValue(Object.assign({ b: 1 }, { get a() {
|
|
Object.defineProperty(this, "b", {
|
|
value: 3,
|
|
enumerable: false
|
|
});
|
|
}, b: 2 }).b, 1, "#1");
|
|
|
|
assert.sameValue(Object.assign({ b: 1 }, { get a() {
|
|
delete this.b;
|
|
}, b: 2 }).b, 1, "#2");
|
|
`
|
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
|
}
|
|
|
|
func TestExportCircular(t *testing.T) {
|
|
vm := New()
|
|
o := vm.NewObject()
|
|
o.Set("o", o)
|
|
v := o.Export()
|
|
if m, ok := v.(map[string]interface{}); ok {
|
|
if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() {
|
|
t.Fatal("Unexpected value")
|
|
}
|
|
} else {
|
|
t.Fatal("Unexpected type")
|
|
}
|
|
|
|
res, err := vm.RunString(`var a = []; a[0] = a;`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
v = res.Export()
|
|
if a, ok := v.([]interface{}); ok {
|
|
if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() {
|
|
t.Fatal("Unexpected value")
|
|
}
|
|
} else {
|
|
t.Fatal("Unexpected type")
|
|
}
|
|
}
|
|
|
|
type test_s struct {
|
|
S *test_s1
|
|
}
|
|
type test_s1 struct {
|
|
S *test_s
|
|
}
|
|
|
|
func TestExportToCircular(t *testing.T) {
|
|
vm := New()
|
|
o := vm.NewObject()
|
|
o.Set("o", o)
|
|
var m map[string]interface{}
|
|
err := vm.ExportTo(o, &m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
type K string
|
|
type T map[K]T
|
|
var m1 T
|
|
err = vm.ExportTo(o, &m1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
type A []A
|
|
var a A
|
|
res, err := vm.RunString("var a = []; a[0] = a;")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = vm.ExportTo(res, &a)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if &a[0] != &a[0][0] {
|
|
t.Fatal("values do not match")
|
|
}
|
|
|
|
o = vm.NewObject()
|
|
o.Set("S", o)
|
|
var s test_s
|
|
err = vm.ExportTo(o, &s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s.S.S != &s {
|
|
t.Fatalf("values do not match: %v, %v", s.S.S, &s)
|
|
}
|
|
|
|
type test_s2 struct {
|
|
S interface{}
|
|
S1 *test_s2
|
|
}
|
|
|
|
var s2 test_s2
|
|
o.Set("S1", o)
|
|
|
|
err = vm.ExportTo(o, &s2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if m, ok := s2.S.(map[string]interface{}); ok {
|
|
if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() {
|
|
t.Fatal("Unexpected m.S")
|
|
}
|
|
} else {
|
|
t.Fatalf("Unexpected s2.S type: %T", s2.S)
|
|
}
|
|
if s2.S1 != &s2 {
|
|
t.Fatal("Unexpected s2.S1")
|
|
}
|
|
|
|
o1 := vm.NewObject()
|
|
o1.Set("S", o)
|
|
o1.Set("S1", o)
|
|
err = vm.ExportTo(o1, &s2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s2.S1.S1 != s2.S1 {
|
|
t.Fatal("Unexpected s2.S1.S1")
|
|
}
|
|
}
|
|
|
|
func TestExportWrappedMap(t *testing.T) {
|
|
vm := New()
|
|
m := map[string]interface{}{
|
|
"test": "failed",
|
|
}
|
|
exported := vm.ToValue(m).Export()
|
|
if exportedMap, ok := exported.(map[string]interface{}); ok {
|
|
exportedMap["test"] = "passed"
|
|
if v := m["test"]; v != "passed" {
|
|
t.Fatalf("Unexpected m[\"test\"]: %v", v)
|
|
}
|
|
} else {
|
|
t.Fatalf("Unexpected export type: %T", exported)
|
|
}
|
|
}
|
|
|
|
func TestExportToWrappedMap(t *testing.T) {
|
|
vm := New()
|
|
m := map[string]interface{}{
|
|
"test": "failed",
|
|
}
|
|
var exported map[string]interface{}
|
|
err := vm.ExportTo(vm.ToValue(m), &exported)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
exported["test"] = "passed"
|
|
if v := m["test"]; v != "passed" {
|
|
t.Fatalf("Unexpected m[\"test\"]: %v", v)
|
|
}
|
|
}
|
|
|
|
func TestExportToWrappedMapCustom(t *testing.T) {
|
|
type CustomMap map[string]bool
|
|
vm := New()
|
|
m := CustomMap{}
|
|
var exported CustomMap
|
|
err := vm.ExportTo(vm.ToValue(m), &exported)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
exported["test"] = true
|
|
if v := m["test"]; v != true {
|
|
t.Fatalf("Unexpected m[\"test\"]: %v", v)
|
|
}
|
|
}
|
|
|
|
func TestExportToSliceNonIterable(t *testing.T) {
|
|
vm := New()
|
|
o := vm.NewObject()
|
|
var a []interface{}
|
|
err := vm.ExportTo(o, &a)
|
|
if err == nil {
|
|
t.Fatal("Expected an error")
|
|
}
|
|
if len(a) != 0 {
|
|
t.Fatalf("a: %v", a)
|
|
}
|
|
if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func ExampleRuntime_ExportTo_iterableToSlice() {
|
|
vm := New()
|
|
v, err := vm.RunString(`
|
|
function reverseIterator() {
|
|
const arr = this;
|
|
let idx = arr.length;
|
|
return {
|
|
next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
|
|
}
|
|
}
|
|
const arr = [1,2,3];
|
|
arr[Symbol.iterator] = reverseIterator;
|
|
arr;
|
|
`)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var arr []int
|
|
err = vm.ExportTo(v, &arr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Println(arr)
|
|
// Output: [3 2 1]
|
|
}
|
|
|
|
func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) {
|
|
vm := New()
|
|
v, err := vm.RunString(`
|
|
function reverseIterator() {
|
|
const arr = this;
|
|
let idx = arr.length;
|
|
return {
|
|
next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
|
|
}
|
|
}
|
|
const arr = [1,2,3];
|
|
arr[Symbol.iterator] = reverseIterator;
|
|
new Proxy(arr, {});
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var arr []int
|
|
err = vm.ExportTo(v, &arr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out := fmt.Sprint(arr); out != "[3 2 1]" {
|
|
t.Fatal(out)
|
|
}
|
|
}
|
|
|
|
func ExampleRuntime_ExportTo_arrayLikeToSlice() {
|
|
vm := New()
|
|
v, err := vm.RunString(`
|
|
({
|
|
length: 3,
|
|
0: 1,
|
|
1: 2,
|
|
2: 3
|
|
});
|
|
`)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var arr []int
|
|
err = vm.ExportTo(v, &arr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Println(arr)
|
|
// Output: [1 2 3]
|
|
}
|
|
|
|
func TestExportArrayToArrayMismatchedLengths(t *testing.T) {
|
|
vm := New()
|
|
a := vm.NewArray(1, 2)
|
|
var a1 [3]int
|
|
err := vm.ExportTo(a, &a1)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExportIterableToArrayMismatchedLengths(t *testing.T) {
|
|
vm := New()
|
|
a, err := vm.RunString(`
|
|
new Map([[1, true], [2, true]]);
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var a1 [3]interface{}
|
|
err = vm.ExportTo(a, &a1)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) {
|
|
vm := New()
|
|
a, err := vm.RunString(`
|
|
({
|
|
length: 2,
|
|
0: true,
|
|
1: true
|
|
});
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var a1 [3]interface{}
|
|
err = vm.ExportTo(a, &a1)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSetForeignReturnValue(t *testing.T) {
|
|
const SCRIPT = `
|
|
var array = [1, 2, 3];
|
|
var arrayTarget = new Proxy(array, {});
|
|
|
|
Object.preventExtensions(array);
|
|
|
|
!Reflect.set(arrayTarget, "foo", 2);
|
|
`
|
|
|
|
testScript(SCRIPT, valueTrue, t)
|
|
}
|
|
|
|
func TestDefinePropertiesUndefinedVal(t *testing.T) {
|
|
const SCRIPT = `
|
|
var target = {};
|
|
var sym = Symbol();
|
|
target[sym] = 1;
|
|
target.foo = 2;
|
|
target[0] = 3;
|
|
|
|
var getOwnKeys = [];
|
|
var proxy = new Proxy(target, {
|
|
getOwnPropertyDescriptor: function(_target, key) {
|
|
getOwnKeys.push(key);
|
|
},
|
|
});
|
|
|
|
Object.defineProperties({}, proxy);
|
|
true;
|
|
`
|
|
|
|
testScript(SCRIPT, valueTrue, t)
|
|
}
|
|
|
|
func ExampleObject_Delete() {
|
|
vm := New()
|
|
obj := vm.NewObject()
|
|
_ = obj.Set("test", true)
|
|
before := obj.Get("test")
|
|
_ = obj.Delete("test")
|
|
after := obj.Get("test")
|
|
fmt.Printf("before: %v, after: %v", before, after)
|
|
// Output: before: true, after: <nil>
|
|
}
|
|
|
|
func ExampleObject_GetOwnPropertyNames() {
|
|
vm := New()
|
|
obj := vm.NewObject()
|
|
obj.DefineDataProperty("test", vm.ToValue(true), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE) // define a non-enumerable property that Keys() would not return
|
|
fmt.Print(obj.GetOwnPropertyNames())
|
|
// Output: [test]
|
|
}
|
|
|
|
func TestObjectEquality(t *testing.T) {
|
|
type CustomInt int
|
|
type S struct {
|
|
F CustomInt
|
|
}
|
|
vm := New()
|
|
vm.Set("s", S{})
|
|
// indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively),
|
|
// but for objects they must behave the same. De-referencing s.F creates a new wrapper each time.
|
|
vm.testScriptWithTestLib(`
|
|
assert.sameValue([s.F].indexOf(s.F), 0, "indexOf");
|
|
assert([s.F].includes(s.F));
|
|
`, _undefined, t)
|
|
}
|
|
|
|
func BenchmarkPut(b *testing.B) {
|
|
v := &Object{}
|
|
|
|
o := &baseObject{
|
|
val: v,
|
|
extensible: true,
|
|
}
|
|
v.self = o
|
|
|
|
o.init()
|
|
|
|
var key Value = asciiString("test")
|
|
var val Value = valueInt(123)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
v.setOwn(key, val, false)
|
|
}
|
|
}
|
|
|
|
func BenchmarkPutStr(b *testing.B) {
|
|
v := &Object{}
|
|
|
|
o := &baseObject{
|
|
val: v,
|
|
extensible: true,
|
|
}
|
|
|
|
o.init()
|
|
|
|
v.self = o
|
|
|
|
var val Value = valueInt(123)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
o.setOwnStr("test", val, false)
|
|
}
|
|
}
|
|
|
|
func BenchmarkGet(b *testing.B) {
|
|
v := &Object{}
|
|
|
|
o := &baseObject{
|
|
val: v,
|
|
extensible: true,
|
|
}
|
|
|
|
o.init()
|
|
|
|
v.self = o
|
|
var n Value = asciiString("test")
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
v.get(n, nil)
|
|
}
|
|
|
|
}
|
|
|
|
func BenchmarkGetStr(b *testing.B) {
|
|
v := &Object{}
|
|
|
|
o := &baseObject{
|
|
val: v,
|
|
extensible: true,
|
|
}
|
|
v.self = o
|
|
|
|
o.init()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
o.getStr("test", nil)
|
|
}
|
|
}
|
|
|
|
func __toString(v Value) string {
|
|
switch v := v.(type) {
|
|
case asciiString:
|
|
return string(v)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func BenchmarkToString1(b *testing.B) {
|
|
v := asciiString("test")
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
v.toString()
|
|
}
|
|
}
|
|
|
|
func BenchmarkToString2(b *testing.B) {
|
|
v := asciiString("test")
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
__toString(v)
|
|
}
|
|
}
|
|
|
|
func BenchmarkConv(b *testing.B) {
|
|
count := int64(0)
|
|
for i := 0; i < b.N; i++ {
|
|
count += valueInt(123).ToInteger()
|
|
}
|
|
if count == 0 {
|
|
b.Fatal("zero")
|
|
}
|
|
}
|
|
|
|
func BenchmarkToUTF8String(b *testing.B) {
|
|
var s String = asciiString("test")
|
|
for i := 0; i < b.N; i++ {
|
|
_ = s.String()
|
|
}
|
|
}
|
|
|
|
func BenchmarkAdd(b *testing.B) {
|
|
var x, y Value
|
|
x = valueInt(2)
|
|
y = valueInt(2)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
if xi, ok := x.(valueInt); ok {
|
|
if yi, ok := y.(valueInt); ok {
|
|
x = xi + yi
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAddString(b *testing.B) {
|
|
var x, y Value
|
|
|
|
tst := asciiString("22")
|
|
x = asciiString("2")
|
|
y = asciiString("2")
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
var z Value
|
|
if xi, ok := x.(String); ok {
|
|
if yi, ok := y.(String); ok {
|
|
z = xi.Concat(yi)
|
|
}
|
|
}
|
|
if !z.StrictEquals(tst) {
|
|
b.Fatalf("Unexpected result %v", x)
|
|
}
|
|
}
|
|
}
|