github.com/nuvolaris/goja@v0.0.0-20230825100449-967811910c6d/object_test.go (about)

     1  package goja
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  )
     9  
    10  func TestDefineProperty(t *testing.T) {
    11  	r := New()
    12  	o := r.NewObject()
    13  
    14  	err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE)
    15  	if err != nil {
    16  		t.Fatal(err)
    17  	}
    18  
    19  	err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int {
    20  		return 1
    21  	}), nil, FLAG_TRUE, FLAG_TRUE)
    22  	if err != nil {
    23  		t.Fatal(err)
    24  	}
    25  
    26  	err = o.DefineAccessorProperty("accessor_rw",
    27  		r.ToValue(func(call FunctionCall) Value {
    28  			return o.Get("__hidden")
    29  		}),
    30  		r.ToValue(func(call FunctionCall) (ret Value) {
    31  			o.Set("__hidden", call.Argument(0))
    32  			return
    33  		}),
    34  		FLAG_TRUE, FLAG_TRUE)
    35  
    36  	if err != nil {
    37  		t.Fatal(err)
    38  	}
    39  
    40  	if v := o.Get("accessor_ro"); v.ToInteger() != 1 {
    41  		t.Fatalf("Unexpected accessor value: %v", v)
    42  	}
    43  
    44  	err = o.Set("accessor_ro", r.ToValue(2))
    45  	if err == nil {
    46  		t.Fatal("Expected an error")
    47  	}
    48  	if ex, ok := err.(*Exception); ok {
    49  		if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" {
    50  			t.Fatalf("Unexpected error: '%s'", msg)
    51  		}
    52  	} else {
    53  		t.Fatalf("Unexected error type: %T", err)
    54  	}
    55  
    56  	err = o.Set("accessor_rw", 42)
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  
    61  	if v := o.Get("accessor_rw"); v.ToInteger() != 42 {
    62  		t.Fatalf("Unexpected value: %v", v)
    63  	}
    64  }
    65  
    66  func TestPropertyOrder(t *testing.T) {
    67  	const SCRIPT = `
    68  	var o = {};
    69  	var sym1 = Symbol(1);
    70  	var sym2 = Symbol(2);
    71  	o[sym2] = 1;
    72  	o[4294967294] = 1;
    73  	o[2] = 1;
    74  	o[1] = 1;
    75  	o[0] = 1;
    76  	o["02"] = 1;
    77  	o[4294967295] = 1;
    78  	o["01"] = 1;
    79  	o["00"] = 1;
    80  	o[sym1] = 1;
    81  	var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1];
    82  	var actual = Reflect.ownKeys(o);
    83  	if (actual.length !== expected.length) {
    84  		throw new Error("Unexpected length: "+actual.length);
    85  	}
    86  	for (var i = 0; i < actual.length; i++) {
    87  		if (actual[i] !== expected[i]) {
    88  			throw new Error("Unexpected list: " + actual);
    89  		}
    90  	}
    91  	`
    92  
    93  	testScript(SCRIPT, _undefined, t)
    94  }
    95  
    96  func TestDefinePropertiesSymbol(t *testing.T) {
    97  	const SCRIPT = `
    98  	var desc = {};
    99  	desc[Symbol.toStringTag] = {value: "Test"};
   100  	var o = {};
   101  	Object.defineProperties(o, desc);
   102  	o[Symbol.toStringTag] === "Test";
   103  	`
   104  
   105  	testScript(SCRIPT, valueTrue, t)
   106  }
   107  
   108  func TestObjectShorthandProperties(t *testing.T) {
   109  	const SCRIPT = `
   110  	var b = 1;
   111  	var a = {b, get() {return "c"}};
   112  
   113  	assert.sameValue(a.b, b, "#1");
   114  	assert.sameValue(a.get(), "c", "#2");
   115  
   116  	var obj = {
   117  		w\u0069th() { return 42; }
   118      };
   119  
   120  	assert.sameValue(obj['with'](), 42, 'property exists');
   121  	`
   122  	testScriptWithTestLib(SCRIPT, _undefined, t)
   123  }
   124  
   125  func TestObjectAssign(t *testing.T) {
   126  	const SCRIPT = `
   127  	assert.sameValue(Object.assign({ b: 1 }, { get a() {
   128            Object.defineProperty(this, "b", {
   129              value: 3,
   130              enumerable: false
   131            });
   132          }, b: 2 }).b, 1, "#1");
   133  
   134  	assert.sameValue(Object.assign({ b: 1 }, { get a() {
   135            delete this.b;
   136          }, b: 2 }).b, 1, "#2");
   137  	`
   138  	testScriptWithTestLib(SCRIPT, _undefined, t)
   139  }
   140  
   141  func TestExportCircular(t *testing.T) {
   142  	vm := New()
   143  	o := vm.NewObject()
   144  	o.Set("o", o)
   145  	v := o.Export()
   146  	if m, ok := v.(map[string]interface{}); ok {
   147  		if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() {
   148  			t.Fatal("Unexpected value")
   149  		}
   150  	} else {
   151  		t.Fatal("Unexpected type")
   152  	}
   153  
   154  	res, err := vm.RunString(`var a = []; a[0] = a;`)
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	v = res.Export()
   159  	if a, ok := v.([]interface{}); ok {
   160  		if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() {
   161  			t.Fatal("Unexpected value")
   162  		}
   163  	} else {
   164  		t.Fatal("Unexpected type")
   165  	}
   166  }
   167  
   168  type test_s struct {
   169  	S *test_s1
   170  }
   171  type test_s1 struct {
   172  	S *test_s
   173  }
   174  
   175  func TestExportToCircular(t *testing.T) {
   176  	vm := New()
   177  	o := vm.NewObject()
   178  	o.Set("o", o)
   179  	var m map[string]interface{}
   180  	err := vm.ExportTo(o, &m)
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  
   185  	type K string
   186  	type T map[K]T
   187  	var m1 T
   188  	err = vm.ExportTo(o, &m1)
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  
   193  	type A []A
   194  	var a A
   195  	res, err := vm.RunString("var a = []; a[0] = a;")
   196  	if err != nil {
   197  		t.Fatal(err)
   198  	}
   199  	err = vm.ExportTo(res, &a)
   200  	if err != nil {
   201  		t.Fatal(err)
   202  	}
   203  	if &a[0] != &a[0][0] {
   204  		t.Fatal("values do not match")
   205  	}
   206  
   207  	o = vm.NewObject()
   208  	o.Set("S", o)
   209  	var s test_s
   210  	err = vm.ExportTo(o, &s)
   211  	if err != nil {
   212  		t.Fatal(err)
   213  	}
   214  	if s.S.S != &s {
   215  		t.Fatalf("values do not match: %v, %v", s.S.S, &s)
   216  	}
   217  
   218  	type test_s2 struct {
   219  		S  interface{}
   220  		S1 *test_s2
   221  	}
   222  
   223  	var s2 test_s2
   224  	o.Set("S1", o)
   225  
   226  	err = vm.ExportTo(o, &s2)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  
   231  	if m, ok := s2.S.(map[string]interface{}); ok {
   232  		if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() {
   233  			t.Fatal("Unexpected m.S")
   234  		}
   235  	} else {
   236  		t.Fatalf("Unexpected s2.S type: %T", s2.S)
   237  	}
   238  	if s2.S1 != &s2 {
   239  		t.Fatal("Unexpected s2.S1")
   240  	}
   241  
   242  	o1 := vm.NewObject()
   243  	o1.Set("S", o)
   244  	o1.Set("S1", o)
   245  	err = vm.ExportTo(o1, &s2)
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	if s2.S1.S1 != s2.S1 {
   250  		t.Fatal("Unexpected s2.S1.S1")
   251  	}
   252  }
   253  
   254  func TestExportWrappedMap(t *testing.T) {
   255  	vm := New()
   256  	m := map[string]interface{}{
   257  		"test": "failed",
   258  	}
   259  	exported := vm.ToValue(m).Export()
   260  	if exportedMap, ok := exported.(map[string]interface{}); ok {
   261  		exportedMap["test"] = "passed"
   262  		if v := m["test"]; v != "passed" {
   263  			t.Fatalf("Unexpected m[\"test\"]: %v", v)
   264  		}
   265  	} else {
   266  		t.Fatalf("Unexpected export type: %T", exported)
   267  	}
   268  }
   269  
   270  func TestExportToWrappedMap(t *testing.T) {
   271  	vm := New()
   272  	m := map[string]interface{}{
   273  		"test": "failed",
   274  	}
   275  	var exported map[string]interface{}
   276  	err := vm.ExportTo(vm.ToValue(m), &exported)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	exported["test"] = "passed"
   281  	if v := m["test"]; v != "passed" {
   282  		t.Fatalf("Unexpected m[\"test\"]: %v", v)
   283  	}
   284  }
   285  
   286  func TestExportToWrappedMapCustom(t *testing.T) {
   287  	type CustomMap map[string]bool
   288  	vm := New()
   289  	m := CustomMap{}
   290  	var exported CustomMap
   291  	err := vm.ExportTo(vm.ToValue(m), &exported)
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	exported["test"] = true
   296  	if v := m["test"]; v != true {
   297  		t.Fatalf("Unexpected m[\"test\"]: %v", v)
   298  	}
   299  }
   300  
   301  func TestExportToSliceNonIterable(t *testing.T) {
   302  	vm := New()
   303  	o := vm.NewObject()
   304  	var a []interface{}
   305  	err := vm.ExportTo(o, &a)
   306  	if err == nil {
   307  		t.Fatal("Expected an error")
   308  	}
   309  	if len(a) != 0 {
   310  		t.Fatalf("a: %v", a)
   311  	}
   312  	if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" {
   313  		t.Fatalf("Unexpected error: %v", err)
   314  	}
   315  }
   316  
   317  func ExampleRuntime_ExportTo_iterableToSlice() {
   318  	vm := New()
   319  	v, err := vm.RunString(`
   320  	function reverseIterator() {
   321  	    const arr = this;
   322  	    let idx = arr.length;
   323  	    return {
   324  			next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
   325  	    }
   326  	}
   327  	const arr = [1,2,3];
   328  	arr[Symbol.iterator] = reverseIterator;
   329  	arr;
   330  	`)
   331  	if err != nil {
   332  		panic(err)
   333  	}
   334  
   335  	var arr []int
   336  	err = vm.ExportTo(v, &arr)
   337  	if err != nil {
   338  		panic(err)
   339  	}
   340  
   341  	fmt.Println(arr)
   342  	// Output: [3 2 1]
   343  }
   344  
   345  func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) {
   346  	vm := New()
   347  	v, err := vm.RunString(`
   348  	function reverseIterator() {
   349  	    const arr = this;
   350  	    let idx = arr.length;
   351  	    return {
   352  			next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
   353  	    }
   354  	}
   355  	const arr = [1,2,3];
   356  	arr[Symbol.iterator] = reverseIterator;
   357  	new Proxy(arr, {});
   358  	`)
   359  	if err != nil {
   360  		t.Fatal(err)
   361  	}
   362  
   363  	var arr []int
   364  	err = vm.ExportTo(v, &arr)
   365  	if err != nil {
   366  		t.Fatal(err)
   367  	}
   368  	if out := fmt.Sprint(arr); out != "[3 2 1]" {
   369  		t.Fatal(out)
   370  	}
   371  }
   372  
   373  func ExampleRuntime_ExportTo_arrayLikeToSlice() {
   374  	vm := New()
   375  	v, err := vm.RunString(`
   376  	({
   377  		length: 3,
   378  		0: 1,
   379  		1: 2,
   380  		2: 3
   381  	});
   382  	`)
   383  	if err != nil {
   384  		panic(err)
   385  	}
   386  
   387  	var arr []int
   388  	err = vm.ExportTo(v, &arr)
   389  	if err != nil {
   390  		panic(err)
   391  	}
   392  
   393  	fmt.Println(arr)
   394  	// Output: [1 2 3]
   395  }
   396  
   397  func TestExportArrayToArrayMismatchedLengths(t *testing.T) {
   398  	vm := New()
   399  	a := vm.NewArray(1, 2)
   400  	var a1 [3]int
   401  	err := vm.ExportTo(a, &a1)
   402  	if err == nil {
   403  		t.Fatal("expected error")
   404  	}
   405  	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
   406  		t.Fatalf("unexpected error: %v", err)
   407  	}
   408  }
   409  
   410  func TestExportIterableToArrayMismatchedLengths(t *testing.T) {
   411  	vm := New()
   412  	a, err := vm.RunString(`
   413  		new Map([[1, true], [2, true]]);
   414  	`)
   415  	if err != nil {
   416  		t.Fatal(err)
   417  	}
   418  
   419  	var a1 [3]interface{}
   420  	err = vm.ExportTo(a, &a1)
   421  	if err == nil {
   422  		t.Fatal("expected error")
   423  	}
   424  	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
   425  		t.Fatalf("unexpected error: %v", err)
   426  	}
   427  }
   428  
   429  func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) {
   430  	vm := New()
   431  	a, err := vm.RunString(`
   432  		({
   433  			length: 2,
   434  			0: true,
   435  			1: true
   436  		});
   437  	`)
   438  	if err != nil {
   439  		t.Fatal(err)
   440  	}
   441  
   442  	var a1 [3]interface{}
   443  	err = vm.ExportTo(a, &a1)
   444  	if err == nil {
   445  		t.Fatal("expected error")
   446  	}
   447  	if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
   448  		t.Fatalf("unexpected error: %v", err)
   449  	}
   450  }
   451  
   452  func TestSetForeignReturnValue(t *testing.T) {
   453  	const SCRIPT = `
   454  	var array = [1, 2, 3];
   455  	var arrayTarget = new Proxy(array, {});
   456  
   457  	Object.preventExtensions(array);
   458  
   459  	!Reflect.set(arrayTarget, "foo", 2);
   460  	`
   461  
   462  	testScript(SCRIPT, valueTrue, t)
   463  }
   464  
   465  func TestDefinePropertiesUndefinedVal(t *testing.T) {
   466  	const SCRIPT = `
   467  var target = {};
   468  var sym = Symbol();
   469  target[sym] = 1;
   470  target.foo = 2;
   471  target[0] = 3;
   472  
   473  var getOwnKeys = [];
   474  var proxy = new Proxy(target, {
   475    getOwnPropertyDescriptor: function(_target, key) {
   476      getOwnKeys.push(key);
   477    },
   478  });
   479  
   480  Object.defineProperties({}, proxy);
   481  	true;
   482  	`
   483  
   484  	testScript(SCRIPT, valueTrue, t)
   485  }
   486  
   487  func ExampleObject_Delete() {
   488  	vm := New()
   489  	obj := vm.NewObject()
   490  	_ = obj.Set("test", true)
   491  	before := obj.Get("test")
   492  	_ = obj.Delete("test")
   493  	after := obj.Get("test")
   494  	fmt.Printf("before: %v, after: %v", before, after)
   495  	// Output: before: true, after: <nil>
   496  }
   497  
   498  func TestObjectEquality(t *testing.T) {
   499  	type CustomInt int
   500  	type S struct {
   501  		F CustomInt
   502  	}
   503  	vm := New()
   504  	vm.Set("s", S{})
   505  	// indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively),
   506  	// but for objects they must behave the same. De-referencing s.F creates a new wrapper each time.
   507  	vm.testScriptWithTestLib(`
   508  	assert.sameValue([s.F].indexOf(s.F), 0, "indexOf");
   509  	assert([s.F].includes(s.F));
   510  	`, _undefined, t)
   511  }
   512  
   513  func BenchmarkPut(b *testing.B) {
   514  	v := &Object{}
   515  
   516  	o := &baseObject{
   517  		val:        v,
   518  		extensible: true,
   519  	}
   520  	v.self = o
   521  
   522  	o.init()
   523  
   524  	var key Value = asciiString("test")
   525  	var val Value = valueInt(123)
   526  
   527  	for i := 0; i < b.N; i++ {
   528  		v.setOwn(key, val, false)
   529  	}
   530  }
   531  
   532  func BenchmarkPutStr(b *testing.B) {
   533  	v := &Object{}
   534  
   535  	o := &baseObject{
   536  		val:        v,
   537  		extensible: true,
   538  	}
   539  
   540  	o.init()
   541  
   542  	v.self = o
   543  
   544  	var val Value = valueInt(123)
   545  
   546  	for i := 0; i < b.N; i++ {
   547  		o.setOwnStr("test", val, false)
   548  	}
   549  }
   550  
   551  func BenchmarkGet(b *testing.B) {
   552  	v := &Object{}
   553  
   554  	o := &baseObject{
   555  		val:        v,
   556  		extensible: true,
   557  	}
   558  
   559  	o.init()
   560  
   561  	v.self = o
   562  	var n Value = asciiString("test")
   563  
   564  	for i := 0; i < b.N; i++ {
   565  		v.get(n, nil)
   566  	}
   567  
   568  }
   569  
   570  func BenchmarkGetStr(b *testing.B) {
   571  	v := &Object{}
   572  
   573  	o := &baseObject{
   574  		val:        v,
   575  		extensible: true,
   576  	}
   577  	v.self = o
   578  
   579  	o.init()
   580  
   581  	for i := 0; i < b.N; i++ {
   582  		o.getStr("test", nil)
   583  	}
   584  }
   585  
   586  func __toString(v Value) string {
   587  	switch v := v.(type) {
   588  	case asciiString:
   589  		return string(v)
   590  	default:
   591  		return ""
   592  	}
   593  }
   594  
   595  func BenchmarkToString1(b *testing.B) {
   596  	v := asciiString("test")
   597  
   598  	for i := 0; i < b.N; i++ {
   599  		v.toString()
   600  	}
   601  }
   602  
   603  func BenchmarkToString2(b *testing.B) {
   604  	v := asciiString("test")
   605  
   606  	for i := 0; i < b.N; i++ {
   607  		__toString(v)
   608  	}
   609  }
   610  
   611  func BenchmarkConv(b *testing.B) {
   612  	count := int64(0)
   613  	for i := 0; i < b.N; i++ {
   614  		count += valueInt(123).ToInteger()
   615  	}
   616  	if count == 0 {
   617  		b.Fatal("zero")
   618  	}
   619  }
   620  
   621  func BenchmarkToUTF8String(b *testing.B) {
   622  	var s String = asciiString("test")
   623  	for i := 0; i < b.N; i++ {
   624  		_ = s.String()
   625  	}
   626  }
   627  
   628  func BenchmarkAdd(b *testing.B) {
   629  	var x, y Value
   630  	x = valueInt(2)
   631  	y = valueInt(2)
   632  
   633  	for i := 0; i < b.N; i++ {
   634  		if xi, ok := x.(valueInt); ok {
   635  			if yi, ok := y.(valueInt); ok {
   636  				x = xi + yi
   637  			}
   638  		}
   639  	}
   640  }
   641  
   642  func BenchmarkAddString(b *testing.B) {
   643  	var x, y Value
   644  
   645  	tst := asciiString("22")
   646  	x = asciiString("2")
   647  	y = asciiString("2")
   648  
   649  	for i := 0; i < b.N; i++ {
   650  		var z Value
   651  		if xi, ok := x.(String); ok {
   652  			if yi, ok := y.(String); ok {
   653  				z = xi.Concat(yi)
   654  			}
   655  		}
   656  		if !z.StrictEquals(tst) {
   657  			b.Fatalf("Unexpected result %v", x)
   658  		}
   659  	}
   660  }