Skip to content
Merged
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
88 changes: 55 additions & 33 deletions src/DataRenderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,19 @@ const testClickableNodeCollapsed = () => {
describe('DataRender', () => {
it('should render booleans: true', () => {
render(<DataRender {...commonProps} value={{ test: true }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('true')).toBeInTheDocument();
});

it('should render booleans: false', () => {
render(<DataRender {...commonProps} value={{ test: false }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('false')).toBeInTheDocument();
});

it('should render strings', () => {
render(<DataRender {...commonProps} value={{ test: 'string' }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText(`"string"`)).toBeInTheDocument();
});

Expand All @@ -90,7 +90,7 @@ describe('DataRender', () => {
value={{ test: 'string' }}
/>
);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText(`string`)).toBeInTheDocument();
expect(screen.queryByText(`"string"`)).not.toBeInTheDocument();
});
Expand All @@ -103,49 +103,71 @@ describe('DataRender', () => {
value={{ test: 'string' }}
/>
);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText(`"string"`)).toBeInTheDocument();
});

it('should render field names without quotes if quotesForFieldNames is undefined', () => {
render(
<DataRender
{...commonProps}
style={{ ...defaultStyles, quotesForFieldNames: undefined }}
value={{ test: 'string' }}
/>
);
expect(screen.getByText(/test:/)).toBeInTheDocument();
});

it('should render field names with quotes if quotesForFieldNames is true', () => {
render(
<DataRender
{...commonProps}
style={{ ...defaultStyles, quotesForFieldNames: true }}
value={{ test: 'string' }}
/>
);
expect(screen.getByText(/"test":/)).toBeInTheDocument();
});

it('should render numbers', () => {
render(<DataRender {...commonProps} value={{ test: 42 }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('42')).toBeInTheDocument();
});

it('should render bigints', () => {
render(<DataRender {...commonProps} value={{ test: BigInt(42) }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('42n')).toBeInTheDocument();
});

it('should render dates', () => {
render(<DataRender {...commonProps} value={{ test: new Date(0) }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('1970-01-01T00:00:00.000Z')).toBeInTheDocument();
});

it('should render nulls', () => {
render(<DataRender {...commonProps} value={{ test: null }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('null')).toBeInTheDocument();
});

it('should render undefineds', () => {
render(<DataRender {...commonProps} value={{ test: undefined }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('undefined')).toBeInTheDocument();
});

it('should render unknown types', () => {
render(<DataRender {...commonProps} value={{ test: Symbol('2020') }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText(/2020/)).toBeInTheDocument();
});

it('should render object with empty key string', () => {
render(<DataRender {...commonProps} value={{ '': 'empty key' }} />);
expect(screen.getByText(/""/)).toBeInTheDocument();
expect(screen.getByText(/"":/)).toBeInTheDocument();
expect(screen.getByText(/empty key/)).toBeInTheDocument();
});

Expand Down Expand Up @@ -211,7 +233,7 @@ describe('DataRender', () => {

it('should render nested objects', () => {
render(<DataRender {...commonProps} value={{ obj: { test: 123 } }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.getByText('123')).toBeInTheDocument();
});

Expand All @@ -224,7 +246,7 @@ describe('DataRender', () => {
/>
);
expect(screen.getByText(/obj/)).toBeInTheDocument();
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
expect(screen.queryByText('123')).not.toBeInTheDocument();
});

Expand All @@ -237,36 +259,36 @@ describe('DataRender', () => {
/>
);
expect(screen.getByText(/obj/)).toBeInTheDocument();
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
expect(screen.queryByText('123')).not.toBeInTheDocument();

rerender(
<DataRender {...commonProps} value={{ obj: { test: 123 } }} shouldExpandNode={allExpanded} />
);
expect(screen.getByText(/obj/)).toBeInTheDocument();
expect(screen.queryByText(/test/)).toBeInTheDocument();
expect(screen.queryByText(/test:/)).toBeInTheDocument();
expect(screen.queryByText('123')).toBeInTheDocument();
});

it('should render nested arrays collapsed', () => {
render(
<DataRender {...commonProps} value={{ test: [123] }} shouldExpandNode={collapseAllNested} />
);
expect(screen.queryByText(/test/)).toBeInTheDocument();
expect(screen.queryByText(/test:/)).toBeInTheDocument();
expect(screen.queryByText('123')).not.toBeInTheDocument();
});

it('should render nested arrays collapsed and expand it once property changed', () => {
const { rerender } = render(
<DataRender {...commonProps} value={{ test: [123] }} shouldExpandNode={collapseAllNested} />
);
expect(screen.queryByText(/test/)).toBeInTheDocument();
expect(screen.queryByText(/test:/)).toBeInTheDocument();
expect(screen.queryByText('123')).not.toBeInTheDocument();

rerender(
<DataRender {...commonProps} value={{ test: [123] }} shouldExpandNode={allExpanded} />
);
expect(screen.queryByText(/test/)).toBeInTheDocument();
expect(screen.queryByText(/test:/)).toBeInTheDocument();
expect(screen.queryByText('123')).toBeInTheDocument();
});

Expand All @@ -277,13 +299,13 @@ describe('DataRender', () => {

it('should collapse and expand objects by clicking on icon', () => {
render(<DataRender {...commonProps} value={{ test: true }} />);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
let buttons = testButtonsExpanded();
fireEvent.click(buttons[0]);
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
buttons = testButtonsCollapsed();
fireEvent.click(buttons[0]);
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
});

it('should collapse and expand objects by clicking on node', () => {
Expand All @@ -296,25 +318,25 @@ describe('DataRender', () => {
);

// open the 'test' node by clicking the icon
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
expect(screen.queryByText(/child/)).not.toBeInTheDocument();
const buttons = testButtonsCollapsed();
fireEvent.click(buttons[0]);
testClickableNodeCollapsed();
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
expect(screen.queryByText(/child/)).not.toBeInTheDocument();
fireEvent.click(buttons[0]);
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
expect(screen.queryByText(/child/)).not.toBeInTheDocument();
});

it('should expand objects by clicking on collapsed content', () => {
render(<DataRender {...commonProps} value={{ test: true }} shouldExpandNode={collapseAll} />);
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
const buttons = testButtonsCollapsed();
fireEvent.click(buttons[1]);
testButtonsExpanded();
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
});

it('should collapse and expand arrays by clicking on icon', () => {
Expand All @@ -339,20 +361,20 @@ describe('DataRender', () => {

it('should expand objects by pressing Spacebar on icon', () => {
render(<DataRender {...commonProps} value={{ test: true }} shouldExpandNode={collapseAll} />);
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
const buttons = testButtonsCollapsed();
fireEvent.keyDown(buttons[0], { key: ' ', code: 'Space' });
testButtonsExpanded();
expect(screen.getByText(/test/)).toBeInTheDocument();
expect(screen.getByText(/test:/)).toBeInTheDocument();
});

it('should not expand objects by pressing other keys on icon', () => {
render(<DataRender {...commonProps} value={{ test: true }} shouldExpandNode={collapseAll} />);
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
const buttons = testButtonsCollapsed();
fireEvent.keyDown(buttons[0], { key: 'Enter', code: 'Enter' });
testButtonsCollapsed();
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
});

it('should expand arrays by pressing Spacebar on icon', () => {
Expand All @@ -373,11 +395,11 @@ describe('DataRender', () => {
<DataRender {...commonProps} value={['test', 'array']} shouldExpandNode={collapseAll} />
);
const buttons = testButtonsCollapsed();
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
expect(screen.queryByText(/array/)).not.toBeInTheDocument();
fireEvent.keyDown(buttons[0], { key: 'Enter', code: 'Enter' });
testButtonsCollapsed();
expect(screen.queryByText(/test/)).not.toBeInTheDocument();
expect(screen.queryByText(/test:/)).not.toBeInTheDocument();
expect(screen.queryByText(/array/)).not.toBeInTheDocument();
});
});
35 changes: 22 additions & 13 deletions src/DataRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export interface StyleProps {
expandIcon: string;
collapseIcon: string;
collapsedContent: string;
noQuotesForStringValues: boolean;
noQuotesForStringValues?: boolean;
quotesForFieldNames?: boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AnyRoad
I updated this PR to make field name quotes optional via quotesForFieldNames style config.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! 👍

}

export interface JsonRenderProps<T> {
Expand All @@ -43,6 +44,14 @@ export interface ExpandableRenderProps {
clickToExpandNode: boolean;
}

function quoteString(value: string, quoted = false) {
if (!value || quoted) {
return `"${value}"`;
}

return value;
}

function ExpandableObject({
field,
value,
Expand Down Expand Up @@ -94,7 +103,7 @@ function ExpandableObject({
aria-expanded={expanded}
aria-controls={expanded ? contentsId : undefined}
/>
{field &&
{(field || field === '') &&
(clickToExpandNode ? (
<span
className={style.clickableLabel}
Expand All @@ -103,10 +112,10 @@ function ExpandableObject({
role='button'
tabIndex={-1}
>
{field}:
{quoteString(field, style.quotesForFieldNames)}:
</span>
) : (
<span className={style.label}>{field}:</span>
<span className={style.label}>{quoteString(field, style.quotesForFieldNames)}:</span>
))}
<span className={style.punctuation}>{openBracket}</span>

Expand Down Expand Up @@ -155,7 +164,9 @@ export interface EmptyRenderProps {
function EmptyObject({ field, openBracket, closeBracket, lastElement, style }: EmptyRenderProps) {
return (
<div className={style.basicChildStyle} role='listitem'>
{field && <span className={style.label}>{field}:</span>}
{(field || field === '') && (
<span className={style.label}>{quoteString(field, style.quotesForFieldNames)}:</span>
)}
<span className={style.punctuation}>{openBracket}</span>
<span className={style.punctuation}>{closeBracket}</span>
{!lastElement && <span className={style.punctuation}>,</span>}
Expand Down Expand Up @@ -235,7 +246,7 @@ function JsonPrimitiveValue({
style,
lastElement
}: JsonRenderProps<string | number | boolean | Date | null | undefined>) {
let stringValue = value;
let stringValue;
let valueStyle = style.otherValue;

if (value === null) {
Expand All @@ -245,7 +256,7 @@ function JsonPrimitiveValue({
stringValue = 'undefined';
valueStyle = style.undefinedValue;
} else if (DataTypeDetection.isString(value)) {
stringValue = style.noQuotesForStringValues ? (value as string) : `"${value}"`;
stringValue = quoteString(value, !style.noQuotesForStringValues);
valueStyle = style.stringValue;
} else if (DataTypeDetection.isBoolean(value)) {
stringValue = value ? 'true' : 'false';
Expand All @@ -259,16 +270,14 @@ function JsonPrimitiveValue({
} else if (DataTypeDetection.isDate(value)) {
stringValue = value.toISOString();
} else {
stringValue = value.toString();
}

if (field === '') {
field = '""';
stringValue = (value as any).toString();
}

return (
<div className={style.basicChildStyle} role='listitem'>
{field && <span className={style.label}>{field}:</span>}
{(field || field === '') && (
<span className={style.label}>{quoteString(field, style.quotesForFieldNames)}:</span>
)}
<span className={valueStyle}>{stringValue}</span>
{!lastElement && <span className={style.punctuation}>,</span>}
</div>
Expand Down
16 changes: 8 additions & 8 deletions src/DataTypeDetection.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
export const isBoolean = (data: any) => {
export const isBoolean = (data: any): data is boolean => {
return typeof data === 'boolean' || data instanceof Boolean;
};

export const isNumber = (data: any) => {
export const isNumber = (data: any): data is number => {
return typeof data === 'number' || data instanceof Number;
};

export const isBigInt = (data: any) => {
export const isBigInt = (data: any): data is BigInt => {
return typeof data === 'bigint' || data instanceof BigInt;
};

export const isDate = (data: unknown): data is Date => {
return !!data && data instanceof Date;
};

export const isString = (data: any) => {
export const isString = (data: any): data is string => {
return typeof data === 'string' || data instanceof String;
};

export const isArray = (data: any) => {
export const isArray = (data: any): data is Array<any> => {
return Array.isArray(data);
};

export const isObject = (data: any) => {
export const isObject = (data: any): data is object => {
return data instanceof Object && data !== null;
};

export const isNull = (data: any) => {
export const isNull = (data: any): data is null => {
return data === null;
};

export const isUndefined = (data: any) => {
export const isUndefined = (data: any): data is undefined => {
return data === undefined;
};
Loading