Skip to content

Commit 3c0b03e

Browse files
committed
Merge branch 'iamdustan-stateless-components'
2 parents 60a3ad2 + 9f27006 commit 3c0b03e

12 files changed

+961
-7
lines changed

src/__tests__/main-test.js

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
jest.autoMockOff();
1414

1515
describe('main', () => {
16-
var docgen;
16+
var docgen, ERROR_MISSING_DEFINITION;
1717

1818
beforeEach(() => {
1919
docgen = require('../main');
20+
({ERROR_MISSING_DEFINITION} = require('../parse'));
2021
});
2122

2223
function test(source) {
@@ -99,4 +100,114 @@ describe('main', () => {
99100
`);
100101
});
101102

103+
describe('Stateless Component definition: ArrowFunctionExpression', () => {
104+
test(`
105+
import React, {PropTypes} from "React";
106+
107+
/**
108+
* Example component description
109+
*/
110+
let Component = props => <div />;
111+
Component.displayName = 'ABC';
112+
Component.defaultProps = {
113+
foo: true
114+
};
115+
116+
Component.propTypes = {
117+
/**
118+
* Example prop description
119+
*/
120+
foo: PropTypes.bool
121+
};
122+
123+
export default Component;
124+
`);
125+
});
126+
127+
describe('Stateless Component definition: FunctionDeclaration', () => {
128+
test(`
129+
import React, {PropTypes} from "React";
130+
131+
/**
132+
* Example component description
133+
*/
134+
function Component (props) {
135+
return <div />;
136+
}
137+
138+
Component.displayName = 'ABC';
139+
Component.defaultProps = {
140+
foo: true
141+
};
142+
143+
Component.propTypes = {
144+
/**
145+
* Example prop description
146+
*/
147+
foo: PropTypes.bool
148+
};
149+
150+
export default Component;
151+
`);
152+
});
153+
154+
describe('Stateless Component definition: FunctionExpression', () => {
155+
test(`
156+
import React, {PropTypes} from "React";
157+
158+
/**
159+
* Example component description
160+
*/
161+
let Component = function(props) {
162+
return React.createElement('div', null);
163+
}
164+
165+
Component.displayName = 'ABC';
166+
Component.defaultProps = {
167+
foo: true
168+
};
169+
170+
Component.propTypes = {
171+
/**
172+
* Example prop description
173+
*/
174+
foo: PropTypes.bool
175+
};
176+
177+
export default Component;
178+
`);
179+
});
180+
181+
describe('Stateless Component definition', () => {
182+
it('is not so greedy', () => {
183+
const source = `
184+
import React, {PropTypes} from "React";
185+
186+
/**
187+
* Example component description
188+
*/
189+
let NotAComponent = function(props) {
190+
let HiddenComponent = () => React.createElement('div', null);
191+
192+
return 7;
193+
}
194+
195+
NotAComponent.displayName = 'ABC';
196+
NotAComponent.defaultProps = {
197+
foo: true
198+
};
199+
200+
NotAComponent.propTypes = {
201+
/**
202+
* Example prop description
203+
*/
204+
foo: PropTypes.bool
205+
};
206+
207+
export default NotAComponent;
208+
`;
209+
210+
expect(() => docgen.parse(source)).toThrow(ERROR_MISSING_DEFINITION);
211+
});
212+
});
102213
});

src/handlers/__tests__/propTypeHandler-test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,16 @@ describe('propTypeHandler', () => {
184184
);
185185
});
186186

187+
describe('stateless component', () => {
188+
test(
189+
propTypesSrc => template(`
190+
var Component = (props) => <div />;
191+
Component.propTypes = ${propTypesSrc};
192+
`),
193+
src => statement(src)
194+
);
195+
});
196+
187197
it('does not error if propTypes cannot be found', () => {
188198
var definition = expression('{fooBar: 42}');
189199
expect(() => propTypeHandler(documentation, definition))

src/resolver/__tests__/findAllComponentDefinitions-test.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,60 @@ describe('findAllComponentDefinitions', () => {
149149
});
150150
});
151151

152+
describe('stateless components', () => {
153+
154+
it('finds stateless components', () => {
155+
var source = `
156+
import React from 'React';
157+
let ComponentA = () => <div />;
158+
function ComponentB () { return React.createElement('div', null); }
159+
const ComponentC = function(props) { return <div>{props.children}</div>; };
160+
var Obj = {
161+
component() { if (true) { return <div />; } }
162+
};
163+
const ComponentD = function(props) {
164+
var result = <div>{props.children}</div>;
165+
return result;
166+
};
167+
const ComponentE = function(props) {
168+
var result = () => <div>{props.children}</div>;
169+
return result();
170+
};
171+
const ComponentF = function(props) {
172+
var helpers = {
173+
comp() { return <div>{props.children}</div>; }
174+
};
175+
return helpers.comp();
176+
};
177+
`;
178+
179+
var result = parse(source);
180+
expect(Array.isArray(result)).toBe(true);
181+
expect(result.length).toBe(7);
182+
});
183+
184+
it('finds React.createElement, independent of the var name', () => {
185+
var source = `
186+
import AlphaBetters from 'react';
187+
function ComponentA () { return AlphaBetters.createElement('div', null); }
188+
function ComponentB () { return 7; }
189+
`;
190+
191+
var result = parse(source);
192+
expect(Array.isArray(result)).toBe(true);
193+
expect(result.length).toBe(1);
194+
});
195+
196+
it('does not process X.createClass of other modules', () => {
197+
var source = `
198+
import R from 'FakeReact';
199+
const ComponentA = () => R.createElement('div', null);
200+
`;
201+
202+
var result = parse(source);
203+
expect(Array.isArray(result)).toBe(true);
204+
expect(result.length).toBe(0);
205+
});
206+
});
207+
152208
});

src/resolver/__tests__/findExportedComponentDefinition-test.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,40 @@ describe('findExportedComponentDefinition', () => {
106106
});
107107
});
108108

109+
describe('stateless components', () => {
110+
111+
it('finds stateless component with JSX', () => {
112+
var source = `
113+
var React = require("React");
114+
var Component = () => <div />;
115+
module.exports = Component;
116+
`;
117+
118+
expect(parse(source)).toBeDefined();
119+
});
120+
121+
it('finds stateless components with React.createElement, independent of the var name', () => {
122+
var source = `
123+
var R = require("React");
124+
var Component = () => R.createElement('div', {});
125+
module.exports = Component;
126+
`;
127+
128+
expect(parse(source)).toBeDefined();
129+
});
130+
131+
it('does not process X.createElement of other modules', () => {
132+
var source = `
133+
var R = require("NoReact");
134+
var Component = () => R.createElement({});
135+
module.exports = Component;
136+
`;
137+
138+
expect(parse(source)).toBeUndefined();
139+
});
140+
141+
});
142+
109143
describe('module.exports = <C>; / exports.foo = <C>;', () => {
110144

111145
describe('React.createClass', () => {
@@ -449,6 +483,75 @@ describe('findExportedComponentDefinition', () => {
449483
});
450484
});
451485

486+
describe.only('stateless components', () => {
487+
488+
it('finds named exports', () => {
489+
var source = `
490+
import React from 'React';
491+
export var somethingElse = 42,
492+
Component = () => <div />;
493+
`;
494+
var result = parse(source);
495+
expect(result).toBeDefined();
496+
expect(result.node.type).toBe('ArrowFunctionExpression');
497+
498+
source = `
499+
import React from 'React';
500+
export let Component = () => <div />,
501+
somethingElse = 42;
502+
`;
503+
result = parse(source);
504+
expect(result).toBeDefined();
505+
expect(result.node.type).toBe('ArrowFunctionExpression');
506+
507+
source = `
508+
import React from 'React';
509+
export const something = 21,
510+
Component = () => <div />,
511+
somethingElse = 42;
512+
`;
513+
result = parse(source);
514+
expect(result).toBeDefined();
515+
expect(result.node.type).toBe('ArrowFunctionExpression');
516+
517+
source = `
518+
import React from 'React';
519+
export var somethingElse = function() {};
520+
export let Component = () => <div />
521+
`;
522+
result = parse(source);
523+
expect(result).toBeDefined();
524+
expect(result.node.type).toBe('ArrowFunctionExpression');
525+
});
526+
527+
it('errors if multiple components are exported', () => {
528+
var source = `
529+
import React from 'React';
530+
export var ComponentA = () => <div />
531+
export var ComponentB = () => <div />
532+
`;
533+
expect(() => parse(source)).toThrow();
534+
535+
source = `
536+
import React from 'React';
537+
export var ComponentA = () => <div />
538+
var ComponentB = () => <div />
539+
export {ComponentB};
540+
`;
541+
expect(() => parse(source)).toThrow();
542+
});
543+
544+
it('accepts multiple definitions if only one is exported', () => {
545+
var source = `
546+
import React from 'React';
547+
var ComponentA = class extends React.Component {}
548+
export var ComponentB = function() { return <div />; };
549+
`;
550+
var result = parse(source);
551+
expect(result).toBeDefined();
552+
expect(result.node.type).toBe('FunctionExpression');
553+
});
554+
});
452555
});
453556

454557
describe('export {<C>};', () => {
@@ -566,6 +669,65 @@ describe('findExportedComponentDefinition', () => {
566669

567670
});
568671

672+
describe('stateless components', () => {
673+
674+
it('finds exported specifiers', () => {
675+
var source = `
676+
import React from 'React';
677+
var foo = 42;
678+
function Component = () { return <div />; }
679+
export {foo, Component};
680+
`;
681+
var result = parse(source);
682+
expect(result).toBeDefined();
683+
expect(result.node.type).toBe('ClassExpression');
684+
685+
source = `
686+
import React from 'React';
687+
var foo = 42;
688+
var Component = () => <div />;
689+
export {Component, foo};
690+
`;
691+
result = parse(source);
692+
expect(result).toBeDefined();
693+
expect(result.node.type).toBe('ClassExpression');
694+
695+
source = `
696+
import React from 'React';
697+
var foo = 42;
698+
var baz = 21;
699+
var Component = function () { return <div />; }
700+
export {foo, Component as bar, baz};
701+
`;
702+
result = parse(source);
703+
expect(result).toBeDefined();
704+
expect(result.node.type).toBe('ClassExpression');
705+
});
706+
707+
it('errors if multiple components are exported', () => {
708+
var source = `
709+
import React from 'React';
710+
var ComponentA = () => <div />;
711+
function ComponentB() { return <div />; }
712+
export {ComponentA as foo, ComponentB};
713+
`;
714+
715+
expect(() => parse(source)).toThrow();
716+
});
717+
718+
it('accepts multiple definitions if only one is exported', () => {
719+
var source = `
720+
import React from 'React';
721+
var ComponentA = () => <div />;
722+
var ComponentB = () => <div />;
723+
export {ComponentA};
724+
`;
725+
var result = parse(source);
726+
expect(result).toBeDefined();
727+
expect(result.node.type).toBe('ArrowFunctionExpression');
728+
});
729+
730+
});
569731
});
570732

571733
// Only applies to classes

0 commit comments

Comments
 (0)