Skip to content

Commit

Permalink
Add support for stateless components
Browse files Browse the repository at this point in the history
  • Loading branch information
iamdustan authored and fkling committed Oct 30, 2015
1 parent 60a3ad2 commit 9f27006
Show file tree
Hide file tree
Showing 12 changed files with 961 additions and 7 deletions.
113 changes: 112 additions & 1 deletion src/__tests__/main-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
jest.autoMockOff();

describe('main', () => {
var docgen;
var docgen, ERROR_MISSING_DEFINITION;

beforeEach(() => {
docgen = require('../main');
({ERROR_MISSING_DEFINITION} = require('../parse'));
});

function test(source) {
Expand Down Expand Up @@ -99,4 +100,114 @@ describe('main', () => {
`);
});

describe('Stateless Component definition: ArrowFunctionExpression', () => {
test(`
import React, {PropTypes} from "React";
/**
* Example component description
*/
let Component = props => <div />;
Component.displayName = 'ABC';
Component.defaultProps = {
foo: true
};
Component.propTypes = {
/**
* Example prop description
*/
foo: PropTypes.bool
};
export default Component;
`);
});

describe('Stateless Component definition: FunctionDeclaration', () => {
test(`
import React, {PropTypes} from "React";
/**
* Example component description
*/
function Component (props) {
return <div />;
}
Component.displayName = 'ABC';
Component.defaultProps = {
foo: true
};
Component.propTypes = {
/**
* Example prop description
*/
foo: PropTypes.bool
};
export default Component;
`);
});

describe('Stateless Component definition: FunctionExpression', () => {
test(`
import React, {PropTypes} from "React";
/**
* Example component description
*/
let Component = function(props) {
return React.createElement('div', null);
}
Component.displayName = 'ABC';
Component.defaultProps = {
foo: true
};
Component.propTypes = {
/**
* Example prop description
*/
foo: PropTypes.bool
};
export default Component;
`);
});

describe('Stateless Component definition', () => {
it('is not so greedy', () => {
const source = `
import React, {PropTypes} from "React";
/**
* Example component description
*/
let NotAComponent = function(props) {
let HiddenComponent = () => React.createElement('div', null);
return 7;
}
NotAComponent.displayName = 'ABC';
NotAComponent.defaultProps = {
foo: true
};
NotAComponent.propTypes = {
/**
* Example prop description
*/
foo: PropTypes.bool
};
export default NotAComponent;
`;

expect(() => docgen.parse(source)).toThrow(ERROR_MISSING_DEFINITION);
});
});
});
10 changes: 10 additions & 0 deletions src/handlers/__tests__/propTypeHandler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ describe('propTypeHandler', () => {
);
});

describe('stateless component', () => {
test(
propTypesSrc => template(`
var Component = (props) => <div />;
Component.propTypes = ${propTypesSrc};
`),
src => statement(src)
);
});

it('does not error if propTypes cannot be found', () => {
var definition = expression('{fooBar: 42}');
expect(() => propTypeHandler(documentation, definition))
Expand Down
56 changes: 56 additions & 0 deletions src/resolver/__tests__/findAllComponentDefinitions-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,60 @@ describe('findAllComponentDefinitions', () => {
});
});

describe('stateless components', () => {

it('finds stateless components', () => {
var source = `
import React from 'React';
let ComponentA = () => <div />;
function ComponentB () { return React.createElement('div', null); }
const ComponentC = function(props) { return <div>{props.children}</div>; };
var Obj = {
component() { if (true) { return <div />; } }
};
const ComponentD = function(props) {
var result = <div>{props.children}</div>;
return result;
};
const ComponentE = function(props) {
var result = () => <div>{props.children}</div>;
return result();
};
const ComponentF = function(props) {
var helpers = {
comp() { return <div>{props.children}</div>; }
};
return helpers.comp();
};
`;

var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(7);
});

it('finds React.createElement, independent of the var name', () => {
var source = `
import AlphaBetters from 'react';
function ComponentA () { return AlphaBetters.createElement('div', null); }
function ComponentB () { return 7; }
`;

var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(1);
});

it('does not process X.createClass of other modules', () => {
var source = `
import R from 'FakeReact';
const ComponentA = () => R.createElement('div', null);
`;

var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(0);
});
});

});
162 changes: 162 additions & 0 deletions src/resolver/__tests__/findExportedComponentDefinition-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,40 @@ describe('findExportedComponentDefinition', () => {
});
});

describe('stateless components', () => {

it('finds stateless component with JSX', () => {
var source = `
var React = require("React");
var Component = () => <div />;
module.exports = Component;
`;

expect(parse(source)).toBeDefined();
});

it('finds stateless components with React.createElement, independent of the var name', () => {
var source = `
var R = require("React");
var Component = () => R.createElement('div', {});
module.exports = Component;
`;

expect(parse(source)).toBeDefined();
});

it('does not process X.createElement of other modules', () => {
var source = `
var R = require("NoReact");
var Component = () => R.createElement({});
module.exports = Component;
`;

expect(parse(source)).toBeUndefined();
});

});

describe('module.exports = <C>; / exports.foo = <C>;', () => {

describe('React.createClass', () => {
Expand Down Expand Up @@ -449,6 +483,75 @@ describe('findExportedComponentDefinition', () => {
});
});

describe.only('stateless components', () => {

it('finds named exports', () => {
var source = `
import React from 'React';
export var somethingElse = 42,
Component = () => <div />;
`;
var result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ArrowFunctionExpression');

source = `
import React from 'React';
export let Component = () => <div />,
somethingElse = 42;
`;
result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ArrowFunctionExpression');

source = `
import React from 'React';
export const something = 21,
Component = () => <div />,
somethingElse = 42;
`;
result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ArrowFunctionExpression');

source = `
import React from 'React';
export var somethingElse = function() {};
export let Component = () => <div />
`;
result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ArrowFunctionExpression');
});

it('errors if multiple components are exported', () => {
var source = `
import React from 'React';
export var ComponentA = () => <div />
export var ComponentB = () => <div />
`;
expect(() => parse(source)).toThrow();

source = `
import React from 'React';
export var ComponentA = () => <div />
var ComponentB = () => <div />
export {ComponentB};
`;
expect(() => parse(source)).toThrow();
});

it('accepts multiple definitions if only one is exported', () => {
var source = `
import React from 'React';
var ComponentA = class extends React.Component {}
export var ComponentB = function() { return <div />; };
`;
var result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('FunctionExpression');
});
});
});

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

});

describe('stateless components', () => {

it('finds exported specifiers', () => {
var source = `
import React from 'React';
var foo = 42;
function Component = () { return <div />; }
export {foo, Component};
`;
var result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassExpression');

source = `
import React from 'React';
var foo = 42;
var Component = () => <div />;
export {Component, foo};
`;
result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassExpression');

source = `
import React from 'React';
var foo = 42;
var baz = 21;
var Component = function () { return <div />; }
export {foo, Component as bar, baz};
`;
result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassExpression');
});

it('errors if multiple components are exported', () => {
var source = `
import React from 'React';
var ComponentA = () => <div />;
function ComponentB() { return <div />; }
export {ComponentA as foo, ComponentB};
`;

expect(() => parse(source)).toThrow();
});

it('accepts multiple definitions if only one is exported', () => {
var source = `
import React from 'React';
var ComponentA = () => <div />;
var ComponentB = () => <div />;
export {ComponentA};
`;
var result = parse(source);
expect(result).toBeDefined();
expect(result.node.type).toBe('ArrowFunctionExpression');
});

});
});

// Only applies to classes
Expand Down
Loading

0 comments on commit 9f27006

Please sign in to comment.