Skip to content

Commit

Permalink
True initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoScabbiolo committed Nov 25, 2017
1 parent 646bcbb commit 0e1b153
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["es2015", "react"]
}
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
coverage
__tests__
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: node_js
node_js:
- 7
- 6
after_script: 'cat ./coverage/lcov.info | coveralls'
141 changes: 141 additions & 0 deletions __tests__/loading-hoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React from 'react';
import PropTypes from 'prop-types';
import chai from 'chai';
import loading from '../index';
import reactTestUtils from 'react-dom/test-utils';
import { shallow, configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { exec } from 'child_process';

configure({ adapter: new Adapter() });

class TestClass extends React.Component {
render() {
return (
<div>
{ this.renderLoading() }
</div>
);
}
}

TestClass.propTypes = {
prop: PropTypes.object
};

describe('loading-hoc', () => {
test('exports', () => {
chai.assert.isFunction(loading);
});

test('doesnt throw', () => {
chai.assert.doesNotThrow(() => loading()());
});

describe('defaults', () => {
const Loading = loading()(TestClass);

test('creates an element', () => {
chai.assert.ok(reactTestUtils.isElement(<Loading loading />));
});

test('renders it withouth loading', () => {
let wrapper = shallow(<Loading loading={false} />);
chai.expect(wrapper.children()).to.have.length(0);
});

test('renders it with loading', () => {
let wrapper = shallow(<Loading loading />);
chai.expect(wrapper.children()).to.have.length(1);
chai.expect(wrapper.html()).to.equal('<div><div>Loading</div></div>');
});

describe('has the proper displayName', () => {
test('by component name', () => {
chai.expect(Loading).to.have.property('displayName', 'TestClass');
});
test('by displayName', () => {
class AnotherTest extends React.Component { }
AnotherTest.displayName = '__Test__';

chai.expect(loading()(AnotherTest)).to.have.property('displayName', '__Test__');
});
});
});

test('custom loading component', () => {
const Loading = loading({ LoadingComponent: () => <span>loading...</span> })(TestClass);
let wrapper = shallow(<Loading loading />);
chai.expect(wrapper.children()).to.have.length(1);
chai.expect(wrapper.html()).to.equal('<div><span>loading...</span></div>');
});

test('className', () => {
const Loading = loading({
LoadingComponent: ({ className }) => <span className={className}>loading...</span>,
className: '__test_class__'
})(TestClass);
let wrapper = shallow(<Loading loading />);
chai.expect(wrapper.children()).to.have.length(1);
chai.expect(wrapper.html()).to.equal('<div><span class="__test_class__">loading...</span></div>');
});

test('optional propType', () => {
const Loading = loading({ loadingPropOptional: true })(TestClass);
const consoleMock = jest.spyOn(global.console, 'error');
consoleMock.mockClear();
shallow(<Loading />);
expect(consoleMock).toHaveBeenCalledTimes(0);
});

test('full displayName', () => {
const Loading = loading({ fullDisplayName: true })(TestClass);
chai.expect(Loading).to.have.property('displayName', 'Loading(TestClass)');
})

test('changes default component', () => {
let baseComponentSymbol = Object.getOwnPropertySymbols(loading)
.find(s => s.toString() === 'Symbol(react-loading-hoc/default-base-component)');
chai.expect(loading[baseComponentSymbol]).not.to.equal(React.Component);
loading.setDefaultBaseComponent(React.Component);
chai.expect(loading[baseComponentSymbol]).to.equal(React.Component);
});

test('changes default loading component', () => {
loading.setDefaultLoadingComponent(() => <span>__test__</span>);
const Loading = loading()(TestClass);
let wrapper = shallow(<Loading loading />);
chai.expect(wrapper.html()).to.equal('<div><span>__test__</span></div>');
});

describe('propTypes', () => {
const Loading = loading()(TestClass);

beforeEach(() => {
// Awfull but necessary hack to prevent React from catching the component
// https://github.com/facebook/react/issues/7047#issuecomment-228614964
TestClass.displayName = Math.random().toString();
});
test('sets propTypes', () => {
chai.expect(Loading.propTypes).to.have.property('loading', PropTypes.bool.isRequired);
});

test('propTypes fails', () => {
const consoleMock = jest.spyOn(global.console, 'error');
consoleMock.mockClear();
shallow(<Loading />);
expect(consoleMock).toHaveBeenCalledTimes(1);
chai.expect(consoleMock.mock.calls[0][0])
.to.be.a('string')
.and.have.string('Failed prop type')
.and.have.string('The prop `loading` is marked as required in');
});

test('propTypes does not fail', () => {
const consoleMock = jest.spyOn(global.console, 'error');
consoleMock.mockClear();
shallow(<Loading loading={false} />);
expect(consoleMock).toHaveBeenCalledTimes(0);
});
});
});
58 changes: 58 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const React = require('react');
const PropTypes = require('prop-types');

const ownSymbol = key => Symbol(`react-loading-hoc/${key}`);

const defaultBaseComponent = ownSymbol('default-base-component');
const defaultLoadingComponent = ownSymbol('default-loading-component');

function LoadingHOC(
{
LoadingComponent,
className,
loadingPropOptional = false,
fullDisplayName = false
} = {}
) {
LoadingComponent = LoadingComponent || LoadingHOC[defaultLoadingComponent];

return function(Component = LoadingHOC[defaultBaseComponent]) {
const Loading = class extends Component {
renderLoading(props = {}) {
if (this.props.loading) {
return React.createElement(
LoadingComponent,
Object.assign(props, { className })
);
}
return undefined;
}
};

// Add loading PropType if the base Component uses PropTypes
if (Component.propTypes) {
Loading.propTypes = Object.assign(Component.propTypes, {
loading: loadingPropOptional ? PropTypes.bool : PropTypes.bool.isRequired
});
}

// Change the display name
let displayName = Component.displayName || Component.name;
Loading.displayName = fullDisplayName ? `Loading(${displayName})` : displayName;

return Loading;
};
}

LoadingHOC[defaultLoadingComponent] = () => React.createElement('div', null, 'Loading');

LoadingHOC[defaultBaseComponent] = React.PureComponent;

LoadingHOC.setDefaultBaseComponent = function(Component) {
LoadingHOC[defaultBaseComponent] = Component;
};
LoadingHOC.setDefaultLoadingComponent = function(Component) {
LoadingHOC[defaultLoadingComponent] = Component;
};

module.exports = LoadingHOC;
73 changes: 73 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"name": "react-loading-hoc",
"version": "1.0.0",
"description": "HOC for React components that can be loading",
"main": "index.js",
"scripts": {
"prepublish": "nsp check",
"pretest": "eslint . --fix",
"precommit": "lint-staged",
"test": "jest --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/MarcoScabbiolo/react-loading-hoc.git"
},
"keywords": ["react", "hoc", "loading"],
"author": "Marco Scabbiolo, scabbiolo.marco@gmail.com",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/MarcoScabbiolo/react-loading-hoc/issues"
},
"homepage": "https://github.com/MarcoScabbiolo/react-loading-hoc#readme",
"dependencies": {
"prop-types": "^15.6.0",
"react": "^16.1.1"
},
"devDependencies": {
"babel-eslint": "^8.0.2",
"babel-jest": "^21.2.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"chai": "^4.1.2",
"coveralls": "^3.0.0",
"enzyme": "^3.2.0",
"enzyme-adapter-react-16": "^1.1.0",
"eslint": "^4.11.0",
"eslint-config-prettier": "^2.8.0",
"eslint-config-xo": "^0.19.0",
"eslint-plugin-prettier": "^2.3.1",
"husky": "^0.14.3",
"jest": "^21.2.1",
"lint-staged": "^5.0.0",
"nsp": "^3.1.0",
"prettier": "^1.8.2",
"react-dom": "^16.1.1",
"react-test-renderer": "^16.1.1"
},
"lint-staged": {
"*.js": ["eslint --fix", "git add"],
"*.json": ["prettier --write", "git add"]
},
"eslintConfig": {
"parser": "babel-eslint",
"env": {
"jest": true,
"node": true
},
"extends": ["xo", "prettier"],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"printWidth": 90
}
]
},
"plugins": ["prettier"]
},
"jest": {
"testEnvironment": "node"
}
}

0 comments on commit 0e1b153

Please sign in to comment.