ai_old/goja/object_test.go
2024-09-20 16:50:35 +08:00

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)
}
}
}