test(napi): add fixture for re-export complex class (#2733)

This commit is contained in:
LongYinan 2025-06-24 16:31:26 +08:00 committed by GitHub
parent 1486b1f174
commit 3dce1533fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 145 additions and 2 deletions

View File

@ -3,4 +3,9 @@
# https://github.com/rust-lang/rust/issues/134820
# pthread_key_create() destructors and segfault after a DSO unloading
[target.'cfg(any(all(target_env = "gnu", not(target_os = "windows")), target_os = "freebsd"))']
rustflags = ["-C", "link-args=-Wl,-z,nodelete"]
rustflags = [
"-C",
"link-args=-Wl,--warn-unresolved-symbols",
"-C",
"link-args=-Wl,-z,nodelete",
]

View File

@ -91,7 +91,7 @@ jobs:
yarn test:cli
yarn test
yarn tsc -p examples/napi/tsconfig.json --noEmit --skipLibCheck
yarn test:macro
RUSTFLAGS="-C link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains" yarn test:macro
toolchain: stable
- host: windows-latest
target: x86_64-pc-windows-msvc

View File

@ -1,6 +1,75 @@
use napi::{bindgen_prelude::ClassInstance, Either};
use napi_derive::napi;
#[napi(object)]
pub struct Shared {
pub value: u32,
}
// Test fixture for GitHub issue #2722: Complex struct with constructor and multiple methods
#[napi]
pub struct ComplexClass {
pub value: String,
pub number: i32,
}
impl From<(String, i32)> for ComplexClass {
fn from(value: (String, i32)) -> Self {
ComplexClass {
value: value.0,
number: value.1,
}
}
}
impl<'env> From<Either<ClassInstance<'env, ComplexClass>, String>> for ComplexClass {
fn from(value: Either<ClassInstance<'env, ComplexClass>, String>) -> Self {
match value {
Either::A(instance) => ComplexClass {
value: (&*instance).value.clone(),
number: instance.number,
},
Either::B(value) => ComplexClass { value, number: 0 },
}
}
}
#[napi]
impl ComplexClass {
#[napi(constructor)]
pub fn new(value: Either<String, ClassInstance<ComplexClass>>, number: i32) -> Self {
let value_str = match value {
Either::A(s) => s,
Either::B(instance) => format!("cloned:{}", (&*instance).value),
};
ComplexClass {
value: value_str,
number,
}
}
#[napi]
pub fn method_one(&self) -> String {
format!("method_one: {}", self.value)
}
#[napi]
pub fn method_two(&self) -> i32 {
self.number * 2
}
#[napi]
pub fn method_three(&self) -> String {
format!("method_three: {} - {}", self.value, self.number)
}
#[napi]
pub fn method_four(&self) -> bool {
self.number > 0
}
#[napi]
pub fn method_five(&self) -> String {
self.value.to_uppercase()
}
}

View File

@ -1050,6 +1050,17 @@ Generated by [AVA](https://avajs.dev).
export function xxh128(input: Buffer): bigint␊
export function xxh3_64(input: Buffer): bigint␊
}␊
export declare class ComplexClass {␊
value: string␊
number: number␊
constructor(value: string | ComplexClass, number: number)␊
methodOne(): string␊
methodTwo(): number␊
methodThree(): string␊
methodFour(): boolean␊
methodFive(): string␊
}␊
export interface Shared {␊
value: number␊
}␊

View File

@ -243,6 +243,7 @@ import {
JSOnlyMethodsClass,
RustOnlyMethodsClass,
OriginalRustNameForJsNamedStruct,
ComplexClass,
} from '../index.cjs'
// import other stuff in `#[napi(module_exports)]`
import nativeAddon from '../index.cjs'
@ -1841,3 +1842,45 @@ test('escapable handle scope', (t) => {
shorterEscapableScope(makeIterFunction())
})
})
test('complex class with multiple methods - issue #2722', (t) => {
// Test creating instance of re-exported class with constructor (Either<String, ClassInstance<ComplexClass>>)
t.notThrows(() => {
const complex = new ComplexClass('test_value', 42)
// Test that constructor worked
t.is(complex.value, 'test_value')
t.is(complex.number, 42)
// Test all methods work
t.is(complex.methodOne(), 'method_one: test_value')
t.is(complex.methodTwo(), 84)
t.is(complex.methodThree(), 'method_three: test_value - 42')
t.is(complex.methodFour(), true)
t.is(complex.methodFive(), 'TEST_VALUE')
})
// Test with Either::B variant (ClassInstance instead of string)
t.notThrows(() => {
const original = new ComplexClass('original', 100)
const complex2 = new ComplexClass(original, -10)
t.is(complex2.value, 'cloned:original') // Should clone the value
t.is(complex2.methodFour(), false)
})
// Test that we can create multiple instances (stress test with Either)
t.notThrows(() => {
const baseInstance = new ComplexClass('base', 999)
for (let i = 0; i < 10; i++) {
// Alternate between string and ClassInstance for Either parameter
const instance =
i % 2 === 0
? new ComplexClass(`test${i}`, i)
: new ComplexClass(baseInstance, i)
const expectedValue = i % 2 === 0 ? `test${i}` : 'cloned:base'
t.is(instance.value, expectedValue)
t.is(instance.number, i)
}
})
})

View File

@ -365,3 +365,4 @@ export const withoutAbortController = __napiModule.exports.withoutAbortControlle
export const xxh64Alias = __napiModule.exports.xxh64Alias
export const xxh2 = __napiModule.exports.xxh2
export const xxh3 = __napiModule.exports.xxh3
export const ComplexClass = __napiModule.exports.ComplexClass

View File

@ -389,3 +389,4 @@ module.exports.withoutAbortController = __napiModule.exports.withoutAbortControl
module.exports.xxh64Alias = __napiModule.exports.xxh64Alias
module.exports.xxh2 = __napiModule.exports.xxh2
module.exports.xxh3 = __napiModule.exports.xxh3
module.exports.ComplexClass = __napiModule.exports.ComplexClass

View File

@ -680,3 +680,4 @@ module.exports.withoutAbortController = nativeBinding.withoutAbortController
module.exports.xxh64Alias = nativeBinding.xxh64Alias
module.exports.xxh2 = nativeBinding.xxh2
module.exports.xxh3 = nativeBinding.xxh3
module.exports.ComplexClass = nativeBinding.ComplexClass

View File

@ -1012,6 +1012,17 @@ export declare namespace xxh3 {
export function xxh128(input: Buffer): bigint
export function xxh3_64(input: Buffer): bigint
}
export declare class ComplexClass {
value: string
number: number
constructor(value: string | ComplexClass, number: number)
methodOne(): string
methodTwo(): number
methodThree(): string
methodFour(): boolean
methodFive(): string
}
export interface Shared {
value: number
}

View File

@ -8,6 +8,7 @@
#[cfg(not(target_family = "wasm"))]
use napi::bindgen_prelude::create_custom_tokio_runtime;
use napi::bindgen_prelude::{JsObjectValue, Object, Result, Symbol};
pub use napi_shared::*;
#[macro_use]
extern crate napi_derive;