Repository: styleguidist/react-styleguidist
Pull request #672 (2017-11-07 10:58:52)

Include unrecognized components
Implements #665: Components that react-docgen does not recognize as react-components will still be included in the style guide. The `displayName` for such components is guessed based on the file name.

I also added an example of an unrecognizable component to the basic example.

codecov-io: # [Codecov](https://codecov.io/gh/styleguidist/react-styleguidist/pull/672?src=pr&el=h1) Report
> Merging [#672](https://codecov.io/gh/styleguidist/react-styleguidist/pull/672?src=pr&el=desc) into [master](https://codecov.io/gh/styleguidist/react-styleguidist/commit/4014477104bce58d04372df6c05e4591a4debad2?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

| [Impacted Files](https://codecov.io/gh/styleguidist/react-styleguidist/pull/672?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [loaders/utils/getProps.js](https://codecov.io/gh/styleguidist/react-styleguidist/pull/672?src=pr&el=tree#diff-bG9hZGVycy91dGlscy9nZXRQcm9wcy5qcw==) | `100% <100%> (ø)` | :arrow_up: |

jfschwarz: It will still show the warning.
sapegin: > It will still show the warning.

That’s good I think. Probably we should mention the `ignore` option there too. But it may be another PR ;-)

Patch:
diff --git a/Readme.md b/Readme.md
index 5b623125..643b3ac9 100644
--- a/Readme.md
+++ b/Readme.md
@@ -37,9 +37,9 @@ Real projects using React Styleguidist:
 
 * [Rumble Charts](https://rumble-charts.github.io/rumble-charts/)
 * [better-react-spinkit](http://better-react-spinkit.benjamintatum.com/)
-* [Semantic UI Components for React](http://hallister.github.io/semantic-react/)
+* [Semantic UI Components for React](https://hallister.github.io/semantic-react/)
 * [Dialog Components](https://dialogs.github.io/dialog-web-components/)
-* [Bulma Components](http://bokuweb.github.io/re-bulma/)
+* [Bulma Components](https://bokuweb.github.io/re-bulma/)
 * [Yammer Components](https://microsoft.github.io/YamUI/)
 * [More projects…](https://github.com/styleguidist/react-styleguidist/issues/127)
 
@@ -49,9 +49,16 @@ Real projects using React Styleguidist:
 * Create React App + TypeScript, see [Configuring webpack](https://react-styleguidist.js.org/docs/webpack.html)
 * TypeScript, see [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript)
 
+## Third-party tools
+
+* [snapguidist](https://github.com/styleguidist/snapguidist): snapshot testing for React Styleguidist
+* [react-styleguidist-visual](https://github.com/unindented/react-styleguidist-visual): automated visual testing for React Styleguidist, using Puppeteer and pixelmatch
+* [styleguidist-scrapper](https://github.com/livechat/styleguidist-scrapper): scrapper script for documentation generated by React Styleguidist
+
 ## Resources
 
 * [Building React Components Library](https://skillsmatter.com/skillscasts/8140-building-react-components-library) talk by [Robert Haritonov](https://github.com/operatino)
+* [Say Cheese: Snapshots and Visual Testing](https://developers.livechatinc.com/blog/snapshots-visual-testing/)
 * [Interview with Artem Sapegin](https://survivejs.com/blog/styleguidist-interview/) about React Styleguidist.
 
 ## Advanced documentation
@@ -66,7 +73,7 @@ The change log can be found on the [Releases page](https://github.com/styleguidi
 
 ## Contributing
 
-Everyone is welcome to contribute. Please take a moment to review the [contributing guidelines](.github/Contributing.md).
+Everyone is welcome to contribute. Please take a moment to read the [contributing guidelines](.github/Contributing.md) and the [developer guide](https://react-styleguidist.js.org/docs/development.html).
 
 ## Sponsor
 
@@ -78,6 +85,6 @@ Everyone is welcome to contribute. Please take a moment to review the [contribut
 
 [Artem Sapegin](http://sapegin.me) and [contributors](https://github.com/styleguidist/react-styleguidist/graphs/contributors).
 
-Logo by @SaraVieira and @okonet
+Logo by [Sara Vieira](https://github.com/SaraVieira) and [Andrey Okonetchnikov](https://github.com/okonet).
 
 MIT License, see the included [License.md](License.md) file.
diff --git a/examples/sections/src/components/WrappedButton/Readme.md b/examples/sections/src/components/WrappedButton/Readme.md
new file mode 100644
index 00000000..7d1a7f0b
--- /dev/null
+++ b/examples/sections/src/components/WrappedButton/Readme.md
@@ -0,0 +1,6 @@
+react-docgen is not able to parse prop types of enhanced components that are imported from other files.
+Styleguidist will add them to your style guide anyway.
+
+```jsx
+<WrappedButton>I'm a wrapped button</WrappedButton>
+```
diff --git a/examples/sections/src/components/WrappedButton/WrappedButton.js b/examples/sections/src/components/WrappedButton/WrappedButton.js
new file mode 100644
index 00000000..bea528c2
--- /dev/null
+++ b/examples/sections/src/components/WrappedButton/WrappedButton.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import Button from '../Button';
+
+const dummyWrapper = WrappedComponent => props => <WrappedComponent {...props} />;
+
+/**
+ * A button wrapped by a Decorator/Enhancer
+ */
+const WrappedButton = dummyWrapper(Button);
+
+export default WrappedButton;
diff --git a/examples/sections/src/components/WrappedButton/index.js b/examples/sections/src/components/WrappedButton/index.js
new file mode 100644
index 00000000..2b5f86c3
--- /dev/null
+++ b/examples/sections/src/components/WrappedButton/index.js
@@ -0,0 +1,1 @@
+export { default } from './WrappedButton.js';
diff --git a/examples/sections/styleguide.config.js b/examples/sections/styleguide.config.js
index 09dd6fc6..35286096 100644
--- a/examples/sections/styleguide.config.js
+++ b/examples/sections/styleguide.config.js
@@ -29,6 +29,7 @@ module.exports = {
 					components: () => [
 						'./src/components/Button/Button.js',
 						'./src/components/RandomButton/RandomButton.js',
+						'./src/components/WrappedButton/WrappedButton.js',
 					],
 				},
 				{
diff --git a/loaders/utils/getNameFromFilePath.js b/loaders/utils/getNameFromFilePath.js
new file mode 100644
index 00000000..8c05f0a1
--- /dev/null
+++ b/loaders/utils/getNameFromFilePath.js
@@ -0,0 +1,12 @@
+'use strict';
+const path = require('path');
+const _ = require('lodash');
+
+module.exports = function getNameFromFilePath(filePath) {
+	let displayName = path.basename(filePath, path.extname(filePath));
+	if (displayName === 'index') {
+		displayName = path.basename(path.dirname(filePath));
+	}
+
+	return _.upperFirst(_.camelCase(displayName));
+};
diff --git a/loaders/utils/getProps.js b/loaders/utils/getProps.js
index 64a0bdb0..d7965579 100644
--- a/loaders/utils/getProps.js
+++ b/loaders/utils/getProps.js
@@ -6,6 +6,7 @@ const reactDocs = require('react-docgen');
 const highlightCodeInMarkdown = require('./highlightCodeInMarkdown');
 const removeDoclets = require('./removeDoclets');
 const requireIt = require('./requireIt');
+const getNameFromFilePath = require('./getNameFromFilePath');
 const doctrine = require('doctrine');
 const _ = require('lodash');
 const logger = require('glogg')('rsg');
@@ -102,5 +103,10 @@ module.exports = function getProps(doc, filepath) {
 		});
 	}
 
+	if (!doc.displayName && filepath) {
+		// Guess the exported component's display name based on the file path
+		doc.displayName = getNameFromFilePath(filepath);
+	}
+
 	return doc;
 };


Test patch:
diff --git a/loaders/utils/__tests__/__snapshots__/getProps.spec.js.snap b/loaders/utils/__tests__/__snapshots__/getProps.spec.js.snap
index 9ee4f8b7..4580d82c 100644
--- a/loaders/utils/__tests__/__snapshots__/getProps.spec.js.snap
+++ b/loaders/utils/__tests__/__snapshots__/getProps.spec.js.snap
@@ -42,6 +42,7 @@ Object {
 
 exports[`should remove non-public methods 1`] = `
 Object {
+  "displayName": "Button",
   "doclets": Object {},
   "methods": Array [
     Object {
@@ -64,6 +65,7 @@ exports[`should return an object for props 1`] = `
 Object {
   "description": "The only true button.
 ",
+  "displayName": "Button",
   "doclets": Object {},
   "methods": Array [],
   "props": Object {
@@ -88,6 +90,7 @@ exports[`should return an object for props with doclets 1`] = `
 Object {
   "description": "The only true button.
 ",
+  "displayName": "Button",
   "doclets": Object {
     "bar": "Bar
 ",
@@ -113,6 +116,7 @@ Object {
 
 exports[`should return an object for props without description 1`] = `
 Object {
+  "displayName": "Button",
   "doclets": Object {},
   "methods": Array [],
   "props": Object {
@@ -130,6 +134,7 @@ exports[`should return require statement for @example doclet 1`] = `
 Object {
   "description": "The only true button.
 ",
+  "displayName": "Button",
   "doclets": Object {},
   "example": RequireStatement {
     "filepath": "!!<rootDir>/loaders/examples-loader.js!../../../test/components/Placeholder/examples.md",
@@ -150,6 +155,7 @@ exports[`should return require statement for @example doclet only when the file
 Object {
   "description": "The only true button.
 ",
+  "displayName": "Button",
   "doclets": Object {
     "example": "example.md
 ",
diff --git a/loaders/utils/__tests__/getNameFromFilePath.spec.js b/loaders/utils/__tests__/getNameFromFilePath.spec.js
new file mode 100644
index 00000000..e52f4180
--- /dev/null
+++ b/loaders/utils/__tests__/getNameFromFilePath.spec.js
@@ -0,0 +1,24 @@
+import path from 'path';
+import getNameFromFilePath from '../getNameFromFilePath';
+
+it('should return the file name without extension', () => {
+	expect(
+		getNameFromFilePath(path.join('an', 'absolute', 'path', 'to', 'YourComponent.js'))
+	).toEqual('YourComponent');
+});
+
+it('should use the directory name if the file name is index.js', () => {
+	expect(
+		getNameFromFilePath(path.join('an', 'absolute', 'path', 'to', 'YourComponent', 'index.js'))
+	).toEqual('YourComponent');
+});
+
+it('should capitalize the display name', () => {
+	expect(
+		getNameFromFilePath(path.join('an', 'absolute', 'path', 'to', 'yourComponent.js'))
+	).toEqual('YourComponent');
+
+	expect(
+		getNameFromFilePath(path.join('an', 'absolute', 'path', 'to', 'your-component', 'index.js'))
+	).toEqual('YourComponent');
+});
diff --git a/loaders/utils/__tests__/getProps.spec.js b/loaders/utils/__tests__/getProps.spec.js
index f4c4cb14..4f31b061 100644
--- a/loaders/utils/__tests__/getProps.spec.js
+++ b/loaders/utils/__tests__/getProps.spec.js
@@ -1,9 +1,11 @@
+import path from 'path';
 import getProps from '../getProps';
 
 jest.mock('../requireIt');
 
 it('should return an object for props', () => {
 	const result = getProps({
+		displayName: 'Button',
 		description: 'The only true button.',
 		methods: [],
 		props: {
@@ -25,6 +27,7 @@ it('should return an object for props', () => {
 
 it('should return an object for props without description', () => {
 	const result = getProps({
+		displayName: 'Button',
 		props: {
 			children: {
 				type: {},
@@ -40,6 +43,7 @@ it('should return an object for props without description', () => {
 it('should remove non-public methods', () => {
 	const result = getProps(
 		{
+			displayName: 'Button',
 			methods: [
 				{
 					docblock: `Public method.
@@ -63,6 +67,7 @@ it('should remove non-public methods', () => {
 it('should return an object for props with doclets', () => {
 	const result = getProps(
 		{
+			displayName: 'Button',
 			description: `
 The only true button.
 
@@ -79,6 +84,7 @@ The only true button.
 it('should return require statement for @example doclet', () => {
 	const result = getProps(
 		{
+			displayName: 'Button',
 			description: `
 The only true button.
 
@@ -94,6 +100,7 @@ The only true button.
 it('should return require statement for @example doclet only when the file exists', () => {
 	const result = getProps(
 		{
+			displayName: 'Button',
 			description: `
 The only true button.
 
@@ -145,3 +152,14 @@ it("should not crash when using doctrine to parse a default prop that isn't in t
 
 	expect(result).toMatchSnapshot();
 });
+
+it('should guess a displayName for components that react-docgen was not able to recognize', () => {
+	const result = getProps(
+		{
+			methods: [],
+			props: {},
+		},
+		path.join('an', 'absolute', 'path', 'to', 'YourComponent.js')
+	);
+	expect(result).toHaveProperty('displayName', 'YourComponent');
+});