Files
cell/source/suite.c
2026-02-02 21:26:46 -06:00

1857 lines
51 KiB
C

/*
* C-level test suite for cell runtime
* Tests core object representation using C API exclusively
* Bypasses parser/bytecode to verify low-level operations
*/
#include "quickjs.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
static int tests_passed = 0;
static int tests_failed = 0;
static const char *js_type_name(JSValue v) {
if (JS_IsNull(v)) return "null";
if (JS_IsBool(v)) return "bool";
if (JS_IsInt(v)) return "int";
if (JS_IsNumber(v)) return "number";
if (JS_IsText(v)) return "string";
if (JS_IsArray(v)) return "array";
if (JS_IsRecord(v)) return "object";
if (JS_IsObject(v)) return "object";
return "unknown";
}
#define TEST(name) static int test_##name(JSContext *ctx)
#define RUN_TEST(name) do { \
printf(" %-50s ", #name); \
if (test_##name(ctx)) { \
printf("PASS\n"); \
tests_passed++; \
} else { \
printf("FAIL\n"); \
tests_failed++; \
} \
} while(0)
#define ASSERT(cond) do { \
if (!(cond)) { printf("[line %d: ASSERT(%s) failed] ", __LINE__, #cond); return 0; } \
} while(0)
#define ASSERT_MSG(cond, msg) do { \
if (!(cond)) { printf("[line %d: %s] ", __LINE__, msg); return 0; } \
} while(0)
#define ASSERT_INT(v, expected) do { \
if (!JS_IsInt(v)) { \
printf("[line %d: expected int %d, got ", __LINE__, (expected)); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf(" (type: %s)] ", js_type_name(v)); \
return 0; \
} \
int _actual = JS_VALUE_GET_INT(v); \
if (_actual != (expected)) { \
printf("[line %d: expected %d, got %d] ", __LINE__, (expected), _actual); \
return 0; \
} \
} while(0)
#define ASSERT_FLOAT(v, expected, tol) do { \
double _d; \
if (JS_ToFloat64(ctx, &_d, v) < 0) { \
printf("[line %d: expected float ~%.6f, got non-number: ", __LINE__, (expected)); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf("] "); \
return 0; \
} \
if (fabs(_d - (expected)) >= (tol)) { \
printf("[line %d: expected ~%.6f (±%.6f), got %.6f] ", __LINE__, (expected), (tol), _d); \
return 0; \
} \
} while(0)
#define ASSERT_STR(v, expected) do { \
if (!JS_IsText(v)) { \
printf("[line %d: expected string \"%s\", got ", __LINE__, (expected)); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf(" (type: %s)] ", js_type_name(v)); \
return 0; \
} \
const char *_s = JS_ToCString(ctx, v); \
if (_s == NULL || strcmp(_s, (expected)) != 0) { \
printf("[line %d: expected \"%s\", got \"%s\"] ", __LINE__, (expected), _s ? _s : "(null)"); \
if (_s) JS_FreeCString(ctx, _s); \
return 0; \
} \
JS_FreeCString(ctx, _s); \
} while(0)
#define ASSERT_TRUE(v) do { \
if (!JS_IsBool(v) || !JS_VALUE_GET_BOOL(v)) { \
printf("[line %d: expected true, got ", __LINE__); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf("] "); \
return 0; \
} \
} while(0)
#define ASSERT_FALSE(v) do { \
if (!JS_IsBool(v) || JS_VALUE_GET_BOOL(v)) { \
printf("[line %d: expected false, got ", __LINE__); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf("] "); \
return 0; \
} \
} while(0)
#define ASSERT_NULL(v) do { \
if (!JS_IsNull(v)) { \
printf("[line %d: expected null, got ", __LINE__); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf(" (type: %s)] ", js_type_name(v)); \
return 0; \
} \
} while(0)
#define ASSERT_TYPE(v, check_fn, type_name) do { \
if (!check_fn(v)) { \
printf("[line %d: expected %s, got ", __LINE__, type_name); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf(" (type: %s)] ", js_type_name(v)); \
return 0; \
} \
} while(0)
#define ASSERT_VALUE(cond, v) do { \
if (!(cond)) { \
printf("[line %d: ASSERT_VALUE(%s) failed, value: ", __LINE__, #cond); \
JS_PrintText(ctx, JS_ToString(ctx, v)); \
printf("] "); \
return 0; \
} \
} while(0)
/* ============================================================================
NUMBER TESTS
============================================================================ */
TEST(int_creation) {
JSValue v = JS_NewInt32(ctx, 42);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 42);
return 1;
}
TEST(int_zero) {
JSValue v = JS_NewInt32(ctx, 0);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 0);
return 1;
}
TEST(int_negative) {
JSValue v = JS_NewInt32(ctx, -100);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == -100);
return 1;
}
TEST(int_max) {
JSValue v = JS_NewInt32(ctx, INT32_MAX);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == INT32_MAX);
return 1;
}
TEST(int_min) {
JSValue v = JS_NewInt32(ctx, INT32_MIN);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == INT32_MIN);
return 1;
}
TEST(float_creation) {
JSValue v = JS_NewFloat64(ctx, 3.14159);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - 3.14159) < 0.00001);
return 1;
}
TEST(float_zero_becomes_int) {
JSValue v = JS_NewFloat64(ctx, 0.0);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 0);
return 1;
}
TEST(float_whole_becomes_int) {
JSValue v = JS_NewFloat64(ctx, 42.0);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 42);
return 1;
}
TEST(float_negative) {
JSValue v = JS_NewFloat64(ctx, -2.5);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - (-2.5)) < 0.00001);
return 1;
}
/* ============================================================================
BOOLEAN TESTS
============================================================================ */
TEST(bool_true) {
JSValue v = JS_TRUE;
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 1);
return 1;
}
TEST(bool_false) {
JSValue v = JS_FALSE;
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 0);
return 1;
}
TEST(bool_new_true) {
JSValue v = JS_NewBool(ctx, 1);
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 1);
return 1;
}
TEST(bool_new_false) {
JSValue v = JS_NewBool(ctx, 0);
ASSERT(JS_IsBool(v));
ASSERT(JS_VALUE_GET_BOOL(v) == 0);
return 1;
}
/* ============================================================================
NULL TESTS
============================================================================ */
TEST(null_value) {
JSValue v = JS_NULL;
ASSERT(JS_IsNull(v));
ASSERT(!JS_IsBool(v));
ASSERT(!JS_IsNumber(v));
return 1;
}
/* ============================================================================
STRING TESTS - IMMEDIATE ASCII
============================================================================ */
TEST(string_immediate_short) {
JSValue v = JS_NewString(ctx, "hello");
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 5);
ASSERT(MIST_GetImmediateASCIIChar(v, 0) == 'h');
ASSERT(MIST_GetImmediateASCIIChar(v, 4) == 'o');
return 1;
}
TEST(string_immediate_empty) {
JSValue v = JS_NewString(ctx, "");
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 0);
return 1;
}
TEST(string_immediate_max) {
JSValue v = JS_NewString(ctx, "1234567"); /* 7 chars = max immediate */
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 7);
return 1;
}
TEST(string_heap_long) {
JSValue v = JS_NewString(ctx, "12345678"); /* 8 chars = heap allocated */
ASSERT(JS_IsText(v));
ASSERT(!MIST_IsImmediateASCII(v));
return 1;
}
TEST(string_to_cstring) {
JSValue v = JS_NewString(ctx, "test");
const char *str = JS_ToCString(ctx, v);
ASSERT(str != NULL);
ASSERT(strcmp(str, "test") == 0);
JS_FreeCString(ctx, str);
return 1;
}
TEST(string_heap_to_cstring) {
JSValue v = JS_NewString(ctx, "this is a longer string");
const char *str = JS_ToCString(ctx, v);
ASSERT(str != NULL);
ASSERT(strcmp(str, "this is a longer string") == 0);
JS_FreeCString(ctx, str);
return 1;
}
/* ============================================================================
OBJECT/RECORD TESTS
============================================================================ */
TEST(object_create) {
JSValue obj = JS_NewObject(ctx);
ASSERT(JS_IsObject(obj));
ASSERT(JS_IsRecord(obj));
return 1;
}
TEST(object_set_get_property) {
JSValue obj = JS_NewObject(ctx);
int r = JS_SetPropertyStr(ctx, obj, "foo", JS_NewInt32(ctx, 42));
ASSERT(r >= 0);
JSValue val = JS_GetPropertyStr(ctx, obj, "foo");
ASSERT_INT(val, 42);
return 1;
}
TEST(object_multiple_properties) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "a", JS_NewInt32(ctx, 1));
JS_SetPropertyStr(ctx, obj, "b", JS_NewInt32(ctx, 2));
JS_SetPropertyStr(ctx, obj, "c", JS_NewInt32(ctx, 3));
JSValue a = JS_GetPropertyStr(ctx, obj, "a");
JSValue b = JS_GetPropertyStr(ctx, obj, "b");
JSValue c = JS_GetPropertyStr(ctx, obj, "c");
ASSERT_INT(a, 1);
ASSERT_INT(b, 2);
ASSERT_INT(c, 3);
return 1;
}
TEST(object_overwrite_property) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 10));
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 20));
JSValue x = JS_GetPropertyStr(ctx, obj, "x");
ASSERT_INT(x, 20);
return 1;
}
TEST(object_missing_property_is_null) {
JSValue obj = JS_NewObject(ctx);
JSValue val = JS_GetPropertyStr(ctx, obj, "nonexistent");
ASSERT(JS_IsNull(val));
return 1;
}
TEST(object_string_property) {
JSValue obj = JS_NewObject(ctx);
JSValue str = JS_NewString(ctx, "hello");
JS_SetPropertyStr(ctx, obj, "msg", str);
JSValue val = JS_GetPropertyStr(ctx, obj, "msg");
ASSERT(JS_IsText(val));
const char *s = JS_ToCString(ctx, val);
ASSERT(strcmp(s, "hello") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(object_nested) {
JSValue outer = JS_NewObject(ctx);
JSValue inner = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, inner, "value", JS_NewInt32(ctx, 99));
JS_SetPropertyStr(ctx, outer, "inner", inner);
JSValue gotInner = JS_GetPropertyStr(ctx, outer, "inner");
ASSERT(JS_IsRecord(gotInner));
JSValue val = JS_GetPropertyStr(ctx, gotInner, "value");
ASSERT_INT(val, 99);
return 1;
}
TEST(object_many_properties_resize) {
JSValue obj = JS_NewObject(ctx);
/* Add many properties to trigger resize */
for (int i = 0; i < 100; i++) {
char key[16];
snprintf(key, sizeof(key), "prop%d", i);
JS_SetPropertyStr(ctx, obj, key, JS_NewInt32(ctx, i * 10));
}
/* Verify all properties */
for (int i = 0; i < 100; i++) {
char key[16];
snprintf(key, sizeof(key), "prop%d", i);
JSValue val = JS_GetPropertyStr(ctx, obj, key);
ASSERT_INT(val, i * 10);
}
return 1;
}
/* ============================================================================
ARRAY TESTS
============================================================================ */
TEST(array_create) {
JSValue arr = JS_NewArray(ctx);
ASSERT(JS_IsObject(arr));
ASSERT(JS_IsArray(arr));
return 1;
}
TEST(array_push_and_length) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 30));
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 3);
return 1;
}
TEST(array_get_by_index) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 100));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 200));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 300));
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2);
ASSERT_INT(v0, 100);
ASSERT_INT(v1, 200);
ASSERT_INT(v2, 300);
return 1;
}
TEST(array_set_by_index) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 0));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 0));
JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, 55));
JS_SetPropertyUint32(ctx, arr, 1, JS_NewInt32(ctx, 66));
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
ASSERT_INT(v0, 55);
ASSERT_INT(v1, 66);
return 1;
}
TEST(array_pop) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
JSValue popped = JS_ArrayPop(ctx, arr);
ASSERT_INT(popped, 3);
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 2);
return 1;
}
TEST(array_out_of_bounds_is_null) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JSValue val = JS_GetPropertyUint32(ctx, arr, 999);
ASSERT(JS_IsNull(val));
return 1;
}
TEST(array_mixed_types) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 42));
JS_ArrayPush(ctx, &arr, JS_NewString(ctx, "hello"));
JS_ArrayPush(ctx, &arr, JS_TRUE);
JS_ArrayPush(ctx, &arr, JS_NULL);
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2);
JSValue v3 = JS_GetPropertyUint32(ctx, arr, 3);
ASSERT(JS_IsInt(v0));
ASSERT(JS_IsText(v1));
ASSERT(JS_IsBool(v2));
ASSERT(JS_IsNull(v3));
return 1;
}
TEST(array_many_elements_resize) {
JSValue arr = JS_NewArray(ctx);
for (int i = 0; i < 1000; i++) {
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, i));
}
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 1000);
/* Verify some values */
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v500 = JS_GetPropertyUint32(ctx, arr, 500);
JSValue v999 = JS_GetPropertyUint32(ctx, arr, 999);
ASSERT_INT(v0, 0);
ASSERT_INT(v500, 500);
ASSERT_INT(v999, 999);
return 1;
}
/* ============================================================================
TYPE CHECK TESTS
============================================================================ */
TEST(type_checks) {
JSValue num = JS_NewInt32(ctx, 42);
JSValue flt = JS_NewFloat64(ctx, 3.14);
JSValue str = JS_NewString(ctx, "test");
JSValue obj = JS_NewObject(ctx);
JSValue arr = JS_NewArray(ctx);
JSValue boo = JS_TRUE;
JSValue nul = JS_NULL;
ASSERT(JS_IsNumber(num));
ASSERT(JS_IsNumber(flt));
ASSERT(JS_IsText(str));
ASSERT(JS_IsRecord(obj));
ASSERT(JS_IsArray(arr));
ASSERT(JS_IsBool(boo));
ASSERT(JS_IsNull(nul));
ASSERT(!JS_IsText(num));
ASSERT(!JS_IsNumber(str));
ASSERT(!JS_IsArray(obj));
ASSERT(!JS_IsRecord(arr));
return 1;
}
/* ============================================================================
EQUALITY TESTS
============================================================================ */
TEST(strict_eq_ints) {
JSValue a = JS_NewInt32(ctx, 42);
JSValue b = JS_NewInt32(ctx, 42);
JSValue c = JS_NewInt32(ctx, 43);
ASSERT(JS_StrictEq(ctx, a, b));
ASSERT(!JS_StrictEq(ctx, a, c));
return 1;
}
TEST(strict_eq_strings) {
JSValue a = JS_NewString(ctx, "hello");
JSValue b = JS_NewString(ctx, "hello");
JSValue c = JS_NewString(ctx, "world");
ASSERT(JS_StrictEq(ctx, a, b));
ASSERT(!JS_StrictEq(ctx, a, c));
return 1;
}
TEST(strict_eq_null) {
JSValue a = JS_NULL;
JSValue b = JS_NULL;
JSValue c = JS_NewInt32(ctx, 0);
ASSERT(JS_StrictEq(ctx, a, b));
ASSERT(!JS_StrictEq(ctx, a, c));
return 1;
}
TEST(strict_eq_bool) {
ASSERT(JS_StrictEq(ctx, JS_TRUE, JS_TRUE));
ASSERT(JS_StrictEq(ctx, JS_FALSE, JS_FALSE));
ASSERT(!JS_StrictEq(ctx, JS_TRUE, JS_FALSE));
return 1;
}
/* ============================================================================
INTRINSIC ARRAY FUNCTION TESTS
============================================================================ */
/* Helper C function: double the value */
static JSValue cfunc_double(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1 || !JS_IsInt(argv[0])) return JS_NULL;
return JS_NewInt32(ctx, JS_VALUE_GET_INT(argv[0]) * 2);
}
/* Helper C function: check if value > 5 */
static JSValue cfunc_gt5(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1 || !JS_IsInt(argv[0])) return JS_FALSE;
return JS_VALUE_GET_INT(argv[0]) > 5 ? JS_TRUE : JS_FALSE;
}
/* Helper C function: check if value is even */
static JSValue cfunc_is_even(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1 || !JS_IsInt(argv[0])) return JS_FALSE;
return (JS_VALUE_GET_INT(argv[0]) % 2) == 0 ? JS_TRUE : JS_FALSE;
}
/* Helper C function: add two values */
static JSValue cfunc_add(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 2 || !JS_IsInt(argv[0]) || !JS_IsInt(argv[1])) return JS_NULL;
return JS_NewInt32(ctx, JS_VALUE_GET_INT(argv[0]) + JS_VALUE_GET_INT(argv[1]));
}
/* Helper C function: check if value equals 30 */
static JSValue cfunc_eq30(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1 || !JS_IsInt(argv[0])) return JS_FALSE;
return JS_VALUE_GET_INT(argv[0]) == 30 ? JS_TRUE : JS_FALSE;
}
TEST(array_slice_basic) {
JSValue arr = JS_NewArray(ctx);
for (int i = 0; i < 5; i++) {
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, i * 10)); /* [0, 10, 20, 30, 40] */
}
/* JS_Array(arr, from, to) = slice */
JSValue sliced = JS_Array(ctx, arr, JS_NewInt32(ctx, 1), JS_NewInt32(ctx, 4), JS_NULL);
ASSERT(JS_IsArray(sliced));
int64_t len;
JS_GetLength(ctx, sliced, &len);
ASSERT(len == 3);
JSValue v0 = JS_GetPropertyUint32(ctx, sliced, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, sliced, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, sliced, 2);
ASSERT_INT(v0, 10);
ASSERT_INT(v1, 20);
ASSERT_INT(v2, 30);
return 1;
}
TEST(array_concat_basic) {
JSValue arr1 = JS_NewArray(ctx);
JSValue arr2 = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr1, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr1, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr2, JS_NewInt32(ctx, 3));
JS_ArrayPush(ctx, &arr2, JS_NewInt32(ctx, 4));
/* JS_Array(arr, arr2) = concat */
JSValue result = JS_Array(ctx, arr1, arr2, JS_NULL, JS_NULL);
ASSERT(JS_IsArray(result));
int64_t len;
JS_GetLength(ctx, result, &len);
ASSERT(len == 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 0), 1);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 1), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 2), 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 3), 4);
return 1;
}
TEST(array_sort_numbers) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 30));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 50));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 40));
JSValue sorted = JS_ArraySort(ctx, arr, JS_NULL);
ASSERT(JS_IsArray(sorted));
int64_t len;
JS_GetLength(ctx, sorted, &len);
ASSERT(len == 5);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 0), 10);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 1), 20);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 2), 30);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 3), 40);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 4), 50);
return 1;
}
TEST(array_find_value) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 30));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 40));
/* Find by value: JS_ArrayFind(arr, target, reverse, from) */
JSValue idx = JS_ArrayFind(ctx, arr, JS_NewInt32(ctx, 30), JS_NULL, JS_NULL);
ASSERT(JS_IsInt(idx));
ASSERT(JS_VALUE_GET_INT(idx) == 2);
/* Not found returns null */
JSValue not_found = JS_ArrayFind(ctx, arr, JS_NewInt32(ctx, 99), JS_NULL, JS_NULL);
ASSERT(JS_IsNull(not_found));
return 1;
}
TEST(array_find_predicate) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 30));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 40));
/* Find by predicate function */
JSValue func = JS_NewCFunction(ctx, cfunc_eq30, "eq30", 1);
JSValue idx = JS_ArrayFind(ctx, arr, func, JS_NULL, JS_NULL);
ASSERT_INT(idx, 2);
return 1;
}
TEST(array_filter_basic) {
JSValue arr = JS_NewArray(ctx);
for (int i = 1; i <= 10; i++) {
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, i));
}
/* Filter for values > 5 */
JSValue func = JS_NewCFunction(ctx, cfunc_gt5, "gt5", 1);
JSValue filtered = JS_ArrayFilter(ctx, arr, func);
ASSERT(JS_IsArray(filtered));
int64_t len;
JS_GetLength(ctx, filtered, &len);
ASSERT(len == 5); /* 6, 7, 8, 9, 10 */
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 0), 6);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 4), 10);
return 1;
}
TEST(array_filter_even) {
JSValue arr = JS_NewArray(ctx);
for (int i = 1; i <= 10; i++) {
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, i));
}
/* Filter for even values */
JSValue func = JS_NewCFunction(ctx, cfunc_is_even, "is_even", 1);
JSValue filtered = JS_ArrayFilter(ctx, arr, func);
ASSERT(JS_IsArray(filtered));
int64_t len;
JS_GetLength(ctx, filtered, &len);
ASSERT(len == 5); /* 2, 4, 6, 8, 10 */
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 0), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 1), 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 2), 6);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 3), 8);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 4), 10);
return 1;
}
TEST(array_map_double) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
/* JS_Array(arr, fn, reverse, exit) = map */
JSValue func = JS_NewCFunction(ctx, cfunc_double, "double", 1);
JSValue mapped = JS_Array(ctx, arr, func, JS_NULL, JS_NULL);
ASSERT(JS_IsArray(mapped));
int64_t len;
JS_GetLength(ctx, mapped, &len);
ASSERT(len == 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, mapped, 0), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, mapped, 1), 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, mapped, 2), 6);
return 1;
}
TEST(array_reduce_sum) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 4));
/* Sum: 1 + 2 + 3 + 4 = 10 */
/* JS_ArrayReduce(arr, fn, initial, reverse) */
JSValue func = JS_NewCFunction(ctx, cfunc_add, "add", 2);
JSValue result = JS_ArrayReduce(ctx, arr, func, JS_NULL, JS_NULL);
ASSERT_INT(result, 10);
return 1;
}
TEST(array_reduce_with_initial) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
/* Sum with initial 100: 100 + 1 + 2 + 3 = 106 */
JSValue func = JS_NewCFunction(ctx, cfunc_add, "add", 2);
JSValue result = JS_ArrayReduce(ctx, arr, func, JS_NewInt32(ctx, 100), JS_NULL);
ASSERT_INT(result, 106);
return 1;
}
TEST(array_foreach_basic) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
/* JS_ArrFor(arr, fn, reverse, exit) */
JSValue func = JS_NewCFunction(ctx, cfunc_double, "double", 1);
JSValue result = JS_ArrFor(ctx, arr, func, JS_NULL, JS_NULL);
ASSERT(JS_IsNull(result));
return 1;
}
/* ============================================================================
GLOBAL OBJECT TEST
============================================================================ */
TEST(global_object) {
JSValue global = JS_GetGlobalObject(ctx);
ASSERT(JS_IsRecord(global));
/* Set something on global */
JS_SetPropertyStr(ctx, global, "testGlobal", JS_NewInt32(ctx, 777));
JSValue val = JS_GetPropertyStr(ctx, global, "testGlobal");
ASSERT_INT(val, 777);
return 1;
}
/* ============================================================================
VALUE CONVERSION TESTS
============================================================================ */
TEST(to_bool_true_values) {
ASSERT(JS_ToBool(ctx, JS_TRUE) == 1);
ASSERT(JS_ToBool(ctx, JS_NewInt32(ctx, 1)) == 1);
ASSERT(JS_ToBool(ctx, JS_NewInt32(ctx, -1)) == 1);
ASSERT(JS_ToBool(ctx, JS_NewString(ctx, "hello")) == 1);
return 1;
}
TEST(to_bool_false_values) {
ASSERT(JS_ToBool(ctx, JS_FALSE) == 0);
ASSERT(JS_ToBool(ctx, JS_NewInt32(ctx, 0)) == 0);
ASSERT(JS_ToBool(ctx, JS_NULL) == 0);
ASSERT(JS_ToBool(ctx, JS_NewString(ctx, "")) == 0);
return 1;
}
TEST(to_int32_from_int) {
int32_t res;
JSValue v = JS_NewInt32(ctx, 12345);
ASSERT(JS_ToInt32(ctx, &res, v) == 0);
ASSERT(res == 12345);
return 1;
}
TEST(to_int32_from_float) {
int32_t res;
JSValue v = JS_NewFloat64(ctx, 3.7);
ASSERT(JS_ToInt32(ctx, &res, v) == 0);
ASSERT(res == 3); /* truncates toward zero */
return 1;
}
TEST(to_int32_negative_float) {
int32_t res;
JSValue v = JS_NewFloat64(ctx, -3.7);
ASSERT(JS_ToInt32(ctx, &res, v) == 0);
ASSERT(res == -3);
return 1;
}
TEST(to_int64_large_value) {
int64_t res;
JSValue v = JS_NewInt64(ctx, 9007199254740992LL); /* 2^53 */
ASSERT(JS_ToInt64(ctx, &res, v) == 0);
ASSERT(res == 9007199254740992LL);
return 1;
}
TEST(to_float64_from_int) {
double res;
JSValue v = JS_NewInt32(ctx, 42);
ASSERT(JS_ToFloat64(ctx, &res, v) == 0);
ASSERT(fabs(res - 42.0) < 0.00001);
return 1;
}
TEST(to_float64_from_float) {
double res;
JSValue v = JS_NewFloat64(ctx, 3.14159);
ASSERT(JS_ToFloat64(ctx, &res, v) == 0);
ASSERT(fabs(res - 3.14159) < 0.00001);
return 1;
}
/* ============================================================================
NEWINT64 / NEWUINT32 EDGE CASES
============================================================================ */
TEST(new_int64_in_int32_range) {
JSValue v = JS_NewInt64(ctx, 1000);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 1000);
return 1;
}
TEST(new_int64_out_of_int32_range) {
JSValue v = JS_NewInt64(ctx, 3000000000LL);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - 3000000000.0) < 1.0);
return 1;
}
TEST(new_uint32_in_int32_range) {
JSValue v = JS_NewUint32(ctx, 1000);
ASSERT(JS_IsInt(v));
ASSERT(JS_VALUE_GET_INT(v) == 1000);
return 1;
}
TEST(new_uint32_out_of_signed_range) {
JSValue v = JS_NewUint32(ctx, 0x80000000);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - 2147483648.0) < 1.0);
return 1;
}
/* ============================================================================
STRING CONVERSION TESTS
============================================================================ */
TEST(to_string_from_int) {
JSValue v = JS_NewInt32(ctx, 42);
JSValue str = JS_ToString(ctx, v);
ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str);
ASSERT(strcmp(s, "42") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(to_string_from_float) {
JSValue v = JS_NewFloat64(ctx, 3.5);
JSValue str = JS_ToString(ctx, v);
ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str);
ASSERT(strcmp(s, "3.5") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(to_string_from_bool) {
JSValue str_true = JS_ToString(ctx, JS_TRUE);
JSValue str_false = JS_ToString(ctx, JS_FALSE);
const char *s1 = JS_ToCString(ctx, str_true);
const char *s2 = JS_ToCString(ctx, str_false);
ASSERT(strcmp(s1, "true") == 0);
ASSERT(strcmp(s2, "false") == 0);
JS_FreeCString(ctx, s1);
JS_FreeCString(ctx, s2);
return 1;
}
TEST(to_string_from_null) {
JSValue str = JS_ToString(ctx, JS_NULL);
const char *s = JS_ToCString(ctx, str);
ASSERT(strcmp(s, "null") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(string_len_with_length) {
JSValue v = JS_NewStringLen(ctx, "hello\0world", 11);
ASSERT(JS_IsText(v));
size_t len;
const char *s = JS_ToCStringLen(ctx, &len, v);
ASSERT(len == 11);
JS_FreeCString(ctx, s);
return 1;
}
/* ============================================================================
CELL TEXT FUNCTION TESTS
============================================================================ */
TEST(cell_text_from_int) {
JSValue result = JS_CellText(ctx, JS_NewInt32(ctx, 123));
ASSERT(JS_IsText(result));
const char *s = JS_ToCString(ctx, result);
ASSERT(strcmp(s, "123") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(cell_lower) {
JSValue text = JS_NewString(ctx, "HELLO");
JSValue result = JS_CellLower(ctx, text);
ASSERT(JS_IsText(result));
const char *s = JS_ToCString(ctx, result);
ASSERT(strcmp(s, "hello") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(cell_upper) {
JSValue text = JS_NewString(ctx, "hello");
JSValue result = JS_CellUpper(ctx, text);
ASSERT(JS_IsText(result));
const char *s = JS_ToCString(ctx, result);
ASSERT(strcmp(s, "HELLO") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(cell_trim_whitespace) {
JSValue text = JS_NewString(ctx, " hello ");
JSValue result = JS_CellTrim(ctx, text, JS_NULL);
ASSERT(JS_IsText(result));
const char *s = JS_ToCString(ctx, result);
ASSERT(strcmp(s, "hello") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(cell_codepoint) {
JSValue text = JS_NewString(ctx, "ABC");
JSValue result = JS_CellCodepoint(ctx, text, JS_NewInt32(ctx, 0));
ASSERT(JS_IsInt(result));
ASSERT(JS_VALUE_GET_INT(result) == 65); /* 'A' */
return 1;
}
TEST(cell_character) {
JSValue result = JS_CellCharacter(ctx, JS_NewInt32(ctx, 65));
ASSERT(JS_IsText(result));
const char *s = JS_ToCString(ctx, result);
ASSERT(strcmp(s, "A") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(cell_search_found) {
JSValue text = JS_NewString(ctx, "hello world");
JSValue pattern = JS_NewString(ctx, "world");
JSValue result = JS_CellSearch(ctx, text, pattern, JS_NULL);
ASSERT(JS_IsInt(result));
ASSERT(JS_VALUE_GET_INT(result) == 6);
return 1;
}
TEST(cell_search_not_found) {
JSValue text = JS_NewString(ctx, "hello world");
JSValue pattern = JS_NewString(ctx, "xyz");
JSValue result = JS_CellSearch(ctx, text, pattern, JS_NULL);
ASSERT(JS_IsNull(result));
return 1;
}
TEST(cell_extract) {
JSValue text = JS_NewString(ctx, "hello world");
JSValue result = JS_CellExtract(ctx, text, JS_NewInt32(ctx, 0), JS_NewInt32(ctx, 5));
ASSERT(JS_IsText(result));
const char *s = JS_ToCString(ctx, result);
ASSERT(strcmp(s, "hello") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(cell_replace) {
JSValue text = JS_NewString(ctx, "hello world");
JSValue pattern = JS_NewString(ctx, "world");
JSValue replacement = JS_NewString(ctx, "there");
JSValue result = JS_CellReplace(ctx, text, pattern, replacement);
ASSERT(JS_IsText(result));
const char *s = JS_ToCString(ctx, result);
ASSERT(strcmp(s, "hello there") == 0);
JS_FreeCString(ctx, s);
return 1;
}
/* ============================================================================
CELL NUMBER FUNCTION TESTS
============================================================================ */
TEST(cell_number_from_string) {
JSValue text = JS_NewString(ctx, "42");
JSValue result = JS_CellNumber(ctx, text);
ASSERT(JS_IsInt(result));
ASSERT(JS_VALUE_GET_INT(result) == 42);
return 1;
}
TEST(cell_abs_positive) {
JSValue result = JS_CellAbs(ctx, JS_NewInt32(ctx, 42));
ASSERT_INT(result, 42);
return 1;
}
TEST(cell_abs_negative) {
JSValue result = JS_CellAbs(ctx, JS_NewInt32(ctx, -42));
ASSERT_INT(result, 42);
return 1;
}
TEST(cell_sign_positive) {
JSValue result = JS_CellSign(ctx, JS_NewInt32(ctx, 42));
ASSERT_INT(result, 1);
return 1;
}
TEST(cell_sign_negative) {
JSValue result = JS_CellSign(ctx, JS_NewInt32(ctx, -42));
ASSERT_INT(result, -1);
return 1;
}
TEST(cell_sign_zero) {
JSValue result = JS_CellSign(ctx, JS_NewInt32(ctx, 0));
ASSERT_INT(result, 0);
return 1;
}
TEST(cell_floor) {
JSValue result = JS_CellFloor(ctx, JS_NewFloat64(ctx, 3.7));
ASSERT_INT(result, 3);
return 1;
}
TEST(cell_floor_negative) {
JSValue result = JS_CellFloor(ctx, JS_NewFloat64(ctx, -3.2));
ASSERT_INT(result, -4);
return 1;
}
TEST(cell_ceiling) {
JSValue result = JS_CellCeiling(ctx, JS_NewFloat64(ctx, 3.2));
ASSERT_INT(result, 4);
return 1;
}
TEST(cell_ceiling_negative) {
JSValue result = JS_CellCeiling(ctx, JS_NewFloat64(ctx, -3.7));
ASSERT_INT(result, -3);
return 1;
}
TEST(cell_round) {
JSValue result1 = JS_CellRound(ctx, JS_NewFloat64(ctx, 3.4));
JSValue result2 = JS_CellRound(ctx, JS_NewFloat64(ctx, 3.6));
ASSERT_INT(result1, 3);
ASSERT_INT(result2, 4);
return 1;
}
TEST(cell_trunc) {
JSValue result1 = JS_CellTrunc(ctx, JS_NewFloat64(ctx, 3.7));
JSValue result2 = JS_CellTrunc(ctx, JS_NewFloat64(ctx, -3.7));
ASSERT_INT(result1, 3);
ASSERT_INT(result2, -3);
return 1;
}
TEST(cell_min) {
JSValue result = JS_CellMin(ctx, JS_NewInt32(ctx, 5), JS_NewInt32(ctx, 3));
ASSERT_INT(result, 3);
return 1;
}
TEST(cell_max) {
JSValue result = JS_CellMax(ctx, JS_NewInt32(ctx, 5), JS_NewInt32(ctx, 3));
ASSERT_INT(result, 5);
return 1;
}
TEST(cell_remainder) {
JSValue result = JS_CellRemainder(ctx, JS_NewInt32(ctx, 10), JS_NewInt32(ctx, 3));
ASSERT_INT(result, 1);
return 1;
}
TEST(cell_modulo) {
JSValue result = JS_CellModulo(ctx, JS_NewInt32(ctx, 10), JS_NewInt32(ctx, 3));
ASSERT_INT(result, 1);
return 1;
}
TEST(cell_neg) {
JSValue result1 = JS_CellNeg(ctx, JS_NewInt32(ctx, 42));
JSValue result2 = JS_CellNeg(ctx, JS_NewInt32(ctx, -42));
ASSERT_INT(result1, -42);
ASSERT_INT(result2, 42);
return 1;
}
TEST(cell_not) {
JSValue result1 = JS_CellNot(ctx, JS_TRUE);
JSValue result2 = JS_CellNot(ctx, JS_FALSE);
ASSERT_FALSE(result1);
ASSERT_TRUE(result2);
return 1;
}
/* ============================================================================
CELL CORE FUNCTION TESTS
============================================================================ */
TEST(cell_length_array) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
JSValue result = JS_CellLength(ctx, arr);
ASSERT_INT(result, 3);
return 1;
}
TEST(cell_length_string) {
JSValue text = JS_NewString(ctx, "hello");
JSValue result = JS_CellLength(ctx, text);
ASSERT_INT(result, 5);
return 1;
}
TEST(cell_reverse_array) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
JSValue reversed = JS_CellReverse(ctx, arr);
ASSERT(JS_IsArray(reversed));
ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 0), 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 1), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 2), 1);
return 1;
}
TEST(cell_reverse_string) {
JSValue text = JS_NewString(ctx, "abc");
JSValue reversed = JS_CellReverse(ctx, text);
ASSERT(JS_IsText(reversed));
const char *s = JS_ToCString(ctx, reversed);
ASSERT(strcmp(s, "cba") == 0);
JS_FreeCString(ctx, s);
return 1;
}
TEST(cell_meme_shallow) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, 10));
JSValue copy = JS_CellMeme(ctx, obj, JS_FALSE);
ASSERT(JS_IsRecord(copy));
JSValue x = JS_GetPropertyStr(ctx, copy, "x");
ASSERT_INT(x, 10);
return 1;
}
/* ============================================================================
JSON TESTS
============================================================================ */
TEST(parse_json_object) {
const char *json = "{\"name\":\"test\",\"value\":42}";
JSValue obj = JS_ParseJSON(ctx, json, strlen(json), "<test>");
ASSERT(JS_IsRecord(obj));
JSValue name = JS_GetPropertyStr(ctx, obj, "name");
JSValue value = JS_GetPropertyStr(ctx, obj, "value");
ASSERT_STR(name, "test");
ASSERT_INT(value, 42);
return 1;
}
TEST(parse_json_array) {
const char *json = "[1,2,3]";
JSValue arr = JS_ParseJSON(ctx, json, strlen(json), "<test>");
ASSERT(JS_IsArray(arr));
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 0), 1);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 1), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 2), 3);
return 1;
}
TEST(parse_json_nested) {
const char *json = "{\"outer\":{\"inner\":99}}";
JSValue obj = JS_ParseJSON(ctx, json, strlen(json), "<test>");
ASSERT(JS_IsRecord(obj));
JSValue outer = JS_GetPropertyStr(ctx, obj, "outer");
ASSERT(JS_IsRecord(outer));
JSValue inner = JS_GetPropertyStr(ctx, outer, "inner");
ASSERT_INT(inner, 99);
return 1;
}
TEST(stringify_json_object) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "a", JS_NewInt32(ctx, 1));
JSValue str = JS_JSONStringify(ctx, obj, JS_NULL, JS_NULL);
ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str);
ASSERT(strstr(s, "\"a\"") != NULL);
ASSERT(strstr(s, "1") != NULL);
JS_FreeCString(ctx, s);
return 1;
}
TEST(stringify_json_array) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JSValue str = JS_JSONStringify(ctx, arr, JS_NULL, JS_NULL);
ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str);
ASSERT(strcmp(s, "[1,2]") == 0);
JS_FreeCString(ctx, s);
return 1;
}
/* ============================================================================
C FUNCTION TESTS
============================================================================ */
static JSValue cfunc_return_42(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
return JS_NewInt32(ctx, 42);
}
static JSValue cfunc_sum_args(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
int sum = 0;
for (int i = 0; i < argc; i++) {
if (JS_IsInt(argv[i])) {
sum += JS_VALUE_GET_INT(argv[i]);
}
}
return JS_NewInt32(ctx, sum);
}
TEST(new_cfunction_no_args) {
JSValue func = JS_NewCFunction(ctx, cfunc_return_42, "return42", 0);
ASSERT(JS_IsFunction(func));
JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL);
ASSERT_INT(result, 42);
return 1;
}
TEST(new_cfunction_with_args) {
JSValue func = JS_NewCFunction(ctx, cfunc_sum_args, "sum", 3);
ASSERT(JS_IsFunction(func));
JSValue args[3] = {
JS_NewInt32(ctx, 10),
JS_NewInt32(ctx, 20),
JS_NewInt32(ctx, 30)
};
JSValue result = JS_Call(ctx, func, JS_NULL, 3, args);
ASSERT_INT(result, 60);
return 1;
}
TEST(call_function_on_global) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue func = JS_NewCFunction(ctx, cfunc_return_42, "testFunc", 0);
JS_SetPropertyStr(ctx, global, "testFunc", func);
JSValue got = JS_GetPropertyStr(ctx, global, "testFunc");
ASSERT(JS_IsFunction(got));
JSValue result = JS_Call(ctx, got, JS_NULL, 0, NULL);
ASSERT_INT(result, 42);
return 1;
}
/* ============================================================================
PROPERTY ACCESS TESTS
============================================================================ */
TEST(get_property_with_jsvalue_key) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "foo", JS_NewInt32(ctx, 123));
JSValue key = JS_NewString(ctx, "foo");
JSValue val = JS_GetProperty(ctx, obj, key);
ASSERT_INT(val, 123);
return 1;
}
TEST(set_property_with_jsvalue_key) {
JSValue obj = JS_NewObject(ctx);
JSValue key = JS_NewString(ctx, "bar");
int r = JS_SetProperty(ctx, obj, key, JS_NewInt32(ctx, 456));
ASSERT(r >= 0);
JSValue val = JS_GetPropertyStr(ctx, obj, "bar");
ASSERT_INT(val, 456);
return 1;
}
TEST(get_own_property_names) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "a", JS_NewInt32(ctx, 1));
JS_SetPropertyStr(ctx, obj, "b", JS_NewInt32(ctx, 2));
JS_SetPropertyStr(ctx, obj, "c", JS_NewInt32(ctx, 3));
JSValue names = JS_GetOwnPropertyNames(ctx, obj);
ASSERT(JS_IsArray(names));
int64_t len;
JS_GetLength(ctx, names, &len);
ASSERT(len == 3);
return 1;
}
TEST(property_uint32_on_object) {
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyUint32(ctx, obj, 0, JS_NewInt32(ctx, 100));
JS_SetPropertyUint32(ctx, obj, 1, JS_NewInt32(ctx, 200));
JSValue v0 = JS_GetPropertyUint32(ctx, obj, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, obj, 1);
ASSERT_INT(v0, 100);
ASSERT_INT(v1, 200);
return 1;
}
/* ============================================================================
PROTOTYPE TESTS
============================================================================ */
TEST(get_prototype) {
JSValue proto = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, proto, "inherited", JS_NewInt32(ctx, 999));
JSValue obj = JS_NewObjectProto(ctx, proto);
JSValue gotProto = JS_GetPrototype(ctx, obj);
ASSERT(JS_IsRecord(gotProto));
JSValue inherited = JS_GetPropertyStr(ctx, gotProto, "inherited");
ASSERT_INT(inherited, 999);
return 1;
}
TEST(cell_proto) {
JSValue proto = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, proto, "value", JS_NewInt32(ctx, 42));
JSValue obj = JS_NewObjectProto(ctx, proto);
JSValue gotProto = JS_CellProto(ctx, obj);
ASSERT(JS_IsRecord(gotProto));
JSValue val = JS_GetPropertyStr(ctx, gotProto, "value");
ASSERT_INT(val, 42);
return 1;
}
/* ============================================================================
ARRAY FROM VALUES TEST
============================================================================ */
TEST(new_array_from) {
JSValue values[4] = {
JS_NewInt32(ctx, 10),
JS_NewInt32(ctx, 20),
JS_NewInt32(ctx, 30),
JS_NewInt32(ctx, 40)
};
JSValue arr = JS_NewArrayFrom(ctx, 4, values);
ASSERT(JS_IsArray(arr));
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 0), 10);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 3), 40);
return 1;
}
TEST(new_array_len) {
JSValue arr = JS_NewArrayLen(ctx, 5);
ASSERT(JS_IsArray(arr));
int64_t len;
JS_GetLength(ctx, arr, &len);
ASSERT(len == 5);
return 1;
}
/* ============================================================================
CELL APPLY / CALL TESTS
============================================================================ */
TEST(cell_apply) {
JSValue func = JS_NewCFunction(ctx, cfunc_sum_args, "sum", 3);
JSValue args = JS_NewArray(ctx);
JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 5));
JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 15));
JSValue result = JS_CellApply(ctx, func, args);
ASSERT_INT(result, 30);
return 1;
}
TEST(cell_call) {
JSValue func = JS_NewCFunction(ctx, cfunc_sum_args, "sum", 2);
JSValue this_obj = JS_NewObject(ctx);
JSValue args = JS_NewArray(ctx);
JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 7));
JS_ArrayPush(ctx, &args, JS_NewInt32(ctx, 8));
JSValue result = JS_CellCall(ctx, func, this_obj, args);
ASSERT_INT(result, 15);
return 1;
}
/* ============================================================================
IMMEDIATE STRING EDGE CASES
============================================================================ */
TEST(string_immediate_one_char) {
JSValue v = JS_NewString(ctx, "x");
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 1);
ASSERT(MIST_GetImmediateASCIIChar(v, 0) == 'x');
return 1;
}
TEST(string_immediate_special_chars) {
JSValue v = JS_NewString(ctx, "a!@#$%^");
ASSERT(JS_IsText(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 7);
return 1;
}
TEST(try_new_immediate_ascii_success) {
JSValue v = MIST_TryNewImmediateASCII("test", 4);
ASSERT(!JS_IsNull(v));
ASSERT(MIST_IsImmediateASCII(v));
ASSERT(MIST_GetImmediateASCIILen(v) == 4);
return 1;
}
TEST(try_new_immediate_ascii_too_long) {
JSValue v = MIST_TryNewImmediateASCII("12345678", 8);
ASSERT(JS_IsNull(v));
return 1;
}
/* ============================================================================
EXCEPTION / ERROR TESTS
============================================================================ */
TEST(has_exception_initially_false) {
ASSERT(!JS_HasException(ctx));
return 1;
}
TEST(new_error) {
JSValue err = JS_NewError(ctx);
ASSERT(JS_IsError(ctx, err));
return 1;
}
/* ============================================================================
FLOAT EDGE CASES
============================================================================ */
TEST(float_small_positive) {
JSValue v = JS_NewFloat64(ctx, 0.001);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - 0.001) < 0.0001);
return 1;
}
TEST(float_large_value) {
JSValue v = JS_NewFloat64(ctx, 1e30);
ASSERT(JS_IsNumber(v));
double d;
JS_ToFloat64(ctx, &d, v);
ASSERT(fabs(d - 1e30) < 1e25);
return 1;
}
/* ============================================================================
MORE EQUALITY TESTS
============================================================================ */
TEST(strict_eq_floats) {
JSValue a = JS_NewFloat64(ctx, 3.14);
JSValue b = JS_NewFloat64(ctx, 3.14);
JSValue c = JS_NewFloat64(ctx, 2.71);
ASSERT(JS_StrictEq(ctx, a, b));
ASSERT(!JS_StrictEq(ctx, a, c));
return 1;
}
TEST(strict_eq_different_types) {
JSValue num = JS_NewInt32(ctx, 1);
JSValue str = JS_NewString(ctx, "1");
JSValue boo = JS_TRUE;
ASSERT(!JS_StrictEq(ctx, num, str));
ASSERT(!JS_StrictEq(ctx, num, boo));
ASSERT(!JS_StrictEq(ctx, str, boo));
return 1;
}
TEST(strict_eq_objects_identity) {
JSValue obj1 = JS_NewObject(ctx);
JSValue obj2 = JS_NewObject(ctx);
/* Different objects are not equal even if empty */
ASSERT(!JS_StrictEq(ctx, obj1, obj2));
/* Same object is equal to itself */
ASSERT(JS_StrictEq(ctx, obj1, obj1));
return 1;
}
/* ============================================================================
TYPE CHECK COMPREHENSIVE
============================================================================ */
TEST(is_function_check) {
JSValue func = JS_NewCFunction(ctx, cfunc_return_42, "test", 0);
JSValue num = JS_NewInt32(ctx, 42);
JSValue obj = JS_NewObject(ctx);
ASSERT(JS_IsFunction(func));
ASSERT(!JS_IsFunction(num));
ASSERT(!JS_IsFunction(obj));
return 1;
}
TEST(is_integer_vs_number) {
JSValue i = JS_NewInt32(ctx, 42);
JSValue f = JS_NewFloat64(ctx, 3.14);
ASSERT(JS_IsInteger(i));
ASSERT(JS_IsInt(i));
ASSERT(!JS_IsInteger(f));
ASSERT(!JS_IsInt(f));
ASSERT(JS_IsNumber(i));
ASSERT(JS_IsNumber(f));
return 1;
}
/* ============================================================================
MAIN TEST RUNNER
============================================================================ */
int run_c_test_suite(JSContext *ctx)
{
printf("\n=== Cell Runtime C Test Suite ===\n\n");
printf("Numbers:\n");
RUN_TEST(int_creation);
RUN_TEST(int_zero);
RUN_TEST(int_negative);
RUN_TEST(int_max);
RUN_TEST(int_min);
RUN_TEST(float_creation);
RUN_TEST(float_zero_becomes_int);
RUN_TEST(float_whole_becomes_int);
RUN_TEST(float_negative);
printf("\nBooleans:\n");
RUN_TEST(bool_true);
RUN_TEST(bool_false);
RUN_TEST(bool_new_true);
RUN_TEST(bool_new_false);
printf("\nNull:\n");
RUN_TEST(null_value);
printf("\nStrings:\n");
RUN_TEST(string_immediate_short);
RUN_TEST(string_immediate_empty);
RUN_TEST(string_immediate_max);
RUN_TEST(string_heap_long);
RUN_TEST(string_to_cstring);
RUN_TEST(string_heap_to_cstring);
printf("\nObjects/Records:\n");
RUN_TEST(object_create);
RUN_TEST(object_set_get_property);
RUN_TEST(object_multiple_properties);
RUN_TEST(object_overwrite_property);
RUN_TEST(object_missing_property_is_null);
RUN_TEST(object_string_property);
RUN_TEST(object_nested);
RUN_TEST(object_many_properties_resize);
printf("\nArrays:\n");
RUN_TEST(array_create);
RUN_TEST(array_push_and_length);
RUN_TEST(array_get_by_index);
RUN_TEST(array_set_by_index);
RUN_TEST(array_pop);
RUN_TEST(array_out_of_bounds_is_null);
RUN_TEST(array_mixed_types);
RUN_TEST(array_many_elements_resize);
printf("\nIntrinsic Array Functions:\n");
RUN_TEST(array_slice_basic);
RUN_TEST(array_concat_basic);
RUN_TEST(array_sort_numbers);
RUN_TEST(array_find_value);
RUN_TEST(array_find_predicate);
RUN_TEST(array_filter_basic);
RUN_TEST(array_filter_even);
RUN_TEST(array_map_double);
RUN_TEST(array_reduce_sum);
RUN_TEST(array_reduce_with_initial);
RUN_TEST(array_foreach_basic);
printf("\nType Checks:\n");
RUN_TEST(type_checks);
printf("\nEquality:\n");
RUN_TEST(strict_eq_ints);
RUN_TEST(strict_eq_strings);
RUN_TEST(strict_eq_null);
RUN_TEST(strict_eq_bool);
printf("\nGlobal Object:\n");
RUN_TEST(global_object);
printf("\nValue Conversions:\n");
RUN_TEST(to_bool_true_values);
RUN_TEST(to_bool_false_values);
RUN_TEST(to_int32_from_int);
RUN_TEST(to_int32_from_float);
RUN_TEST(to_int32_negative_float);
RUN_TEST(to_int64_large_value);
RUN_TEST(to_float64_from_int);
RUN_TEST(to_float64_from_float);
printf("\nNewInt64/NewUint32 Edge Cases:\n");
RUN_TEST(new_int64_in_int32_range);
RUN_TEST(new_int64_out_of_int32_range);
RUN_TEST(new_uint32_in_int32_range);
RUN_TEST(new_uint32_out_of_signed_range);
printf("\nString Conversions:\n");
RUN_TEST(to_string_from_int);
RUN_TEST(to_string_from_float);
RUN_TEST(to_string_from_bool);
RUN_TEST(to_string_from_null);
RUN_TEST(string_len_with_length);
printf("\nCell Text Functions:\n");
RUN_TEST(cell_text_from_int);
RUN_TEST(cell_lower);
RUN_TEST(cell_upper);
RUN_TEST(cell_trim_whitespace);
RUN_TEST(cell_codepoint);
RUN_TEST(cell_character);
RUN_TEST(cell_search_found);
RUN_TEST(cell_search_not_found);
RUN_TEST(cell_extract);
RUN_TEST(cell_replace);
printf("\nCell Number Functions:\n");
RUN_TEST(cell_number_from_string);
RUN_TEST(cell_abs_positive);
RUN_TEST(cell_abs_negative);
RUN_TEST(cell_sign_positive);
RUN_TEST(cell_sign_negative);
RUN_TEST(cell_sign_zero);
RUN_TEST(cell_floor);
RUN_TEST(cell_floor_negative);
RUN_TEST(cell_ceiling);
RUN_TEST(cell_ceiling_negative);
RUN_TEST(cell_round);
RUN_TEST(cell_trunc);
RUN_TEST(cell_min);
RUN_TEST(cell_max);
RUN_TEST(cell_remainder);
RUN_TEST(cell_modulo);
RUN_TEST(cell_neg);
RUN_TEST(cell_not);
printf("\nCell Core Functions:\n");
RUN_TEST(cell_length_array);
RUN_TEST(cell_length_string);
RUN_TEST(cell_reverse_array);
RUN_TEST(cell_reverse_string);
RUN_TEST(cell_meme_shallow);
printf("\nJSON:\n");
RUN_TEST(parse_json_object);
RUN_TEST(parse_json_array);
RUN_TEST(parse_json_nested);
RUN_TEST(stringify_json_object);
RUN_TEST(stringify_json_array);
printf("\nC Functions:\n");
RUN_TEST(new_cfunction_no_args);
RUN_TEST(new_cfunction_with_args);
RUN_TEST(call_function_on_global);
printf("\nProperty Access:\n");
RUN_TEST(get_property_with_jsvalue_key);
RUN_TEST(set_property_with_jsvalue_key);
RUN_TEST(get_own_property_names);
RUN_TEST(property_uint32_on_object);
printf("\nPrototypes:\n");
RUN_TEST(get_prototype);
RUN_TEST(cell_proto);
printf("\nArray Creation:\n");
RUN_TEST(new_array_from);
RUN_TEST(new_array_len);
printf("\nCell Apply/Call:\n");
RUN_TEST(cell_apply);
RUN_TEST(cell_call);
printf("\nImmediate String Edge Cases:\n");
RUN_TEST(string_immediate_one_char);
RUN_TEST(string_immediate_special_chars);
RUN_TEST(try_new_immediate_ascii_success);
RUN_TEST(try_new_immediate_ascii_too_long);
printf("\nExceptions/Errors:\n");
RUN_TEST(has_exception_initially_false);
RUN_TEST(new_error);
printf("\nFloat Edge Cases:\n");
RUN_TEST(float_small_positive);
RUN_TEST(float_large_value);
printf("\nMore Equality Tests:\n");
RUN_TEST(strict_eq_floats);
RUN_TEST(strict_eq_different_types);
RUN_TEST(strict_eq_objects_identity);
printf("\nComprehensive Type Checks:\n");
RUN_TEST(is_function_check);
RUN_TEST(is_integer_vs_number);
printf("\n=================================\n");
printf("Results: %d passed, %d failed\n", tests_passed, tests_failed);
printf("=================================\n\n");
return tests_failed == 0 ? 0 : 1;
}