/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "script/scriptmanager.h" #include "script/scriptdef.h" #include "script/module/modulebase.h" #include "util/memory.h" #include "util/string.h" typedef struct { double numberValue; int32_t intValue; bool_t boolValue; char_t stringValue[SCRIPT_VALUE_STRING_MAX]; vec3 vec3Value; void *pointerValue; jerry_value_t callbackValue; } testwidgethandle_t; static scriptproto_t TEST_WIDGET_PROTO; static uint8_t TEST_SENTINEL; static double TEST_GLOBAL_VALUE; moduleBaseFunction(testWidgetConstructor) { testwidgethandle_t *inst = (testwidgethandle_t *)memoryAllocate( sizeof(testwidgethandle_t) ); memoryZero(inst, sizeof(testwidgethandle_t)); inst->callbackValue = jerry_undefined(); jerry_object_set_native_ptr( callInfo->this_value, &TEST_WIDGET_PROTO.info, inst ); return jerry_undefined(); } static scriptvalue_t testWidgetSetNumber( void *handle, const scriptvalue_t *args, const uint32_t argc ) { ((testwidgethandle_t *)handle)->numberValue = args[0].as.number; return scriptValueVoid(); } static scriptvalue_t testWidgetGetNumberValue(void *handle) { return scriptValueNumber(((testwidgethandle_t *)handle)->numberValue); } static scriptvalue_t testWidgetGetNumberMethod( void *handle, const scriptvalue_t *args, const uint32_t argc ) { return testWidgetGetNumberValue(handle); } static scriptvalue_t testWidgetSetInt( void *handle, const scriptvalue_t *args, const uint32_t argc ) { ((testwidgethandle_t *)handle)->intValue = args[0].as.intValue; return scriptValueVoid(); } static scriptvalue_t testWidgetSetBool( void *handle, const scriptvalue_t *args, const uint32_t argc ) { ((testwidgethandle_t *)handle)->boolValue = args[0].as.boolValue; return scriptValueVoid(); } static scriptvalue_t testWidgetGetBoolMethod( void *handle, const scriptvalue_t *args, const uint32_t argc ) { return scriptValueBool(((testwidgethandle_t *)handle)->boolValue); } static scriptvalue_t testWidgetSetString( void *handle, const scriptvalue_t *args, const uint32_t argc ) { stringCopy( ((testwidgethandle_t *)handle)->stringValue, args[0].as.string, SCRIPT_VALUE_STRING_MAX - 1 ); return scriptValueVoid(); } static scriptvalue_t testWidgetGetStringMethod( void *handle, const scriptvalue_t *args, const uint32_t argc ) { return scriptValueString(((testwidgethandle_t *)handle)->stringValue); } static scriptvalue_t testWidgetSetVec3( void *handle, const scriptvalue_t *args, const uint32_t argc ) { testwidgethandle_t *inst = (testwidgethandle_t *)handle; inst->vec3Value[0] = args[0].as.vec3Value[0]; inst->vec3Value[1] = args[0].as.vec3Value[1]; inst->vec3Value[2] = args[0].as.vec3Value[2]; return scriptValueVoid(); } static scriptvalue_t testWidgetGetVec3Value(void *handle) { return scriptValueVec3(((testwidgethandle_t *)handle)->vec3Value); } static scriptvalue_t testWidgetGetVec3Method( void *handle, const scriptvalue_t *args, const uint32_t argc ) { return testWidgetGetVec3Value(handle); } static scriptvalue_t testWidgetSetPointer( void *handle, const scriptvalue_t *args, const uint32_t argc ) { ((testwidgethandle_t *)handle)->pointerValue = args[0].as.pointer; return scriptValueVoid(); } static scriptvalue_t testWidgetGetSentinelPointer( void *handle, const scriptvalue_t *args, const uint32_t argc ) { return scriptValuePointer(&TEST_SENTINEL); } static scriptvalue_t testWidgetPointerMatches( void *handle, const scriptvalue_t *args, const uint32_t argc ) { return scriptValueBool( ((testwidgethandle_t *)handle)->pointerValue == args[0].as.pointer ); } static scriptvalue_t testWidgetSetCallback( void *handle, const scriptvalue_t *args, const uint32_t argc ) { testwidgethandle_t *inst = (testwidgethandle_t *)handle; if(jerry_value_is_function(inst->callbackValue)) { jerry_value_free(inst->callbackValue); } inst->callbackValue = args[0].as.value; return scriptValueVoid(); } static scriptvalue_t testWidgetInvokeCallback( void *handle, const scriptvalue_t *args, const uint32_t argc ) { testwidgethandle_t *inst = (testwidgethandle_t *)handle; if(jerry_value_is_function(inst->callbackValue)) { errorret_t ret = scriptManagerCallValue( jerry_undefined(), inst->callbackValue, "TestWidget", "callback" ); errorCatch(ret); } return scriptValueVoid(); } static scriptvalue_t testWidgetDispose( void *handle, const scriptvalue_t *args, const uint32_t argc ) { testwidgethandle_t *inst = (testwidgethandle_t *)handle; if(jerry_value_is_function(inst->callbackValue)) { jerry_value_free(inst->callbackValue); } inst->callbackValue = jerry_undefined(); return scriptValueVoid(); } static void testWidgetSetNumberProp(void *handle, const scriptvalue_t *value) { ((testwidgethandle_t *)handle)->numberValue = value->as.number; } static void testWidgetSetVec3Prop(void *handle, const scriptvalue_t *value) { testwidgethandle_t *inst = (testwidgethandle_t *)handle; inst->vec3Value[0] = value->as.vec3Value[0]; inst->vec3Value[1] = value->as.vec3Value[1]; inst->vec3Value[2] = value->as.vec3Value[2]; } static scriptfuncdef_t TEST_WIDGET_FUNCS[] = { { "setNumber", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_NUMBER }, 1, testWidgetSetNumber }, { "getNumber", SCRIPT_TYPE_NUMBER, { 0 }, 0, testWidgetGetNumberMethod }, { "setInt", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_INT }, 1, testWidgetSetInt }, { "setBool", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_BOOL }, 1, testWidgetSetBool }, { "getBool", SCRIPT_TYPE_BOOL, { 0 }, 0, testWidgetGetBoolMethod }, { "setString", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_STRING }, 1, testWidgetSetString }, { "getString", SCRIPT_TYPE_STRING, { 0 }, 0, testWidgetGetStringMethod }, { "setVec3", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_VEC3 }, 1, testWidgetSetVec3 }, { "getVec3", SCRIPT_TYPE_VEC3, { 0 }, 0, testWidgetGetVec3Method }, { "setPointer", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_POINTER }, 1, testWidgetSetPointer }, { "getSentinelPointer", SCRIPT_TYPE_POINTER, { 0 }, 0, testWidgetGetSentinelPointer }, { "pointerMatches", SCRIPT_TYPE_BOOL, { SCRIPT_TYPE_POINTER }, 1, testWidgetPointerMatches }, { "setCallback", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_CALLBACK }, 1, testWidgetSetCallback }, { "invokeCallback", SCRIPT_TYPE_VOID, { 0 }, 0, testWidgetInvokeCallback }, { "dispose", SCRIPT_TYPE_VOID, { 0 }, 0, testWidgetDispose }, { NULL, SCRIPT_TYPE_VOID, { 0 }, 0, NULL } }; static scriptpropdef_t TEST_WIDGET_PROPS[] = { { "numberProp", SCRIPT_TYPE_NUMBER, testWidgetGetNumberValue, testWidgetSetNumberProp }, { "vec3Prop", SCRIPT_TYPE_VEC3, testWidgetGetVec3Value, testWidgetSetVec3Prop }, { NULL, SCRIPT_TYPE_VOID, NULL, NULL } }; static scriptvalue_t testGlobalGetValue(void *handle) { return scriptValueNumber(TEST_GLOBAL_VALUE); } static void testGlobalSetValue(void *handle, const scriptvalue_t *value) { TEST_GLOBAL_VALUE = value->as.number; } static scriptvalue_t testGlobalCompute( void *handle, const scriptvalue_t *args, const uint32_t argc ) { return scriptValueNumber(args[0].as.number + args[1].as.number); } static scriptpropdef_t TEST_GLOBAL_PROPS[] = { { "value", SCRIPT_TYPE_NUMBER, testGlobalGetValue, testGlobalSetValue }, { NULL, SCRIPT_TYPE_VOID, NULL, NULL } }; static scriptfuncdef_t TEST_GLOBAL_FUNCS[] = { { "compute", SCRIPT_TYPE_NUMBER, { SCRIPT_TYPE_NUMBER, SCRIPT_TYPE_NUMBER }, 2, testGlobalCompute }, { NULL, SCRIPT_TYPE_VOID, { 0 }, 0, NULL } }; static int scriptdef_setup(void **state) { errorret_t ret = scriptManagerInit(); if(errorIsNotOk(ret)) { errorCatch(ret); return -1; } scriptProtoInit( &TEST_WIDGET_PROTO, "TestWidget", sizeof(testwidgethandle_t), testWidgetConstructor ); scriptProtoDefineFuncDefs(&TEST_WIDGET_PROTO, TEST_WIDGET_FUNCS); scriptProtoDefinePropDefs(&TEST_WIDGET_PROTO, TEST_WIDGET_PROPS); TEST_GLOBAL_VALUE = 0.0; jerry_value_t globalObj = jerry_object(); scriptDefDefineGlobalProps(globalObj, TEST_GLOBAL_PROPS); scriptDefDefineGlobalFuncs(globalObj, TEST_GLOBAL_FUNCS); moduleBaseSetValue("TestGlobal", globalObj); jerry_value_free(globalObj); return 0; } static int scriptdef_teardown(void **state) { errorret_t ret = scriptManagerDispose(); if(errorIsNotOk(ret)) errorCatch(ret); assert_int_equal(memoryGetAllocatedCount(), 0); return 0; } static void test_scriptdef_number_and_int_and_bool_roundtrip(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "var w = new TestWidget();" "w.setNumber(3.5); w.setInt(7); w.setBool(true);" "w.getNumber() + ',' + w.getBool();", &result ); assert_true(errorIsOk(ret)); char_t buf[64]; moduleBaseToString(result, buf, sizeof(buf)); assert_string_equal(buf, "3.5,true"); jerry_value_free(result); } static void test_scriptdef_string_method_roundtrip(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "var w = new TestWidget();" "w.setString('hello'); w.getString();", &result ); assert_true(errorIsOk(ret)); char_t buf[32]; moduleBaseToString(result, buf, sizeof(buf)); assert_string_equal(buf, "hello"); jerry_value_free(result); } static void test_scriptdef_vec3_method_roundtrip(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "var w = new TestWidget();" "w.setVec3(4, 5, 6);" "var v = w.getVec3();" "v.x + ',' + v.y + ',' + v.z;", &result ); assert_true(errorIsOk(ret)); char_t buf[32]; moduleBaseToString(result, buf, sizeof(buf)); assert_string_equal(buf, "4,5,6"); jerry_value_free(result); } static void test_scriptdef_vec3_property_roundtrip(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "var w = new TestWidget();" "w.vec3Prop = { x: 1, y: 2, z: 3 };" "var v = w.vec3Prop;" "v.x + ',' + v.y + ',' + v.z;", &result ); assert_true(errorIsOk(ret)); char_t buf[32]; moduleBaseToString(result, buf, sizeof(buf)); assert_string_equal(buf, "1,2,3"); jerry_value_free(result); } static void test_scriptdef_number_property_roundtrip(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "var w = new TestWidget(); w.numberProp = 42; w.numberProp;", &result ); assert_true(errorIsOk(ret)); assert_true(jerry_value_is_number(result)); assert_int_equal((int)jerry_value_as_number(result), 42); jerry_value_free(result); } static void test_scriptdef_pointer_roundtrip(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "var w = new TestWidget();" "var p = w.getSentinelPointer();" "w.setPointer(p);" "w.pointerMatches(p);", &result ); assert_true(errorIsOk(ret)); assert_true(jerry_value_is_true(result)); jerry_value_free(result); } static void test_scriptdef_callback_can_be_invoked_later(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "var calls = 0;" "var w = new TestWidget();" "w.setCallback(function() { calls++; });" "w.invokeCallback(); w.invokeCallback();" "w.dispose();" "calls;", &result ); assert_true(errorIsOk(ret)); assert_true(jerry_value_is_number(result)); assert_int_equal((int)jerry_value_as_number(result), 2); jerry_value_free(result); } static void test_scriptdef_wrong_type_args_throw(void **state) { errorret_t ret = scriptManagerExec( "var w = new TestWidget(); w.setNumber('nope');", NULL ); assert_true(errorIsNotOk(ret)); errorCatch(ret); ret = scriptManagerExec( "var w = new TestWidget(); w.setString(123);", NULL ); assert_true(errorIsNotOk(ret)); errorCatch(ret); ret = scriptManagerExec( "var w = new TestWidget(); w.setPointer(42);", NULL ); assert_true(errorIsNotOk(ret)); errorCatch(ret); } static void test_scriptdef_global_object_props_and_funcs(void **state) { jerry_value_t result; errorret_t ret = scriptManagerExec( "TestGlobal.value = 10;" "TestGlobal.value + ',' + TestGlobal.compute(2, 3);", &result ); assert_true(errorIsOk(ret)); char_t buf[32]; moduleBaseToString(result, buf, sizeof(buf)); assert_string_equal(buf, "10,5"); jerry_value_free(result); } int main(void) { assertInit(); const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown( test_scriptdef_number_and_int_and_bool_roundtrip, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_string_method_roundtrip, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_vec3_method_roundtrip, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_vec3_property_roundtrip, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_number_property_roundtrip, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_pointer_roundtrip, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_callback_can_be_invoked_later, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_wrong_type_args_throw, scriptdef_setup, scriptdef_teardown ), cmocka_unit_test_setup_teardown( test_scriptdef_global_object_props_and_funcs, scriptdef_setup, scriptdef_teardown ), }; return cmocka_run_group_tests(tests, NULL, NULL); }