@@ -290,4 +290,82 @@ describe('hoistNonReactStatics', () => {
290290 expect ( ( Hoc2 as any ) . originalStatic ) . toBe ( 'original' ) ;
291291 expect ( ( Hoc2 as any ) . hoc1Static ) . toBe ( 'hoc1' ) ;
292292 } ) ;
293+
294+ it ( 'handles Symbol.hasInstance from Function.prototype without throwing' , ( ) => {
295+ expect ( Object . getOwnPropertySymbols ( Function . prototype ) ) . toContain ( Symbol . hasInstance ) ;
296+
297+ class Source extends React . Component {
298+ static customStatic = 'value' ;
299+ }
300+ class Target extends React . Component { }
301+
302+ // This should not throw "Cannot convert a Symbol value to a string"
303+ expect ( ( ) => hoistNonReactStatics ( Target , Source ) ) . not . toThrow ( ) ;
304+ expect ( ( Target as any ) . customStatic ) . toBe ( 'value' ) ;
305+ } ) ;
306+
307+ it ( 'handles components with Symbol.hasInstance defined' , ( ) => {
308+ class Source extends React . Component {
309+ static customStatic = 'value' ;
310+ static [ Symbol . hasInstance ] ( instance : unknown ) {
311+ return instance instanceof Source ;
312+ }
313+ }
314+ class Target extends React . Component { }
315+
316+ // This should not throw
317+ expect ( ( ) => hoistNonReactStatics ( Target , Source ) ) . not . toThrow ( ) ;
318+ expect ( ( Target as any ) . customStatic ) . toBe ( 'value' ) ;
319+ // Symbol.hasInstance should be hoisted
320+ expect ( typeof ( Target as any ) [ Symbol . hasInstance ] ) . toBe ( 'function' ) ;
321+ } ) ;
322+
323+ it ( 'does not rely on String() for symbol keys (simulating minifier transformation)' , ( ) => {
324+ const sym = Symbol ( 'test' ) ;
325+ // eslint-disable-next-line prefer-template
326+ expect ( ( ) => '' + ( sym as any ) ) . toThrow ( 'Cannot convert a Symbol value to a string' ) ;
327+
328+ // But accessing an object with a symbol key should NOT throw
329+ const obj : Record < string , boolean > = { name : true } ;
330+ expect ( obj [ sym as any ] ) . toBeUndefined ( ) ; // No error, just undefined
331+
332+ // Now test the actual function - it should work because it shouldn't
333+ // need to convert symbols to strings
334+ class Source extends React . Component {
335+ static customStatic = 'value' ;
336+ }
337+ // Add a symbol property that will be iterated over
338+ ( Source as any ) [ Symbol . for ( 'test.symbol' ) ] = 'symbolValue' ;
339+
340+ class Target extends React . Component { }
341+
342+ expect ( ( ) => hoistNonReactStatics ( Target , Source ) ) . not . toThrow ( ) ;
343+ expect ( ( Target as any ) . customStatic ) . toBe ( 'value' ) ;
344+ expect ( ( Target as any ) [ Symbol . for ( 'test.symbol' ) ] ) . toBe ( 'symbolValue' ) ;
345+ } ) ;
346+
347+ it ( 'works when String() throws for symbols (simulating aggressive minifier)' , ( ) => {
348+ const OriginalString = globalThis . String ;
349+ globalThis . String = function ( value : unknown ) {
350+ if ( typeof value === 'symbol' ) {
351+ throw new TypeError ( 'Cannot convert a Symbol value to a string' ) ;
352+ }
353+ return OriginalString ( value ) ;
354+ } as StringConstructor ;
355+
356+ try {
357+ class Source extends React . Component {
358+ static customStatic = 'value' ;
359+ }
360+ ( Source as any ) [ Symbol . for ( 'test.symbol' ) ] = 'symbolValue' ;
361+
362+ class Target extends React . Component { }
363+
364+ expect ( ( ) => hoistNonReactStatics ( Target , Source ) ) . not . toThrow ( ) ;
365+ expect ( ( Target as any ) . customStatic ) . toBe ( 'value' ) ;
366+ expect ( ( Target as any ) [ Symbol . for ( 'test.symbol' ) ] ) . toBe ( 'symbolValue' ) ;
367+ } finally {
368+ globalThis . String = OriginalString ;
369+ }
370+ } ) ;
293371} ) ;
0 commit comments