/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "clar.h" #include "test_jerry_port_common.h" #include "test_rocky_common.h" #include "applib/rockyjs/api/rocky_api_global.h" #include "applib/rockyjs/pbl_jerry_port.h" // Standard #include "string.h" // Fakes #include "fake_app_timer.h" #include "fake_time.h" #include "fake_logging.h" // Stubs #include "stubs_app_manager.h" #include "stubs_app_state.h" #include "stubs_logging.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" #include "stubs_resources.h" #include "stubs_sleep.h" #include "stubs_serial.h" #include "stubs_syscalls.h" #include "stubs_sys_exit.h" size_t heap_bytes_free(void) { return 123456; } static Window s_app_window_stack_get_top_window; Window *app_window_stack_get_top_window() { return &s_app_window_stack_get_top_window; } static int s_prv_api_init__callcount; static void prv_api_init(void) { s_prv_api_init__callcount++; } static int s_prv_api_add__callcount; static bool s_prv_api_add__result; static bool prv_api_add(const char *event_name, jerry_value_t handler) { s_prv_api_add__callcount++; return s_prv_api_add__result; } static int s_prv_api_remove__callcount; static void prv_api_remove(const char *event_name, jerry_value_t handler) { s_prv_api_remove__callcount++; } int s_prv_listener_a1__callcount; JERRY_FUNCTION(prv_listener_a1) { s_prv_listener_a1__callcount++; return jerry_create_undefined(); } int s_prv_listener_a2__callcount; JERRY_FUNCTION(prv_listener_a2) { s_prv_listener_a2__callcount++; return jerry_create_undefined(); } int s_prv_listener_b__callcount; JERRY_FUNCTION(prv_listener_b) { s_prv_listener_b__callcount++; return jerry_create_undefined(); } void test_rocky_api_global__initialize(void) { fake_app_timer_init(); rocky_runtime_context_init(); jerry_init(JERRY_INIT_EMPTY); s_app_window_stack_get_top_window = (Window){}; s_app_event_loop_callback = NULL; s_log_internal__expected = NULL; s_prv_api_init__callcount = 0; s_prv_api_add__callcount = 0; s_prv_api_add__result = false; s_prv_api_remove__callcount = 0; s_prv_listener_a1__callcount = 0; s_prv_listener_a2__callcount = 0; s_prv_listener_b__callcount = 0; } void test_rocky_api_global__cleanup(void) { fake_app_timer_deinit(); s_log_internal__expected = NULL; jerry_cleanup(); rocky_runtime_context_deinit(); rocky_global_deinit(); } void test_rocky_api_global__global(void) { char test_object[] = "var t = typeof _rocky"; // global doesn't exist in plain Jerry context EXECUTE_SCRIPT(test_object); ASSERT_JS_GLOBAL_EQUALS_S("t", "undefined"); // rocky_global_init() injects global... static const RockyGlobalAPI *apis[] = { NULL, }; rocky_global_init(apis); EXECUTE_SCRIPT(test_object); ASSERT_JS_GLOBAL_EQUALS_S("t", "object"); // ...which also has a method .on() EXECUTE_SCRIPT("var t = typeof _rocky.on"); ASSERT_JS_GLOBAL_EQUALS_S("t", "function"); /// ...which is an alias of .addEventListener() EXECUTE_SCRIPT("var a = (_rocky.on === _rocky.addEventListener);"); ASSERT_JS_GLOBAL_EQUALS_B("a", true); } void test_rocky_api_global__calls_init_and_notifies_about_apis(void) { static const RockyGlobalAPI api = { .init = prv_api_init, .add_handler = prv_api_add, }; static const RockyGlobalAPI *apis[] = { &api, NULL, }; rocky_global_init(apis); cl_assert_equal_i(1, s_prv_api_init__callcount); s_prv_api_add__result = true; s_log_internal__expected = (const char *[]){NULL}; EXECUTE_SCRIPT("_rocky.on('foo', function(){})"); cl_assert_equal_i(1, s_prv_api_add__callcount); cl_assert_equal_b(true, rocky_global_has_event_handlers("foo")); s_prv_api_add__result = false; s_log_internal__expected = (const char *[]){ "Unknown event 'bar'", NULL }; EXECUTE_SCRIPT("_rocky.on('bar', function(){})"); cl_assert_equal_i(2, s_prv_api_add__callcount); cl_assert_equal_b(false, rocky_global_has_event_handlers("bar")); cl_assert(*s_log_internal__expected == NULL); } void test_rocky_api_global__can_unsubsribe_event_handlers(void) { static const RockyGlobalAPI api = { .add_handler = prv_api_add, .remove_handler = prv_api_remove, }; static const RockyGlobalAPI *apis[] = { &api, NULL, }; rocky_global_init(apis); s_prv_api_add__result = true; EXECUTE_SCRIPT( "var f1 = function(){};\n" "var f2 = function(){};\n" "_rocky.on('foo', f1)\n" ); cl_assert_equal_i(1, s_prv_api_add__callcount); cl_assert_equal_b(true, rocky_global_has_event_handlers("foo")); // variables f1, f2 continue to exist between EXECUTE_SCRIPT calls EXECUTE_SCRIPT("var t = typeof f2;"); ASSERT_JS_GLOBAL_EQUALS_S("t", "function"); // rocky.off exists EXECUTE_SCRIPT( "t = typeof _rocky.off;\n" "var eq = _rocky.off === _rocky.removeEventListener;\n" ); ASSERT_JS_GLOBAL_EQUALS_S("t", "function"); ASSERT_JS_GLOBAL_EQUALS_B("eq", true); // from MDN docs: // Calling removeEventListener() with arguments that do not identify // any currently registered EventListener on the EventTarget has no effect. EXECUTE_SCRIPT( "_rocky.off('foo', f2);\n" "_rocky.off('unknownevent', f1);\n" ); cl_assert_equal_i(0, s_prv_api_remove__callcount); cl_assert_equal_b(true, rocky_global_has_event_handlers("foo")); EXECUTE_SCRIPT("_rocky.off('foo', f1);\n"); cl_assert_equal_i(1, s_prv_api_remove__callcount); cl_assert_equal_b(false, rocky_global_has_event_handlers("foo")); } void prv_add_event_listener_to_list(const char *event_name, jerry_value_t listener); int jerry_obj_refcount(jerry_value_t o); void test_rocky_api_global__refcount(void) { jerry_value_t o = jerry_create_object(); cl_assert_equal_i(1, jerry_obj_refcount(o)); jerry_acquire_value(o); cl_assert_equal_i(2, jerry_obj_refcount(o)); jerry_acquire_value(o); cl_assert_equal_i(3, jerry_obj_refcount(o)); jerry_release_value(o); cl_assert_equal_i(2, jerry_obj_refcount(o)); jerry_release_value(o); cl_assert_equal_i(1, jerry_obj_refcount(o)); jerry_release_value(o); cl_assert_equal_i(0, jerry_obj_refcount(o)); } void test_rocky_api_global__calls_listeners(void) { static const RockyGlobalAPI *apis[] = {NULL}; rocky_global_init(apis); prv_add_event_listener_to_list("a", jerry_create_external_function(prv_listener_a1)); cl_assert_equal_b(true, rocky_global_has_event_handlers("a")); cl_assert_equal_b(false, rocky_global_has_event_handlers("b")); prv_add_event_listener_to_list("b", jerry_create_external_function(prv_listener_b)); cl_assert_equal_b(true, rocky_global_has_event_handlers("a")); cl_assert_equal_b(true, rocky_global_has_event_handlers("b")); prv_add_event_listener_to_list("a", jerry_create_external_function(prv_listener_a2)); cl_assert_equal_b(true, rocky_global_has_event_handlers("a")); cl_assert_equal_b(true, rocky_global_has_event_handlers("b")); jerry_value_t a_event = rocky_global_create_event("a"); rocky_global_call_event_handlers(a_event); cl_assert_equal_i(1, s_prv_listener_a1__callcount); cl_assert_equal_i(1, s_prv_listener_a2__callcount); cl_assert_equal_i(0, s_prv_listener_b__callcount); jerry_release_value(a_event); jerry_value_t b_event = rocky_global_create_event("b"); rocky_global_call_event_handlers(b_event); cl_assert_equal_i(1, s_prv_listener_a1__callcount); cl_assert_equal_i(1, s_prv_listener_a2__callcount); cl_assert_equal_i(1, s_prv_listener_b__callcount); jerry_release_value(b_event); } void test_rocky_api_global__adds_listener_only_once(void) { static const RockyGlobalAPI *apis[] = {NULL}; rocky_global_init(apis); const jerry_value_t f = jerry_create_external_function(prv_listener_a1); prv_add_event_listener_to_list("a", f); prv_add_event_listener_to_list("a", f); cl_assert_equal_b(true, rocky_global_has_event_handlers("a")); jerry_value_t a_event = rocky_global_create_event("a"); rocky_global_call_event_handlers(a_event); // as second .on('a', f) "replaces" first, f will only be called once cl_assert_equal_i(1, s_prv_listener_a1__callcount); jerry_release_value(a_event); } void test_rocky_api_global__event_constructor(void) { static const RockyGlobalAPI *apis[] = {NULL}; rocky_global_init(apis); EXECUTE_SCRIPT( "_rocky.Event.prototype.myCustomThing = 'xyz';\n" "var e = new _rocky.Event('myevent');\n" "var t = e.type;\n" "var c = e.myCustomThing;\n" ); ASSERT_JS_GLOBAL_EQUALS_S("t", "myevent"); ASSERT_JS_GLOBAL_EQUALS_S("c", "xyz"); } void test_rocky_api_global__call_event_handlers_async(void) { static const RockyGlobalAPI api = { .init = prv_api_init, .add_handler = prv_api_add, }; static const RockyGlobalAPI *apis[] = { &api, NULL, }; rocky_global_init(apis); s_prv_api_add__result = true; EXECUTE_SCRIPT("var is_called = false; _rocky.on('a', function(e) { is_called = true; });"); jerry_value_t a_event = rocky_global_create_event("a"); rocky_global_call_event_handlers_async(a_event); ASSERT_JS_GLOBAL_EQUALS_B("is_called", false); s_process_manager_callback(s_process_manager_callback_data); ASSERT_JS_GLOBAL_EQUALS_B("is_called", true); }