Skip to content

Commit

Permalink
Fix Wrong union description with TypeScript styleguidist#1621
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuruog committed Jul 23, 2020
1 parent 9b6b6f6 commit 1e6a574
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 90 deletions.
128 changes: 65 additions & 63 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -28,6 +28,7 @@
"node": ">=10"
},
"dependencies": {
"@tippyjs/react": "4.1.0",
"@vxna/mini-html-webpack-template": "^1.0.0",
"acorn": "^6.4.1",
"acorn-jsx": "^5.1.0",
Expand Down
20 changes: 20 additions & 0 deletions src/client/rsg-components/ComplexType/ComplexType.spec.tsx
@@ -0,0 +1,20 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import ComplexType from './ComplexTypeRenderder';

function renderComponent(name = 'color', raw = 'red | blue') {
return render(<ComplexType name={name} raw={raw} />);
}

describe('ComplexType', () => {
test('should render name', () => {
const { getByRole } = renderComponent();
expect(getByRole('button')).toHaveTextContent('color');
});

test('should render raw text in the tooltip', () => {
const { container, getByRole } = renderComponent();
fireEvent.focus(getByRole('button'));
expect(container.querySelector('[data-tippy-root]')).toHaveTextContent('red | blue');
});
});
36 changes: 36 additions & 0 deletions src/client/rsg-components/ComplexType/ComplexTypeRenderder.tsx
@@ -0,0 +1,36 @@
import React from 'react';
import Styled, { JssInjectedProps } from 'rsg-components/Styled';
import { MdInfoOutline } from 'react-icons/md';
import Text from 'rsg-components/Text';
import Tooltip from 'rsg-components/Tooltip';
import * as Rsg from '../../../typings';

export const styles = ({ space }: Rsg.Theme) => ({
complexType: {
alignItems: 'center',
cursor: 'pointer',
display: 'inline-flex',
'& span': {
marginRight: space[0],
cursor: 'pointer',
},
},
});

export interface ComplexTypeProps extends JssInjectedProps {
name: string;
raw: string;
}

function ComplexTypeRenderer({ classes, name, raw }: ComplexTypeProps) {
return (
<Tooltip placement="right" content={raw}>
<span className={classes.complexType}>
<Text>{name}</Text>
<MdInfoOutline />
</span>
</Tooltip>
);
}

export default Styled<ComplexTypeProps>(styles)(ComplexTypeRenderer);
1 change: 1 addition & 0 deletions src/client/rsg-components/ComplexType/index.ts
@@ -0,0 +1 @@
export { default } from 'rsg-components/ComplexType/ComplexTypeRenderder';
31 changes: 20 additions & 11 deletions src/client/rsg-components/Props/Props.spec.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { render } from '@testing-library/react';
import { render, fireEvent } from '@testing-library/react';
import { parse } from 'react-docgen';
import PropsRenderer, { columns, getRowKey } from './PropsRenderer';
import { unquote, getType, showSpaces, PropDescriptor } from './util';
Expand Down Expand Up @@ -513,7 +513,7 @@ describe('props columns', () => {
{
name: 'Foo',
description: 'Converts foo to bar',
type: {type: 'NameExpression', name: 'Array' },
type: { type: 'NameExpression', name: 'Array' },
},
],
param: [
Expand Down Expand Up @@ -551,7 +551,7 @@ describe('props columns', () => {
{
title: 'Foo',
description: 'Returns foo from bar',
type: {type: 'NameExpression', name: 'Array' },
type: { type: 'NameExpression', name: 'Array' },
},
],
},
Expand Down Expand Up @@ -658,21 +658,27 @@ describe('props columns', () => {
});

test('should render object type with body in tooltip', () => {
const { getByText } = renderFn(['foo: { bar: string }']);
const { container, getByRole } = renderFn(['foo: { bar: string }']);
fireEvent.focus(getByRole('button'));

expect(getByText('object').title).toMatchInlineSnapshot(`"{ bar: string }"`);
expect(getByRole('button')).toHaveTextContent('object');
expect(container.querySelector('[data-tippy-root]')).toHaveTextContent('{ bar: string }');
});

test('should render function type with body in tooltip', () => {
const { getByText } = renderFn(['foo: () => void']);
const { container, getByRole } = renderFn(['foo: () => void']);
fireEvent.focus(getByRole('button'));

expect(getByText('function').title).toMatchInlineSnapshot(`"() => void"`);
expect(getByRole('button')).toHaveTextContent('function');
expect(container.querySelector('[data-tippy-root]')).toHaveTextContent('() => void');
});

test('should render union type with body in tooltip', () => {
const { getByText } = renderFn(['foo: "bar" | number']);
const { container, getByRole } = renderFn(['foo: "bar" | number']);
fireEvent.focus(getByRole('button'));

expect(getByText('union').title).toMatchInlineSnapshot(`"\\"bar\\" | number"`);
expect(getByRole('button')).toHaveTextContent('union');
expect(container.querySelector('[data-tippy-root]')).toHaveTextContent('"bar" | number');
});

test('should render enum type', () => {
Expand All @@ -696,9 +702,12 @@ describe('props columns', () => {
});

test('should render tuple type with body in tooltip', () => {
const { getByText } = renderFn(['foo: ["bar", number]']);
const { container, getByRole } = renderFn(['foo: ["bar", number]']);

expect(getByText('tuple').title).toMatchInlineSnapshot(`"[\\"bar\\", number]"`);
fireEvent.focus(getByRole('button'));

expect(getByRole('button')).toHaveTextContent('tuple');
expect(container.querySelector('[data-tippy-root]')).toHaveTextContent('["bar", number]');
});

test('should render custom class type', () => {
Expand Down
24 changes: 8 additions & 16 deletions src/client/rsg-components/Props/renderType.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { PropTypeDescriptor } from 'react-docgen';
import Type from 'rsg-components/Type';
import Text from 'rsg-components/Text';
import ComplexType from 'rsg-components/ComplexType';

import { getType, PropDescriptor, TypeDescriptor } from './util';

Expand All @@ -28,31 +28,23 @@ export function renderType(type: ExtendedPropTypeDescriptor): string {
}
}

function renderComplexType(name: string, title: string): React.ReactNode {
return (
<Text size="small" underlined title={title}>
{name}
</Text>
);
}

function renderAdvancedType(type: TypeDescriptor): React.ReactNode {
if (!type) {
return 'unknown';
return <Type>unknown</Type>;
}

switch (type.name) {
case 'enum':
return type.name;
return <Type>{type.name}</Type>;
case 'literal':
return type.value;
return <Type>{type.value}</Type>;
case 'signature':
return renderComplexType(type.type, type.raw);
return <ComplexType name={type.type} raw={type.raw} />;
case 'union':
case 'tuple':
return renderComplexType(type.name, type.raw);
return <ComplexType name={type.name} raw={type.raw} />;
default:
return (type as any).raw || (type as any).name;
return <Type>{(type as any).raw || (type as any).name}</Type>;
}
}

Expand All @@ -62,7 +54,7 @@ export default function renderTypeColumn(prop: PropDescriptor): React.ReactNode
return null;
}
if (prop.flowType || prop.tsType) {
return <Type>{renderAdvancedType(type as any)}</Type>;
return renderAdvancedType(type as any);
}
return <Type>{renderType(type)}</Type>;
}
74 changes: 74 additions & 0 deletions src/client/rsg-components/Tooltip/Tooltip.spec.tsx
@@ -0,0 +1,74 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import Tooltip, { TooltipPlacement } from './TooltipRenderer';

function renderComponent(content = 'tooltip', placement?: TooltipPlacement) {
return render(
<Tooltip content={content} placement={placement}>
<div data-testid="child" />
</Tooltip>
);
}

describe('Tooltip', () => {
test('should render child component as is', () => {
const { container, getByTestId } = renderComponent();
expect(container).toContainElement(getByTestId('child'));
});

test('should the child component be wrapped by "role=button" element', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('child').closest('span')).toHaveAttribute('role', 'button');
});

test('should render content in the tooltop body', () => {
const { container, getByRole } = renderComponent();
fireEvent.focus(getByRole('button'));
expect(container.querySelector('[data-tippy-root]')).toHaveTextContent('tooltip');
});

test('should show the tooltip by focus in', async () => {
const { container, getByRole } = renderComponent();
fireEvent.focus(getByRole('button'));
await waitFor(() =>
expect(container.querySelector('[data-state="visible"]')).toBeInTheDocument()
);
expect(container.querySelector('[data-state]')).toHaveAttribute('data-state', 'visible');
});

test('should show the tooltip by click', async () => {
const { container, getByRole } = renderComponent();
fireEvent.click(getByRole('button'));
await waitFor(() =>
expect(container.querySelector('[data-state="visible"]')).toBeInTheDocument()
);
expect(container.querySelector('[data-state]')).toHaveAttribute('data-state', 'visible');
});

test('should show the tooltip by mouse enter', async () => {
const { container, getByRole } = renderComponent();
fireEvent.mouseEnter(getByRole('button'));
await waitFor(() =>
expect(container.querySelector('[data-state="visible"]')).toBeInTheDocument()
);
expect(container.querySelector('[data-state]')).toHaveAttribute('data-state', 'visible');
});

describe.each([['top'], ['right'], ['left'], ['bottom']])(
'Test placement attribute',
placement => {
test(`should have ${placement} in data-placement attribute`, async () => {
// @ts-ignore
const { container, getByRole } = renderComponent(undefined, placement);
fireEvent.focus(getByRole('button'));
await waitFor(() =>
expect(container.querySelector('[data-state="visible"]')).toBeInTheDocument()
);
expect(container.querySelector('[data-placement]')).toHaveAttribute(
'data-placement',
placement
);
});
}
);
});
51 changes: 51 additions & 0 deletions src/client/rsg-components/Tooltip/TooltipRenderer.tsx
@@ -0,0 +1,51 @@
import React from 'react';
import Tippy from '@tippyjs/react';
import Styled, { JssInjectedProps } from 'rsg-components/Styled';
import * as Rsg from '../../../typings';

export const styles = ({ space, color, borderRadius, fontSize }: Rsg.Theme) => ({
tooltip: {
'&.tippy-box': {
transitionProperty: [['opacity']],
'&[data-state="hidden"]': {
opacity: 0,
},
},
'& .tippy-content': {
padding: space[0],
border: `1px ${color.border} solid`,
borderRadius,
background: color.baseBackground,
boxShadow: [[0, 2, 4, 'rgba(0,0,0,.15)']],
fontSize: fontSize.small,
color: color.type,
},
},
});

export type TooltipPlacement = 'top' | 'right' | 'bottom' | 'left';

export interface TooltipProps extends JssInjectedProps {
children: React.ReactNode;
content: React.ReactNode;
placement?: TooltipPlacement;
}

function TooltipRenderer({ classes, children, content, placement = 'top' }: TooltipProps) {
return (
<Tippy
content={content}
className={classes.tooltip}
interactive
placement={placement}
trigger="click mouseenter focus"
arrow={false}
>
<span role="button" tabIndex={0}>
{children}
</span>
</Tippy>
);
}

export default Styled<TooltipProps>(styles)(TooltipRenderer);
1 change: 1 addition & 0 deletions src/client/rsg-components/Tooltip/index.ts
@@ -0,0 +1 @@
export { default } from 'rsg-components/Tooltip/TooltipRenderer';

0 comments on commit 1e6a574

Please sign in to comment.