diff --git a/test-app/app/src/main/assets/app/mainpage.js b/test-app/app/src/main/assets/app/mainpage.js index 031ae8ddd..1893b4b73 100644 --- a/test-app/app/src/main/assets/app/mainpage.js +++ b/test-app/app/src/main/assets/app/mainpage.js @@ -54,6 +54,7 @@ require("./tests/testInterfaceImplementation"); require("./tests/testRuntimeImplementedAPIs"); require("./tests/testsInstanceOfOperator"); require("./tests/testReleaseNativeCounterpart"); +require("./tests/testJSONObjects"); require("./tests/kotlin/companions/testCompanionObjectsSupport"); require("./tests/kotlin/properties/testPropertiesSupport"); require("./tests/kotlin/delegation/testDelegationSupport"); diff --git a/test-app/app/src/main/assets/app/tests/testGC.js b/test-app/app/src/main/assets/app/tests/testGC.js index 4039a9559..89e337625 100644 --- a/test-app/app/src/main/assets/app/tests/testGC.js +++ b/test-app/app/src/main/assets/app/tests/testGC.js @@ -1,31 +1,33 @@ -describe("Tests garbage collection", function () { - +// Run GC tests only in Full marking mode +let describeFunc = __markingMode == 0 ? describe : xdescribe; + +describeFunc("Tests garbage collection", function () { var myCustomEquality = function(first, second) { return first == second; }; - + beforeEach(function() { jasmine.addCustomEqualityTester(myCustomEquality); }); xit("TestGarbageCollection", function (done) { - var normalTest = function () { + var normalTest = function () { __log("TEST: TestGarbageCollection"); - + var obj = new com.tns.tests.ClassX(); - + obj.dummy(); - + obj = null; - + gc(); java.lang.System.gc(); gc(); java.lang.System.gc(); gc(); java.lang.System.gc(); - + new java.lang.Thread(new java.lang.Runnable("ThreadFunc", { run: function() { var isCollected = com.tns.tests.ClassX.IsCollected; @@ -37,62 +39,62 @@ describe("Tests garbage collection", function () { }; normalTest(); }); - + // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method it("test1", function () { - + function createObjects(name) { var c1 = new com.tns.tests.Class1(); - + var cb1 = new com.tns.tests.Class1.Callback1(name, { getMessage: function() { var msg = c1.getMessage(); return msg; } }); - + return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); } - + expect(createObjects("Callback5")).toBe(true); expect(createObjects("Callback26")).toBe(true); - + gc(); java.lang.System.gc(); }); - + // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method it("test2", function () { - + function indref1() { this.class1 = new com.tns.tests.Class1(); } indref1.prototype.getMessage = function() { return "~~~" + this.class1.getMessage(); } - + function createObjects(name) { var c1 = new indref1(); - + var cb1 = new com.tns.tests.Class1.Callback1(name, { getMessage: function() { var msg = c1.getMessage(); return msg; } }); - + return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); } - + expect(createObjects("Callback55")).toBe(true); expect(createObjects("Callback56")).toBe(true); gc(); java.lang.System.gc(); }); - + // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method it("test3", function () { - + function indref2() { this.helper = new indref2helper(); } @@ -105,29 +107,29 @@ describe("Tests garbage collection", function () { indref2helper.prototype.getMessage = function() { return "***" + this.class1.getMessage(); } - + function createObjects(name) { var c1 = new indref2(); - + var cb1 = new com.tns.tests.Class1.Callback1(name, { getMessage: function() { var msg = c1.getMessage(); return msg; } }); - + return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); } - + expect(createObjects("Callback91")).toBe(true); expect(createObjects("Callback92")).toBe(true); gc(); java.lang.System.gc(); }); - + // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method it("test4", function () { - + function indref3() { this.helper = new indref3helper(); } @@ -136,54 +138,54 @@ describe("Tests garbage collection", function () { } function indref3helper() { this._class1 = new com.tns.tests.Class1(); - + Object.defineProperty(this, "class1", { get: function() { - return this._class1 - } + return this._class1 + } }); } indref3helper.prototype.getMessage = function() { return "^^^" + this.class1.getMessage(); - } - + } + function createObjects(name) { var c1 = new indref3(); - + var cb1 = new com.tns.tests.Class1.Callback1(name, { getMessage: function() { var msg = c1.getMessage(); return msg; } }); - + return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); } - + expect(createObjects("Callback1133")).toBe(true); expect(createObjects("Callback1134")).toBe(true); gc(); java.lang.System.gc(); }); - + // this test has implicit assert in com.tns.Runtime.getJavaObjectByID method //originally test was commented out xit("test5", function () { - + function indref4() { this.helper = new indref4helper(); } indref4.prototype.getMessage = function() { return "&&&" + this.helper.getMessageZZZ(); } - function indref4helper() { + function indref4helper() { var _class1 = new com.tns.tests.Class1(); - + __log("indref4helper _class1=" + _class1); - + Object.defineProperty(this, "class1", { get: function() { - return _class1 + return _class1 } ,enumerable: false }); @@ -191,42 +193,42 @@ describe("Tests garbage collection", function () { indref4helper.prototype.getMessageZZZ = function() { return "```" + this.class1.getMessage(); } - + function createObjects(name) { var c1 = new indref4(); - + var cb1 = new com.tns.tests.Class1.Callback1(name, { getMessage: function() { var msg = c1.getMessage(); return msg; } }); - + return com.tns.tests.Class1.Class2.printMessageWithDelay(cb1, 5 * 1000); } - + expect(createObjects("Callback1178")).toBe(true); expect(createObjects("Callback1179")).toBe(true); gc(); java.lang.System.gc(); }); - + it("testAccessingStringFieldWontLeak", function () { - + __log("TEST: testAccessingStringFieldWontLeak"); var dummy = new com.tns.tests.DummyClass(); - + for (var i=0; i<10000; i++) { var name = dummy.nameField; - + expect(name).toBe("dummy"); } }); xit("should persist JavaScript object when it reappears after GC", function () { - + function getObject() { var o = new java.lang.Object(); o.x = 123; @@ -287,7 +289,7 @@ describe("Tests garbage collection", function () { gc(); java.lang.System.gc(); }) - + it("should properly reintroduce Java object back in a callback", function () { function getTestObject() { return new com.tns.tests.BadEqualsTest( @@ -300,19 +302,19 @@ describe("Tests garbage collection", function () { } var test = getTestObject(); - + // flush LRU cache for (var i=0; i<65536; i++) { new java.lang.Object().hashCode(); } - + gc(); java.lang.Runtime.getRuntime().gc(); gc(); java.lang.Runtime.getRuntime().gc(); gc(); java.lang.Runtime.getRuntime().gc(); - + test.postCallback(); }); }); \ No newline at end of file diff --git a/test-app/app/src/main/assets/app/tests/testJSONObjects.js b/test-app/app/src/main/assets/app/tests/testJSONObjects.js new file mode 100644 index 000000000..8b37ab200 --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testJSONObjects.js @@ -0,0 +1,62 @@ +describe("Test JSONObject conversions", () => { + it("org.json.JSONObject.from method is defined", () => { + expect(typeof org.json.JSONObject.from).toBe("function"); + }); + + it("JSONObject.from takes at least one argument", () => { + expect(() => org.json.JSONObject.from()).toThrowError(); + }); + + it("JSONObject.from with boolean", () => { + let param = true; + let actual = org.json.JSONObject.from(param); + expect(actual).toBe(param); + }); + + it("JSONObject.from with string", () => { + let param = "some param"; + let actual = org.json.JSONObject.from(param); + expect(actual).toBe(param); + }); + + it("JSONObject.from with number", () => { + let param = 123; + let actual = org.json.JSONObject.from(param); + expect(actual).toBe(param); + }); + + it("JSONObject.from with date must serialize it to JSON using ISO8601", () => { + let timestamp = 1570696661136; + let param = new Date(timestamp); + let actual = org.json.JSONObject.from(param); + expect(actual).toBe("2019-10-10T08:37:41.136Z"); + }); + + it("JSONObject.from with object", () => { + let param = { + prop1: "prop1 value", + prop2: 123, + prop3: { + prop4: "prop 4 value" + } + }; + let actual = org.json.JSONObject.from(param); + expect(actual instanceof org.json.JSONObject).toBe(true); + let actualStr = com.tns.tests.JSONObjectMethods.testWithObject(actual); + expect(actualStr).toBe(`{"prop1":"prop1 value","prop2":123,"prop3":{"prop4":"prop 4 value"}}`); + }); + + it("JSONObject.from with array", () => { + let param = [{ + prop1: "item 1, prop1 value", + prop2: 123 + }, { + prop1: "item 2, prop1 value", + prop2: 456 + }]; + let actual = org.json.JSONObject.from(param); + expect(actual instanceof org.json.JSONArray).toBe(true); + let actualStr = com.tns.tests.JSONObjectMethods.testWithArray(actual); + expect(actualStr).toBe(`[{"prop1":"item 1, prop1 value","prop2":123},{"prop1":"item 2, prop1 value","prop2":456}]`); + }); +}); \ No newline at end of file diff --git a/test-app/app/src/main/java/com/tns/tests/JSONObjectMethods.java b/test-app/app/src/main/java/com/tns/tests/JSONObjectMethods.java new file mode 100644 index 000000000..9bac67ea3 --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/JSONObjectMethods.java @@ -0,0 +1,14 @@ +package com.tns.tests; + +import org.json.JSONArray; +import org.json.JSONObject; + +public class JSONObjectMethods { + public static String testWithObject(JSONObject obj) { + return obj.toString(); + } + + public static String testWithArray(JSONArray obj) { + return obj.toString(); + } +} diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index bd3ecc348..1f8a95269 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -149,6 +149,7 @@ add_library( src/main/cpp/JniSignatureParser.cpp src/main/cpp/JsArgConverter.cpp src/main/cpp/JsArgToArrayConverter.cpp + src/main/cpp/JSONObjectHelper.cpp src/main/cpp/Logger.cpp src/main/cpp/ManualInstrumentation.cpp src/main/cpp/MetadataMethodInfo.cpp diff --git a/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp b/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp new file mode 100644 index 000000000..9ca78d5a2 --- /dev/null +++ b/test-app/runtime/src/main/cpp/JSONObjectHelper.cpp @@ -0,0 +1,114 @@ +#include "NativeScriptException.h" +#include "JSONObjectHelper.h" +#include "ArgConverter.h" +#include +#include + +using namespace v8; +using namespace tns; + +JSONObjectHelper::JSONObjectHelper() + : m_objectManager(nullptr), m_serializeFunc(nullptr) { +} + +void JSONObjectHelper::CreateConvertFunctions(Isolate *isolate, const Local &global, ObjectManager* objectManager) { + m_objectManager = objectManager; + + m_serializeFunc = new Persistent(isolate, CreateSerializeFunc(isolate)); + + Local context = isolate->GetCurrentContext(); + + Local extData = External::New(isolate, this); + Local fromFunc = FunctionTemplate::New(isolate, ConvertCallbackStatic, extData)->GetFunction(context).ToLocalChecked(); + + Local jsonObjectFunc = global->Get(context, ArgConverter::ConvertToV8String(isolate, "org")) + .ToLocalChecked().As()->Get(context, ArgConverter::ConvertToV8String(isolate, "json")) + .ToLocalChecked().As()->Get(context, ArgConverter::ConvertToV8String(isolate, "JSONObject")) + .ToLocalChecked().As(); + + jsonObjectFunc->Set(context, ArgConverter::ConvertToV8String(isolate, "from"), fromFunc); +} + +void JSONObjectHelper::ConvertCallbackStatic(const FunctionCallbackInfo& info) { + try { + Local extData = info.Data().As(); + auto thiz = reinterpret_cast(extData->Value()); + thiz->ConvertCallback(info); + } catch (NativeScriptException& e) { + e.ReThrowToV8(); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToV8(); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToV8(); + } +} + +void JSONObjectHelper::ConvertCallback(const FunctionCallbackInfo& info) { + if (info.Length() < 1) { + NativeScriptException nsEx(std::string("The \"from\" function expects one parameter")); + nsEx.ReThrowToV8(); + return; + } + + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + Local serializeFunc = m_serializeFunc->Get(isolate); + Local args[] = { info[0] }; + Local result; + TryCatch tc(isolate); + if (!serializeFunc->Call(context, Undefined(isolate), 1, args).ToLocal(&result)) { + throw NativeScriptException(tc, "Error serializing to JSONObject"); + } + + info.GetReturnValue().Set(result); +} + +Local JSONObjectHelper::CreateSerializeFunc(Isolate* isolate) { + std::string source = + "(() => function serialize(data) {" + " let store;" + " switch (typeof data) {" + " case \"string\":" + " case \"boolean\":" + " case \"number\": {" + " return data;" + " }" + " case \"object\": {" + " if (!data) {" + " return null;" + " }" + "" + " if (data instanceof Date) {" + " return data.toJSON();" + " }" + "" + " if (Array.isArray(data)) {" + " store = new org.json.JSONArray();" + " data.forEach((item) => store.put(serialize(item)));" + " return store;" + " }" + "" + " store = new org.json.JSONObject();" + " Object.keys(data).forEach((key) => store.put(key, serialize(data[key])));" + " return store;" + " }" + " default:" + " return null;" + " }" + "})()"; + + Local context = isolate->GetCurrentContext(); + + Local