//////////////////////////////////////////////////////////////////////////////// // Copyright © 2022 xx foundation // // // // Use of this source code is governed by a license that can be found in the // // LICENSE file. // //////////////////////////////////////////////////////////////////////////////// //go:build js && wasm package utils import ( "encoding/base64" "encoding/json" "sort" "syscall/js" "testing" ) import ( "bytes" "fmt" "strings" ) // Tests that CopyBytesToGo returns a byte slice that matches the Uint8Array. func TestCopyBytesToGo(t *testing.T) { for i, val := range testBytes { // Create Uint8Array and set each element individually jsBytes := Uint8Array.New(len(val)) for j, v := range val { jsBytes.SetIndex(j, v) } goBytes := CopyBytesToGo(jsBytes) if !bytes.Equal(val, goBytes) { t.Errorf("Failed to recevie expected bytes from Uint8Array (%d)."+ "\nexpected: %d\nreceived: %d", i, val, goBytes) } } } // Tests that CopyBytesToJS returns a Javascript Uint8Array with values matching // the original byte slice. func TestCopyBytesToJS(t *testing.T) { for i, val := range testBytes { jsBytes := CopyBytesToJS(val) // Generate the expected string to match the output of toString() on a // Uint8Array expected := strings.ReplaceAll(fmt.Sprintf("%d", val), " ", ",")[1:] expected = expected[:len(expected)-1] // Get the string value of the Uint8Array jsString := jsBytes.Call("toString").String() if expected != jsString { t.Errorf("Failed to recevie expected string representation of "+ "the Uint8Array (%d).\nexpected: %s\nreceived: %s", i, expected, jsString) } } } // Tests that a byte slice converted to Javascript via CopyBytesToJS and // converted back to Go via CopyBytesToGo matches the original. func TestCopyBytesToJSCopyBytesToGo(t *testing.T) { for i, val := range testBytes { jsBytes := CopyBytesToJS(val) goBytes := CopyBytesToGo(jsBytes) if !bytes.Equal(val, goBytes) { t.Errorf("Failed to recevie expected bytes from Uint8Array (%d)."+ "\nexpected: %d\nreceived: %d", i, val, goBytes) } } } // Tests that JsToJson can convert a Javascript object to JSON that matches the // output of json.Marshal on the Go version of the same object. func TestJsToJson(t *testing.T) { testObj := map[string]any{ "nil": nil, "bool": true, "int": 1, "float": 1.5, "string": "I am string", "array": []any{1, 2, 3}, "object": map[string]any{"int": 5}, } expected, err := json.Marshal(testObj) if err != nil { t.Errorf("Failed to JSON marshal test object: %+v", err) } jsJson := JsToJson(js.ValueOf(testObj)) // Javascript does not return the JSON object fields sorted so the letters // of each Javascript string are sorted and compared er := []rune(string(expected)) sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] }) jj := []rune(jsJson) sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] }) if string(er) != string(jj) { t.Errorf("Recieved incorrect JSON from Javascript object."+ "\nexpected: %s\nreceived: %s", expected, jsJson) } } // Tests that JsToJson return a null object when the Javascript object is // undefined. func TestJsToJson_Undefined(t *testing.T) { expected, err := json.Marshal(nil) if err != nil { t.Errorf("Failed to JSON marshal test object: %+v", err) } jsJson := JsToJson(js.Undefined()) if string(expected) != jsJson { t.Errorf("Recieved incorrect JSON from Javascript object."+ "\nexpected: %s\nreceived: %s", expected, jsJson) } } // Tests that JsonToJS can convert a JSON object with multiple types to a // Javascript object and that all values match. func TestJsonToJS(t *testing.T) { testObj := map[string]any{ "nil": nil, "bool": true, "int": 1, "float": 1.5, "string": "I am string", "bytes": []byte{1, 2, 3}, "array": []any{1, 2, 3}, "object": map[string]any{"int": 5}, } jsonData, err := json.Marshal(testObj) if err != nil { t.Errorf("Failed to JSON marshal test object: %+v", err) } jsObj, err := JsonToJS(jsonData) if err != nil { t.Errorf("Failed to convert JSON to Javascript object: %+v", err) } for key, val := range testObj { jsVal := jsObj.Get(key) switch key { case "nil": if !jsVal.IsNull() { t.Errorf("Key %s is not null.", key) } case "bool": if jsVal.Bool() != val { t.Errorf("Incorrect value for key %s."+ "\nexpected: %t\nreceived: %t", key, val, jsVal.Bool()) } case "int": if jsVal.Int() != val { t.Errorf("Incorrect value for key %s."+ "\nexpected: %d\nreceived: %d", key, val, jsVal.Int()) } case "float": if jsVal.Float() != val { t.Errorf("Incorrect value for key %s."+ "\nexpected: %f\nreceived: %f", key, val, jsVal.Float()) } case "string": if jsVal.String() != val { t.Errorf("Incorrect value for key %s."+ "\nexpected: %s\nreceived: %s", key, val, jsVal.String()) } case "bytes": if jsVal.String() != base64.StdEncoding.EncodeToString(val.([]byte)) { t.Errorf("Incorrect value for key %s."+ "\nexpected: %s\nreceived: %s", key, base64.StdEncoding.EncodeToString(val.([]byte)), jsVal.String()) } case "array": for i, v := range val.([]any) { if jsVal.Index(i).Int() != v { t.Errorf("Incorrect value for key %s index %d."+ "\nexpected: %d\nreceived: %d", key, i, v, jsVal.Index(i).Int()) } } case "object": if jsVal.Get("int").Int() != val.(map[string]any)["int"] { t.Errorf("Incorrect value for key %s."+ "\nexpected: %d\nreceived: %d", key, val.(map[string]any)["int"], jsVal.Get("int").Int()) } } } } // Tests that JSON can be converted to a Javascript object via JsonToJS and back // to JSON using JsToJson and matches the original. func TestJsonToJSJsToJson(t *testing.T) { testObj := map[string]any{ "nil": nil, "bool": true, "int": 1, "float": 1.5, "string": "I am string", "bytes": []byte{1, 2, 3}, "array": []any{1, 2, 3}, "object": map[string]any{"int": 5}, } jsonData, err := json.Marshal(testObj) if err != nil { t.Errorf("Failed to JSON marshal test object: %+v", err) } jsObj, err := JsonToJS(jsonData) if err != nil { t.Errorf("Failed to convert the Javascript object to JSON: %+v", err) } jsJson := JsToJson(jsObj) // Javascript does not return the JSON object fields sorted so the letters // of each Javascript string are sorted and compared er := []rune(string(jsonData)) sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] }) jj := []rune(jsJson) sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] }) if string(er) != string(jj) { t.Errorf("JSON from Javascript does not match original."+ "\nexpected: %s\nreceived: %s", jsonData, jsJson) } } // Tests that JsErrorToJson can convert a Javascript object to JSON that matches // the output of json.Marshal on the Go version of the same object. func TestJsErrorToJson(t *testing.T) { testObj := map[string]any{ "nil": nil, "bool": true, "int": 1, "float": 1.5, "string": "I am string", "array": []any{1, 2, 3}, "object": map[string]any{"int": 5}, } expected, err := json.Marshal(testObj) if err != nil { t.Errorf("Failed to JSON marshal test object: %+v", err) } jsJson := JsErrorToJson(js.ValueOf(testObj)) // Javascript does not return the JSON object fields sorted so the letters // of each Javascript string are sorted and compared er := []rune(string(expected)) sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] }) jj := []rune(jsJson) sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] }) if string(er) != string(jj) { t.Errorf("Recieved incorrect JSON from Javascript object."+ "\nexpected: %s\nreceived: %s", expected, jsJson) } } // Tests that JsErrorToJson return a null object when the Javascript object is // undefined. func TestJsErrorToJson_Undefined(t *testing.T) { expected, err := json.Marshal(nil) if err != nil { t.Errorf("Failed to JSON marshal test object: %+v", err) } jsJson := JsErrorToJson(js.Undefined()) if string(expected) != jsJson { t.Errorf("Recieved incorrect JSON from Javascript object."+ "\nexpected: %s\nreceived: %s", expected, jsJson) } } // Tests that JsErrorToJson returns a JSON object containing the original error // string. func TestJsErrorToJson_ErrorObject(t *testing.T) { expected := "An error" jsErr := Error.New(expected) jsJson := JsErrorToJson(jsErr) if !strings.Contains(jsJson, expected) { t.Errorf("Recieved incorrect JSON from Javascript error."+ "\nexpected: %s\nreceived: %s", expected, jsJson) } }