Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions code/renderers/react/src/docs/jsxDecorator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,39 @@ describe('converts React Symbol to displayName string', () => {
it.each(symbolCases)('"%s" to "%s"', (symbol, expectedValue) => {
expect(getReactSymbolName(Symbol(symbol))).toEqual(expectedValue);
});

// Test edge cases that could cause "Cannot convert a Symbol value to a string" errors
describe('handles edge cases safely', () => {
it('handles null input', () => {
expect(getReactSymbolName(null)).toEqual('unknown');
});

it('handles undefined input', () => {
expect(getReactSymbolName(undefined)).toEqual('unknown');
});

it('handles object without toString method', () => {
const objWithoutToString = Object.create(null);
expect(getReactSymbolName(objWithoutToString)).toEqual('unknown');
});

it('handles object with $$typeof that lacks toString', () => {
const objWithBadTypeof = {
$$typeof: Object.create(null),
};
expect(getReactSymbolName(objWithBadTypeof)).toEqual('unknown');
});

it('handles regular objects', () => {
const regularObject = { prop: 'value' };
expect(getReactSymbolName(regularObject)).toEqual('[object Object]');
});

it('handles primitive values', () => {
expect(getReactSymbolName(42)).toEqual('42');
expect(getReactSymbolName('test')).toEqual('Test');
});
});
});

describe('renderJsx', () => {
Expand Down Expand Up @@ -260,6 +293,30 @@ describe('renderJsx', () => {
).toMatchInlineSnapshot(`<Button label={<p>Abcd</p>} />`);
});

it('handles React node as prop without symbol conversion errors', () => {
function ComponentWithNodeProp({ children, icon }: any) {
return <div>{icon}{children}</div>;
}

// This test specifically ensures that passing React nodes as props doesn't cause
// "Cannot convert a Symbol value to a string" errors in the docs rendering
const reactNodeProp = <span>Icon</span>;
const component = createElement(
ComponentWithNodeProp,
{
icon: reactNodeProp,
},
'Content'
);

// This should not throw any errors about symbol conversion
expect(() => renderJsx(component)).not.toThrow();

const result = renderJsx(component);
expect(result).toContain('ComponentWithNodeProp');
expect(result).toContain('<span>Icon</span>');
});

it('Suspense', () => {
expect(
renderJsx(
Expand Down
22 changes: 21 additions & 1 deletion code/renderers/react/src/docs/jsxDecorator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,28 @@ const toPascalCase = (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
* null.
*/
export const getReactSymbolName = (elementType: any): string => {
// Handle null/undefined inputs safely
if (elementType == null) {
return 'unknown';
}

const elementName = elementType.$$typeof || elementType;
const symbolDescription: string = elementName.toString().replace(/^Symbol\((.*)\)$/, '$1');

// Safe string conversion with fallback for objects without toString
let symbolDescription: string;
try {
if (typeof elementName === 'symbol') {
symbolDescription = elementName.toString().replace(/^Symbol\((.*)\)$/, '$1');
} else if (elementName && typeof elementName.toString === 'function') {
symbolDescription = elementName.toString().replace(/^Symbol\((.*)\)$/, '$1');
} else {
// Fallback for objects without toString method
symbolDescription = String(elementName).replace(/^Symbol\((.*)\)$/, '$1');
}
} catch (error) {
// Fallback to a safe string representation
symbolDescription = 'unknown';
}

const reactComponentName = symbolDescription
.split('.')
Expand Down