This repo contains the base architecture of the project, while further development was moved to a private monorepo (for now). You might also like to checkout the first ever version of the project for prior updates.
- TypeScript
- Theming
- Styled System
- Doc Update
-
Structural TestingAutomated Visual Testing - CircleCI Setup
- TypeScript ESLint Setup
- Git Hooks
-
Create the application:
$ npx create-react-app gorg-ui-v3 --typescript
-
Add Storybook:
$ cd gorg-ui-v3 && npx -p @storybook/cli sb init
-
Quickly check that all environments are working properly:
# Run the test runner (Jest) in a terminal: $ yarn test # Start the component explorer on port 9009: $ yarn run storybook # Run the frontend app proper on port 3000: $ yarn start
-
Automated Testing via StoryShots:
Install the package and its types:
$ yarn add -D @storybook/addon-storyshots @types/storybook__addon-storyshots
Create a
src/storybook.test.js
file and put:import initStoryshots from "@storybook/addon-storyshots"; initStoryshots();
-
Configure the App for Jest (Component Story Format (CSF) version):
Configure Jest to work with Webpack's
require.context()
:// .storybook/config.js import { configure } from "@storybook/react"; const req = require.context("../src/components", true, /\.stories\.js$/); // <- import all the stories at once configure(req, module);
The above works only during the build with Webpack, polyfill this to work with Jest by first installing Macro (for CRA v2+):
$ yarn add -D require-context.macro
In the same file, import the Macro and run it in place of
require.context
:import requireContext from "require-context.macro"; // const req = require.context('../src/components', true, /\.stories\.js$/); <-- replaced const req = requireContext("../src/components", true, /\.stories\.js$/);
StoryShots is dependent on
react-test-renderer
:$ yarn add -D react-test-renderer
-
Setting Up Addons - Knobs
Install Addon Knobs:
$ yarn add @storybook/addon-knobs
Register Knobs in your
.storybook/addons.js
file:import "@storybook/addon-actions/register"; import "@storybook/addon-knobs/register"; import "@storybook/addon-links/register";
Usage:
import { withKnobs, text } from "@storybook/addon-knobs/react"; import Button from "./Button"; export default { title: "Button", component: Button, decorators: [withKnobs] }; export const Default = () => ( <Button>{text("children", "Simple Button")}</Button> );
-
Finishing up TypeScript Setup
Add custom Webpack config by creating
.storybook/webpack.config.js
file and put:module.exports = ({ config, mode }) => { config.module.rules.push({ test: /\.(ts|tsx)$/, loader: require.resolve("babel-loader"), options: { presets: [["react-app", { flow: false, typescript: true }]] } }); config.resolve.extensions.push(".ts", ".tsx"); return config; };
Make sure to add
babel-loader
:$ yarn add -D babel-loader
Update extensions accordingly (from
.js
to.ts|tsx
):.storybook/config.js
=>.storybook/config.ts
requireContext('../src/components', true, /\.stories\.js$/)
=>requireContext('../src/components', true, /\.stories\.tsx$/)
Could not find a declaration file for module '@storybook/addon-knobs/react'
// BEFORE:
import { withKnobs, object } from "@storybook/addon-knobs/react";
// AFTER:
import { withKnobs, object } from "@storybook/addon-knobs";
Type '{ color?: ... }' is not assignable to type 'ButtonHTMLAttributes'
// FIX 1: Set styled component type to `any`:
const Button: any = styled("button") < ButtonProps > ``;
// Fix 2: Define custom prop `textColor` in place of `color`:
const textColor = system({
textColor: {
property: "color", // <-- CSS property
scale: "colors" // <-- key reference in the `theme` object
}
});
For Fix 2, make sure to do the same thing for prop bg|backgroundColor
since this is also part of the built-in API color
.
π DocsPage Setup
-
Install DocsPage:
$ yarn add -D @storybook/addon-docs
-
Create the file
.storybook/presets.js
and put:module.exports = ["@storybook/addon-docs/react/preset"];
-
Install
react-docgen-typescript-loader
:$ yarn add -D react-docgen-typescript-loader
May need to specify the
tsconfig
for the loader in case props are not displaying:// webpack.config.js config.module.rules.push({ ... use: [ { loader: require.resolve('react-docgen-typescript-loader'), options: { tsconfigPath: path.resolve(__dirname, '../tsconfig.json') } } ] });
Storybook tests UI visually via an add-on called storyshot-puppeteer
:
Same as
storyshots
, it works by comparing screenshots β only this time it takes screenshots of the browser and not code
-
Install
storyshot-puppeteer
:$ yarn add D @storybook/addon-storyshots-puppeteer
-
Update the test file
storybook.test.ts
to override the test comparison withimageSnapshot
from thepuppeteer
add-on:// storybook.test.ts import initStoryshots from "@storybook/addon-storyshots"; import { imageSnapshot } from "@storybook/addon-storyshots-puppeteer"; initStoryshots({ test: imageSnapshot({ storybookUrl: "http://localhost:9009/" }) });
Note that the tests now depend on having an instance of Storybook running, so make sure Storybook server is up and running before running the tests.
Running two terminals (one for Storybook, another one for Testing) at the same time on CI environment is made possible by start-server-and-test
.
-
Install it:
$ yarn add start-server-and-test
-
Sample script:
// package.json "scripts": { "test": "react-scripts test --coverage", "storybook": "start-storybook -p 9009 -s public", "ci:test": "start-test storybook 9009 test", }
Collecting Jest Data via Junit (CRA version)
-
Install
jest-junit
:$ yarn add -D jest-junit
-
Tell Junit to save the report output to a directory of your choice (
test-reports
in my case):// package.json "jest-junit": { "outputDirectory": "test-reports" }
$ CI=true yarn run test:coverage -i --reporters=default --reporters=jest-junit
CI=true
- force Jest to run tests once and finish the process-i
- force Jest to use only the virtualized build environment within the virtual machine--reporters=default --reporters=jest-junit
- tell Jest to generate a Junit report
# .circleci/config.yml
- store_artifacts:
path: test-reports/ # upload the test result folder as an artifact
- store_test_results:
path: test-reports/ # display test result in build summary section
TypeScript ESLint Setup
-
Install the required dependencies:
$ yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
-
Create the
.eslintrc
file and put:{ "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"] }
In the same file, add the following recommended rules:
// .eslintrc { ... // Add this to avoid runtime error "parserOptions": { "project": "./tsconfig.json" }, "extends": [ // Rules which recommended for all projects by the ESLint Team "eslint:recommended", // Make all eslint rules compatible with TS "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", // for type-checking to work properly with highly-valuable rules "plugin:@typescript-eslint/recommended-requiring-type-checking" ] }
$ npx install-peerdeps --dev eslint-config-airbnb
// .eslintrc
{
...
"extends": [
// ESLint rules, including ES6+ and React
"airbnb",
// Enable the linting rules for React hooks
"airbnb/hooks",
...
],
}
$ yarn add -D eslint-config-prettier
// .eslintrc
{
...
"extends": [
...
// Disable above code formatting related rules
"prettier",
// Required if a config uses a plugin to avoid conflict with Prettier
"prettier/@typescript-eslint",
"prettier/react"
],
}
In case of Prettier not configured in a workspace, add eslint-plugin-prettier
to enforce the formatter.
$ yarn add -D prettier eslint-plugin-prettier
// .eslintrc
{
...
"plugins": [..., "prettier"],
"extends": [
"prettier/react"
"plugin:prettier/recommended"
],
"prettier/prettier": [
"error",
{
"no-var-keyword": true,
"printWidth": 100,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2
}
]
}
-
Install the required dependencies:
$ yarn add -D husky lint-staged prettier pretty-quick
-
Sample config files:
// .huskyrc { "hooks": { "pre-commit": "pretty-quick --staged --verbose && lint-staged", "pre-push": "yarn run test:all" } }
// .lintstagedrc { "src/**/*.{js,ts,tsx}": [ "eslint --fix", "git add" ] }