const assert = require("node:assert"); const nativeTests = require("./build/Debug/napitests.node"); const secondAddon = require("./build/Debug/second_addon.node"); const asyncFinalizeAddon = require("./build/Debug/async_finalize_addon.node"); const testReferenceUnrefInFinalizer = require("./build/Debug/test_reference_unref_in_finalizer.node"); const testReferenceUnrefInFinalizerExperimental = require("./build/Debug/test_reference_unref_in_finalizer_experimental.node"); async function gcUntil(fn) { const MAX = 100; for (let i = 0; i < MAX; i++) { await new Promise(resolve => { setTimeout(resolve, 1); }); if (typeof Bun == "object") { Bun.gc(true); } else { // if this fails, you need to pass --expose-gc to node global.gc(); } if (fn()) { return; } } throw new Error(`Condition was not met after ${MAX} GC attempts`); } nativeTests.test_napi_class_constructor_handle_scope = () => { const NapiClass = nativeTests.get_class_with_constructor(); const x = new NapiClass(); console.log("x.foo =", x.foo); }; nativeTests.test_napi_handle_scope_finalizer = async () => { // Create a weak reference, which will be collected eventually // Pass false in Node.js so it does not create a handle scope nativeTests.create_ref_with_finalizer(Boolean(process.isBun)); // Wait until it actually has been collected by ticking the event loop and forcing GC await gcUntil(() => nativeTests.was_finalize_called()); }; nativeTests.test_napi_async_work_execute_null_check = () => { const res = nativeTests.create_async_work_with_null_execute(); if (res) { console.log("success!"); } else { console.log("failure!"); } }; nativeTests.test_napi_async_work_complete_null_check = async () => { nativeTests.create_async_work_with_null_complete(); await gcUntil(() => true); }; nativeTests.test_napi_async_work_cancel = () => { // UV_THREADPOOL_SIZE is set to 2, create two blocking tasks, // then create another and cancel it, ensuring the work is not // scheduled before `napi_cancel_async_work` is called const res = nativeTests.test_cancel_async_work(result => { if (result) { console.log("success!"); } else { console.log("failure!"); } }); if (!res) { console.log("failure!"); } }; nativeTests.test_promise_with_threadsafe_function = async () => { await new Promise(resolve => setTimeout(resolve, 1)); // create_promise_with_threadsafe_function returns a promise that calls our function from another // thread (via napi_threadsafe_function) and resolves with its return value return await nativeTests.create_promise_with_threadsafe_function(() => 1234); }; nativeTests.test_get_exception = (_, value) => { function thrower() { throw value; } try { const result = nativeTests.call_and_get_exception(thrower); console.log("got same exception back?", result === value); } catch (e) { console.log("native module threw", typeof e, e); throw e; } }; nativeTests.test_get_property = () => { const objects = [ {}, { foo: "bar" }, { get foo() { throw new Error("get foo"); }, }, { set foo(newValue) {}, }, new Proxy( {}, { get(_target, key) { throw new Error(`proxy get ${key}`); }, }, ), 5, "hello", null, undefined, ]; const keys = [ "foo", { toString() { throw new Error("toString"); }, }, { [Symbol.toPrimitive]() { throw new Error("Symbol.toPrimitive"); }, }, "toString", "slice", ]; for (const object of objects) { for (const key of keys) { try { const ret = nativeTests.perform_get(object, key); console.log("native function returned", ret); } catch (e) { console.log("threw", e.name); } } } }; nativeTests.test_set_property = () => { const objects = [ {}, { foo: "bar" }, { set foo(value) { throw new Error(`set foo to ${value}`); }, }, { // getter but no setter get foo() {}, }, new Proxy( {}, { set(_target, key, value) { throw new Error(`proxy set ${key} to ${value}`); }, }, ), null, undefined, ]; const keys = [ "foo", { toString() { throw new Error("toString"); }, }, { [Symbol.toPrimitive]() { throw new Error("Symbol.toPrimitive"); }, }, ]; for (const object of objects) { for (const key of keys) { console.log(objects.indexOf(object) + ", " + keys.indexOf(key)); try { const ret = nativeTests.perform_set(object, key, 42); console.log("native function returned", ret); if (object[key] != 42) { throw new Error("setting property did not throw an error, but the property was not actually set"); } } catch (e) { console.log("threw", e.name); } } } }; nativeTests.test_number_integer_conversions_from_js = () => { const i32 = { min: -(2 ** 31), max: 2 ** 31 - 1 }; const u32Max = 2 ** 32 - 1; // this is not the actual max value for i64, but rather the highest double that is below the true max value const i64 = { min: -(2 ** 63), max: 2 ** 63 - 1024 }; const i32Cases = [ // special values [Infinity, 0], [-Infinity, 0], [NaN, 0], // normal [0.0, 0], [1.0, 1], [-1.0, -1], // truncation [1.25, 1], [-1.25, -1], // limits [i32.min, i32.min], [i32.max, i32.max], // wrap around [i32.min - 1.0, i32.max], [i32.max + 1.0, i32.min], [i32.min - 2.0, i32.max - 1], [i32.max + 2.0, i32.min + 1], // type errors ["5", undefined], [new Number(5), undefined], ]; for (const [input, expectedOutput] of i32Cases) { const actualOutput = nativeTests.double_to_i32(input); console.log(`${input} as i32 => ${actualOutput}`); assert(actualOutput === expectedOutput); } const u32Cases = [ // special values [Infinity, 0], [-Infinity, 0], [NaN, 0], // normal [0.0, 0], [1.0, 1], // truncation [1.25, 1], [-1.25, u32Max], // limits [u32Max, u32Max], // wrap around [-1.0, u32Max], [u32Max + 1.0, 0], [-2.0, u32Max - 1], [u32Max + 2.0, 1], // type errors ["5", undefined], [new Number(5), undefined], ]; for (const [input, expectedOutput] of u32Cases) { const actualOutput = nativeTests.double_to_u32(input); console.log(`${input} as u32 => ${actualOutput}`); assert(actualOutput === expectedOutput); } const i64Cases = [ // special values [Infinity, 0], [-Infinity, 0], [NaN, 0], // normal [0.0, 0], [1.0, 1], [-1.0, -1], // truncation [1.25, 1], [-1.25, -1], // limits [i64.min, i64.min], [i64.max, i64.max], // clamp [i64.min - 4096.0, i64.min], // this one clamps to the exact max value of i64 (2**63 - 1), which is then rounded // to exactly 2**63 since that's the closest double that can be represented [i64.max + 4096.0, 2 ** 63], // type errors ["5", undefined], [new Number(5), undefined], ]; for (const [input, expectedOutput] of i64Cases) { const actualOutput = nativeTests.double_to_i64(input); console.log( `${typeof input == "number" ? input.toFixed(2) : input} as i64 => ${typeof actualOutput == "number" ? actualOutput.toFixed(2) : actualOutput}`, ); assert(actualOutput === expectedOutput); } }; nativeTests.test_create_array_with_length = () => { for (const size of [0, 5]) { const array = nativeTests.make_empty_array(size); console.log("length =", array.length); // should be 0 as array contains empty slots console.log("number of keys =", Object.keys(array).length); } }; nativeTests.test_throw_functions_exhaustive = () => { for (const errorKind of ["error", "type_error", "range_error", "syntax_error"]) { for (const code of [undefined, "", "error code"]) { for (const msg of [undefined, "", "error message"]) { try { nativeTests.throw_error(code, msg, errorKind); console.log(`napi_throw_${errorKind}(${code ?? "nullptr"}, ${msg ?? "nullptr"}) did not throw`); } catch (e) { console.log( `napi_throw_${errorKind} threw ${e.name}: message ${JSON.stringify(e.message)}, code ${JSON.stringify(e.code)}`, ); } } } } }; nativeTests.test_create_error_functions_exhaustive = () => { for (const errorKind of ["error", "type_error", "range_error", "syntax_error"]) { // null (JavaScript null) is changed to nullptr by the native function for (const code of [undefined, null, "", 42, "error code"]) { for (const msg of [undefined, null, "", 42, "error message"]) { try { nativeTests.create_and_throw_error(code, msg, errorKind); console.log( `napi_create_${errorKind}(${code === null ? "nullptr" : code}, ${msg === null ? "nullptr" : msg}) did not make an error`, ); } catch (e) { console.log( `create_and_throw_error(${errorKind}) threw ${e.name}: message ${JSON.stringify(e.message)}, code ${JSON.stringify(e.code)}`, ); } } } } }; nativeTests.test_type_tag = () => { const o1 = {}; const o2 = {}; nativeTests.add_tag(o1, 1, 2); try { // re-tag nativeTests.add_tag(o1, 1, 2); } catch (e) { console.log("tagging already-tagged object threw", e.toString()); } console.log("tagging non-object succeeds: ", !nativeTests.try_add_tag(null, 0, 0)); nativeTests.add_tag(o2, 3, 4); console.log("o1 matches o1:", nativeTests.check_tag(o1, 1, 2)); console.log("o1 matches o2:", nativeTests.check_tag(o1, 3, 4)); console.log("o2 matches o1:", nativeTests.check_tag(o2, 1, 2)); console.log("o2 matches o2:", nativeTests.check_tag(o2, 3, 4)); }; nativeTests.test_napi_class = () => { const NapiClass = nativeTests.get_class_with_constructor(); const instance = new NapiClass(); console.log("static data =", NapiClass.getStaticData()); console.log("static getter =", NapiClass.getter); console.log("foo =", instance.foo); console.log("data =", instance.getData()); }; nativeTests.test_subclass_napi_class = () => { const NapiClass = nativeTests.get_class_with_constructor(); class Subclass extends NapiClass {} const instance = new Subclass(); console.log("subclass static data =", Subclass.getStaticData()); console.log("subclass static getter =", Subclass.getter); console.log("subclass foo =", instance.foo); console.log("subclass data =", instance.getData()); }; nativeTests.test_napi_class_non_constructor_call = () => { const NapiClass = nativeTests.get_class_with_constructor(); console.log("non-constructor call NapiClass() =", NapiClass()); console.log("global foo set to ", typeof foo != "undefined" ? foo : undefined); }; nativeTests.test_reflect_construct_napi_class = () => { const NapiClass = nativeTests.get_class_with_constructor(); let instance = Reflect.construct(NapiClass, [], Object); console.log("reflect constructed foo =", instance.foo); console.log("reflect constructed data =", instance.getData?.()); class Foo {} instance = Reflect.construct(NapiClass, [], Foo); console.log("reflect constructed foo =", instance.foo); console.log("reflect constructed data =", instance.getData?.()); }; nativeTests.test_reflect_construct_no_prototype_crash = () => { // This test verifies the fix for jsDynamicCast being called on JSValue(0) // when a NAPI class constructor is called via Reflect.construct with a // newTarget that has no prototype property. const NapiClass = nativeTests.get_class_with_constructor(); // Test 1: Constructor function with deleted prototype property // This case should work without crashing function ConstructorWithoutPrototype() {} delete ConstructorWithoutPrototype.prototype; try { const instance1 = Reflect.construct(NapiClass, [], ConstructorWithoutPrototype); console.log("constructor without prototype: success - no crash"); } catch (e) { console.log("constructor without prototype error:", e.message); } // Test 2: Regular constructor (control test) // This should always work function NormalConstructor() {} try { const instance2 = Reflect.construct(NapiClass, [], NormalConstructor); console.log("normal constructor: success - no crash"); } catch (e) { console.log("normal constructor error:", e.message); } // Test 3: Reflect.construct with Proxy newTarget (prototype returns undefined) function ProxyObject() {} const proxyTarget = new Proxy(ProxyObject, { get(target, prop) { if (prop === "prototype") { return undefined; } return target[prop]; }, }); const instance3 = Reflect.construct(NapiClass, [], proxyTarget); console.log("✓ Success - no crash!"); }; nativeTests.test_napi_wrap = () => { const values = [ {}, {}, // should be able to be wrapped differently than the distinct empty object above 5, new Number(5), "abc", new String("abc"), null, Symbol("abc"), Symbol.for("abc"), new (nativeTests.get_class_with_constructor())(), new Proxy( {}, Object.fromEntries( [ "apply", "construct", "defineProperty", "deleteProperty", "get", "getOwnPropertyDescriptor", "getPrototypeOf", "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf", ].map(name => [ name, () => { throw new Error("oops"); }, ]), ), ), ]; const wrapSuccess = Array(values.length).fill(false); for (const [i, v] of values.entries()) { wrapSuccess[i] = nativeTests.try_wrap(v, i + 1); console.log(`${typeof v} did wrap: `, wrapSuccess[i]); } for (const [i, v] of values.entries()) { if (wrapSuccess[i]) { if (nativeTests.try_unwrap(v) !== i + 1) { throw new Error("could not unwrap same value"); } } else { if (nativeTests.try_unwrap(v) !== undefined) { throw new Error("value unwraps without being successfully wrapped"); } } } }; nativeTests.test_napi_wrap_proxy = () => { const target = {}; const proxy = new Proxy(target, {}); assert(nativeTests.try_wrap(target, 5)); assert(nativeTests.try_wrap(proxy, 6)); console.log(nativeTests.try_unwrap(target), nativeTests.try_unwrap(proxy)); }; nativeTests.test_napi_wrap_cross_addon = () => { const wrapped = {}; console.log("wrap succeeds:", nativeTests.try_wrap(wrapped, 42)); console.log("unwrapped from other addon", secondAddon.try_unwrap(wrapped)); }; nativeTests.test_napi_wrap_prototype = () => { class Foo {} console.log("wrap prototype succeeds:", nativeTests.try_wrap(Foo.prototype, 42)); // wrapping should not look at prototype chain console.log("unwrap instance:", nativeTests.try_unwrap(new Foo())); }; nativeTests.test_napi_remove_wrap = () => { const targets = [{}, new (nativeTests.get_class_with_constructor())()]; for (const t of targets) { const target = {}; // fails assert(nativeTests.try_remove_wrap(target) === undefined); // wrap it assert(nativeTests.try_wrap(target, 5)); // remove yields the wrapped value assert(nativeTests.try_remove_wrap(target) === 5); // neither remove nor unwrap work anymore assert(nativeTests.try_unwrap(target) === undefined); assert(nativeTests.try_remove_wrap(target) === undefined); // can re-wrap assert(nativeTests.try_wrap(target, 6)); assert(nativeTests.try_unwrap(target) === 6); } }; // parameters to create_wrap are: object, ask_for_ref, strong const createWrapWithoutRef = o => nativeTests.create_wrap(o, false, false); const createWrapWithWeakRef = o => nativeTests.create_wrap(o, true, false); const createWrapWithStrongRef = o => nativeTests.create_wrap(o, true, true); nativeTests.test_wrap_lifetime_without_ref = async () => { let object = { foo: "bar" }; assert(createWrapWithoutRef(object) === object); assert(nativeTests.get_wrap_data(object) === 42); object = undefined; await gcUntil(() => nativeTests.was_wrap_finalize_called()); }; nativeTests.test_wrap_lifetime_with_weak_ref = async () => { // this looks the same as test_wrap_lifetime_without_ref because it is -- these cases should behave the same let object = { foo: "bar" }; assert(createWrapWithWeakRef(object) === object); assert(nativeTests.get_wrap_data(object) === 42); object = undefined; await gcUntil(() => nativeTests.was_wrap_finalize_called()); }; nativeTests.test_wrap_lifetime_with_strong_ref = async () => { let object = { foo: "bar" }; assert(createWrapWithStrongRef(object) === object); assert(nativeTests.get_wrap_data(object) === 42); object = undefined; // still referenced by native module so this should fail try { await gcUntil(() => nativeTests.was_wrap_finalize_called()); throw new Error("object was garbage collected while still referenced by native code"); } catch (e) { if (!e.toString().includes("Condition was not met")) { throw e; } } // can still get the value using the ref assert(nativeTests.get_wrap_data_from_ref() === 42); // now we free it nativeTests.unref_wrapped_value(); await gcUntil(() => nativeTests.was_wrap_finalize_called()); }; nativeTests.test_remove_wrap_lifetime_with_weak_ref = async () => { let object = { foo: "bar" }; assert(createWrapWithWeakRef(object) === object); assert(nativeTests.get_wrap_data(object) === 42); nativeTests.remove_wrap(object); assert(nativeTests.get_wrap_data(object) === undefined); assert(nativeTests.get_wrap_data_from_ref() === undefined); assert(nativeTests.get_object_from_ref() === object); object = undefined; // ref will stop working once the object is collected await gcUntil(() => nativeTests.get_object_from_ref() === undefined); // finalizer shouldn't have been called assert(nativeTests.was_wrap_finalize_called() === false); }; nativeTests.test_remove_wrap_lifetime_with_strong_ref = async () => { let object = { foo: "bar" }; assert(createWrapWithStrongRef(object) === object); assert(nativeTests.get_wrap_data(object) === 42); nativeTests.remove_wrap(object); assert(nativeTests.get_wrap_data(object) === undefined); assert(nativeTests.get_wrap_data_from_ref() === undefined); assert(nativeTests.get_object_from_ref() === object); object = undefined; // finalizer should not be called and object should not be freed try { await gcUntil(() => nativeTests.was_wrap_finalize_called() || nativeTests.get_object_from_ref() === undefined); throw new Error("finalizer ran"); } catch (e) { if (!e.toString().includes("Condition was not met")) { throw e; } } // native code can still get the object assert(JSON.stringify(nativeTests.get_object_from_ref()) === `{"foo":"bar"}`); // now it gets deleted nativeTests.unref_wrapped_value(); await gcUntil(() => nativeTests.get_object_from_ref() === undefined); }; nativeTests.test_ref_deleted_in_cleanup = () => { let object = { foo: "bar" }; assert(createWrapWithWeakRef(object) === object); assert(nativeTests.get_wrap_data(object) === 42); }; nativeTests.test_ref_deleted_in_async_finalize = () => { asyncFinalizeAddon.create_ref(); }; nativeTests.test_reference_unref_in_finalizer = async gc => { // Create objects with finalizers that will call napi_reference_unref when GC'd let objects = testReferenceUnrefInFinalizer.test_reference_unref_in_finalizer(); // Clear the reference to allow GC objects = null; // Force GC multiple times to ensure finalizers run if (gc) { gc(); gc(); } // Allocate large ArrayBuffers to trigger GC pressure const buffers = []; for (let i = 0; i < 100; i++) { buffers.push(new ArrayBuffer(10 * 1024 * 1024)); // 10MB each if (gc && i % 10 === 0) { gc(); } } // Wait for async operations await new Promise(resolve => setTimeout(resolve, 50)); // Force final GC if (gc) { gc(); gc(); } // Get stats to verify finalizers were called const stats = testReferenceUnrefInFinalizer.get_stats(); console.log(`Finalizers called: ${stats.finalizersCalled}, Unrefs succeeded: ${stats.unrefsSucceeded}`); if (stats.finalizersCalled === 0) { throw new Error("No finalizers were called - test did not properly trigger GC"); } if (stats.unrefsSucceeded === 0) { throw new Error("No napi_reference_unref calls succeeded"); } console.log("SUCCESS: napi_reference_unref worked in finalizers without crashing"); }; nativeTests.test_reference_unref_in_finalizer_experimental = async gc => { // This test is expected to CRASH when the finalizer runs // The experimental NAPI module enforces GC checks and will abort the process console.log("WARNING: This test will crash the process - this is expected behavior!"); // Create objects with finalizers that will call napi_reference_unref when GC'd let objects = testReferenceUnrefInFinalizerExperimental.test_reference_unref_in_finalizer_experimental(); // Clear the reference to allow GC objects = null; // Force GC to trigger the finalizers - this should crash the process if (gc) { gc(); gc(); } // Allocate memory to ensure GC runs for (let i = 0; i < 5; i++) { new ArrayBuffer(10 * 1024 * 1024); if (gc) gc(); } // If we get here, the test has FAILED - the process should have crashed console.log("ERROR: Process did not crash as expected!"); console.log("ERROR: The GC check for experimental modules is NOT working!"); throw new Error("Test FAILED: napi_reference_unref should have aborted for experimental module"); }; nativeTests.test_create_bigint_words = () => { console.log(nativeTests.create_weird_bigints()); }; nativeTests.test_bigint_word_count = () => { // Test with a 2-word BigInt const bigint = 0x123456789abcdef0123456789abcdefn; const result = nativeTests.test_bigint_actual_word_count(bigint); console.log(`BigInt: ${bigint.toString(16)}`); console.log(`Queried word count: ${result.queriedWordCount}`); console.log(`Actual word count: ${result.actualWordCount}`); console.log(`Sign bit: ${result.signBit}`); // Both counts should be 2 for this BigInt if (result.queriedWordCount === 2 && result.actualWordCount === 2) { console.log("✅ PASS: Word count correctly returns 2"); } else { console.log( `❌ FAIL: Expected word count 2, got queried=${result.queriedWordCount}, actual=${result.actualWordCount}`, ); } }; nativeTests.test_ref_unref_underflow = () => { // Test that napi_reference_unref properly handles refCount == 0 const obj = { test: "value" }; const result = nativeTests.test_reference_unref_underflow(obj); console.log(`First unref count: ${result.firstUnrefCount}`); console.log(`Second unref status: ${result.secondUnrefStatus}`); // First unref should succeed and return count of 0 // Second unref should fail with napi_generic_failure (status = 1) if (result.firstUnrefCount === 0 && result.secondUnrefStatus === 1) { console.log("✅ PASS: Reference unref correctly prevents underflow"); } else { console.log( `❌ FAIL: Expected firstUnrefCount=0, secondUnrefStatus=1, got ${result.firstUnrefCount}, ${result.secondUnrefStatus}`, ); } }; nativeTests.test_get_value_string = () => { function to16Bit(string) { if (typeof Bun != "object") return string; const jsc = require("bun:jsc"); const codeUnits = new DataView(new ArrayBuffer(2 * string.length)); for (let i = 0; i < string.length; i++) { codeUnits.setUint16(2 * i, string.charCodeAt(i), true); } const decoder = new TextDecoder("utf-16le"); const string16Bit = decoder.decode(codeUnits); // make sure we succeeded in making a UTF-16 string assert(jsc.jscDescribe(string16Bit).includes("8Bit:(0)")); return string16Bit; } function assert8Bit(string) { if (typeof Bun != "object") return string; const jsc = require("bun:jsc"); // make sure we succeeded in making a Latin-1 string assert(jsc.jscDescribe(string).includes("8Bit:(1)")); return string; } // test all of our get_value_string_XXX functions on a variety of inputs for (const [string, description] of [ ["hello", "simple latin-1"], [to16Bit("hello"), "16-bit encoded with only BMP characters"], [assert8Bit("café"), "8-bit with non-ascii characters"], [to16Bit("café"), "16-bit with non-ascii but latin-1 characters"], ["你好小圆面包", "16-bit, all BMP, all outside latin-1"], ["🐱🏳️‍⚧️", "16-bit with many surrogate pairs"], // TODO(@190n) handle these correctly // ["\ud801", "unpaired high surrogate"], // ["\udc02", "unpaired low surrogate"], ]) { console.log(`test napi_get_value_string on ${string} (${description})`); for (const encoding of ["latin1", "utf8", "utf16"]) { console.log(encoding); const fn = nativeTests[`test_get_value_string_${encoding}`]; fn(string); } } }; nativeTests.test_constructor_order = () => { require("./build/Debug/constructor_order_addon.node"); }; // Cleanup hook tests nativeTests.test_cleanup_hook_order = () => { const addon = require("./build/Debug/test_cleanup_hook_order.node"); addon.test(); }; nativeTests.test_cleanup_hook_remove_nonexistent = () => { const addon = require("./build/Debug/test_cleanup_hook_remove_nonexistent.node"); addon.test(); }; nativeTests.test_async_cleanup_hook_remove_nonexistent = () => { const addon = require("./build/Debug/test_async_cleanup_hook_remove_nonexistent.node"); addon.test(); }; nativeTests.test_cleanup_hook_duplicates = () => { const addon = require("./build/Debug/test_cleanup_hook_duplicates.node"); addon.test(); }; nativeTests.test_cleanup_hook_mixed_order = () => { const addon = require("./build/Debug/test_cleanup_hook_mixed_order.node"); addon.test(); }; nativeTests.test_cleanup_hook_modification_during_iteration = () => { const addon = require("./build/Debug/test_cleanup_hook_modification_during_iteration.node"); addon.test(); }; // Test for napi_typeof with boxed primitive objects (String, Number, Boolean) // See: https://github.com/oven-sh/bun/issues/25351 nativeTests.test_napi_typeof_boxed_primitives = () => { // napi_valuetype enum values (from node_api_types.h): // napi_undefined = 0, napi_null = 1, napi_boolean = 2, napi_number = 3, // napi_string = 4, napi_symbol = 5, napi_object = 6, napi_function = 7, // napi_external = 8, napi_bigint = 9 const napi_string = 4; const napi_object = 6; // Primitive string should be napi_string const primitiveStringType = nativeTests.napi_get_typeof("hello"); assert.strictEqual( primitiveStringType, napi_string, `primitive string should be napi_string (${napi_string}), got ${primitiveStringType}`, ); console.log("PASS: primitive string returns napi_string"); // String object should be napi_object const stringObjectType = nativeTests.napi_get_typeof(new String("hello")); assert.strictEqual( stringObjectType, napi_object, `String object should be napi_object (${napi_object}), got ${stringObjectType}`, ); console.log("PASS: String object returns napi_object"); // Number object should be napi_object const numberObjectType = nativeTests.napi_get_typeof(new Number(42)); assert.strictEqual( numberObjectType, napi_object, `Number object should be napi_object (${napi_object}), got ${numberObjectType}`, ); console.log("PASS: Number object returns napi_object"); // Boolean object should be napi_object const booleanObjectType = nativeTests.napi_get_typeof(new Boolean(true)); assert.strictEqual( booleanObjectType, napi_object, `Boolean object should be napi_object (${napi_object}), got ${booleanObjectType}`, ); console.log("PASS: Boolean object returns napi_object"); console.log("All boxed primitive tests passed!"); }; // https://github.com/oven-sh/bun/issues/25933 // Test that napi_typeof returns napi_function for callbacks wrapped in // AsyncContextFrame (which happens inside AsyncLocalStorage.run()). nativeTests.test_napi_typeof_async_context_frame = async () => { const { AsyncLocalStorage } = require("node:async_hooks"); const als = new AsyncLocalStorage(); await als.run({ key: "value" }, () => { return new Promise(resolve => { // Pass a callback to the native addon. Because we're inside // AsyncLocalStorage.run(), Bun wraps it in AsyncContextFrame. // The native call_js_cb will call napi_typeof on the received // js_callback and print the result. nativeTests.test_issue_25933(() => {}); // The threadsafe function callback fires asynchronously. setTimeout(resolve, 50); }); }); }; // Test that napi_make_callback works when the func is an AsyncContextFrame // (received by a threadsafe function's call_js_cb inside AsyncLocalStorage.run()). nativeTests.test_make_callback_with_async_context = async () => { const { AsyncLocalStorage } = require("node:async_hooks"); const als = new AsyncLocalStorage(); await als.run({ key: "value" }, () => { return new Promise(resolve => { nativeTests.test_napi_make_callback_async_context_frame(() => {}); setTimeout(resolve, 50); }); }); }; // Test that napi_create_threadsafe_function with call_js_cb=NULL accepts an // AsyncContextFrame as the func (received from another threadsafe function's call_js_cb). nativeTests.test_create_tsfn_with_async_context = async () => { const { AsyncLocalStorage } = require("node:async_hooks"); const als = new AsyncLocalStorage(); await als.run({ key: "value" }, () => { return new Promise(resolve => { nativeTests.test_napi_create_tsfn_async_context_frame(() => {}); setTimeout(resolve, 100); }); }); }; module.exports = nativeTests;