Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stateless Components #28

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my failing test case for the bag of Components. I’m not terribly worried about this right now, personally.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bag of components still fails. I’m personally okay with this for a first iteration and if folks open an issue about it then it can be tackled.

});
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();
};
`;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function for this isn’t quite recursive enough. While all of these resolve as expected, the following two cases do not resolve correctly yet:

const ComponentG = function(props) {
  var nested = {
    helpers: {
      comp() { return <div>{props.children}</div>; }
    }
  };
  return nested.helpers.comp();
};
const ComponentF = function(props) {
  var other = () => <div>{props.children}</div>;
  var helpers = {
    comp: other
  };
  return helpers.comp();
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works like a champ.


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