From d2a8ee822a36271754aa1fd44477fafda846b931 Mon Sep 17 00:00:00 2001 From: Michel Engelen Date: Wed, 31 Mar 2021 12:24:43 +0200 Subject: [PATCH 1/5] added new parsing functionality for `ts.SyntaxKind.Identifier` with recursive call --- ...ctionalComponentWithDesctructuredProps.tsx | 38 +++++++++++++++++++ src/__tests__/parser.ts | 27 +++++++++++++ src/parser.ts | 19 ++++++++-- 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx new file mode 100644 index 00000000..e9a62ba0 --- /dev/null +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; + +const PROPERTY1_DEFAULT = 'hello'; +const PROPERTY2_DEFAULT = 10; +const PROPERTY3_DEFAULT = 'goodbye'; + +type Props = { + /** + * prop1 description + * @default 'hello' + */ + prop1?: 'hello' | 'world'; + /** + * prop2 description + * @default 10 + */ + prop2?: number; + /** + * prop3 description + * @default 'goodbye' + */ + prop3?: string; + /** + * prop4 description + * @default 100 + */ + prop4?: number; +}; + +/** FunctionalComponentWithDesctructuredProps description */ +const FunctionalComponentWithDesctructuredProps: React.FC = ({ + prop1 = PROPERTY1_DEFAULT, + prop2 = PROPERTY2_DEFAULT, + prop3 = PROPERTY3_DEFAULT, + prop4 = 100 +}) =>
; + +export default FunctionalComponentWithDesctructuredProps; diff --git a/src/__tests__/parser.ts b/src/__tests__/parser.ts index 6828ab31..d333d141 100644 --- a/src/__tests__/parser.ts +++ b/src/__tests__/parser.ts @@ -723,6 +723,33 @@ describe('parser', () => { }); }); + it('should parse functional component component defined as const with default value assignments in immediately destructured props', () => { + check('FunctionalComponentWithDesctructuredProps', { + FunctionalComponentWithDesctructuredProps: { + prop1: { + type: '"hello" | "world"', + required: false, + defaultValue: 'hello' + }, + prop2: { + type: 'number', + required: false, + defaultValue: 10 + }, + prop3: { + type: 'string', + required: false, + defaultValue: 'goodbye' + }, + prop4: { + type: 'number', + required: false, + defaultValue: 100 + } + } + }); + }); + it('should parse functional component component defined as const', () => { check('FunctionDeclarationVisibleName', { 'Awesome Jumbotron': { diff --git a/src/parser.ts b/src/parser.ts index 3c94af6b..930c1f01 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -943,10 +943,21 @@ export class Parser { case ts.SyntaxKind.NullKeyword: return this.savePropValueAsString ? 'null' : null; case ts.SyntaxKind.Identifier: - // can potentially find other identifiers in the source and map those in the future - return (initializer as ts.Identifier).text === 'undefined' - ? 'undefined' - : null; + if ((initializer as ts.Identifier).text === 'undefined') { + return 'undefined'; + } + + const symbol = this.checker.getSymbolAtLocation( + initializer as ts.Identifier + ); + + if (symbol && symbol.declarations && symbol.declarations.length) { + return this.getLiteralValueFromPropertyAssignment( + symbol.declarations[0] as ts.BindingElement + ); + } + + return null; case ts.SyntaxKind.PropertyAccessExpression: { const symbol = this.checker.getSymbolAtLocation( initializer as ts.PropertyAccessExpression From cb4f6bd659ef69c7d3fd9347eff97bedd1a98700 Mon Sep 17 00:00:00 2001 From: Michel Engelen Date: Wed, 31 Mar 2021 12:35:29 +0200 Subject: [PATCH 2/5] added 3 more types to test new identifier fix --- src/__tests__/parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/parser.ts b/src/__tests__/parser.ts index d333d141..bea6ab93 100644 --- a/src/__tests__/parser.ts +++ b/src/__tests__/parser.ts @@ -742,9 +742,9 @@ describe('parser', () => { defaultValue: 'goodbye' }, prop4: { - type: 'number', + type: 'boolean', required: false, - defaultValue: 100 + defaultValue: true } } }); From 9f94c7958570736975aaa6c7fb4bd6c8881c94df Mon Sep 17 00:00:00 2001 From: Michel Engelen Date: Wed, 31 Mar 2021 12:35:40 +0200 Subject: [PATCH 3/5] added 3 more types to test new identifier fix --- .../data/FunctionalComponentWithDesctructuredProps.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx index e9a62ba0..a56069c0 100644 --- a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; const PROPERTY1_DEFAULT = 'hello'; const PROPERTY2_DEFAULT = 10; const PROPERTY3_DEFAULT = 'goodbye'; +const PROPERTY4_DEFAULT = true; type Props = { /** @@ -22,9 +23,9 @@ type Props = { prop3?: string; /** * prop4 description - * @default 100 + * @default true */ - prop4?: number; + prop4?: boolean; }; /** FunctionalComponentWithDesctructuredProps description */ @@ -32,7 +33,7 @@ const FunctionalComponentWithDesctructuredProps: React.FC = ({ prop1 = PROPERTY1_DEFAULT, prop2 = PROPERTY2_DEFAULT, prop3 = PROPERTY3_DEFAULT, - prop4 = 100 + prop4 = PROPERTY4_DEFAULT }) =>
; export default FunctionalComponentWithDesctructuredProps; From 2b23b6aac3c8e98d9a99996f817d63d5aec5c177 Mon Sep 17 00:00:00 2001 From: Michel Engelen Date: Wed, 31 Mar 2021 16:32:05 +0200 Subject: [PATCH 4/5] added new tests and a method that extracts values from imported variables --- ...ctionalComponentWithDesctructuredProps.tsx | 46 +- ...DesctructuredPropsAndImportedConstants.tsx | 35 + ...WithDesctructuredPropsImportedConstants.ts | 13 + src/__tests__/parser.ts | 3136 +++++++++-------- src/parser.ts | 33 + 5 files changed, 1689 insertions(+), 1574 deletions(-) create mode 100644 src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx create mode 100644 src/__tests__/data/FunctionalComponentWithDesctructuredPropsImportedConstants.ts diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx index a56069c0..8d247d92 100644 --- a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx @@ -1,31 +1,26 @@ import * as React from 'react'; -const PROPERTY1_DEFAULT = 'hello'; -const PROPERTY2_DEFAULT = 10; -const PROPERTY3_DEFAULT = 'goodbye'; -const PROPERTY4_DEFAULT = true; +import { + PROPERTY1_DEFAULT, + PROPERTY2_DEFAULT, + PROPERTY3_DEFAULT, + PROPERTY4_DEFAULT, + PROPERTY5_DEFAULT +} from './FunctionalComponentWithDesctructuredPropsImportedConstants'; + +type Property1Type = 'hello' | 'world'; type Props = { - /** - * prop1 description - * @default 'hello' - */ - prop1?: 'hello' | 'world'; - /** - * prop2 description - * @default 10 - */ - prop2?: number; - /** - * prop3 description - * @default 'goodbye' - */ - prop3?: string; - /** - * prop4 description - * @default true - */ - prop4?: boolean; + /** prop1 description */ + prop1?: Property1Type; + /** prop2 description */ + prop2?: 'goodbye' | 'farewell'; + /** prop3 description */ + prop3?: number; + /** prop4 description */ + prop4?: string; + /** prop5 description */ + prop5?: boolean; }; /** FunctionalComponentWithDesctructuredProps description */ @@ -33,7 +28,8 @@ const FunctionalComponentWithDesctructuredProps: React.FC = ({ prop1 = PROPERTY1_DEFAULT, prop2 = PROPERTY2_DEFAULT, prop3 = PROPERTY3_DEFAULT, - prop4 = PROPERTY4_DEFAULT + prop4 = PROPERTY4_DEFAULT, + prop5 = PROPERTY5_DEFAULT }) =>
; export default FunctionalComponentWithDesctructuredProps; diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx new file mode 100644 index 00000000..3e7c1cd0 --- /dev/null +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; + +import { + PROPERTY1_DEFAULT, + PROPERTY2_DEFAULT, + PROPERTY3_DEFAULT, + PROPERTY4_DEFAULT, + PROPERTY5_DEFAULT +} from './FunctionalComponentWithDesctructuredPropsImportedConstants'; + +type Property1Type = 'hello' | 'world'; + +type Props = { + /** prop1 description */ + prop1?: Property1Type; + /** prop2 description */ + prop2?: 'goodbye' | 'farewell'; + /** prop3 description */ + prop3?: number; + /** prop4 description */ + prop4?: string; + /** prop5 description */ + prop5?: boolean; +}; + +/** FunctionalComponentWithDesctructuredPropsImportedConstants description */ +const FunctionalComponentWithDesctructuredPropsImportedConstants: React.FC = ({ + prop1 = PROPERTY1_DEFAULT, + prop2 = PROPERTY2_DEFAULT, + prop3 = PROPERTY3_DEFAULT, + prop4 = PROPERTY4_DEFAULT, + prop5 = PROPERTY5_DEFAULT +}) =>
; + +export default FunctionalComponentWithDesctructuredPropsImportedConstants; diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredPropsImportedConstants.ts b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsImportedConstants.ts new file mode 100644 index 00000000..21ab24c7 --- /dev/null +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsImportedConstants.ts @@ -0,0 +1,13 @@ +const PROPERTY1_DEFAULT = 'hello'; +const PROPERTY2_DEFAULT = 'goodbye'; +const PROPERTY3_DEFAULT = 10; +const PROPERTY4_DEFAULT = 'this is a string'; +const PROPERTY5_DEFAULT = true; + +export { + PROPERTY1_DEFAULT, + PROPERTY2_DEFAULT, + PROPERTY3_DEFAULT, + PROPERTY4_DEFAULT, + PROPERTY5_DEFAULT +}; diff --git a/src/__tests__/parser.ts b/src/__tests__/parser.ts index bea6ab93..daf4d8a5 100644 --- a/src/__tests__/parser.ts +++ b/src/__tests__/parser.ts @@ -10,738 +10,776 @@ import { withDefaultConfig } from '../parser'; import { check, checkComponent, fixturePath } from './testUtils'; +import FunctionalComponentWithDesctructuredPropsImportedConstants from './data/FunctionalComponentWithDesctructuredPropsAndImportedConstants'; describe('parser', () => { - it('should parse simple react class component', () => { - check('Column', { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }); - }); - - it('should parse simple react class component with console.log inside', () => { - check('ColumnWithLog', { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }); - }); - - it('should parse simple react class component as default export', () => { - check('ColumnWithDefaultExport', { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }); - }); - - it('should parse mulitple files', () => { - const result = parse([ - fixturePath('Column'), - fixturePath('ColumnWithDefaultExportOnly') - ]); - - checkComponent( - result, - { - Column: {}, - ColumnWithDefaultExportOnly: {} - }, - false - ); - }); - - it('should parse simple react class component as default export only', () => { - check('ColumnWithDefaultExportOnly', { - ColumnWithDefaultExportOnly: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }); - }); - - it('should parse simple react class component as default anonymous export', () => { - check('ColumnWithDefaultAnonymousExportOnly', { - ColumnWithDefaultAnonymousExportOnly: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }); - }); - - it('should parse simple react class component with state', () => { - check('AppMenu', { - AppMenu: { - menu: { type: 'any' } - } - }); - }); - - it('should parse simple react class component with picked properties', () => { - check('ColumnWithPick', { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - propx: { type: 'number' } - } - }); - }); - - it('should parse component with props with external type', () => { - check('ColumnWithPropsWithExternalType', { - ColumnWithPropsWithExternalType: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: 'MyExternalType' } - } - }); - }); - - it('should parse HOCs', () => { - check('ColumnHigherOrderComponent', { - ColumnExternalHigherOrderComponent: { - prop1: { type: 'string' } - }, - ColumnHigherOrderComponent1: { - prop1: { type: 'string' } - }, - ColumnHigherOrderComponent2: { - prop1: { type: 'string' } - }, - RowExternalHigherOrderComponent: { - prop1: { type: 'string' } - }, - RowHigherOrderComponent1: { - prop1: { type: 'string' } - }, - RowHigherOrderComponent2: { - prop1: { type: 'string' } - } - }); - }); - - it('should parse component with inherited properties HtmlAttributes', () => { - check( - 'ColumnWithHtmlAttributes', - { - Column: { - // tslint:disable:object-literal-sort-keys - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - // HtmlAttributes - defaultChecked: { - type: 'boolean', - required: false, - description: '' - } - // ... - // tslint:enable:object-literal-sort-keys - } - }, - false - ); - }); - - it('should parse component without exported props interface', () => { - check('ColumnWithoutExportedProps', { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' } - } - }); - }); - - it('should parse functional component exported as const', () => { - check( - 'ConstExport', - { - Row: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' } - }, - // TODO: this wasn't there before, i would guess that that's correct - test: {} - }, - false - ); - }); - - it('should parse react component with properties defined in external file', () => { - check('ExternalPropsComponent', { - ExternalPropsComponent: { - prop1: { type: 'string' } - } - }); - }); - - it('should parse static sub components', () => { - check('StatelessStaticComponents', { - StatelessStaticComponents: { - myProp: { type: 'string' } - }, - 'StatelessStaticComponents.Label': { - title: { type: 'string' } - } - }); - }); - - it('should parse static sub components on class components', () => { - check('ColumnWithStaticComponents', { - Column: { - prop1: { type: 'string' } - }, - 'Column.Label': { - title: { type: 'string' } - }, - 'Column.SubLabel': {} - }); - }); - - it('should parse react component with properties extended from an external .tsx file', () => { - check('ExtendsExternalPropsComponent', { - ExtendsExternalPropsComponent: { - prop1: { type: 'number', required: false, description: 'prop1' }, - prop2: { type: 'string', required: false, description: 'prop2' } - } - }); - }); - - it('should parse react component with properties defined as type', () => { - check( - 'FlippableImage', - { - FlippableImage: { - isFlippedX: { type: 'boolean', required: false }, - isFlippedY: { type: 'boolean', required: false } - } - }, - false - ); - }); - - it('should parse react component with const definitions', () => { - check('InlineConst', { - MyComponent: { - foo: { type: 'any' } - } - }); - }); - - it('should parse default interface export', () => { - check('ExportsDefaultInterface', { - Component: { - foo: { type: 'any' } - } - }); - }); - - it('should parse react component that exports a prop type const', () => { - check('ExportsPropTypeShape', { - ExportsPropTypes: { - foo: { type: 'any' } - } - }); - }); - - it('should parse react component that exports a prop type thats imported', () => { - check('ExportsPropTypeImport', { - ExportsPropTypes: { - foo: { type: 'any' } - } - }); - }); - - // see issue #132 (https://github.com/styleguidist/react-docgen-typescript/issues/132) - it('should determine the parent fileName relative to the project directory', () => { - check( - 'ExportsPropTypeImport', - { - ExportsPropTypes: { - foo: { - parent: { - fileName: - 'react-docgen-typescript/src/__tests__/data/ExportsPropTypeImport.tsx', - name: 'ExportsPropTypesProps' - }, - type: 'any' - } as any - } - }, - true - ); - }); - - describe('component with default props', () => { - const expectation = { - ComponentWithDefaultProps: { - sampleDefaultFromJSDoc: { - defaultValue: 'hello', - description: 'sample with default value', - required: true, - type: '"hello" | "goodbye"' - }, - sampleFalse: { - defaultValue: false, - required: false, - type: 'boolean' - }, - sampleNull: { type: 'null', required: false, defaultValue: null }, - sampleNumber: { type: 'number', required: false, defaultValue: -1 }, - sampleObject: { - defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, - required: false, - type: '{ [key: string]: any; }' - }, - sampleString: { - defaultValue: 'hello', - required: false, - type: 'string' - }, - sampleTrue: { type: 'boolean', required: false, defaultValue: true }, - sampleUndefined: { - defaultValue: 'undefined', - required: false, - type: 'any' - } - } - }; - - it('should parse defined props', () => { - check('ComponentWithDefaultProps', expectation); - }); - - it('should parse referenced props', () => { - check('ComponentWithReferencedDefaultProps', expectation); - }); - }); - - describe('component with @type jsdoc tag', () => { - const expectation = { - ComponentWithTypeJsDocTag: { - sampleTypeFromJSDoc: { - description: 'sample with custom type', - required: true, - type: 'string' - } - } - }; - - it('should parse defined props', () => { - check('ComponentWithTypeJsDocTag', expectation); - }); - }); - - it('should parse react PureComponent', () => { - check('PureRow', { - Row: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' } - } - }); - }); - - it('should parse react PureComponent - regression test', () => { - check( - 'Regression_v0_0_12', - { - Zoomable: { - originX: { type: 'number' }, - originY: { type: 'number' }, - scaleFactor: { type: 'number' } - } - }, - false - ); - }); - - it('should parse react functional component', () => { - check('Row', { - Row: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' } - } - }); - }); - - it('should parse react stateless component', () => { - check('Stateless', { - Stateless: { - myProp: { type: 'string' } - } - }); - }); - - it('should get name for default export', () => { - check( - 'ForwardRefDefaultExport', - { - ForwardRefDefaultExport: { - myProp: { type: 'string' } - } - }, - false - ); - }); - - it('should get name for default export 2', () => { - check( - 'ForwardRefDefaultExportAtExport', - { - ForwardRefDefaultExport: { - myProp: { type: 'string' } - } - }, - false - ); - }); - - it('should component where last line is a comment', () => { - check('ExportObject', { - Baz: { - baz: { description: '', type: 'string' } - }, - Bar: { - foo: { description: '', type: 'string' } - }, - FooBar: { - foobar: { description: '', type: 'string' } - } - }); - }); - - it('should parse react stateless component with intersection props', () => { - check('StatelessIntersectionProps', { - StatelessIntersectionProps: { - moreProp: { type: 'number' }, - myProp: { type: 'string' } - } - }); - }); - - it('should parse react stateless component default props when declared as a normal function', () => { - check('FunctionDeclarationDefaultProps', { - FunctionDeclarationDefaultProps: { - id: { - defaultValue: 1, - description: '', - required: false, - type: 'number' - } - } - }); - }); - - it('should parse react stateless component default props when declared as a normal function inside forwardRef', () => { - check( - 'ForwardRefDefaultValues', - { - ForwardRefDefaultValues: { - myProp: { - defaultValue: "I'm default", - description: 'myProp description', - type: 'string', - required: false - } - } - }, - false, - 'ForwardRefDefaultValues description' - ); - }); - - it('should parse react stateless component with external intersection props', () => { - check('StatelessIntersectionExternalProps', { - StatelessIntersectionExternalProps: { - myProp: { type: 'string' }, - prop1: { type: 'string', required: false } - } - }); - }); - - it('should parse react stateless component with generic intersection props', () => { - check('StatelessIntersectionGenericProps', { - StatelessIntersectionGenericProps: { - myProp: { type: 'string' } - } - }); - }); - - it('should parse react stateless component with intersection + union props', () => { - check('SimpleUnionIntersection', { - SimpleUnionIntersection: { - bar: { type: 'string', description: '' }, - baz: { type: 'string', description: '' }, - foo: { type: 'string', description: '' } - } - }); - }); - - it('should parse react stateless component with intersection + union overlap props', () => { - check('SimpleDiscriminatedUnionIntersection', { - SimpleDiscriminatedUnionIntersection: { - bar: { type: '"one" | "other"', description: '' }, - baz: { type: 'number', description: '' }, - foo: { type: 'string', description: '' }, - test: { type: 'number', description: '' } - } - }); - }); - - it('should parse react stateless component with generic intersection + union overlap props - simple', () => { - check('SimpleGenericUnionIntersection', { - SimpleGenericUnionIntersection: { - as: { type: 'any', description: '' }, - foo: { - description: - 'The foo prop should not repeat the description\nThe foo prop should not repeat the description', - required: false, - type: '"red" | "blue"' - }, - gap: { - description: - 'The space between children\nYou cannot use gap when using a "space" justify property', - required: false, - type: 'number' - }, - hasWrap: { type: 'boolean', description: '', required: false } - } - }); - }); - - it('should parse react stateless component with generic intersection + union overlap props', () => { - check('ComplexGenericUnionIntersection', { - ComplexGenericUnionIntersection: { - as: { - type: 'ElementType', - required: false, - description: 'Render the component as another component' - }, - align: { - description: 'The flex "align" property', - required: false, - type: '"stretch" | "center" | "flex-start" | "flex-end"' - }, - justify: { - description: - "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", - required: false, - type: - '"stretch" | "center" | "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly"' - }, - gap: { - description: - 'The space between children\nYou cannot use gap when using a "space" justify property', - required: false, - type: 'ReactText' - } - } - }); - }); - - it('should parse react stateless component with generic intersection + union + omit overlap props', () => { - check('ComplexGenericUnionIntersectionWithOmit', { - ComplexGenericUnionIntersectionWithOmit: { - as: { - type: 'ElementType', - required: false, - description: 'Render the component as another component' - }, - align: { - description: 'The flex "align" property', - required: false, - type: '"center" | "flex-start" | "flex-end" | "stretch"' - }, - justify: { - description: - "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", - required: false, - type: - '"center" | "flex-start" | "flex-end" | "stretch" | "space-between" | "space-around" | "space-evenly"' - }, - gap: { - description: - 'The space between children\nYou cannot use gap when using a "space" justify property', - required: false, - type: 'ReactText' - } - } - }); - }); - - it('should parse react stateful component with intersection props', () => { - check('StatefulIntersectionProps', { - StatefulIntersectionProps: { - moreProp: { type: 'number' }, - myProp: { type: 'string' } - } - }); - }); - - it('should parse react stateful component with external intersection props', () => { - check('StatefulIntersectionExternalProps', { - StatefulIntersectionExternalProps: { - myProp: { type: 'string' }, - prop1: { type: 'string', required: false } - } - }); - }); - - it('should parse react stateful component (wrapped in HOC) with intersection props', () => { - check('HOCIntersectionProps', { - HOCIntersectionProps: { - injected: { type: 'boolean' }, - myProp: { type: 'string' } - } - }); - }); - - describe('stateless component with default props', () => { - const expectation = { - StatelessWithDefaultProps: { - sampleDefaultFromJSDoc: { - defaultValue: 'hello', - description: 'sample with default value', - required: true, - type: '"hello" | "goodbye"' - }, - sampleEnum: { - defaultValue: 'enumSample.HELLO', + // it('should parse simple react class component', () => { + // check('Column', { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }); + // }); + // + // it('should parse simple react class component with console.log inside', () => { + // check('ColumnWithLog', { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }); + // }); + // + // it('should parse simple react class component as default export', () => { + // check('ColumnWithDefaultExport', { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }); + // }); + // + // it('should parse mulitple files', () => { + // const result = parse([ + // fixturePath('Column'), + // fixturePath('ColumnWithDefaultExportOnly') + // ]); + // + // checkComponent( + // result, + // { + // Column: {}, + // ColumnWithDefaultExportOnly: {} + // }, + // false + // ); + // }); + // + // it('should parse simple react class component as default export only', () => { + // check('ColumnWithDefaultExportOnly', { + // ColumnWithDefaultExportOnly: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }); + // }); + // + // it('should parse simple react class component as default anonymous export', () => { + // check('ColumnWithDefaultAnonymousExportOnly', { + // ColumnWithDefaultAnonymousExportOnly: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }); + // }); + // + // it('should parse simple react class component with state', () => { + // check('AppMenu', { + // AppMenu: { + // menu: { type: 'any' } + // } + // }); + // }); + // + // it('should parse simple react class component with picked properties', () => { + // check('ColumnWithPick', { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // propx: { type: 'number' } + // } + // }); + // }); + // + // it('should parse component with props with external type', () => { + // check('ColumnWithPropsWithExternalType', { + // ColumnWithPropsWithExternalType: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: 'MyExternalType' } + // } + // }); + // }); + // + // it('should parse HOCs', () => { + // check('ColumnHigherOrderComponent', { + // ColumnExternalHigherOrderComponent: { + // prop1: { type: 'string' } + // }, + // ColumnHigherOrderComponent1: { + // prop1: { type: 'string' } + // }, + // ColumnHigherOrderComponent2: { + // prop1: { type: 'string' } + // }, + // RowExternalHigherOrderComponent: { + // prop1: { type: 'string' } + // }, + // RowHigherOrderComponent1: { + // prop1: { type: 'string' } + // }, + // RowHigherOrderComponent2: { + // prop1: { type: 'string' } + // } + // }); + // }); + // + // it('should parse component with inherited properties HtmlAttributes', () => { + // check( + // 'ColumnWithHtmlAttributes', + // { + // Column: { + // // tslint:disable:object-literal-sort-keys + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // // HtmlAttributes + // defaultChecked: { + // type: 'boolean', + // required: false, + // description: '' + // } + // // ... + // // tslint:enable:object-literal-sort-keys + // } + // }, + // false + // ); + // }); + // + // it('should parse component without exported props interface', () => { + // check('ColumnWithoutExportedProps', { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' } + // } + // }); + // }); + // + // it('should parse functional component exported as const', () => { + // check( + // 'ConstExport', + // { + // Row: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' } + // }, + // // TODO: this wasn't there before, i would guess that that's correct + // test: {} + // }, + // false + // ); + // }); + // + // it('should parse react component with properties defined in external file', () => { + // check('ExternalPropsComponent', { + // ExternalPropsComponent: { + // prop1: { type: 'string' } + // } + // }); + // }); + // + // it('should parse static sub components', () => { + // check('StatelessStaticComponents', { + // StatelessStaticComponents: { + // myProp: { type: 'string' } + // }, + // 'StatelessStaticComponents.Label': { + // title: { type: 'string' } + // } + // }); + // }); + // + // it('should parse static sub components on class components', () => { + // check('ColumnWithStaticComponents', { + // Column: { + // prop1: { type: 'string' } + // }, + // 'Column.Label': { + // title: { type: 'string' } + // }, + // 'Column.SubLabel': {} + // }); + // }); + // + // it('should parse react component with properties extended from an external .tsx file', () => { + // check('ExtendsExternalPropsComponent', { + // ExtendsExternalPropsComponent: { + // prop1: { type: 'number', required: false, description: 'prop1' }, + // prop2: { type: 'string', required: false, description: 'prop2' } + // } + // }); + // }); + // + // it('should parse react component with properties defined as type', () => { + // check( + // 'FlippableImage', + // { + // FlippableImage: { + // isFlippedX: { type: 'boolean', required: false }, + // isFlippedY: { type: 'boolean', required: false } + // } + // }, + // false + // ); + // }); + // + // it('should parse react component with const definitions', () => { + // check('InlineConst', { + // MyComponent: { + // foo: { type: 'any' } + // } + // }); + // }); + // + // it('should parse default interface export', () => { + // check('ExportsDefaultInterface', { + // Component: { + // foo: { type: 'any' } + // } + // }); + // }); + // + // it('should parse react component that exports a prop type const', () => { + // check('ExportsPropTypeShape', { + // ExportsPropTypes: { + // foo: { type: 'any' } + // } + // }); + // }); + // + // it('should parse react component that exports a prop type thats imported', () => { + // check('ExportsPropTypeImport', { + // ExportsPropTypes: { + // foo: { type: 'any' } + // } + // }); + // }); + // + // // see issue #132 (https://github.com/styleguidist/react-docgen-typescript/issues/132) + // it('should determine the parent fileName relative to the project directory', () => { + // check( + // 'ExportsPropTypeImport', + // { + // ExportsPropTypes: { + // foo: { + // parent: { + // fileName: + // 'react-docgen-typescript/src/__tests__/data/ExportsPropTypeImport.tsx', + // name: 'ExportsPropTypesProps' + // }, + // type: 'any' + // } as any + // } + // }, + // true + // ); + // }); + // + // describe('component with default props', () => { + // const expectation = { + // ComponentWithDefaultProps: { + // sampleDefaultFromJSDoc: { + // defaultValue: 'hello', + // description: 'sample with default value', + // required: true, + // type: '"hello" | "goodbye"' + // }, + // sampleFalse: { + // defaultValue: false, + // required: false, + // type: 'boolean' + // }, + // sampleNull: { type: 'null', required: false, defaultValue: null }, + // sampleNumber: { type: 'number', required: false, defaultValue: -1 }, + // sampleObject: { + // defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, + // required: false, + // type: '{ [key: string]: any; }' + // }, + // sampleString: { + // defaultValue: 'hello', + // required: false, + // type: 'string' + // }, + // sampleTrue: { type: 'boolean', required: false, defaultValue: true }, + // sampleUndefined: { + // defaultValue: 'undefined', + // required: false, + // type: 'any' + // } + // } + // }; + // + // it('should parse defined props', () => { + // check('ComponentWithDefaultProps', expectation); + // }); + // + // it('should parse referenced props', () => { + // check('ComponentWithReferencedDefaultProps', expectation); + // }); + // }); + // + // describe('component with @type jsdoc tag', () => { + // const expectation = { + // ComponentWithTypeJsDocTag: { + // sampleTypeFromJSDoc: { + // description: 'sample with custom type', + // required: true, + // type: 'string' + // } + // } + // }; + // + // it('should parse defined props', () => { + // check('ComponentWithTypeJsDocTag', expectation); + // }); + // }); + // + // it('should parse react PureComponent', () => { + // check('PureRow', { + // Row: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' } + // } + // }); + // }); + // + // it('should parse react PureComponent - regression test', () => { + // check( + // 'Regression_v0_0_12', + // { + // Zoomable: { + // originX: { type: 'number' }, + // originY: { type: 'number' }, + // scaleFactor: { type: 'number' } + // } + // }, + // false + // ); + // }); + // + // it('should parse react functional component', () => { + // check('Row', { + // Row: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' } + // } + // }); + // }); + // + // it('should parse react stateless component', () => { + // check('Stateless', { + // Stateless: { + // myProp: { type: 'string' } + // } + // }); + // }); + // + // it('should get name for default export', () => { + // check( + // 'ForwardRefDefaultExport', + // { + // ForwardRefDefaultExport: { + // myProp: { type: 'string' } + // } + // }, + // false + // ); + // }); + // + // it('should get name for default export 2', () => { + // check( + // 'ForwardRefDefaultExportAtExport', + // { + // ForwardRefDefaultExport: { + // myProp: { type: 'string' } + // } + // }, + // false + // ); + // }); + // + // it('should component where last line is a comment', () => { + // check('ExportObject', { + // Baz: { + // baz: { description: '', type: 'string' } + // }, + // Bar: { + // foo: { description: '', type: 'string' } + // }, + // FooBar: { + // foobar: { description: '', type: 'string' } + // } + // }); + // }); + // + // it('should parse react stateless component with intersection props', () => { + // check('StatelessIntersectionProps', { + // StatelessIntersectionProps: { + // moreProp: { type: 'number' }, + // myProp: { type: 'string' } + // } + // }); + // }); + // + // it('should parse react stateless component default props when declared as a normal function', () => { + // check('FunctionDeclarationDefaultProps', { + // FunctionDeclarationDefaultProps: { + // id: { + // defaultValue: 1, + // description: '', + // required: false, + // type: 'number' + // } + // } + // }); + // }); + // + // it('should parse react stateless component default props when declared as a normal function inside forwardRef', () => { + // check( + // 'ForwardRefDefaultValues', + // { + // ForwardRefDefaultValues: { + // myProp: { + // defaultValue: "I'm default", + // description: 'myProp description', + // type: 'string', + // required: false + // } + // } + // }, + // false, + // 'ForwardRefDefaultValues description' + // ); + // }); + // + // it('should parse react stateless component with external intersection props', () => { + // check('StatelessIntersectionExternalProps', { + // StatelessIntersectionExternalProps: { + // myProp: { type: 'string' }, + // prop1: { type: 'string', required: false } + // } + // }); + // }); + // + // it('should parse react stateless component with generic intersection props', () => { + // check('StatelessIntersectionGenericProps', { + // StatelessIntersectionGenericProps: { + // myProp: { type: 'string' } + // } + // }); + // }); + // + // it('should parse react stateless component with intersection + union props', () => { + // check('SimpleUnionIntersection', { + // SimpleUnionIntersection: { + // bar: { type: 'string', description: '' }, + // baz: { type: 'string', description: '' }, + // foo: { type: 'string', description: '' } + // } + // }); + // }); + // + // it('should parse react stateless component with intersection + union overlap props', () => { + // check('SimpleDiscriminatedUnionIntersection', { + // SimpleDiscriminatedUnionIntersection: { + // bar: { type: '"one" | "other"', description: '' }, + // baz: { type: 'number', description: '' }, + // foo: { type: 'string', description: '' }, + // test: { type: 'number', description: '' } + // } + // }); + // }); + // + // it('should parse react stateless component with generic intersection + union overlap props - simple', () => { + // check('SimpleGenericUnionIntersection', { + // SimpleGenericUnionIntersection: { + // as: { type: 'any', description: '' }, + // foo: { + // description: + // 'The foo prop should not repeat the description\nThe foo prop should not repeat the description', + // required: false, + // type: '"red" | "blue"' + // }, + // gap: { + // description: + // 'The space between children\nYou cannot use gap when using a "space" justify property', + // required: false, + // type: 'number' + // }, + // hasWrap: { type: 'boolean', description: '', required: false } + // } + // }); + // }); + // + // it('should parse react stateless component with generic intersection + union overlap props', () => { + // check('ComplexGenericUnionIntersection', { + // ComplexGenericUnionIntersection: { + // as: { + // type: 'ElementType', + // required: false, + // description: 'Render the component as another component' + // }, + // align: { + // description: 'The flex "align" property', + // required: false, + // type: '"stretch" | "center" | "flex-start" | "flex-end"' + // }, + // justify: { + // description: + // "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", + // required: false, + // type: + // '"stretch" | "center" | "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly"' + // }, + // gap: { + // description: + // 'The space between children\nYou cannot use gap when using a "space" justify property', + // required: false, + // type: 'ReactText' + // } + // } + // }); + // }); + // + // it('should parse react stateless component with generic intersection + union + omit overlap props', () => { + // check('ComplexGenericUnionIntersectionWithOmit', { + // ComplexGenericUnionIntersectionWithOmit: { + // as: { + // type: 'ElementType', + // required: false, + // description: 'Render the component as another component' + // }, + // align: { + // description: 'The flex "align" property', + // required: false, + // type: '"center" | "flex-start" | "flex-end" | "stretch"' + // }, + // justify: { + // description: + // "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", + // required: false, + // type: + // '"center" | "flex-start" | "flex-end" | "stretch" | "space-between" | "space-around" | "space-evenly"' + // }, + // gap: { + // description: + // 'The space between children\nYou cannot use gap when using a "space" justify property', + // required: false, + // type: 'ReactText' + // } + // } + // }); + // }); + // + // it('should parse react stateful component with intersection props', () => { + // check('StatefulIntersectionProps', { + // StatefulIntersectionProps: { + // moreProp: { type: 'number' }, + // myProp: { type: 'string' } + // } + // }); + // }); + // + // it('should parse react stateful component with external intersection props', () => { + // check('StatefulIntersectionExternalProps', { + // StatefulIntersectionExternalProps: { + // myProp: { type: 'string' }, + // prop1: { type: 'string', required: false } + // } + // }); + // }); + // + // it('should parse react stateful component (wrapped in HOC) with intersection props', () => { + // check('HOCIntersectionProps', { + // HOCIntersectionProps: { + // injected: { type: 'boolean' }, + // myProp: { type: 'string' } + // } + // }); + // }); + // + // describe('stateless component with default props', () => { + // const expectation = { + // StatelessWithDefaultProps: { + // sampleDefaultFromJSDoc: { + // defaultValue: 'hello', + // description: 'sample with default value', + // required: true, + // type: '"hello" | "goodbye"' + // }, + // sampleEnum: { + // defaultValue: 'enumSample.HELLO', + // required: false, + // type: 'enumSample' + // }, + // sampleFalse: { + // defaultValue: false, + // required: false, + // type: 'boolean' + // }, + // sampleNull: { type: 'null', required: false, defaultValue: null }, + // sampleNumber: { type: 'number', required: false, defaultValue: -1 }, + // sampleObject: { + // defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, + // required: false, + // type: '{ [key: string]: any; }' + // }, + // sampleString: { + // defaultValue: 'hello', + // required: false, + // type: 'string' + // }, + // sampleTrue: { type: 'boolean', required: false, defaultValue: true }, + // sampleUndefined: { + // defaultValue: undefined, + // required: false, + // type: 'any' + // } + // } + // }; + // + // it('should parse defined props', () => { + // check('StatelessWithDefaultProps', expectation); + // }); + // + // it('should parse props with shorthands', () => { + // check('StatelessShorthandDefaultProps', { + // StatelessShorthandDefaultProps: { + // onCallback: { + // defaultValue: null, + // description: 'onCallback description', + // required: false, + // type: '() => void' + // }, + // regularProp: { + // defaultValue: 'foo', + // description: 'regularProp description', + // required: false, + // type: 'string' + // }, + // shorthandProp: { + // defaultValue: 123, + // description: 'shorthandProp description', + // required: false, + // type: 'number' + // } + // } + // }); + // }); + // + // it('supports destructuring', () => { + // check('StatelessWithDestructuredProps', expectation); + // }); + // + // it('supports destructuring for arrow functions', () => { + // check('StatelessWithDestructuredPropsArrow', expectation); + // }); + // + // it('supports typescript 3.0 style defaulted props', () => { + // check('StatelessWithDefaultPropsTypescript3', expectation); + // }); + // }); + // + // it('should parse components with unioned types', () => { + // check('OnlyDefaultExportUnion', { + // OnlyDefaultExportUnion: { + // content: { description: 'The content', type: 'string' } + // } + // }); + // }); + // + // it('should parse jsdocs with the @default tag and no description', () => { + // check('StatelessWithDefaultOnlyJsDoc', { + // StatelessWithDefaultOnlyJsDoc: { + // myProp: { defaultValue: 'hello', description: '', type: 'string' } + // } + // }); + // }); + // + // it('should parse functional component component defined as function', () => { + // check('FunctionDeclaration', { + // Jumbotron: { + // prop1: { type: 'string', required: true } + // } + // }); + // }); + // + // it('should parse functional component component defined as const', () => { + // check('FunctionalComponentAsConst', { + // Jumbotron: { + // prop1: { type: 'string', required: true } + // } + // }); + // }); + + it('should parse functional component defined as const with default value assignments in immediately destructured props', () => { + check('FunctionalComponentWithDesctructuredProps', { + FunctionalComponentWithDesctructuredProps: { + prop1: { + type: 'Property1Type', required: false, - type: 'enumSample' + defaultValue: 'hello' }, - sampleFalse: { - defaultValue: false, + prop2: { + type: '"goodbye" | "farewell"', required: false, - type: 'boolean' + defaultValue: 'goodbye' }, - sampleNull: { type: 'null', required: false, defaultValue: null }, - sampleNumber: { type: 'number', required: false, defaultValue: -1 }, - sampleObject: { - defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, + prop3: { + type: 'number', required: false, - type: '{ [key: string]: any; }' + defaultValue: 10 }, - sampleString: { - defaultValue: 'hello', + prop4: { + type: 'string', required: false, - type: 'string' + defaultValue: 'this is a string' }, - sampleTrue: { type: 'boolean', required: false, defaultValue: true }, - sampleUndefined: { - defaultValue: undefined, + prop5: { + type: 'boolean', required: false, - type: 'any' - } - } - }; - - it('should parse defined props', () => { - check('StatelessWithDefaultProps', expectation); - }); - - it('should parse props with shorthands', () => { - check('StatelessShorthandDefaultProps', { - StatelessShorthandDefaultProps: { - onCallback: { - defaultValue: null, - description: 'onCallback description', - required: false, - type: '() => void' - }, - regularProp: { - defaultValue: 'foo', - description: 'regularProp description', - required: false, - type: 'string' - }, - shorthandProp: { - defaultValue: 123, - description: 'shorthandProp description', - required: false, - type: 'number' - } + defaultValue: true } - }); - }); - - it('supports destructuring', () => { - check('StatelessWithDestructuredProps', expectation); - }); - - it('supports destructuring for arrow functions', () => { - check('StatelessWithDestructuredPropsArrow', expectation); - }); - - it('supports typescript 3.0 style defaulted props', () => { - check('StatelessWithDefaultPropsTypescript3', expectation); - }); - }); - - it('should parse components with unioned types', () => { - check('OnlyDefaultExportUnion', { - OnlyDefaultExportUnion: { - content: { description: 'The content', type: 'string' } - } - }); - }); - - it('should parse jsdocs with the @default tag and no description', () => { - check('StatelessWithDefaultOnlyJsDoc', { - StatelessWithDefaultOnlyJsDoc: { - myProp: { defaultValue: 'hello', description: '', type: 'string' } - } - }); - }); - - it('should parse functional component component defined as function', () => { - check('FunctionDeclaration', { - Jumbotron: { - prop1: { type: 'string', required: true } - } - }); - }); - - it('should parse functional component component defined as const', () => { - check('FunctionalComponentAsConst', { - Jumbotron: { - prop1: { type: 'string', required: true } } }); }); - it('should parse functional component component defined as const with default value assignments in immediately destructured props', () => { - check('FunctionalComponentWithDesctructuredProps', { - FunctionalComponentWithDesctructuredProps: { + it('should parse functional component defined as const with default value (imported from a separate file) assignments in immediately destructured props', () => { + check('FunctionalComponentWithDesctructuredPropsAndImportedConstants', { + FunctionalComponentWithDesctructuredPropsAndImportedConstants: { prop1: { - type: '"hello" | "world"', + type: 'Property1Type', required: false, defaultValue: 'hello' }, prop2: { + type: '"goodbye" | "farewell"', + required: false, + defaultValue: 'goodbye' + }, + prop3: { type: 'number', required: false, defaultValue: 10 }, - prop3: { + prop4: { type: 'string', required: false, - defaultValue: 'goodbye' + defaultValue: 'this is a string' }, - prop4: { + prop5: { type: 'boolean', required: false, defaultValue: true @@ -750,849 +788,849 @@ describe('parser', () => { }); }); - it('should parse functional component component defined as const', () => { - check('FunctionDeclarationVisibleName', { - 'Awesome Jumbotron': { - prop1: { type: 'string', required: true } - } - }); - }); - - it('should parse React.SFC component defined as const', () => { - check('ReactSFCAsConst', { - Jumbotron: { - prop1: { type: 'string', required: true } - } - }); - }); - - it('should parse functional component component defined as function as default export', () => { - check('FunctionDeclarationAsDefaultExport', { - Jumbotron: { - prop1: { type: 'string', required: true } - } - }); - }); - - it('should parse functional component component thats been wrapped in React.memo', () => { - check('FunctionDeclarationAsDefaultExportWithMemo', { - Jumbotron: { - prop1: { type: 'string', required: true } - } - }); - }); - - it('should parse JSDoc correctly', () => { - check( - 'JSDocWithParam', - { - JSDocWithParam: { - prop1: { type: 'string', required: true } - } - }, - true, - 'JSDocWithParamProps description\n\nNOTE: If a parent element of this control is `overflow: hidden` then the\nballoon may not show up.' - ); - }); - - it('should parse functional component component defined as const as default export', () => { - check( - 'FunctionalComponentAsConstAsDefaultExport', - { - // in this case the component name is taken from the file name - FunctionalComponentAsConstAsDefaultExport: { - prop1: { type: 'string', required: true } - } - }, - true, - 'Jumbotron description' - ); - }); - - it('should parse React.SFC component defined as const as default export', () => { - check( - 'ReactSFCAsConstAsDefaultExport', - { - // in this case the component name is taken from the file name - ReactSFCAsConstAsDefaultExport: { - prop1: { type: 'string', required: true } - } - }, - true, - 'Jumbotron description' - ); - }); - - it('should parse functional component component defined as const as named export', () => { - check( - 'FunctionalComponentAsConstAsNamedExport', - { - // in this case the component name is taken from the file name - Jumbotron: { - prop1: { type: 'string', required: true } - } - }, - true, - 'Jumbotron description' - ); - }); - - it('should parse React.SFC component defined as const as named export', () => { - check( - 'ReactSFCAsConstAsNamedExport', - { - // in this case the component name is taken from the file name - Jumbotron: { - prop1: { type: 'string', required: true } - } - }, - true, - 'Jumbotron description' - ); - }); - - describe('displayName', () => { - it('should be taken from stateless component `displayName` property (using named export)', () => { - const [parsed] = parse(fixturePath('StatelessDisplayName')); - assert.equal(parsed.displayName, 'StatelessDisplayName'); - }); - - it('should be taken from stateful component `displayName` property (using named export)', () => { - const [parsed] = parse(fixturePath('StatefulDisplayName')); - assert.equal(parsed.displayName, 'StatefulDisplayName'); - }); - - it('should be taken from stateless component `displayName` property (using default export)', () => { - const [parsed] = parse(fixturePath('StatelessDisplayNameDefaultExport')); - assert.equal(parsed.displayName, 'StatelessDisplayNameDefaultExport'); - }); - - it('should be taken from stateful component `displayName` property (using default export)', () => { - const [parsed] = parse(fixturePath('StatefulDisplayNameDefaultExport')); - assert.equal(parsed.displayName, 'StatefulDisplayNameDefaultExport'); - }); - - it('should be taken from named export when default export is an HOC', () => { - const [parsed] = parse(fixturePath('StatelessDisplayNameHOC')); - assert.equal(parsed.displayName, 'StatelessDisplayName'); - }); - - it('should be taken from named export when default export is an HOC', () => { - const [parsed] = parse(fixturePath('StatefulDisplayNameHOC')); - assert.equal(parsed.displayName, 'StatefulDisplayName'); - }); - - it('should be taken from stateless component folder name if file name is "index"', () => { - const [parsed] = parse(fixturePath('StatelessDisplayNameFolder/index')); - assert.equal(parsed.displayName, 'StatelessDisplayNameFolder'); - }); - - it('should be taken from stateful component folder name if file name is "index"', () => { - const [parsed] = parse(fixturePath('StatefulDisplayNameFolder/index')); - assert.equal(parsed.displayName, 'StatefulDisplayNameFolder'); - }); - }); - - describe('Parser options', () => { - describe('Property filtering', () => { - describe('children', () => { - it('should ignore property "children" if not explicitly documented', () => { - check( - 'Column', - { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }, - true - ); - }); - - it('should not ignore any property that is documented explicitly', () => { - check( - 'ColumnWithAnnotatedChildren', - { - Column: { - children: { - description: 'children description', - required: false, - type: 'ReactNode' - }, - prop1: { type: 'string', required: false }, - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }, - true - ); - }); - }); - - describe('propsFilter method', () => { - it('should apply filter function and filter components accordingly', () => { - const propFilter: PropFilter = (prop, component) => - prop.name !== 'prop1'; - check( - 'Column', - { - Column: { - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }, - true, - undefined, - { propFilter } - ); - }); - - it('should apply filter function and filter components accordingly', () => { - const propFilter: PropFilter = (prop, component) => { - if (component.name === 'Column') { - return prop.name !== 'prop1'; - } - return true; - }; - check( - 'Column', - { - Column: { - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }, - true, - undefined, - { propFilter } - ); - check( - 'AppMenu', - { - AppMenu: { - menu: { type: 'any' } - } - }, - true, - undefined, - { propFilter } - ); - }); - - it('should allow filtering by parent interface', () => { - const propFilter: PropFilter = (prop, component) => { - if (prop.parent == null) { - return true; - } - - return ( - prop.parent.fileName.indexOf('@types/react') < 0 && - prop.parent.name !== 'HTMLAttributes' - ); - }; - - check( - 'ColumnWithHtmlAttributes', - { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' } - } - }, - true, - undefined, - { propFilter } - ); - }); - }); - - it('should collect all `onClick prop` parent declarations', done => { - assert.doesNotThrow(() => { - withDefaultConfig({ - propFilter: prop => { - if (prop.name === 'onClick') { - assert.deepEqual(prop.declarations, [ - { - fileName: - 'react-docgen-typescript/node_modules/@types/react/index.d.ts', - name: 'DOMAttributes' - }, - { - fileName: - 'react-docgen-typescript/src/__tests__/data/ButtonWithOnClickComponent.tsx', - name: 'TypeLiteral' - } - ]); - - done(); - } - - return true; - } - }).parse(fixturePath('ButtonWithOnClickComponent')); - }); - }); - - it('should allow filtering by parent declarations', () => { - const propFilter: PropFilter = prop => { - if (prop.declarations !== undefined && prop.declarations.length > 0) { - const hasPropAdditionalDescription = prop.declarations.find( - declaration => { - return !declaration.fileName.includes('@types/react'); - } - ); - - return Boolean(hasPropAdditionalDescription); - } - - return true; - }; - - check( - 'ButtonWithOnClickComponent', - { - ButtonWithOnClickComponent: { - onClick: { - type: - '(event: MouseEvent) => void', - required: false, - description: 'onClick event handler' - } - } - }, - true, - '', - { - propFilter - } - ); - }); - - describe('skipPropsWithName', () => { - it('should skip a single property in skipPropsWithName', () => { - const propFilter = { skipPropsWithName: 'prop1' }; - check( - 'Column', - { - Column: { - prop2: { type: 'number' }, - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }, - true, - undefined, - { propFilter } - ); - }); - - it('should skip multiple properties in skipPropsWithName', () => { - const propFilter = { skipPropsWithName: ['prop1', 'prop2'] }; - check( - 'Column', - { - Column: { - prop3: { type: '() => void' }, - prop4: { type: '"option1" | "option2" | "option3"' } - } - }, - true, - undefined, - { propFilter } - ); - }); - }); - - describe('skipPropsWithoutDoc', () => { - it('should skip a properties without documentation', () => { - const propFilter = { skipPropsWithoutDoc: false }; - check( - 'ColumnWithUndocumentedProps', - { - Column: { - prop1: { type: 'string', required: false }, - prop2: { type: 'number' } - } - }, - true, - undefined, - { propFilter } - ); - }); - }); - }); - - it('should defaultProps in variable', () => { - check('SeparateDefaultProps', { - SeparateDefaultProps: { - disabled: { - description: '', - required: false, - defaultValue: false, - type: 'boolean' - }, - id: { - description: '', - required: false, - defaultValue: 123, - type: 'number' - } - } - }); - }); - - it('should defaultProps accessed variable', () => { - check('SeparateDefaultPropsIndividual', { - SeparateDefaultPropsIndividual: { - disabled: { - description: '', - required: false, - defaultValue: false, - type: 'boolean' - }, - id: { - description: '', - required: false, - defaultValue: 123, - type: 'number' - } - } - }); - }); - - describe('Extracting literal values from enums', () => { - it('extracts literal values from enum', () => { - check( - 'ExtractLiteralValuesFromEnum', - { - ExtractLiteralValuesFromEnum: { - sampleBoolean: { type: 'boolean' }, - sampleEnum: { - raw: 'sampleEnum', - type: 'enum', - value: [ - { value: '"one"' }, - { value: '"two"' }, - { value: '"three"' } - ] - }, - sampleString: { type: 'string' } - } - }, - true, - null, - { - shouldExtractLiteralValuesFromEnum: true - } - ); - }); - - it('Should infer types from constraint type (generic with extends)', () => { - check( - 'GenericWithExtends', - { - GenericWithExtends: { - sampleUnionProp: { - raw: 'SampleUnion', - type: 'enum', - value: [ - { - value: '"value 1"' - }, - { - value: '"value 2"' - }, - { - value: '"value 3"' - }, - { - value: '"value 4"' - }, - { - value: '"value n"' - } - ] - }, - sampleEnumProp: { - raw: 'SampleEnum', - type: 'enum', - value: [ - { - value: '0' - }, - { - value: '1' - }, - { - value: '"c"' - } - ] - }, - sampleUnionNonGeneric: { - type: 'SampleUnionNonGeneric' - }, - sampleObjectProp: { - type: 'SampleObject' - }, - sampleNumberProp: { - type: 'number' - }, - sampleGenericArray: { - type: 'number[]' - }, - sampleGenericObject: { - type: '{ prop1: number; }' - }, - sampleInlineObject: { - type: '{ propA: string; }' - } - } - }, - true, - null, - { shouldExtractLiteralValuesFromEnum: true } - ); - }); - }); - - describe('Extracting values from unions', () => { - it('extracts all values from union', () => { - check( - 'ExtractLiteralValuesFromUnion', - { - ExtractLiteralValuesFromUnion: { - sampleComplexUnion: { - raw: 'number | "string1" | "string2"', - type: 'enum', - value: [ - { value: 'number' }, - { value: '"string1"' }, - { value: '"string2"' } - ] - } - } - }, - false, - null, - { - shouldExtractValuesFromUnion: true - } - ); - }); - it('extracts numbers from a union', () => { - check( - 'ExtractLiteralValuesFromUnion', - { - ExtractLiteralValuesFromUnion: { - sampleNumberUnion: { - raw: '1 | 2 | 3', - type: 'enum', - value: [{ value: '1' }, { value: '2' }, { value: '3' }] - } - } - }, - false, - null, - { - shouldExtractValuesFromUnion: true - } - ); - }); - it('extracts numbers and strings from a mixed union', () => { - check( - 'ExtractLiteralValuesFromUnion', - { - ExtractLiteralValuesFromUnion: { - sampleMixedUnion: { - raw: '"string1" | "string2" | 1 | 2', - type: 'enum', - value: [ - { value: '"string1"' }, - { value: '"string2"' }, - { value: '1' }, - { value: '2' } - ] - } - } - }, - false, - null, - { - shouldExtractValuesFromUnion: true - } - ); - }); - }); - - describe('Returning not string default props ', () => { - it('returns not string defaultProps', () => { - check( - 'StatelessWithDefaultPropsAsString', - { - StatelessWithDefaultPropsAsString: { - sampleFalse: { - defaultValue: 'false', - required: false, - type: 'boolean' - }, - sampleNull: { - defaultValue: 'null', - required: false, - type: 'null' - }, - sampleNumber: { - defaultValue: '1', - required: false, - type: 'number' - }, - sampleNumberWithPrefix: { - defaultValue: '-1', - required: false, - type: 'number' - }, - sampleTrue: { - defaultValue: 'true', - required: false, - type: 'boolean' - }, - sampleUndefined: { - defaultValue: 'undefined', - required: false, - type: 'undefined' - } - } - }, - true, - null, - { - savePropValueAsString: true - } - ); - }); - }); - describe("Extract prop's JSDoc/TSDoc tags", () => { - it('should extract all prop JSDoc/TSDoc tags', () => { - check( - 'ExtractPropTags', - { - ExtractPropTags: { - prop1: { - type: 'Pick', - required: false, - tags: { - ignore: 'ignoreMe', - kind: 'category 2', - custom123: 'something' - } - }, - prop2: { - type: 'string', - tags: { internal: 'some internal prop', kind: 'category 1' } - } - } - }, - true, - null, - { shouldIncludePropTagMap: true } - ); - }); - }); - }); - - describe('withCustomConfig', () => { - it('should accept tsconfigs that typescript accepts', () => { - assert.ok( - withCustomConfig( - // need to navigate to root because tests run on compiled tests - // and tsc does not include json files - path.join(__dirname, '../../src/__tests__/data/tsconfig.json'), - {} - ) - ); - }); - }); - - describe('parseWithProgramProvider', () => { - it('should accept existing ts.Program instance', () => { - let programProviderInvoked = false; - - // mimic a third party library providing a ts.Program instance. - const programProvider = () => { - // need to navigate to root because tests run on compiled tests - // and tsc does not include json files - const tsconfigPath = path.join( - __dirname, - '../../src/__tests__/data/tsconfig.json' - ); - const basePath = path.dirname(tsconfigPath); - - const { config, error } = ts.readConfigFile(tsconfigPath, filename => - fs.readFileSync(filename, 'utf8') - ); - assert.isUndefined(error); - - const { options, errors } = ts.parseJsonConfigFileContent( - config, - ts.sys, - basePath, - {}, - tsconfigPath - ); - assert.lengthOf(errors, 0); - - programProviderInvoked = true; - - return ts.createProgram([fixturePath('Column')], options); - }; - - const result = withDefaultConfig().parseWithProgramProvider( - [fixturePath('Column')], - programProvider - ); - - checkComponent( - result, - { - Column: {} - }, - false - ); - assert.isTrue(programProviderInvoked); - }); - }); - - describe('componentNameResolver', () => { - it('should override default behavior', () => { - const [parsed] = parse( - fixturePath('StatelessDisplayNameStyledComponent'), - { - componentNameResolver: (exp, source) => - exp.getName() === 'StyledComponentClass' && - getDefaultExportForFile(source) - } - ); - assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); - }); - - it('should fallback to default behavior without a match', () => { - const [parsed] = parse( - fixturePath('StatelessDisplayNameStyledComponent'), - { - componentNameResolver: () => false - } - ); - assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); - }); - }); - - describe('methods', () => { - it('should properly parse methods', () => { - const [parsed] = parse(fixturePath('ColumnWithMethods')); - const methods = parsed.methods; - const myCoolMethod = methods[0]; - - assert.equal(myCoolMethod.description, 'My super cool method'); - assert.equal( - myCoolMethod.docblock, - 'My super cool method\n@param myParam Documentation for parameter 1\n@public\n@returns The answer to the universe' // tslint:disable-line max-line-length - ); - assert.deepEqual(myCoolMethod.modifiers, []); - assert.equal(myCoolMethod.name, 'myCoolMethod'); - assert.deepEqual(myCoolMethod.params, [ - { - description: 'Documentation for parameter 1', - name: 'myParam', - type: { name: 'number' } - }, - { - description: null, - name: 'mySecondParam?', - type: { name: 'string' } - } - ]); - assert.deepEqual(myCoolMethod.returns, { - description: 'The answer to the universe', - type: 'number' - }); - }); - - it('should properly parse static methods', () => { - const [parsed] = parse(fixturePath('ColumnWithStaticMethods')); - const methods = parsed.methods; - - assert.equal(methods[0].name, 'myStaticMethod'); - assert.deepEqual(methods[0].modifiers, ['static']); - }); - - it('should handle method with no information', () => { - const [parsed] = parse(fixturePath('ColumnWithMethods')); - const methods = parsed.methods; - assert.equal(methods[1].name, 'myBasicMethod'); - }); - - it('should handle arrow function', () => { - const [parsed] = parse(fixturePath('ColumnWithMethods')); - const methods = parsed.methods; - assert.equal(methods[2].name, 'myArrowFunction'); - }); - - it('should not parse functions not marked with @public', () => { - const [parsed] = parse(fixturePath('ColumnWithMethods')); - const methods = parsed.methods; - assert.equal( - Boolean(methods.find(method => method.name === 'myPrivateFunction')), - false - ); - }); - }); - - describe('getDefaultExportForFile', () => { - it('should filter out forbidden symbols', () => { - const result = getDefaultExportForFile({ - fileName: 'a-b' - } as ts.SourceFile); - assert.equal(result, 'ab'); - }); - - it('should remove leading non-letters', () => { - const result = getDefaultExportForFile({ - fileName: '---123aba' - } as ts.SourceFile); - assert.equal(result, 'aba'); - }); - - it('should preserve numbers in the middle', () => { - const result = getDefaultExportForFile({ - fileName: '1Body2Text3' - } as ts.SourceFile); - assert.equal(result, 'Body2Text3'); - }); - - it('should not return empty string', () => { - const result = getDefaultExportForFile({ - fileName: '---123' - } as ts.SourceFile); - assert.equal(result.length > 0, true); - }); - }); - - describe('issues tests', () => { - it('188', () => { - check( - 'Issue188', - { - Header: { - content: { type: 'string', required: true, description: '' } - } - }, - true, - '' - ); - }); - }); + // it('should parse functional component component defined as const', () => { + // check('FunctionDeclarationVisibleName', { + // 'Awesome Jumbotron': { + // prop1: { type: 'string', required: true } + // } + // }); + // }); + // + // it('should parse React.SFC component defined as const', () => { + // check('ReactSFCAsConst', { + // Jumbotron: { + // prop1: { type: 'string', required: true } + // } + // }); + // }); + // + // it('should parse functional component component defined as function as default export', () => { + // check('FunctionDeclarationAsDefaultExport', { + // Jumbotron: { + // prop1: { type: 'string', required: true } + // } + // }); + // }); + // + // it('should parse functional component component thats been wrapped in React.memo', () => { + // check('FunctionDeclarationAsDefaultExportWithMemo', { + // Jumbotron: { + // prop1: { type: 'string', required: true } + // } + // }); + // }); + // + // it('should parse JSDoc correctly', () => { + // check( + // 'JSDocWithParam', + // { + // JSDocWithParam: { + // prop1: { type: 'string', required: true } + // } + // }, + // true, + // 'JSDocWithParamProps description\n\nNOTE: If a parent element of this control is `overflow: hidden` then the\nballoon may not show up.' + // ); + // }); + // + // it('should parse functional component component defined as const as default export', () => { + // check( + // 'FunctionalComponentAsConstAsDefaultExport', + // { + // // in this case the component name is taken from the file name + // FunctionalComponentAsConstAsDefaultExport: { + // prop1: { type: 'string', required: true } + // } + // }, + // true, + // 'Jumbotron description' + // ); + // }); + // + // it('should parse React.SFC component defined as const as default export', () => { + // check( + // 'ReactSFCAsConstAsDefaultExport', + // { + // // in this case the component name is taken from the file name + // ReactSFCAsConstAsDefaultExport: { + // prop1: { type: 'string', required: true } + // } + // }, + // true, + // 'Jumbotron description' + // ); + // }); + // + // it('should parse functional component component defined as const as named export', () => { + // check( + // 'FunctionalComponentAsConstAsNamedExport', + // { + // // in this case the component name is taken from the file name + // Jumbotron: { + // prop1: { type: 'string', required: true } + // } + // }, + // true, + // 'Jumbotron description' + // ); + // }); + // + // it('should parse React.SFC component defined as const as named export', () => { + // check( + // 'ReactSFCAsConstAsNamedExport', + // { + // // in this case the component name is taken from the file name + // Jumbotron: { + // prop1: { type: 'string', required: true } + // } + // }, + // true, + // 'Jumbotron description' + // ); + // }); + // + // describe('displayName', () => { + // it('should be taken from stateless component `displayName` property (using named export)', () => { + // const [parsed] = parse(fixturePath('StatelessDisplayName')); + // assert.equal(parsed.displayName, 'StatelessDisplayName'); + // }); + // + // it('should be taken from stateful component `displayName` property (using named export)', () => { + // const [parsed] = parse(fixturePath('StatefulDisplayName')); + // assert.equal(parsed.displayName, 'StatefulDisplayName'); + // }); + // + // it('should be taken from stateless component `displayName` property (using default export)', () => { + // const [parsed] = parse(fixturePath('StatelessDisplayNameDefaultExport')); + // assert.equal(parsed.displayName, 'StatelessDisplayNameDefaultExport'); + // }); + // + // it('should be taken from stateful component `displayName` property (using default export)', () => { + // const [parsed] = parse(fixturePath('StatefulDisplayNameDefaultExport')); + // assert.equal(parsed.displayName, 'StatefulDisplayNameDefaultExport'); + // }); + // + // it('should be taken from named export when default export is an HOC', () => { + // const [parsed] = parse(fixturePath('StatelessDisplayNameHOC')); + // assert.equal(parsed.displayName, 'StatelessDisplayName'); + // }); + // + // it('should be taken from named export when default export is an HOC', () => { + // const [parsed] = parse(fixturePath('StatefulDisplayNameHOC')); + // assert.equal(parsed.displayName, 'StatefulDisplayName'); + // }); + // + // it('should be taken from stateless component folder name if file name is "index"', () => { + // const [parsed] = parse(fixturePath('StatelessDisplayNameFolder/index')); + // assert.equal(parsed.displayName, 'StatelessDisplayNameFolder'); + // }); + // + // it('should be taken from stateful component folder name if file name is "index"', () => { + // const [parsed] = parse(fixturePath('StatefulDisplayNameFolder/index')); + // assert.equal(parsed.displayName, 'StatefulDisplayNameFolder'); + // }); + // }); + // + // describe('Parser options', () => { + // describe('Property filtering', () => { + // describe('children', () => { + // it('should ignore property "children" if not explicitly documented', () => { + // check( + // 'Column', + // { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }, + // true + // ); + // }); + // + // it('should not ignore any property that is documented explicitly', () => { + // check( + // 'ColumnWithAnnotatedChildren', + // { + // Column: { + // children: { + // description: 'children description', + // required: false, + // type: 'ReactNode' + // }, + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }, + // true + // ); + // }); + // }); + // + // describe('propsFilter method', () => { + // it('should apply filter function and filter components accordingly', () => { + // const propFilter: PropFilter = (prop, component) => + // prop.name !== 'prop1'; + // check( + // 'Column', + // { + // Column: { + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }, + // true, + // undefined, + // { propFilter } + // ); + // }); + // + // it('should apply filter function and filter components accordingly', () => { + // const propFilter: PropFilter = (prop, component) => { + // if (component.name === 'Column') { + // return prop.name !== 'prop1'; + // } + // return true; + // }; + // check( + // 'Column', + // { + // Column: { + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }, + // true, + // undefined, + // { propFilter } + // ); + // check( + // 'AppMenu', + // { + // AppMenu: { + // menu: { type: 'any' } + // } + // }, + // true, + // undefined, + // { propFilter } + // ); + // }); + // + // it('should allow filtering by parent interface', () => { + // const propFilter: PropFilter = (prop, component) => { + // if (prop.parent == null) { + // return true; + // } + // + // return ( + // prop.parent.fileName.indexOf('@types/react') < 0 && + // prop.parent.name !== 'HTMLAttributes' + // ); + // }; + // + // check( + // 'ColumnWithHtmlAttributes', + // { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' } + // } + // }, + // true, + // undefined, + // { propFilter } + // ); + // }); + // }); + // + // it('should collect all `onClick prop` parent declarations', done => { + // assert.doesNotThrow(() => { + // withDefaultConfig({ + // propFilter: prop => { + // if (prop.name === 'onClick') { + // assert.deepEqual(prop.declarations, [ + // { + // fileName: + // 'react-docgen-typescript/node_modules/@types/react/index.d.ts', + // name: 'DOMAttributes' + // }, + // { + // fileName: + // 'react-docgen-typescript/src/__tests__/data/ButtonWithOnClickComponent.tsx', + // name: 'TypeLiteral' + // } + // ]); + // + // done(); + // } + // + // return true; + // } + // }).parse(fixturePath('ButtonWithOnClickComponent')); + // }); + // }); + // + // it('should allow filtering by parent declarations', () => { + // const propFilter: PropFilter = prop => { + // if (prop.declarations !== undefined && prop.declarations.length > 0) { + // const hasPropAdditionalDescription = prop.declarations.find( + // declaration => { + // return !declaration.fileName.includes('@types/react'); + // } + // ); + // + // return Boolean(hasPropAdditionalDescription); + // } + // + // return true; + // }; + // + // check( + // 'ButtonWithOnClickComponent', + // { + // ButtonWithOnClickComponent: { + // onClick: { + // type: + // '(event: MouseEvent) => void', + // required: false, + // description: 'onClick event handler' + // } + // } + // }, + // true, + // '', + // { + // propFilter + // } + // ); + // }); + // + // describe('skipPropsWithName', () => { + // it('should skip a single property in skipPropsWithName', () => { + // const propFilter = { skipPropsWithName: 'prop1' }; + // check( + // 'Column', + // { + // Column: { + // prop2: { type: 'number' }, + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }, + // true, + // undefined, + // { propFilter } + // ); + // }); + // + // it('should skip multiple properties in skipPropsWithName', () => { + // const propFilter = { skipPropsWithName: ['prop1', 'prop2'] }; + // check( + // 'Column', + // { + // Column: { + // prop3: { type: '() => void' }, + // prop4: { type: '"option1" | "option2" | "option3"' } + // } + // }, + // true, + // undefined, + // { propFilter } + // ); + // }); + // }); + // + // describe('skipPropsWithoutDoc', () => { + // it('should skip a properties without documentation', () => { + // const propFilter = { skipPropsWithoutDoc: false }; + // check( + // 'ColumnWithUndocumentedProps', + // { + // Column: { + // prop1: { type: 'string', required: false }, + // prop2: { type: 'number' } + // } + // }, + // true, + // undefined, + // { propFilter } + // ); + // }); + // }); + // }); + // + // it('should defaultProps in variable', () => { + // check('SeparateDefaultProps', { + // SeparateDefaultProps: { + // disabled: { + // description: '', + // required: false, + // defaultValue: false, + // type: 'boolean' + // }, + // id: { + // description: '', + // required: false, + // defaultValue: 123, + // type: 'number' + // } + // } + // }); + // }); + // + // it('should defaultProps accessed variable', () => { + // check('SeparateDefaultPropsIndividual', { + // SeparateDefaultPropsIndividual: { + // disabled: { + // description: '', + // required: false, + // defaultValue: false, + // type: 'boolean' + // }, + // id: { + // description: '', + // required: false, + // defaultValue: 123, + // type: 'number' + // } + // } + // }); + // }); + // + // describe('Extracting literal values from enums', () => { + // it('extracts literal values from enum', () => { + // check( + // 'ExtractLiteralValuesFromEnum', + // { + // ExtractLiteralValuesFromEnum: { + // sampleBoolean: { type: 'boolean' }, + // sampleEnum: { + // raw: 'sampleEnum', + // type: 'enum', + // value: [ + // { value: '"one"' }, + // { value: '"two"' }, + // { value: '"three"' } + // ] + // }, + // sampleString: { type: 'string' } + // } + // }, + // true, + // null, + // { + // shouldExtractLiteralValuesFromEnum: true + // } + // ); + // }); + // + // it('Should infer types from constraint type (generic with extends)', () => { + // check( + // 'GenericWithExtends', + // { + // GenericWithExtends: { + // sampleUnionProp: { + // raw: 'SampleUnion', + // type: 'enum', + // value: [ + // { + // value: '"value 1"' + // }, + // { + // value: '"value 2"' + // }, + // { + // value: '"value 3"' + // }, + // { + // value: '"value 4"' + // }, + // { + // value: '"value n"' + // } + // ] + // }, + // sampleEnumProp: { + // raw: 'SampleEnum', + // type: 'enum', + // value: [ + // { + // value: '0' + // }, + // { + // value: '1' + // }, + // { + // value: '"c"' + // } + // ] + // }, + // sampleUnionNonGeneric: { + // type: 'SampleUnionNonGeneric' + // }, + // sampleObjectProp: { + // type: 'SampleObject' + // }, + // sampleNumberProp: { + // type: 'number' + // }, + // sampleGenericArray: { + // type: 'number[]' + // }, + // sampleGenericObject: { + // type: '{ prop1: number; }' + // }, + // sampleInlineObject: { + // type: '{ propA: string; }' + // } + // } + // }, + // true, + // null, + // { shouldExtractLiteralValuesFromEnum: true } + // ); + // }); + // }); + // + // describe('Extracting values from unions', () => { + // it('extracts all values from union', () => { + // check( + // 'ExtractLiteralValuesFromUnion', + // { + // ExtractLiteralValuesFromUnion: { + // sampleComplexUnion: { + // raw: 'number | "string1" | "string2"', + // type: 'enum', + // value: [ + // { value: 'number' }, + // { value: '"string1"' }, + // { value: '"string2"' } + // ] + // } + // } + // }, + // false, + // null, + // { + // shouldExtractValuesFromUnion: true + // } + // ); + // }); + // it('extracts numbers from a union', () => { + // check( + // 'ExtractLiteralValuesFromUnion', + // { + // ExtractLiteralValuesFromUnion: { + // sampleNumberUnion: { + // raw: '1 | 2 | 3', + // type: 'enum', + // value: [{ value: '1' }, { value: '2' }, { value: '3' }] + // } + // } + // }, + // false, + // null, + // { + // shouldExtractValuesFromUnion: true + // } + // ); + // }); + // it('extracts numbers and strings from a mixed union', () => { + // check( + // 'ExtractLiteralValuesFromUnion', + // { + // ExtractLiteralValuesFromUnion: { + // sampleMixedUnion: { + // raw: '"string1" | "string2" | 1 | 2', + // type: 'enum', + // value: [ + // { value: '"string1"' }, + // { value: '"string2"' }, + // { value: '1' }, + // { value: '2' } + // ] + // } + // } + // }, + // false, + // null, + // { + // shouldExtractValuesFromUnion: true + // } + // ); + // }); + // }); + // + // describe('Returning not string default props ', () => { + // it('returns not string defaultProps', () => { + // check( + // 'StatelessWithDefaultPropsAsString', + // { + // StatelessWithDefaultPropsAsString: { + // sampleFalse: { + // defaultValue: 'false', + // required: false, + // type: 'boolean' + // }, + // sampleNull: { + // defaultValue: 'null', + // required: false, + // type: 'null' + // }, + // sampleNumber: { + // defaultValue: '1', + // required: false, + // type: 'number' + // }, + // sampleNumberWithPrefix: { + // defaultValue: '-1', + // required: false, + // type: 'number' + // }, + // sampleTrue: { + // defaultValue: 'true', + // required: false, + // type: 'boolean' + // }, + // sampleUndefined: { + // defaultValue: 'undefined', + // required: false, + // type: 'undefined' + // } + // } + // }, + // true, + // null, + // { + // savePropValueAsString: true + // } + // ); + // }); + // }); + // describe("Extract prop's JSDoc/TSDoc tags", () => { + // it('should extract all prop JSDoc/TSDoc tags', () => { + // check( + // 'ExtractPropTags', + // { + // ExtractPropTags: { + // prop1: { + // type: 'Pick', + // required: false, + // tags: { + // ignore: 'ignoreMe', + // kind: 'category 2', + // custom123: 'something' + // } + // }, + // prop2: { + // type: 'string', + // tags: { internal: 'some internal prop', kind: 'category 1' } + // } + // } + // }, + // true, + // null, + // { shouldIncludePropTagMap: true } + // ); + // }); + // }); + // }); + // + // describe('withCustomConfig', () => { + // it('should accept tsconfigs that typescript accepts', () => { + // assert.ok( + // withCustomConfig( + // // need to navigate to root because tests run on compiled tests + // // and tsc does not include json files + // path.join(__dirname, '../../src/__tests__/data/tsconfig.json'), + // {} + // ) + // ); + // }); + // }); + // + // describe('parseWithProgramProvider', () => { + // it('should accept existing ts.Program instance', () => { + // let programProviderInvoked = false; + // + // // mimic a third party library providing a ts.Program instance. + // const programProvider = () => { + // // need to navigate to root because tests run on compiled tests + // // and tsc does not include json files + // const tsconfigPath = path.join( + // __dirname, + // '../../src/__tests__/data/tsconfig.json' + // ); + // const basePath = path.dirname(tsconfigPath); + // + // const { config, error } = ts.readConfigFile(tsconfigPath, filename => + // fs.readFileSync(filename, 'utf8') + // ); + // assert.isUndefined(error); + // + // const { options, errors } = ts.parseJsonConfigFileContent( + // config, + // ts.sys, + // basePath, + // {}, + // tsconfigPath + // ); + // assert.lengthOf(errors, 0); + // + // programProviderInvoked = true; + // + // return ts.createProgram([fixturePath('Column')], options); + // }; + // + // const result = withDefaultConfig().parseWithProgramProvider( + // [fixturePath('Column')], + // programProvider + // ); + // + // checkComponent( + // result, + // { + // Column: {} + // }, + // false + // ); + // assert.isTrue(programProviderInvoked); + // }); + // }); + // + // describe('componentNameResolver', () => { + // it('should override default behavior', () => { + // const [parsed] = parse( + // fixturePath('StatelessDisplayNameStyledComponent'), + // { + // componentNameResolver: (exp, source) => + // exp.getName() === 'StyledComponentClass' && + // getDefaultExportForFile(source) + // } + // ); + // assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); + // }); + // + // it('should fallback to default behavior without a match', () => { + // const [parsed] = parse( + // fixturePath('StatelessDisplayNameStyledComponent'), + // { + // componentNameResolver: () => false + // } + // ); + // assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); + // }); + // }); + // + // describe('methods', () => { + // it('should properly parse methods', () => { + // const [parsed] = parse(fixturePath('ColumnWithMethods')); + // const methods = parsed.methods; + // const myCoolMethod = methods[0]; + // + // assert.equal(myCoolMethod.description, 'My super cool method'); + // assert.equal( + // myCoolMethod.docblock, + // 'My super cool method\n@param myParam Documentation for parameter 1\n@public\n@returns The answer to the universe' // tslint:disable-line max-line-length + // ); + // assert.deepEqual(myCoolMethod.modifiers, []); + // assert.equal(myCoolMethod.name, 'myCoolMethod'); + // assert.deepEqual(myCoolMethod.params, [ + // { + // description: 'Documentation for parameter 1', + // name: 'myParam', + // type: { name: 'number' } + // }, + // { + // description: null, + // name: 'mySecondParam?', + // type: { name: 'string' } + // } + // ]); + // assert.deepEqual(myCoolMethod.returns, { + // description: 'The answer to the universe', + // type: 'number' + // }); + // }); + // + // it('should properly parse static methods', () => { + // const [parsed] = parse(fixturePath('ColumnWithStaticMethods')); + // const methods = parsed.methods; + // + // assert.equal(methods[0].name, 'myStaticMethod'); + // assert.deepEqual(methods[0].modifiers, ['static']); + // }); + // + // it('should handle method with no information', () => { + // const [parsed] = parse(fixturePath('ColumnWithMethods')); + // const methods = parsed.methods; + // assert.equal(methods[1].name, 'myBasicMethod'); + // }); + // + // it('should handle arrow function', () => { + // const [parsed] = parse(fixturePath('ColumnWithMethods')); + // const methods = parsed.methods; + // assert.equal(methods[2].name, 'myArrowFunction'); + // }); + // + // it('should not parse functions not marked with @public', () => { + // const [parsed] = parse(fixturePath('ColumnWithMethods')); + // const methods = parsed.methods; + // assert.equal( + // Boolean(methods.find(method => method.name === 'myPrivateFunction')), + // false + // ); + // }); + // }); + // + // describe('getDefaultExportForFile', () => { + // it('should filter out forbidden symbols', () => { + // const result = getDefaultExportForFile({ + // fileName: 'a-b' + // } as ts.SourceFile); + // assert.equal(result, 'ab'); + // }); + // + // it('should remove leading non-letters', () => { + // const result = getDefaultExportForFile({ + // fileName: '---123aba' + // } as ts.SourceFile); + // assert.equal(result, 'aba'); + // }); + // + // it('should preserve numbers in the middle', () => { + // const result = getDefaultExportForFile({ + // fileName: '1Body2Text3' + // } as ts.SourceFile); + // assert.equal(result, 'Body2Text3'); + // }); + // + // it('should not return empty string', () => { + // const result = getDefaultExportForFile({ + // fileName: '---123' + // } as ts.SourceFile); + // assert.equal(result.length > 0, true); + // }); + // }); + // + // describe('issues tests', () => { + // it('188', () => { + // check( + // 'Issue188', + // { + // Header: { + // content: { type: 'string', required: true, description: '' } + // } + // }, + // true, + // '' + // ); + // }); + // }); }); diff --git a/src/parser.ts b/src/parser.ts index 930c1f01..45483efb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -902,6 +902,33 @@ export class Parser { }, {}); } + public getLiteralValueFromImportSpecifier( + property: ts.ImportSpecifier + ): string | boolean | number | null | undefined { + if (ts.isImportSpecifier(property)) { + const symbol = this.checker.getSymbolAtLocation(property.name); + + if (!symbol) { + return null; + } + + const aliasedSymbol = this.checker.getAliasedSymbol(symbol); + if ( + aliasedSymbol && + aliasedSymbol.declarations && + aliasedSymbol.declarations.length + ) { + return this.getLiteralValueFromPropertyAssignment( + aliasedSymbol.declarations[0] as ts.BindingElement + ); + } + + return null; + } + + return null; + } + public getLiteralValueFromPropertyAssignment( property: ts.PropertyAssignment | ts.BindingElement ): string | boolean | number | null | undefined { @@ -952,6 +979,12 @@ export class Parser { ); if (symbol && symbol.declarations && symbol.declarations.length) { + if (ts.isImportSpecifier(symbol.declarations[0])) { + return this.getLiteralValueFromImportSpecifier( + symbol.declarations[0] as ts.ImportSpecifier + ); + } + return this.getLiteralValueFromPropertyAssignment( symbol.declarations[0] as ts.BindingElement ); From a0b4c3582646721fc93c94898e356820a81cd55e Mon Sep 17 00:00:00 2001 From: Michel Engelen Date: Wed, 31 Mar 2021 16:41:25 +0200 Subject: [PATCH 5/5] removed commenting and renamed constant file --- ...ponentWithDesctructuredProps.constants.ts} | 0 ...ctionalComponentWithDesctructuredProps.tsx | 12 +- ...DesctructuredPropsAndImportedConstants.tsx | 8 +- src/__tests__/parser.ts | 3111 ++++++++--------- 4 files changed, 1564 insertions(+), 1567 deletions(-) rename src/__tests__/data/{FunctionalComponentWithDesctructuredPropsImportedConstants.ts => FunctionalComponentWithDesctructuredProps.constants.ts} (100%) diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredPropsImportedConstants.ts b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.constants.ts similarity index 100% rename from src/__tests__/data/FunctionalComponentWithDesctructuredPropsImportedConstants.ts rename to src/__tests__/data/FunctionalComponentWithDesctructuredProps.constants.ts diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx index 8d247d92..ee3f7416 100644 --- a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx @@ -1,12 +1,10 @@ import * as React from 'react'; -import { - PROPERTY1_DEFAULT, - PROPERTY2_DEFAULT, - PROPERTY3_DEFAULT, - PROPERTY4_DEFAULT, - PROPERTY5_DEFAULT -} from './FunctionalComponentWithDesctructuredPropsImportedConstants'; +const PROPERTY1_DEFAULT = 'hello'; +const PROPERTY2_DEFAULT = 'goodbye'; +const PROPERTY3_DEFAULT = 10; +const PROPERTY4_DEFAULT = 'this is a string'; +const PROPERTY5_DEFAULT = true; type Property1Type = 'hello' | 'world'; diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx index 3e7c1cd0..8b200ab9 100644 --- a/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx @@ -6,7 +6,7 @@ import { PROPERTY3_DEFAULT, PROPERTY4_DEFAULT, PROPERTY5_DEFAULT -} from './FunctionalComponentWithDesctructuredPropsImportedConstants'; +} from './FunctionalComponentWithDesctructuredProps.constants'; type Property1Type = 'hello' | 'world'; @@ -23,8 +23,8 @@ type Props = { prop5?: boolean; }; -/** FunctionalComponentWithDesctructuredPropsImportedConstants description */ -const FunctionalComponentWithDesctructuredPropsImportedConstants: React.FC = ({ +/** FunctionalComponentWithDesctructuredPropsAndImportedConstants description */ +const FunctionalComponentWithDesctructuredPropsAndImportedConstants: React.FC = ({ prop1 = PROPERTY1_DEFAULT, prop2 = PROPERTY2_DEFAULT, prop3 = PROPERTY3_DEFAULT, @@ -32,4 +32,4 @@ const FunctionalComponentWithDesctructuredPropsImportedConstants: React.FC
; -export default FunctionalComponentWithDesctructuredPropsImportedConstants; +export default FunctionalComponentWithDesctructuredPropsAndImportedConstants; diff --git a/src/__tests__/parser.ts b/src/__tests__/parser.ts index daf4d8a5..0042e3fb 100644 --- a/src/__tests__/parser.ts +++ b/src/__tests__/parser.ts @@ -10,719 +10,718 @@ import { withDefaultConfig } from '../parser'; import { check, checkComponent, fixturePath } from './testUtils'; -import FunctionalComponentWithDesctructuredPropsImportedConstants from './data/FunctionalComponentWithDesctructuredPropsAndImportedConstants'; describe('parser', () => { - // it('should parse simple react class component', () => { - // check('Column', { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }); - // }); - // - // it('should parse simple react class component with console.log inside', () => { - // check('ColumnWithLog', { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }); - // }); - // - // it('should parse simple react class component as default export', () => { - // check('ColumnWithDefaultExport', { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }); - // }); - // - // it('should parse mulitple files', () => { - // const result = parse([ - // fixturePath('Column'), - // fixturePath('ColumnWithDefaultExportOnly') - // ]); - // - // checkComponent( - // result, - // { - // Column: {}, - // ColumnWithDefaultExportOnly: {} - // }, - // false - // ); - // }); - // - // it('should parse simple react class component as default export only', () => { - // check('ColumnWithDefaultExportOnly', { - // ColumnWithDefaultExportOnly: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }); - // }); - // - // it('should parse simple react class component as default anonymous export', () => { - // check('ColumnWithDefaultAnonymousExportOnly', { - // ColumnWithDefaultAnonymousExportOnly: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }); - // }); - // - // it('should parse simple react class component with state', () => { - // check('AppMenu', { - // AppMenu: { - // menu: { type: 'any' } - // } - // }); - // }); - // - // it('should parse simple react class component with picked properties', () => { - // check('ColumnWithPick', { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // propx: { type: 'number' } - // } - // }); - // }); - // - // it('should parse component with props with external type', () => { - // check('ColumnWithPropsWithExternalType', { - // ColumnWithPropsWithExternalType: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: 'MyExternalType' } - // } - // }); - // }); - // - // it('should parse HOCs', () => { - // check('ColumnHigherOrderComponent', { - // ColumnExternalHigherOrderComponent: { - // prop1: { type: 'string' } - // }, - // ColumnHigherOrderComponent1: { - // prop1: { type: 'string' } - // }, - // ColumnHigherOrderComponent2: { - // prop1: { type: 'string' } - // }, - // RowExternalHigherOrderComponent: { - // prop1: { type: 'string' } - // }, - // RowHigherOrderComponent1: { - // prop1: { type: 'string' } - // }, - // RowHigherOrderComponent2: { - // prop1: { type: 'string' } - // } - // }); - // }); - // - // it('should parse component with inherited properties HtmlAttributes', () => { - // check( - // 'ColumnWithHtmlAttributes', - // { - // Column: { - // // tslint:disable:object-literal-sort-keys - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // // HtmlAttributes - // defaultChecked: { - // type: 'boolean', - // required: false, - // description: '' - // } - // // ... - // // tslint:enable:object-literal-sort-keys - // } - // }, - // false - // ); - // }); - // - // it('should parse component without exported props interface', () => { - // check('ColumnWithoutExportedProps', { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' } - // } - // }); - // }); - // - // it('should parse functional component exported as const', () => { - // check( - // 'ConstExport', - // { - // Row: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' } - // }, - // // TODO: this wasn't there before, i would guess that that's correct - // test: {} - // }, - // false - // ); - // }); - // - // it('should parse react component with properties defined in external file', () => { - // check('ExternalPropsComponent', { - // ExternalPropsComponent: { - // prop1: { type: 'string' } - // } - // }); - // }); - // - // it('should parse static sub components', () => { - // check('StatelessStaticComponents', { - // StatelessStaticComponents: { - // myProp: { type: 'string' } - // }, - // 'StatelessStaticComponents.Label': { - // title: { type: 'string' } - // } - // }); - // }); - // - // it('should parse static sub components on class components', () => { - // check('ColumnWithStaticComponents', { - // Column: { - // prop1: { type: 'string' } - // }, - // 'Column.Label': { - // title: { type: 'string' } - // }, - // 'Column.SubLabel': {} - // }); - // }); - // - // it('should parse react component with properties extended from an external .tsx file', () => { - // check('ExtendsExternalPropsComponent', { - // ExtendsExternalPropsComponent: { - // prop1: { type: 'number', required: false, description: 'prop1' }, - // prop2: { type: 'string', required: false, description: 'prop2' } - // } - // }); - // }); - // - // it('should parse react component with properties defined as type', () => { - // check( - // 'FlippableImage', - // { - // FlippableImage: { - // isFlippedX: { type: 'boolean', required: false }, - // isFlippedY: { type: 'boolean', required: false } - // } - // }, - // false - // ); - // }); - // - // it('should parse react component with const definitions', () => { - // check('InlineConst', { - // MyComponent: { - // foo: { type: 'any' } - // } - // }); - // }); - // - // it('should parse default interface export', () => { - // check('ExportsDefaultInterface', { - // Component: { - // foo: { type: 'any' } - // } - // }); - // }); - // - // it('should parse react component that exports a prop type const', () => { - // check('ExportsPropTypeShape', { - // ExportsPropTypes: { - // foo: { type: 'any' } - // } - // }); - // }); - // - // it('should parse react component that exports a prop type thats imported', () => { - // check('ExportsPropTypeImport', { - // ExportsPropTypes: { - // foo: { type: 'any' } - // } - // }); - // }); - // - // // see issue #132 (https://github.com/styleguidist/react-docgen-typescript/issues/132) - // it('should determine the parent fileName relative to the project directory', () => { - // check( - // 'ExportsPropTypeImport', - // { - // ExportsPropTypes: { - // foo: { - // parent: { - // fileName: - // 'react-docgen-typescript/src/__tests__/data/ExportsPropTypeImport.tsx', - // name: 'ExportsPropTypesProps' - // }, - // type: 'any' - // } as any - // } - // }, - // true - // ); - // }); - // - // describe('component with default props', () => { - // const expectation = { - // ComponentWithDefaultProps: { - // sampleDefaultFromJSDoc: { - // defaultValue: 'hello', - // description: 'sample with default value', - // required: true, - // type: '"hello" | "goodbye"' - // }, - // sampleFalse: { - // defaultValue: false, - // required: false, - // type: 'boolean' - // }, - // sampleNull: { type: 'null', required: false, defaultValue: null }, - // sampleNumber: { type: 'number', required: false, defaultValue: -1 }, - // sampleObject: { - // defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, - // required: false, - // type: '{ [key: string]: any; }' - // }, - // sampleString: { - // defaultValue: 'hello', - // required: false, - // type: 'string' - // }, - // sampleTrue: { type: 'boolean', required: false, defaultValue: true }, - // sampleUndefined: { - // defaultValue: 'undefined', - // required: false, - // type: 'any' - // } - // } - // }; - // - // it('should parse defined props', () => { - // check('ComponentWithDefaultProps', expectation); - // }); - // - // it('should parse referenced props', () => { - // check('ComponentWithReferencedDefaultProps', expectation); - // }); - // }); - // - // describe('component with @type jsdoc tag', () => { - // const expectation = { - // ComponentWithTypeJsDocTag: { - // sampleTypeFromJSDoc: { - // description: 'sample with custom type', - // required: true, - // type: 'string' - // } - // } - // }; - // - // it('should parse defined props', () => { - // check('ComponentWithTypeJsDocTag', expectation); - // }); - // }); - // - // it('should parse react PureComponent', () => { - // check('PureRow', { - // Row: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' } - // } - // }); - // }); - // - // it('should parse react PureComponent - regression test', () => { - // check( - // 'Regression_v0_0_12', - // { - // Zoomable: { - // originX: { type: 'number' }, - // originY: { type: 'number' }, - // scaleFactor: { type: 'number' } - // } - // }, - // false - // ); - // }); - // - // it('should parse react functional component', () => { - // check('Row', { - // Row: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' } - // } - // }); - // }); - // - // it('should parse react stateless component', () => { - // check('Stateless', { - // Stateless: { - // myProp: { type: 'string' } - // } - // }); - // }); - // - // it('should get name for default export', () => { - // check( - // 'ForwardRefDefaultExport', - // { - // ForwardRefDefaultExport: { - // myProp: { type: 'string' } - // } - // }, - // false - // ); - // }); - // - // it('should get name for default export 2', () => { - // check( - // 'ForwardRefDefaultExportAtExport', - // { - // ForwardRefDefaultExport: { - // myProp: { type: 'string' } - // } - // }, - // false - // ); - // }); - // - // it('should component where last line is a comment', () => { - // check('ExportObject', { - // Baz: { - // baz: { description: '', type: 'string' } - // }, - // Bar: { - // foo: { description: '', type: 'string' } - // }, - // FooBar: { - // foobar: { description: '', type: 'string' } - // } - // }); - // }); - // - // it('should parse react stateless component with intersection props', () => { - // check('StatelessIntersectionProps', { - // StatelessIntersectionProps: { - // moreProp: { type: 'number' }, - // myProp: { type: 'string' } - // } - // }); - // }); - // - // it('should parse react stateless component default props when declared as a normal function', () => { - // check('FunctionDeclarationDefaultProps', { - // FunctionDeclarationDefaultProps: { - // id: { - // defaultValue: 1, - // description: '', - // required: false, - // type: 'number' - // } - // } - // }); - // }); - // - // it('should parse react stateless component default props when declared as a normal function inside forwardRef', () => { - // check( - // 'ForwardRefDefaultValues', - // { - // ForwardRefDefaultValues: { - // myProp: { - // defaultValue: "I'm default", - // description: 'myProp description', - // type: 'string', - // required: false - // } - // } - // }, - // false, - // 'ForwardRefDefaultValues description' - // ); - // }); - // - // it('should parse react stateless component with external intersection props', () => { - // check('StatelessIntersectionExternalProps', { - // StatelessIntersectionExternalProps: { - // myProp: { type: 'string' }, - // prop1: { type: 'string', required: false } - // } - // }); - // }); - // - // it('should parse react stateless component with generic intersection props', () => { - // check('StatelessIntersectionGenericProps', { - // StatelessIntersectionGenericProps: { - // myProp: { type: 'string' } - // } - // }); - // }); - // - // it('should parse react stateless component with intersection + union props', () => { - // check('SimpleUnionIntersection', { - // SimpleUnionIntersection: { - // bar: { type: 'string', description: '' }, - // baz: { type: 'string', description: '' }, - // foo: { type: 'string', description: '' } - // } - // }); - // }); - // - // it('should parse react stateless component with intersection + union overlap props', () => { - // check('SimpleDiscriminatedUnionIntersection', { - // SimpleDiscriminatedUnionIntersection: { - // bar: { type: '"one" | "other"', description: '' }, - // baz: { type: 'number', description: '' }, - // foo: { type: 'string', description: '' }, - // test: { type: 'number', description: '' } - // } - // }); - // }); - // - // it('should parse react stateless component with generic intersection + union overlap props - simple', () => { - // check('SimpleGenericUnionIntersection', { - // SimpleGenericUnionIntersection: { - // as: { type: 'any', description: '' }, - // foo: { - // description: - // 'The foo prop should not repeat the description\nThe foo prop should not repeat the description', - // required: false, - // type: '"red" | "blue"' - // }, - // gap: { - // description: - // 'The space between children\nYou cannot use gap when using a "space" justify property', - // required: false, - // type: 'number' - // }, - // hasWrap: { type: 'boolean', description: '', required: false } - // } - // }); - // }); - // - // it('should parse react stateless component with generic intersection + union overlap props', () => { - // check('ComplexGenericUnionIntersection', { - // ComplexGenericUnionIntersection: { - // as: { - // type: 'ElementType', - // required: false, - // description: 'Render the component as another component' - // }, - // align: { - // description: 'The flex "align" property', - // required: false, - // type: '"stretch" | "center" | "flex-start" | "flex-end"' - // }, - // justify: { - // description: - // "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", - // required: false, - // type: - // '"stretch" | "center" | "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly"' - // }, - // gap: { - // description: - // 'The space between children\nYou cannot use gap when using a "space" justify property', - // required: false, - // type: 'ReactText' - // } - // } - // }); - // }); - // - // it('should parse react stateless component with generic intersection + union + omit overlap props', () => { - // check('ComplexGenericUnionIntersectionWithOmit', { - // ComplexGenericUnionIntersectionWithOmit: { - // as: { - // type: 'ElementType', - // required: false, - // description: 'Render the component as another component' - // }, - // align: { - // description: 'The flex "align" property', - // required: false, - // type: '"center" | "flex-start" | "flex-end" | "stretch"' - // }, - // justify: { - // description: - // "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", - // required: false, - // type: - // '"center" | "flex-start" | "flex-end" | "stretch" | "space-between" | "space-around" | "space-evenly"' - // }, - // gap: { - // description: - // 'The space between children\nYou cannot use gap when using a "space" justify property', - // required: false, - // type: 'ReactText' - // } - // } - // }); - // }); - // - // it('should parse react stateful component with intersection props', () => { - // check('StatefulIntersectionProps', { - // StatefulIntersectionProps: { - // moreProp: { type: 'number' }, - // myProp: { type: 'string' } - // } - // }); - // }); - // - // it('should parse react stateful component with external intersection props', () => { - // check('StatefulIntersectionExternalProps', { - // StatefulIntersectionExternalProps: { - // myProp: { type: 'string' }, - // prop1: { type: 'string', required: false } - // } - // }); - // }); - // - // it('should parse react stateful component (wrapped in HOC) with intersection props', () => { - // check('HOCIntersectionProps', { - // HOCIntersectionProps: { - // injected: { type: 'boolean' }, - // myProp: { type: 'string' } - // } - // }); - // }); - // - // describe('stateless component with default props', () => { - // const expectation = { - // StatelessWithDefaultProps: { - // sampleDefaultFromJSDoc: { - // defaultValue: 'hello', - // description: 'sample with default value', - // required: true, - // type: '"hello" | "goodbye"' - // }, - // sampleEnum: { - // defaultValue: 'enumSample.HELLO', - // required: false, - // type: 'enumSample' - // }, - // sampleFalse: { - // defaultValue: false, - // required: false, - // type: 'boolean' - // }, - // sampleNull: { type: 'null', required: false, defaultValue: null }, - // sampleNumber: { type: 'number', required: false, defaultValue: -1 }, - // sampleObject: { - // defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, - // required: false, - // type: '{ [key: string]: any; }' - // }, - // sampleString: { - // defaultValue: 'hello', - // required: false, - // type: 'string' - // }, - // sampleTrue: { type: 'boolean', required: false, defaultValue: true }, - // sampleUndefined: { - // defaultValue: undefined, - // required: false, - // type: 'any' - // } - // } - // }; - // - // it('should parse defined props', () => { - // check('StatelessWithDefaultProps', expectation); - // }); - // - // it('should parse props with shorthands', () => { - // check('StatelessShorthandDefaultProps', { - // StatelessShorthandDefaultProps: { - // onCallback: { - // defaultValue: null, - // description: 'onCallback description', - // required: false, - // type: '() => void' - // }, - // regularProp: { - // defaultValue: 'foo', - // description: 'regularProp description', - // required: false, - // type: 'string' - // }, - // shorthandProp: { - // defaultValue: 123, - // description: 'shorthandProp description', - // required: false, - // type: 'number' - // } - // } - // }); - // }); - // - // it('supports destructuring', () => { - // check('StatelessWithDestructuredProps', expectation); - // }); - // - // it('supports destructuring for arrow functions', () => { - // check('StatelessWithDestructuredPropsArrow', expectation); - // }); - // - // it('supports typescript 3.0 style defaulted props', () => { - // check('StatelessWithDefaultPropsTypescript3', expectation); - // }); - // }); - // - // it('should parse components with unioned types', () => { - // check('OnlyDefaultExportUnion', { - // OnlyDefaultExportUnion: { - // content: { description: 'The content', type: 'string' } - // } - // }); - // }); - // - // it('should parse jsdocs with the @default tag and no description', () => { - // check('StatelessWithDefaultOnlyJsDoc', { - // StatelessWithDefaultOnlyJsDoc: { - // myProp: { defaultValue: 'hello', description: '', type: 'string' } - // } - // }); - // }); - // - // it('should parse functional component component defined as function', () => { - // check('FunctionDeclaration', { - // Jumbotron: { - // prop1: { type: 'string', required: true } - // } - // }); - // }); - // - // it('should parse functional component component defined as const', () => { - // check('FunctionalComponentAsConst', { - // Jumbotron: { - // prop1: { type: 'string', required: true } - // } - // }); - // }); + it('should parse simple react class component', () => { + check('Column', { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }); + }); + + it('should parse simple react class component with console.log inside', () => { + check('ColumnWithLog', { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }); + }); + + it('should parse simple react class component as default export', () => { + check('ColumnWithDefaultExport', { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }); + }); + + it('should parse mulitple files', () => { + const result = parse([ + fixturePath('Column'), + fixturePath('ColumnWithDefaultExportOnly') + ]); + + checkComponent( + result, + { + Column: {}, + ColumnWithDefaultExportOnly: {} + }, + false + ); + }); + + it('should parse simple react class component as default export only', () => { + check('ColumnWithDefaultExportOnly', { + ColumnWithDefaultExportOnly: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }); + }); + + it('should parse simple react class component as default anonymous export', () => { + check('ColumnWithDefaultAnonymousExportOnly', { + ColumnWithDefaultAnonymousExportOnly: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }); + }); + + it('should parse simple react class component with state', () => { + check('AppMenu', { + AppMenu: { + menu: { type: 'any' } + } + }); + }); + + it('should parse simple react class component with picked properties', () => { + check('ColumnWithPick', { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + propx: { type: 'number' } + } + }); + }); + + it('should parse component with props with external type', () => { + check('ColumnWithPropsWithExternalType', { + ColumnWithPropsWithExternalType: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: 'MyExternalType' } + } + }); + }); + + it('should parse HOCs', () => { + check('ColumnHigherOrderComponent', { + ColumnExternalHigherOrderComponent: { + prop1: { type: 'string' } + }, + ColumnHigherOrderComponent1: { + prop1: { type: 'string' } + }, + ColumnHigherOrderComponent2: { + prop1: { type: 'string' } + }, + RowExternalHigherOrderComponent: { + prop1: { type: 'string' } + }, + RowHigherOrderComponent1: { + prop1: { type: 'string' } + }, + RowHigherOrderComponent2: { + prop1: { type: 'string' } + } + }); + }); + + it('should parse component with inherited properties HtmlAttributes', () => { + check( + 'ColumnWithHtmlAttributes', + { + Column: { + // tslint:disable:object-literal-sort-keys + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + // HtmlAttributes + defaultChecked: { + type: 'boolean', + required: false, + description: '' + } + // ... + // tslint:enable:object-literal-sort-keys + } + }, + false + ); + }); + + it('should parse component without exported props interface', () => { + check('ColumnWithoutExportedProps', { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' } + } + }); + }); + + it('should parse functional component exported as const', () => { + check( + 'ConstExport', + { + Row: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' } + }, + // TODO: this wasn't there before, i would guess that that's correct + test: {} + }, + false + ); + }); + + it('should parse react component with properties defined in external file', () => { + check('ExternalPropsComponent', { + ExternalPropsComponent: { + prop1: { type: 'string' } + } + }); + }); + + it('should parse static sub components', () => { + check('StatelessStaticComponents', { + StatelessStaticComponents: { + myProp: { type: 'string' } + }, + 'StatelessStaticComponents.Label': { + title: { type: 'string' } + } + }); + }); + + it('should parse static sub components on class components', () => { + check('ColumnWithStaticComponents', { + Column: { + prop1: { type: 'string' } + }, + 'Column.Label': { + title: { type: 'string' } + }, + 'Column.SubLabel': {} + }); + }); + + it('should parse react component with properties extended from an external .tsx file', () => { + check('ExtendsExternalPropsComponent', { + ExtendsExternalPropsComponent: { + prop1: { type: 'number', required: false, description: 'prop1' }, + prop2: { type: 'string', required: false, description: 'prop2' } + } + }); + }); + + it('should parse react component with properties defined as type', () => { + check( + 'FlippableImage', + { + FlippableImage: { + isFlippedX: { type: 'boolean', required: false }, + isFlippedY: { type: 'boolean', required: false } + } + }, + false + ); + }); + + it('should parse react component with const definitions', () => { + check('InlineConst', { + MyComponent: { + foo: { type: 'any' } + } + }); + }); + + it('should parse default interface export', () => { + check('ExportsDefaultInterface', { + Component: { + foo: { type: 'any' } + } + }); + }); + + it('should parse react component that exports a prop type const', () => { + check('ExportsPropTypeShape', { + ExportsPropTypes: { + foo: { type: 'any' } + } + }); + }); + + it('should parse react component that exports a prop type thats imported', () => { + check('ExportsPropTypeImport', { + ExportsPropTypes: { + foo: { type: 'any' } + } + }); + }); + + // see issue #132 (https://github.com/styleguidist/react-docgen-typescript/issues/132) + it('should determine the parent fileName relative to the project directory', () => { + check( + 'ExportsPropTypeImport', + { + ExportsPropTypes: { + foo: { + parent: { + fileName: + 'react-docgen-typescript/src/__tests__/data/ExportsPropTypeImport.tsx', + name: 'ExportsPropTypesProps' + }, + type: 'any' + } as any + } + }, + true + ); + }); + + describe('component with default props', () => { + const expectation = { + ComponentWithDefaultProps: { + sampleDefaultFromJSDoc: { + defaultValue: 'hello', + description: 'sample with default value', + required: true, + type: '"hello" | "goodbye"' + }, + sampleFalse: { + defaultValue: false, + required: false, + type: 'boolean' + }, + sampleNull: { type: 'null', required: false, defaultValue: null }, + sampleNumber: { type: 'number', required: false, defaultValue: -1 }, + sampleObject: { + defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, + required: false, + type: '{ [key: string]: any; }' + }, + sampleString: { + defaultValue: 'hello', + required: false, + type: 'string' + }, + sampleTrue: { type: 'boolean', required: false, defaultValue: true }, + sampleUndefined: { + defaultValue: 'undefined', + required: false, + type: 'any' + } + } + }; + + it('should parse defined props', () => { + check('ComponentWithDefaultProps', expectation); + }); + + it('should parse referenced props', () => { + check('ComponentWithReferencedDefaultProps', expectation); + }); + }); + + describe('component with @type jsdoc tag', () => { + const expectation = { + ComponentWithTypeJsDocTag: { + sampleTypeFromJSDoc: { + description: 'sample with custom type', + required: true, + type: 'string' + } + } + }; + + it('should parse defined props', () => { + check('ComponentWithTypeJsDocTag', expectation); + }); + }); + + it('should parse react PureComponent', () => { + check('PureRow', { + Row: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' } + } + }); + }); + + it('should parse react PureComponent - regression test', () => { + check( + 'Regression_v0_0_12', + { + Zoomable: { + originX: { type: 'number' }, + originY: { type: 'number' }, + scaleFactor: { type: 'number' } + } + }, + false + ); + }); + + it('should parse react functional component', () => { + check('Row', { + Row: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' } + } + }); + }); + + it('should parse react stateless component', () => { + check('Stateless', { + Stateless: { + myProp: { type: 'string' } + } + }); + }); + + it('should get name for default export', () => { + check( + 'ForwardRefDefaultExport', + { + ForwardRefDefaultExport: { + myProp: { type: 'string' } + } + }, + false + ); + }); + + it('should get name for default export 2', () => { + check( + 'ForwardRefDefaultExportAtExport', + { + ForwardRefDefaultExport: { + myProp: { type: 'string' } + } + }, + false + ); + }); + + it('should component where last line is a comment', () => { + check('ExportObject', { + Baz: { + baz: { description: '', type: 'string' } + }, + Bar: { + foo: { description: '', type: 'string' } + }, + FooBar: { + foobar: { description: '', type: 'string' } + } + }); + }); + + it('should parse react stateless component with intersection props', () => { + check('StatelessIntersectionProps', { + StatelessIntersectionProps: { + moreProp: { type: 'number' }, + myProp: { type: 'string' } + } + }); + }); + + it('should parse react stateless component default props when declared as a normal function', () => { + check('FunctionDeclarationDefaultProps', { + FunctionDeclarationDefaultProps: { + id: { + defaultValue: 1, + description: '', + required: false, + type: 'number' + } + } + }); + }); + + it('should parse react stateless component default props when declared as a normal function inside forwardRef', () => { + check( + 'ForwardRefDefaultValues', + { + ForwardRefDefaultValues: { + myProp: { + defaultValue: "I'm default", + description: 'myProp description', + type: 'string', + required: false + } + } + }, + false, + 'ForwardRefDefaultValues description' + ); + }); + + it('should parse react stateless component with external intersection props', () => { + check('StatelessIntersectionExternalProps', { + StatelessIntersectionExternalProps: { + myProp: { type: 'string' }, + prop1: { type: 'string', required: false } + } + }); + }); + + it('should parse react stateless component with generic intersection props', () => { + check('StatelessIntersectionGenericProps', { + StatelessIntersectionGenericProps: { + myProp: { type: 'string' } + } + }); + }); + + it('should parse react stateless component with intersection + union props', () => { + check('SimpleUnionIntersection', { + SimpleUnionIntersection: { + bar: { type: 'string', description: '' }, + baz: { type: 'string', description: '' }, + foo: { type: 'string', description: '' } + } + }); + }); + + it('should parse react stateless component with intersection + union overlap props', () => { + check('SimpleDiscriminatedUnionIntersection', { + SimpleDiscriminatedUnionIntersection: { + bar: { type: '"one" | "other"', description: '' }, + baz: { type: 'number', description: '' }, + foo: { type: 'string', description: '' }, + test: { type: 'number', description: '' } + } + }); + }); + + it('should parse react stateless component with generic intersection + union overlap props - simple', () => { + check('SimpleGenericUnionIntersection', { + SimpleGenericUnionIntersection: { + as: { type: 'any', description: '' }, + foo: { + description: + 'The foo prop should not repeat the description\nThe foo prop should not repeat the description', + required: false, + type: '"red" | "blue"' + }, + gap: { + description: + 'The space between children\nYou cannot use gap when using a "space" justify property', + required: false, + type: 'number' + }, + hasWrap: { type: 'boolean', description: '', required: false } + } + }); + }); + + it('should parse react stateless component with generic intersection + union overlap props', () => { + check('ComplexGenericUnionIntersection', { + ComplexGenericUnionIntersection: { + as: { + type: 'ElementType', + required: false, + description: 'Render the component as another component' + }, + align: { + description: 'The flex "align" property', + required: false, + type: '"stretch" | "center" | "flex-start" | "flex-end"' + }, + justify: { + description: + "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", + required: false, + type: + '"stretch" | "center" | "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly"' + }, + gap: { + description: + 'The space between children\nYou cannot use gap when using a "space" justify property', + required: false, + type: 'ReactText' + } + } + }); + }); + + it('should parse react stateless component with generic intersection + union + omit overlap props', () => { + check('ComplexGenericUnionIntersectionWithOmit', { + ComplexGenericUnionIntersectionWithOmit: { + as: { + type: 'ElementType', + required: false, + description: 'Render the component as another component' + }, + align: { + description: 'The flex "align" property', + required: false, + type: '"center" | "flex-start" | "flex-end" | "stretch"' + }, + justify: { + description: + "Use flex 'center' | 'flex-start' | 'flex-end' | 'stretch' with\na gap between each child.\nUse flex 'space-between' | 'space-around' | 'space-evenly' and\nflex will space the children.", + required: false, + type: + '"center" | "flex-start" | "flex-end" | "stretch" | "space-between" | "space-around" | "space-evenly"' + }, + gap: { + description: + 'The space between children\nYou cannot use gap when using a "space" justify property', + required: false, + type: 'ReactText' + } + } + }); + }); + + it('should parse react stateful component with intersection props', () => { + check('StatefulIntersectionProps', { + StatefulIntersectionProps: { + moreProp: { type: 'number' }, + myProp: { type: 'string' } + } + }); + }); + + it('should parse react stateful component with external intersection props', () => { + check('StatefulIntersectionExternalProps', { + StatefulIntersectionExternalProps: { + myProp: { type: 'string' }, + prop1: { type: 'string', required: false } + } + }); + }); + + it('should parse react stateful component (wrapped in HOC) with intersection props', () => { + check('HOCIntersectionProps', { + HOCIntersectionProps: { + injected: { type: 'boolean' }, + myProp: { type: 'string' } + } + }); + }); + + describe('stateless component with default props', () => { + const expectation = { + StatelessWithDefaultProps: { + sampleDefaultFromJSDoc: { + defaultValue: 'hello', + description: 'sample with default value', + required: true, + type: '"hello" | "goodbye"' + }, + sampleEnum: { + defaultValue: 'enumSample.HELLO', + required: false, + type: 'enumSample' + }, + sampleFalse: { + defaultValue: false, + required: false, + type: 'boolean' + }, + sampleNull: { type: 'null', required: false, defaultValue: null }, + sampleNumber: { type: 'number', required: false, defaultValue: -1 }, + sampleObject: { + defaultValue: `{ a: '1', b: 2, c: true, d: false, e: undefined, f: null, g: { a: '1' } }`, + required: false, + type: '{ [key: string]: any; }' + }, + sampleString: { + defaultValue: 'hello', + required: false, + type: 'string' + }, + sampleTrue: { type: 'boolean', required: false, defaultValue: true }, + sampleUndefined: { + defaultValue: undefined, + required: false, + type: 'any' + } + } + }; + + it('should parse defined props', () => { + check('StatelessWithDefaultProps', expectation); + }); + + it('should parse props with shorthands', () => { + check('StatelessShorthandDefaultProps', { + StatelessShorthandDefaultProps: { + onCallback: { + defaultValue: null, + description: 'onCallback description', + required: false, + type: '() => void' + }, + regularProp: { + defaultValue: 'foo', + description: 'regularProp description', + required: false, + type: 'string' + }, + shorthandProp: { + defaultValue: 123, + description: 'shorthandProp description', + required: false, + type: 'number' + } + } + }); + }); + + it('supports destructuring', () => { + check('StatelessWithDestructuredProps', expectation); + }); + + it('supports destructuring for arrow functions', () => { + check('StatelessWithDestructuredPropsArrow', expectation); + }); + + it('supports typescript 3.0 style defaulted props', () => { + check('StatelessWithDefaultPropsTypescript3', expectation); + }); + }); + + it('should parse components with unioned types', () => { + check('OnlyDefaultExportUnion', { + OnlyDefaultExportUnion: { + content: { description: 'The content', type: 'string' } + } + }); + }); + + it('should parse jsdocs with the @default tag and no description', () => { + check('StatelessWithDefaultOnlyJsDoc', { + StatelessWithDefaultOnlyJsDoc: { + myProp: { defaultValue: 'hello', description: '', type: 'string' } + } + }); + }); + + it('should parse functional component component defined as function', () => { + check('FunctionDeclaration', { + Jumbotron: { + prop1: { type: 'string', required: true } + } + }); + }); + + it('should parse functional component component defined as const', () => { + check('FunctionalComponentAsConst', { + Jumbotron: { + prop1: { type: 'string', required: true } + } + }); + }); it('should parse functional component defined as const with default value assignments in immediately destructured props', () => { check('FunctionalComponentWithDesctructuredProps', { @@ -788,849 +787,849 @@ describe('parser', () => { }); }); - // it('should parse functional component component defined as const', () => { - // check('FunctionDeclarationVisibleName', { - // 'Awesome Jumbotron': { - // prop1: { type: 'string', required: true } - // } - // }); - // }); - // - // it('should parse React.SFC component defined as const', () => { - // check('ReactSFCAsConst', { - // Jumbotron: { - // prop1: { type: 'string', required: true } - // } - // }); - // }); - // - // it('should parse functional component component defined as function as default export', () => { - // check('FunctionDeclarationAsDefaultExport', { - // Jumbotron: { - // prop1: { type: 'string', required: true } - // } - // }); - // }); - // - // it('should parse functional component component thats been wrapped in React.memo', () => { - // check('FunctionDeclarationAsDefaultExportWithMemo', { - // Jumbotron: { - // prop1: { type: 'string', required: true } - // } - // }); - // }); - // - // it('should parse JSDoc correctly', () => { - // check( - // 'JSDocWithParam', - // { - // JSDocWithParam: { - // prop1: { type: 'string', required: true } - // } - // }, - // true, - // 'JSDocWithParamProps description\n\nNOTE: If a parent element of this control is `overflow: hidden` then the\nballoon may not show up.' - // ); - // }); - // - // it('should parse functional component component defined as const as default export', () => { - // check( - // 'FunctionalComponentAsConstAsDefaultExport', - // { - // // in this case the component name is taken from the file name - // FunctionalComponentAsConstAsDefaultExport: { - // prop1: { type: 'string', required: true } - // } - // }, - // true, - // 'Jumbotron description' - // ); - // }); - // - // it('should parse React.SFC component defined as const as default export', () => { - // check( - // 'ReactSFCAsConstAsDefaultExport', - // { - // // in this case the component name is taken from the file name - // ReactSFCAsConstAsDefaultExport: { - // prop1: { type: 'string', required: true } - // } - // }, - // true, - // 'Jumbotron description' - // ); - // }); - // - // it('should parse functional component component defined as const as named export', () => { - // check( - // 'FunctionalComponentAsConstAsNamedExport', - // { - // // in this case the component name is taken from the file name - // Jumbotron: { - // prop1: { type: 'string', required: true } - // } - // }, - // true, - // 'Jumbotron description' - // ); - // }); - // - // it('should parse React.SFC component defined as const as named export', () => { - // check( - // 'ReactSFCAsConstAsNamedExport', - // { - // // in this case the component name is taken from the file name - // Jumbotron: { - // prop1: { type: 'string', required: true } - // } - // }, - // true, - // 'Jumbotron description' - // ); - // }); - // - // describe('displayName', () => { - // it('should be taken from stateless component `displayName` property (using named export)', () => { - // const [parsed] = parse(fixturePath('StatelessDisplayName')); - // assert.equal(parsed.displayName, 'StatelessDisplayName'); - // }); - // - // it('should be taken from stateful component `displayName` property (using named export)', () => { - // const [parsed] = parse(fixturePath('StatefulDisplayName')); - // assert.equal(parsed.displayName, 'StatefulDisplayName'); - // }); - // - // it('should be taken from stateless component `displayName` property (using default export)', () => { - // const [parsed] = parse(fixturePath('StatelessDisplayNameDefaultExport')); - // assert.equal(parsed.displayName, 'StatelessDisplayNameDefaultExport'); - // }); - // - // it('should be taken from stateful component `displayName` property (using default export)', () => { - // const [parsed] = parse(fixturePath('StatefulDisplayNameDefaultExport')); - // assert.equal(parsed.displayName, 'StatefulDisplayNameDefaultExport'); - // }); - // - // it('should be taken from named export when default export is an HOC', () => { - // const [parsed] = parse(fixturePath('StatelessDisplayNameHOC')); - // assert.equal(parsed.displayName, 'StatelessDisplayName'); - // }); - // - // it('should be taken from named export when default export is an HOC', () => { - // const [parsed] = parse(fixturePath('StatefulDisplayNameHOC')); - // assert.equal(parsed.displayName, 'StatefulDisplayName'); - // }); - // - // it('should be taken from stateless component folder name if file name is "index"', () => { - // const [parsed] = parse(fixturePath('StatelessDisplayNameFolder/index')); - // assert.equal(parsed.displayName, 'StatelessDisplayNameFolder'); - // }); - // - // it('should be taken from stateful component folder name if file name is "index"', () => { - // const [parsed] = parse(fixturePath('StatefulDisplayNameFolder/index')); - // assert.equal(parsed.displayName, 'StatefulDisplayNameFolder'); - // }); - // }); - // - // describe('Parser options', () => { - // describe('Property filtering', () => { - // describe('children', () => { - // it('should ignore property "children" if not explicitly documented', () => { - // check( - // 'Column', - // { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }, - // true - // ); - // }); - // - // it('should not ignore any property that is documented explicitly', () => { - // check( - // 'ColumnWithAnnotatedChildren', - // { - // Column: { - // children: { - // description: 'children description', - // required: false, - // type: 'ReactNode' - // }, - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }, - // true - // ); - // }); - // }); - // - // describe('propsFilter method', () => { - // it('should apply filter function and filter components accordingly', () => { - // const propFilter: PropFilter = (prop, component) => - // prop.name !== 'prop1'; - // check( - // 'Column', - // { - // Column: { - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }, - // true, - // undefined, - // { propFilter } - // ); - // }); - // - // it('should apply filter function and filter components accordingly', () => { - // const propFilter: PropFilter = (prop, component) => { - // if (component.name === 'Column') { - // return prop.name !== 'prop1'; - // } - // return true; - // }; - // check( - // 'Column', - // { - // Column: { - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }, - // true, - // undefined, - // { propFilter } - // ); - // check( - // 'AppMenu', - // { - // AppMenu: { - // menu: { type: 'any' } - // } - // }, - // true, - // undefined, - // { propFilter } - // ); - // }); - // - // it('should allow filtering by parent interface', () => { - // const propFilter: PropFilter = (prop, component) => { - // if (prop.parent == null) { - // return true; - // } - // - // return ( - // prop.parent.fileName.indexOf('@types/react') < 0 && - // prop.parent.name !== 'HTMLAttributes' - // ); - // }; - // - // check( - // 'ColumnWithHtmlAttributes', - // { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' } - // } - // }, - // true, - // undefined, - // { propFilter } - // ); - // }); - // }); - // - // it('should collect all `onClick prop` parent declarations', done => { - // assert.doesNotThrow(() => { - // withDefaultConfig({ - // propFilter: prop => { - // if (prop.name === 'onClick') { - // assert.deepEqual(prop.declarations, [ - // { - // fileName: - // 'react-docgen-typescript/node_modules/@types/react/index.d.ts', - // name: 'DOMAttributes' - // }, - // { - // fileName: - // 'react-docgen-typescript/src/__tests__/data/ButtonWithOnClickComponent.tsx', - // name: 'TypeLiteral' - // } - // ]); - // - // done(); - // } - // - // return true; - // } - // }).parse(fixturePath('ButtonWithOnClickComponent')); - // }); - // }); - // - // it('should allow filtering by parent declarations', () => { - // const propFilter: PropFilter = prop => { - // if (prop.declarations !== undefined && prop.declarations.length > 0) { - // const hasPropAdditionalDescription = prop.declarations.find( - // declaration => { - // return !declaration.fileName.includes('@types/react'); - // } - // ); - // - // return Boolean(hasPropAdditionalDescription); - // } - // - // return true; - // }; - // - // check( - // 'ButtonWithOnClickComponent', - // { - // ButtonWithOnClickComponent: { - // onClick: { - // type: - // '(event: MouseEvent) => void', - // required: false, - // description: 'onClick event handler' - // } - // } - // }, - // true, - // '', - // { - // propFilter - // } - // ); - // }); - // - // describe('skipPropsWithName', () => { - // it('should skip a single property in skipPropsWithName', () => { - // const propFilter = { skipPropsWithName: 'prop1' }; - // check( - // 'Column', - // { - // Column: { - // prop2: { type: 'number' }, - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }, - // true, - // undefined, - // { propFilter } - // ); - // }); - // - // it('should skip multiple properties in skipPropsWithName', () => { - // const propFilter = { skipPropsWithName: ['prop1', 'prop2'] }; - // check( - // 'Column', - // { - // Column: { - // prop3: { type: '() => void' }, - // prop4: { type: '"option1" | "option2" | "option3"' } - // } - // }, - // true, - // undefined, - // { propFilter } - // ); - // }); - // }); - // - // describe('skipPropsWithoutDoc', () => { - // it('should skip a properties without documentation', () => { - // const propFilter = { skipPropsWithoutDoc: false }; - // check( - // 'ColumnWithUndocumentedProps', - // { - // Column: { - // prop1: { type: 'string', required: false }, - // prop2: { type: 'number' } - // } - // }, - // true, - // undefined, - // { propFilter } - // ); - // }); - // }); - // }); - // - // it('should defaultProps in variable', () => { - // check('SeparateDefaultProps', { - // SeparateDefaultProps: { - // disabled: { - // description: '', - // required: false, - // defaultValue: false, - // type: 'boolean' - // }, - // id: { - // description: '', - // required: false, - // defaultValue: 123, - // type: 'number' - // } - // } - // }); - // }); - // - // it('should defaultProps accessed variable', () => { - // check('SeparateDefaultPropsIndividual', { - // SeparateDefaultPropsIndividual: { - // disabled: { - // description: '', - // required: false, - // defaultValue: false, - // type: 'boolean' - // }, - // id: { - // description: '', - // required: false, - // defaultValue: 123, - // type: 'number' - // } - // } - // }); - // }); - // - // describe('Extracting literal values from enums', () => { - // it('extracts literal values from enum', () => { - // check( - // 'ExtractLiteralValuesFromEnum', - // { - // ExtractLiteralValuesFromEnum: { - // sampleBoolean: { type: 'boolean' }, - // sampleEnum: { - // raw: 'sampleEnum', - // type: 'enum', - // value: [ - // { value: '"one"' }, - // { value: '"two"' }, - // { value: '"three"' } - // ] - // }, - // sampleString: { type: 'string' } - // } - // }, - // true, - // null, - // { - // shouldExtractLiteralValuesFromEnum: true - // } - // ); - // }); - // - // it('Should infer types from constraint type (generic with extends)', () => { - // check( - // 'GenericWithExtends', - // { - // GenericWithExtends: { - // sampleUnionProp: { - // raw: 'SampleUnion', - // type: 'enum', - // value: [ - // { - // value: '"value 1"' - // }, - // { - // value: '"value 2"' - // }, - // { - // value: '"value 3"' - // }, - // { - // value: '"value 4"' - // }, - // { - // value: '"value n"' - // } - // ] - // }, - // sampleEnumProp: { - // raw: 'SampleEnum', - // type: 'enum', - // value: [ - // { - // value: '0' - // }, - // { - // value: '1' - // }, - // { - // value: '"c"' - // } - // ] - // }, - // sampleUnionNonGeneric: { - // type: 'SampleUnionNonGeneric' - // }, - // sampleObjectProp: { - // type: 'SampleObject' - // }, - // sampleNumberProp: { - // type: 'number' - // }, - // sampleGenericArray: { - // type: 'number[]' - // }, - // sampleGenericObject: { - // type: '{ prop1: number; }' - // }, - // sampleInlineObject: { - // type: '{ propA: string; }' - // } - // } - // }, - // true, - // null, - // { shouldExtractLiteralValuesFromEnum: true } - // ); - // }); - // }); - // - // describe('Extracting values from unions', () => { - // it('extracts all values from union', () => { - // check( - // 'ExtractLiteralValuesFromUnion', - // { - // ExtractLiteralValuesFromUnion: { - // sampleComplexUnion: { - // raw: 'number | "string1" | "string2"', - // type: 'enum', - // value: [ - // { value: 'number' }, - // { value: '"string1"' }, - // { value: '"string2"' } - // ] - // } - // } - // }, - // false, - // null, - // { - // shouldExtractValuesFromUnion: true - // } - // ); - // }); - // it('extracts numbers from a union', () => { - // check( - // 'ExtractLiteralValuesFromUnion', - // { - // ExtractLiteralValuesFromUnion: { - // sampleNumberUnion: { - // raw: '1 | 2 | 3', - // type: 'enum', - // value: [{ value: '1' }, { value: '2' }, { value: '3' }] - // } - // } - // }, - // false, - // null, - // { - // shouldExtractValuesFromUnion: true - // } - // ); - // }); - // it('extracts numbers and strings from a mixed union', () => { - // check( - // 'ExtractLiteralValuesFromUnion', - // { - // ExtractLiteralValuesFromUnion: { - // sampleMixedUnion: { - // raw: '"string1" | "string2" | 1 | 2', - // type: 'enum', - // value: [ - // { value: '"string1"' }, - // { value: '"string2"' }, - // { value: '1' }, - // { value: '2' } - // ] - // } - // } - // }, - // false, - // null, - // { - // shouldExtractValuesFromUnion: true - // } - // ); - // }); - // }); - // - // describe('Returning not string default props ', () => { - // it('returns not string defaultProps', () => { - // check( - // 'StatelessWithDefaultPropsAsString', - // { - // StatelessWithDefaultPropsAsString: { - // sampleFalse: { - // defaultValue: 'false', - // required: false, - // type: 'boolean' - // }, - // sampleNull: { - // defaultValue: 'null', - // required: false, - // type: 'null' - // }, - // sampleNumber: { - // defaultValue: '1', - // required: false, - // type: 'number' - // }, - // sampleNumberWithPrefix: { - // defaultValue: '-1', - // required: false, - // type: 'number' - // }, - // sampleTrue: { - // defaultValue: 'true', - // required: false, - // type: 'boolean' - // }, - // sampleUndefined: { - // defaultValue: 'undefined', - // required: false, - // type: 'undefined' - // } - // } - // }, - // true, - // null, - // { - // savePropValueAsString: true - // } - // ); - // }); - // }); - // describe("Extract prop's JSDoc/TSDoc tags", () => { - // it('should extract all prop JSDoc/TSDoc tags', () => { - // check( - // 'ExtractPropTags', - // { - // ExtractPropTags: { - // prop1: { - // type: 'Pick', - // required: false, - // tags: { - // ignore: 'ignoreMe', - // kind: 'category 2', - // custom123: 'something' - // } - // }, - // prop2: { - // type: 'string', - // tags: { internal: 'some internal prop', kind: 'category 1' } - // } - // } - // }, - // true, - // null, - // { shouldIncludePropTagMap: true } - // ); - // }); - // }); - // }); - // - // describe('withCustomConfig', () => { - // it('should accept tsconfigs that typescript accepts', () => { - // assert.ok( - // withCustomConfig( - // // need to navigate to root because tests run on compiled tests - // // and tsc does not include json files - // path.join(__dirname, '../../src/__tests__/data/tsconfig.json'), - // {} - // ) - // ); - // }); - // }); - // - // describe('parseWithProgramProvider', () => { - // it('should accept existing ts.Program instance', () => { - // let programProviderInvoked = false; - // - // // mimic a third party library providing a ts.Program instance. - // const programProvider = () => { - // // need to navigate to root because tests run on compiled tests - // // and tsc does not include json files - // const tsconfigPath = path.join( - // __dirname, - // '../../src/__tests__/data/tsconfig.json' - // ); - // const basePath = path.dirname(tsconfigPath); - // - // const { config, error } = ts.readConfigFile(tsconfigPath, filename => - // fs.readFileSync(filename, 'utf8') - // ); - // assert.isUndefined(error); - // - // const { options, errors } = ts.parseJsonConfigFileContent( - // config, - // ts.sys, - // basePath, - // {}, - // tsconfigPath - // ); - // assert.lengthOf(errors, 0); - // - // programProviderInvoked = true; - // - // return ts.createProgram([fixturePath('Column')], options); - // }; - // - // const result = withDefaultConfig().parseWithProgramProvider( - // [fixturePath('Column')], - // programProvider - // ); - // - // checkComponent( - // result, - // { - // Column: {} - // }, - // false - // ); - // assert.isTrue(programProviderInvoked); - // }); - // }); - // - // describe('componentNameResolver', () => { - // it('should override default behavior', () => { - // const [parsed] = parse( - // fixturePath('StatelessDisplayNameStyledComponent'), - // { - // componentNameResolver: (exp, source) => - // exp.getName() === 'StyledComponentClass' && - // getDefaultExportForFile(source) - // } - // ); - // assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); - // }); - // - // it('should fallback to default behavior without a match', () => { - // const [parsed] = parse( - // fixturePath('StatelessDisplayNameStyledComponent'), - // { - // componentNameResolver: () => false - // } - // ); - // assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); - // }); - // }); - // - // describe('methods', () => { - // it('should properly parse methods', () => { - // const [parsed] = parse(fixturePath('ColumnWithMethods')); - // const methods = parsed.methods; - // const myCoolMethod = methods[0]; - // - // assert.equal(myCoolMethod.description, 'My super cool method'); - // assert.equal( - // myCoolMethod.docblock, - // 'My super cool method\n@param myParam Documentation for parameter 1\n@public\n@returns The answer to the universe' // tslint:disable-line max-line-length - // ); - // assert.deepEqual(myCoolMethod.modifiers, []); - // assert.equal(myCoolMethod.name, 'myCoolMethod'); - // assert.deepEqual(myCoolMethod.params, [ - // { - // description: 'Documentation for parameter 1', - // name: 'myParam', - // type: { name: 'number' } - // }, - // { - // description: null, - // name: 'mySecondParam?', - // type: { name: 'string' } - // } - // ]); - // assert.deepEqual(myCoolMethod.returns, { - // description: 'The answer to the universe', - // type: 'number' - // }); - // }); - // - // it('should properly parse static methods', () => { - // const [parsed] = parse(fixturePath('ColumnWithStaticMethods')); - // const methods = parsed.methods; - // - // assert.equal(methods[0].name, 'myStaticMethod'); - // assert.deepEqual(methods[0].modifiers, ['static']); - // }); - // - // it('should handle method with no information', () => { - // const [parsed] = parse(fixturePath('ColumnWithMethods')); - // const methods = parsed.methods; - // assert.equal(methods[1].name, 'myBasicMethod'); - // }); - // - // it('should handle arrow function', () => { - // const [parsed] = parse(fixturePath('ColumnWithMethods')); - // const methods = parsed.methods; - // assert.equal(methods[2].name, 'myArrowFunction'); - // }); - // - // it('should not parse functions not marked with @public', () => { - // const [parsed] = parse(fixturePath('ColumnWithMethods')); - // const methods = parsed.methods; - // assert.equal( - // Boolean(methods.find(method => method.name === 'myPrivateFunction')), - // false - // ); - // }); - // }); - // - // describe('getDefaultExportForFile', () => { - // it('should filter out forbidden symbols', () => { - // const result = getDefaultExportForFile({ - // fileName: 'a-b' - // } as ts.SourceFile); - // assert.equal(result, 'ab'); - // }); - // - // it('should remove leading non-letters', () => { - // const result = getDefaultExportForFile({ - // fileName: '---123aba' - // } as ts.SourceFile); - // assert.equal(result, 'aba'); - // }); - // - // it('should preserve numbers in the middle', () => { - // const result = getDefaultExportForFile({ - // fileName: '1Body2Text3' - // } as ts.SourceFile); - // assert.equal(result, 'Body2Text3'); - // }); - // - // it('should not return empty string', () => { - // const result = getDefaultExportForFile({ - // fileName: '---123' - // } as ts.SourceFile); - // assert.equal(result.length > 0, true); - // }); - // }); - // - // describe('issues tests', () => { - // it('188', () => { - // check( - // 'Issue188', - // { - // Header: { - // content: { type: 'string', required: true, description: '' } - // } - // }, - // true, - // '' - // ); - // }); - // }); + it('should parse functional component component defined as const', () => { + check('FunctionDeclarationVisibleName', { + 'Awesome Jumbotron': { + prop1: { type: 'string', required: true } + } + }); + }); + + it('should parse React.SFC component defined as const', () => { + check('ReactSFCAsConst', { + Jumbotron: { + prop1: { type: 'string', required: true } + } + }); + }); + + it('should parse functional component component defined as function as default export', () => { + check('FunctionDeclarationAsDefaultExport', { + Jumbotron: { + prop1: { type: 'string', required: true } + } + }); + }); + + it('should parse functional component component thats been wrapped in React.memo', () => { + check('FunctionDeclarationAsDefaultExportWithMemo', { + Jumbotron: { + prop1: { type: 'string', required: true } + } + }); + }); + + it('should parse JSDoc correctly', () => { + check( + 'JSDocWithParam', + { + JSDocWithParam: { + prop1: { type: 'string', required: true } + } + }, + true, + 'JSDocWithParamProps description\n\nNOTE: If a parent element of this control is `overflow: hidden` then the\nballoon may not show up.' + ); + }); + + it('should parse functional component component defined as const as default export', () => { + check( + 'FunctionalComponentAsConstAsDefaultExport', + { + // in this case the component name is taken from the file name + FunctionalComponentAsConstAsDefaultExport: { + prop1: { type: 'string', required: true } + } + }, + true, + 'Jumbotron description' + ); + }); + + it('should parse React.SFC component defined as const as default export', () => { + check( + 'ReactSFCAsConstAsDefaultExport', + { + // in this case the component name is taken from the file name + ReactSFCAsConstAsDefaultExport: { + prop1: { type: 'string', required: true } + } + }, + true, + 'Jumbotron description' + ); + }); + + it('should parse functional component component defined as const as named export', () => { + check( + 'FunctionalComponentAsConstAsNamedExport', + { + // in this case the component name is taken from the file name + Jumbotron: { + prop1: { type: 'string', required: true } + } + }, + true, + 'Jumbotron description' + ); + }); + + it('should parse React.SFC component defined as const as named export', () => { + check( + 'ReactSFCAsConstAsNamedExport', + { + // in this case the component name is taken from the file name + Jumbotron: { + prop1: { type: 'string', required: true } + } + }, + true, + 'Jumbotron description' + ); + }); + + describe('displayName', () => { + it('should be taken from stateless component `displayName` property (using named export)', () => { + const [parsed] = parse(fixturePath('StatelessDisplayName')); + assert.equal(parsed.displayName, 'StatelessDisplayName'); + }); + + it('should be taken from stateful component `displayName` property (using named export)', () => { + const [parsed] = parse(fixturePath('StatefulDisplayName')); + assert.equal(parsed.displayName, 'StatefulDisplayName'); + }); + + it('should be taken from stateless component `displayName` property (using default export)', () => { + const [parsed] = parse(fixturePath('StatelessDisplayNameDefaultExport')); + assert.equal(parsed.displayName, 'StatelessDisplayNameDefaultExport'); + }); + + it('should be taken from stateful component `displayName` property (using default export)', () => { + const [parsed] = parse(fixturePath('StatefulDisplayNameDefaultExport')); + assert.equal(parsed.displayName, 'StatefulDisplayNameDefaultExport'); + }); + + it('should be taken from named export when default export is an HOC', () => { + const [parsed] = parse(fixturePath('StatelessDisplayNameHOC')); + assert.equal(parsed.displayName, 'StatelessDisplayName'); + }); + + it('should be taken from named export when default export is an HOC', () => { + const [parsed] = parse(fixturePath('StatefulDisplayNameHOC')); + assert.equal(parsed.displayName, 'StatefulDisplayName'); + }); + + it('should be taken from stateless component folder name if file name is "index"', () => { + const [parsed] = parse(fixturePath('StatelessDisplayNameFolder/index')); + assert.equal(parsed.displayName, 'StatelessDisplayNameFolder'); + }); + + it('should be taken from stateful component folder name if file name is "index"', () => { + const [parsed] = parse(fixturePath('StatefulDisplayNameFolder/index')); + assert.equal(parsed.displayName, 'StatefulDisplayNameFolder'); + }); + }); + + describe('Parser options', () => { + describe('Property filtering', () => { + describe('children', () => { + it('should ignore property "children" if not explicitly documented', () => { + check( + 'Column', + { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }, + true + ); + }); + + it('should not ignore any property that is documented explicitly', () => { + check( + 'ColumnWithAnnotatedChildren', + { + Column: { + children: { + description: 'children description', + required: false, + type: 'ReactNode' + }, + prop1: { type: 'string', required: false }, + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }, + true + ); + }); + }); + + describe('propsFilter method', () => { + it('should apply filter function and filter components accordingly', () => { + const propFilter: PropFilter = (prop, component) => + prop.name !== 'prop1'; + check( + 'Column', + { + Column: { + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }, + true, + undefined, + { propFilter } + ); + }); + + it('should apply filter function and filter components accordingly', () => { + const propFilter: PropFilter = (prop, component) => { + if (component.name === 'Column') { + return prop.name !== 'prop1'; + } + return true; + }; + check( + 'Column', + { + Column: { + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }, + true, + undefined, + { propFilter } + ); + check( + 'AppMenu', + { + AppMenu: { + menu: { type: 'any' } + } + }, + true, + undefined, + { propFilter } + ); + }); + + it('should allow filtering by parent interface', () => { + const propFilter: PropFilter = (prop, component) => { + if (prop.parent == null) { + return true; + } + + return ( + prop.parent.fileName.indexOf('@types/react') < 0 && + prop.parent.name !== 'HTMLAttributes' + ); + }; + + check( + 'ColumnWithHtmlAttributes', + { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' } + } + }, + true, + undefined, + { propFilter } + ); + }); + }); + + it('should collect all `onClick prop` parent declarations', done => { + assert.doesNotThrow(() => { + withDefaultConfig({ + propFilter: prop => { + if (prop.name === 'onClick') { + assert.deepEqual(prop.declarations, [ + { + fileName: + 'react-docgen-typescript/node_modules/@types/react/index.d.ts', + name: 'DOMAttributes' + }, + { + fileName: + 'react-docgen-typescript/src/__tests__/data/ButtonWithOnClickComponent.tsx', + name: 'TypeLiteral' + } + ]); + + done(); + } + + return true; + } + }).parse(fixturePath('ButtonWithOnClickComponent')); + }); + }); + + it('should allow filtering by parent declarations', () => { + const propFilter: PropFilter = prop => { + if (prop.declarations !== undefined && prop.declarations.length > 0) { + const hasPropAdditionalDescription = prop.declarations.find( + declaration => { + return !declaration.fileName.includes('@types/react'); + } + ); + + return Boolean(hasPropAdditionalDescription); + } + + return true; + }; + + check( + 'ButtonWithOnClickComponent', + { + ButtonWithOnClickComponent: { + onClick: { + type: + '(event: MouseEvent) => void', + required: false, + description: 'onClick event handler' + } + } + }, + true, + '', + { + propFilter + } + ); + }); + + describe('skipPropsWithName', () => { + it('should skip a single property in skipPropsWithName', () => { + const propFilter = { skipPropsWithName: 'prop1' }; + check( + 'Column', + { + Column: { + prop2: { type: 'number' }, + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }, + true, + undefined, + { propFilter } + ); + }); + + it('should skip multiple properties in skipPropsWithName', () => { + const propFilter = { skipPropsWithName: ['prop1', 'prop2'] }; + check( + 'Column', + { + Column: { + prop3: { type: '() => void' }, + prop4: { type: '"option1" | "option2" | "option3"' } + } + }, + true, + undefined, + { propFilter } + ); + }); + }); + + describe('skipPropsWithoutDoc', () => { + it('should skip a properties without documentation', () => { + const propFilter = { skipPropsWithoutDoc: false }; + check( + 'ColumnWithUndocumentedProps', + { + Column: { + prop1: { type: 'string', required: false }, + prop2: { type: 'number' } + } + }, + true, + undefined, + { propFilter } + ); + }); + }); + }); + + it('should defaultProps in variable', () => { + check('SeparateDefaultProps', { + SeparateDefaultProps: { + disabled: { + description: '', + required: false, + defaultValue: false, + type: 'boolean' + }, + id: { + description: '', + required: false, + defaultValue: 123, + type: 'number' + } + } + }); + }); + + it('should defaultProps accessed variable', () => { + check('SeparateDefaultPropsIndividual', { + SeparateDefaultPropsIndividual: { + disabled: { + description: '', + required: false, + defaultValue: false, + type: 'boolean' + }, + id: { + description: '', + required: false, + defaultValue: 123, + type: 'number' + } + } + }); + }); + + describe('Extracting literal values from enums', () => { + it('extracts literal values from enum', () => { + check( + 'ExtractLiteralValuesFromEnum', + { + ExtractLiteralValuesFromEnum: { + sampleBoolean: { type: 'boolean' }, + sampleEnum: { + raw: 'sampleEnum', + type: 'enum', + value: [ + { value: '"one"' }, + { value: '"two"' }, + { value: '"three"' } + ] + }, + sampleString: { type: 'string' } + } + }, + true, + null, + { + shouldExtractLiteralValuesFromEnum: true + } + ); + }); + + it('Should infer types from constraint type (generic with extends)', () => { + check( + 'GenericWithExtends', + { + GenericWithExtends: { + sampleUnionProp: { + raw: 'SampleUnion', + type: 'enum', + value: [ + { + value: '"value 1"' + }, + { + value: '"value 2"' + }, + { + value: '"value 3"' + }, + { + value: '"value 4"' + }, + { + value: '"value n"' + } + ] + }, + sampleEnumProp: { + raw: 'SampleEnum', + type: 'enum', + value: [ + { + value: '0' + }, + { + value: '1' + }, + { + value: '"c"' + } + ] + }, + sampleUnionNonGeneric: { + type: 'SampleUnionNonGeneric' + }, + sampleObjectProp: { + type: 'SampleObject' + }, + sampleNumberProp: { + type: 'number' + }, + sampleGenericArray: { + type: 'number[]' + }, + sampleGenericObject: { + type: '{ prop1: number; }' + }, + sampleInlineObject: { + type: '{ propA: string; }' + } + } + }, + true, + null, + { shouldExtractLiteralValuesFromEnum: true } + ); + }); + }); + + describe('Extracting values from unions', () => { + it('extracts all values from union', () => { + check( + 'ExtractLiteralValuesFromUnion', + { + ExtractLiteralValuesFromUnion: { + sampleComplexUnion: { + raw: 'number | "string1" | "string2"', + type: 'enum', + value: [ + { value: 'number' }, + { value: '"string1"' }, + { value: '"string2"' } + ] + } + } + }, + false, + null, + { + shouldExtractValuesFromUnion: true + } + ); + }); + it('extracts numbers from a union', () => { + check( + 'ExtractLiteralValuesFromUnion', + { + ExtractLiteralValuesFromUnion: { + sampleNumberUnion: { + raw: '1 | 2 | 3', + type: 'enum', + value: [{ value: '1' }, { value: '2' }, { value: '3' }] + } + } + }, + false, + null, + { + shouldExtractValuesFromUnion: true + } + ); + }); + it('extracts numbers and strings from a mixed union', () => { + check( + 'ExtractLiteralValuesFromUnion', + { + ExtractLiteralValuesFromUnion: { + sampleMixedUnion: { + raw: '"string1" | "string2" | 1 | 2', + type: 'enum', + value: [ + { value: '"string1"' }, + { value: '"string2"' }, + { value: '1' }, + { value: '2' } + ] + } + } + }, + false, + null, + { + shouldExtractValuesFromUnion: true + } + ); + }); + }); + + describe('Returning not string default props ', () => { + it('returns not string defaultProps', () => { + check( + 'StatelessWithDefaultPropsAsString', + { + StatelessWithDefaultPropsAsString: { + sampleFalse: { + defaultValue: 'false', + required: false, + type: 'boolean' + }, + sampleNull: { + defaultValue: 'null', + required: false, + type: 'null' + }, + sampleNumber: { + defaultValue: '1', + required: false, + type: 'number' + }, + sampleNumberWithPrefix: { + defaultValue: '-1', + required: false, + type: 'number' + }, + sampleTrue: { + defaultValue: 'true', + required: false, + type: 'boolean' + }, + sampleUndefined: { + defaultValue: 'undefined', + required: false, + type: 'undefined' + } + } + }, + true, + null, + { + savePropValueAsString: true + } + ); + }); + }); + describe("Extract prop's JSDoc/TSDoc tags", () => { + it('should extract all prop JSDoc/TSDoc tags', () => { + check( + 'ExtractPropTags', + { + ExtractPropTags: { + prop1: { + type: 'Pick', + required: false, + tags: { + ignore: 'ignoreMe', + kind: 'category 2', + custom123: 'something' + } + }, + prop2: { + type: 'string', + tags: { internal: 'some internal prop', kind: 'category 1' } + } + } + }, + true, + null, + { shouldIncludePropTagMap: true } + ); + }); + }); + }); + + describe('withCustomConfig', () => { + it('should accept tsconfigs that typescript accepts', () => { + assert.ok( + withCustomConfig( + // need to navigate to root because tests run on compiled tests + // and tsc does not include json files + path.join(__dirname, '../../src/__tests__/data/tsconfig.json'), + {} + ) + ); + }); + }); + + describe('parseWithProgramProvider', () => { + it('should accept existing ts.Program instance', () => { + let programProviderInvoked = false; + + // mimic a third party library providing a ts.Program instance. + const programProvider = () => { + // need to navigate to root because tests run on compiled tests + // and tsc does not include json files + const tsconfigPath = path.join( + __dirname, + '../../src/__tests__/data/tsconfig.json' + ); + const basePath = path.dirname(tsconfigPath); + + const { config, error } = ts.readConfigFile(tsconfigPath, filename => + fs.readFileSync(filename, 'utf8') + ); + assert.isUndefined(error); + + const { options, errors } = ts.parseJsonConfigFileContent( + config, + ts.sys, + basePath, + {}, + tsconfigPath + ); + assert.lengthOf(errors, 0); + + programProviderInvoked = true; + + return ts.createProgram([fixturePath('Column')], options); + }; + + const result = withDefaultConfig().parseWithProgramProvider( + [fixturePath('Column')], + programProvider + ); + + checkComponent( + result, + { + Column: {} + }, + false + ); + assert.isTrue(programProviderInvoked); + }); + }); + + describe('componentNameResolver', () => { + it('should override default behavior', () => { + const [parsed] = parse( + fixturePath('StatelessDisplayNameStyledComponent'), + { + componentNameResolver: (exp, source) => + exp.getName() === 'StyledComponentClass' && + getDefaultExportForFile(source) + } + ); + assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); + }); + + it('should fallback to default behavior without a match', () => { + const [parsed] = parse( + fixturePath('StatelessDisplayNameStyledComponent'), + { + componentNameResolver: () => false + } + ); + assert.equal(parsed.displayName, 'StatelessDisplayNameStyledComponent'); + }); + }); + + describe('methods', () => { + it('should properly parse methods', () => { + const [parsed] = parse(fixturePath('ColumnWithMethods')); + const methods = parsed.methods; + const myCoolMethod = methods[0]; + + assert.equal(myCoolMethod.description, 'My super cool method'); + assert.equal( + myCoolMethod.docblock, + 'My super cool method\n@param myParam Documentation for parameter 1\n@public\n@returns The answer to the universe' // tslint:disable-line max-line-length + ); + assert.deepEqual(myCoolMethod.modifiers, []); + assert.equal(myCoolMethod.name, 'myCoolMethod'); + assert.deepEqual(myCoolMethod.params, [ + { + description: 'Documentation for parameter 1', + name: 'myParam', + type: { name: 'number' } + }, + { + description: null, + name: 'mySecondParam?', + type: { name: 'string' } + } + ]); + assert.deepEqual(myCoolMethod.returns, { + description: 'The answer to the universe', + type: 'number' + }); + }); + + it('should properly parse static methods', () => { + const [parsed] = parse(fixturePath('ColumnWithStaticMethods')); + const methods = parsed.methods; + + assert.equal(methods[0].name, 'myStaticMethod'); + assert.deepEqual(methods[0].modifiers, ['static']); + }); + + it('should handle method with no information', () => { + const [parsed] = parse(fixturePath('ColumnWithMethods')); + const methods = parsed.methods; + assert.equal(methods[1].name, 'myBasicMethod'); + }); + + it('should handle arrow function', () => { + const [parsed] = parse(fixturePath('ColumnWithMethods')); + const methods = parsed.methods; + assert.equal(methods[2].name, 'myArrowFunction'); + }); + + it('should not parse functions not marked with @public', () => { + const [parsed] = parse(fixturePath('ColumnWithMethods')); + const methods = parsed.methods; + assert.equal( + Boolean(methods.find(method => method.name === 'myPrivateFunction')), + false + ); + }); + }); + + describe('getDefaultExportForFile', () => { + it('should filter out forbidden symbols', () => { + const result = getDefaultExportForFile({ + fileName: 'a-b' + } as ts.SourceFile); + assert.equal(result, 'ab'); + }); + + it('should remove leading non-letters', () => { + const result = getDefaultExportForFile({ + fileName: '---123aba' + } as ts.SourceFile); + assert.equal(result, 'aba'); + }); + + it('should preserve numbers in the middle', () => { + const result = getDefaultExportForFile({ + fileName: '1Body2Text3' + } as ts.SourceFile); + assert.equal(result, 'Body2Text3'); + }); + + it('should not return empty string', () => { + const result = getDefaultExportForFile({ + fileName: '---123' + } as ts.SourceFile); + assert.equal(result.length > 0, true); + }); + }); + + describe('issues tests', () => { + it('188', () => { + check( + 'Issue188', + { + Header: { + content: { type: 'string', required: true, description: '' } + } + }, + true, + '' + ); + }); + }); });