Buffer Components
A shared set of UI Components using React and CSS Modules.
Demo: https://bufferapp.github.io/buffer-components/
Usage
Install the package and save the exact version:
npm install @bufferapp/components -SE
Now in your code you can import a specific component:
import Button from '@bufferapp/components/Button';
Requirements
For the component library you're required to use a few plugins and a valid Webpack config.
First, you'll need React installed (0.14 or newer):
npm i react react-dom -SE
In addition to your Babel configuration (not documented), you'll need some Webpack plugins:
npm i css-loader \
style-loader \
postcss-loader \
postcss-import \
postcss-custom-properties \
postcss-hexrgba -SDE
Your Webpack config should use the proper config, here is an example:
const PostCSSImport = require('postcss-import');
const PostCSSCustomProperties = require('postcss-custom-properties');
const PostCSShexrgba = require('postcss-hexrgba');
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loaders: [
'style-loader',
`css-loader?modules&importLoaders=1&localIdentName=[name]_[local]_[hash:base64:5]`,
'postcss-loader',
],
},
],
},
postcss: [
PostCSSImport,
PostCSSCustomProperties,
PostCSShexrgba,
],
};
Table Of Contents
Quick Start
Install Node Modules
npm i
Start React Storybook
npm start
Open http://localhost:9001
Test
Run Linter And Test
npm run test
Run Test and watch for changes
npm run test-watch
Note - does not run all tests the first time
Update Test Snapshots
npm run test-update
Note: only commit these if you have manually inspected them with a story
Component Anatomy
src/ # root
+-- MyComponent/ # component root
`-- index.js # component logic
`-- story.js # storybook entry
`-- test.js # component tests
+-- __snapshots__/ # jest snapshot location
`-- test.js.snap # actual component snapshot
FAQ
What is a component
In the current implementation components are all functional and stateless.
This means that UI is a function of state since we're using pure functions to build our views.
UI = f(state)
How do I determine the scope of a component
This is a tough question, it really depends. But as a general rule, a component should be simple enough to be reusable across multiple applications and be not much longer than 150 lines of code. This is a good one to seek advice if you're not sure.
What's the development workflow look like?
Note: this is a way to do this, but not necessarily the way to build components. For this workflow let's create a component called NewComponent
.
- Create a branch with the name of the new component
Note: also make sure you're up to date
git checkout master
git pull -r
git checkout -b task/add-newcomponent
- Install dependencies and start the storybook
npm i && npm start
open http://localhost:9001 in your browser
- Create a
NewComponent
folder under src
(see Component Anatomy)
src/
+-- NewComponent/
- Create a story for the
NewComponent
src/
+-- NewComponent/
`-- story.js
populate story.js with a default story
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import NewComponent from './index';
storiesOf('Card')
.add('Default', () => (
<NewComponent />
));
Now when you look at Storybook you should see a broken story (red screen)
- Implement your component
src/
+-- NewComponent/
`-- story.js
`-- index.js
populate index.js with the new component
import React from 'react';
const NewComponent = () => <div>NewComponent</div>;
export default NewComponent;
- Write a snapshot test
src/
+-- NewComponent/
`-- story.js
`-- index.js
`-- test.js
populate test.js with a test
jest.unmock('./index');
import React from 'react';
import { renderAndCheckSnapshot } from '../testHelpers';
import NewComponent from './index';
describe('NewComponent', () => {
it('NewComponent component', () => {
renderAndCheckSnapshot(<NewComponent />);
});
});
- Run the test for the first time
It's important to note that this creates a snapshot of the component. All tests ran in the future will be tested against this snapshot to ensure they haven't changed.
npm t
- Commit it!
git add .
git commit -m "Add NewComponent"
git push -u origin task/add-newcomponent
At this point it's a good idea to generate a PR on github :)
How do I write tests for a component?
Since components are functional and stateless we can use snapshot testing to get complete coverage.
So to write a test you can render a component with a given set of properties and check it against a know good state (the snapshot)
So tests end up looking like this:
jest.unmock('./index');
import React from 'react';
import { renderAndCheckSnapshot } from '../testHelpers';
import NewComponent from './index';
describe('NewComponent', () => {
it('NewComponent component', () => {
renderAndCheckSnapshot(<NewComponent />);
});
it('NewComponent width=100', () => {
renderAndCheckSnapshot(<NewComponent width={100}/>);
});
});
You're verifying that each property change has the expected outcome in HTML.
The first time the test is run it generates a new snapshot. The second time it's checked against the snapshot.
If you're curious here's what the renderAndCheckSnapshot
function does:
import renderer from 'react-test-renderer';
export const renderAndCheckSnapshot = (component) => {
const renderedComponent = renderer.create(component);
const tree = renderedComponent.toJSON();
expect(tree).toMatchSnapshot();
};
How Do I Update A Snapshot
Let's say you've got a component that starts like this
import React from 'react';
const NewComponent = () => <div>NewComponent</div>;
export default NewComponent;
and a test that looks like this
jest.unmock('./index');
import React from 'react';
import { renderAndCheckSnapshot } from '../testHelpers';
import NewComponent from './index';
describe('NewComponent', () => {
it('NewComponent component', () => {
renderAndCheckSnapshot(<NewComponent />);
});
});
Now you want to add height
property to the component
import React, { PropTypes } from 'react';
const NewComponent = ({ height }) => <div style={{ height }}>NewComponent</div>;
NewComponent.propTypes = {
height: PropTypes.number
};
export default NewComponent;
Next time you run tests they'll fail because the output changed.
So after verifying that the output looks the way you want it to in Storybook (http://localhost:9001) and add a new test for the height
property
jest.unmock('./index');
import React from 'react';
import { renderAndCheckSnapshot } from '../testHelpers';
import NewComponent from './index';
describe('NewComponent', () => {
it('NewComponent component', () => {
renderAndCheckSnapshot(<NewComponent />);
});
it('NewComponent component', () => {
renderAndCheckSnapshot(<NewComponent height={100}/>);
});
});
you can update the snapshots
npm run test-update
How do determine what a component does?
There's a pattern you can follow
- Look at the Component.propTypes section
- This is essentially the API of the component
- Look at the render function
- Look at any helper functions
- Ask one of the contributors :)