diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 000000000..19eb536a1 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,13 @@ +# https://github.com/browserslist/browserslist + +# include browsers based on global coverage +> 1% + +# exclude all Internet Explorer versions +not ie > 0 + +# include browsers we are committed to support +last 1 Chrome version +last 1 Firefox version +last 1 Edge version +last 1 Safari version diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index d49c26d61..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,17 +0,0 @@ -const path = require('path'); - -module.exports = { - root: true, - extends: ['plugin:patternfly-react/recommended'], - rules: { - 'import/first': 'off' - }, - overrides: [ - { - files: ['**/__mocks__/**', '**/Stories/**', '*.stories.js', '*.test.js'], - rules: { - 'import/no-extraneous-dependencies': 'off' - } - } - ], -}; diff --git a/.gitignore b/.gitignore index 096ed519c..49c06cdfb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,15 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. +# https://git-scm.com/docs/gitignore -# dependencies -/node_modules +# files generated by package manager +/node_modules/ +/yarn-debug.log +/yarn-error.log -# testing -/coverage - -# production -/dist - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -.idea +# build artifacts and other generated files +/dist/ +/coverage/ +/cosmos/ +# editor files +/.idea/ +/.vscode/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 99c723616..000000000 --- a/.npmignore +++ /dev/null @@ -1,18 +0,0 @@ -coverage/ -scripts/ -config/ -src/ -storybook/ - -README-create-react-app.md -.travis.yml - -.idea -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local diff --git a/.prettierrc b/.prettierrc index 026543951..81b259b61 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,7 +2,7 @@ "semi": true, "singleQuote": true, "tabWidth": 2, - "trailingComma": "none", "useTabs": false, + "trailingComma": "none", "printWidth": 120 } diff --git a/.stylelintrc b/.stylelintrc deleted file mode 100644 index f5d14f0a6..000000000 --- a/.stylelintrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "defaultSeverity": "warning", - "extends": ["stylelint-config-standard"] -} diff --git a/.travis.yml b/.travis.yml index fe3e64623..d0844ff45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: node_js node_js: - - '10' + - 'lts/*' cache: yarn +before_install: + - curl -o- -L https://yarnpkg.com/install.sh | bash + - export PATH="$HOME/.yarn/bin:$PATH" + - yarn install script: -- yarn test -- yarn coveralls -- yarn dist - + - yarn build + - yarn coveralls diff --git a/README.md b/README.md index 4bcafe03a..e27905a06 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,3 @@ # web-ui-components Set of reusable components identified during [kubevirt/web-ui](https://github.com/kubevirt/web-ui) development. - diff --git a/config/babel.config.js b/config/babel.config.js new file mode 100644 index 000000000..387aec93f --- /dev/null +++ b/config/babel.config.js @@ -0,0 +1,6 @@ +// https://babeljs.io/docs/en/next/options + +module.exports = { + presets: ['@babel/preset-env', '@babel/preset-react'], + plugins: ['@babel/plugin-proposal-class-properties'] +}; diff --git a/config/cosmos.config.js b/config/cosmos.config.js new file mode 100644 index 000000000..c9e461ba0 --- /dev/null +++ b/config/cosmos.config.js @@ -0,0 +1,24 @@ +// https://github.com/react-cosmos/react-cosmos#config + +const paths = require('./paths'); + +module.exports = { + rootPath: paths.projectRoot, + + // by convention, all fixtures use the '.fixture.js' suffix + fileMatch: '**/*.fixture.js', + + // additional modules to load along with every component + globalImports: ['@babel/polyfill'], + + // path to Cosmos proxies + proxiesPath: `${paths.src}/cosmos/proxies.js`, + + // webpack server settings + webpackConfigPath: `${paths.config}/cosmos.webpack.config.js`, + watchDirs: [paths.src], + port: 9000, + + // directory where cosmos-export tool generates the static application + outputPath: paths.cosmosExport +}; diff --git a/config/cosmos.webpack.config.js b/config/cosmos.webpack.config.js new file mode 100644 index 000000000..e860838ed --- /dev/null +++ b/config/cosmos.webpack.config.js @@ -0,0 +1,23 @@ +// https://webpack.js.org/configuration/ + +const chalk = require('chalk'); +const paths = require('./paths'); +const babelOptions = require('./babel.config'); + +const webpackMode = process.env.NODE_ENV === 'production' ? 'production' : 'development'; +console.log(chalk`Running webpack in {white ${webpackMode}} mode.`); + +module.exports = { + mode: webpackMode, + + module: { + rules: [ + { + test: /\.js$/, + include: paths.src, + loader: 'babel-loader', + options: babelOptions + } + ] + } +}; diff --git a/config/env.js b/config/env.js deleted file mode 100644 index 30a6c7f1b..000000000 --- a/config/env.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const paths = require('./paths'); - -// Make sure that including paths.js after env.js will read .env variables. -delete require.cache[require.resolve('./paths')]; - -const NODE_ENV = process.env.NODE_ENV; -if (!NODE_ENV) { - throw new Error( - 'The NODE_ENV environment variable is required but was not specified.' - ); -} - -// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use -var dotenvFiles = [ - `${paths.dotenv}.${NODE_ENV}.local`, - `${paths.dotenv}.${NODE_ENV}`, - // Don't include `.env.local` for `test` environment - // since normally you expect tests to produce the same - // results for everyone - NODE_ENV !== 'test' && `${paths.dotenv}.local`, - paths.dotenv, -].filter(Boolean); - -// Load environment variables from .env* files. Suppress warnings using silent -// if this file is missing. dotenv will never modify any environment variables -// that have already been set. Variable expansion is supported in .env files. -// https://github.com/motdotla/dotenv -// https://github.com/motdotla/dotenv-expand -dotenvFiles.forEach(dotenvFile => { - if (fs.existsSync(dotenvFile)) { - require('dotenv-expand')( - require('dotenv').config({ - path: dotenvFile, - }) - ); - } -}); - -// We support resolving modules according to `NODE_PATH`. -// This lets you use absolute paths in imports inside large monorepos: -// https://github.com/facebookincubator/create-react-app/issues/253. -// It works similar to `NODE_PATH` in Node itself: -// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders -// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. -// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. -// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 -// We also resolve them to make sure all tools using them work consistently. -const appDirectory = fs.realpathSync(process.cwd()); -process.env.NODE_PATH = (process.env.NODE_PATH || '') - .split(path.delimiter) - .filter(folder => folder && !path.isAbsolute(folder)) - .map(folder => path.resolve(appDirectory, folder)) - .join(path.delimiter); - -// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be -// injected into the application via DefinePlugin in Webpack configuration. -const REACT_APP = /^REACT_APP_/i; - -function getClientEnvironment(publicUrl) { - const raw = Object.keys(process.env) - .filter(key => REACT_APP.test(key)) - .reduce( - (env, key) => { - env[key] = process.env[key]; - return env; - }, - { - // Useful for determining whether we’re running in production mode. - // Most importantly, it switches React into the correct mode. - NODE_ENV: process.env.NODE_ENV || 'development', - // Useful for resolving the correct path to static assets in `public`. - // For example, . - // This should only be used as an escape hatch. Normally you would put - // images into the `src` and `import` them in code to get their paths. - PUBLIC_URL: publicUrl, - } - ); - // Stringify all values so we can feed into Webpack DefinePlugin - const stringified = { - 'process.env': Object.keys(raw).reduce((env, key) => { - env[key] = JSON.stringify(raw[key]); - return env; - }, {}), - }; - - return { raw, stringified }; -} - -module.exports = getClientEnvironment; diff --git a/config/eslint.browser.js b/config/eslint.browser.js new file mode 100644 index 000000000..46c67c200 --- /dev/null +++ b/config/eslint.browser.js @@ -0,0 +1,55 @@ +// https://eslint.org/docs/user-guide/configuring +// This configuration covers code meant to execute in browser or browser-like environment. + +const chalk = require('chalk'); +const paths = require('./paths'); +const pkg = require(paths.packageJson); + +// resolve React version for use with eslint-plugin-react +const reactVersion = /^\D*(\d+).*$/.exec(pkg.peerDependencies.react)[1]; +console.log(chalk`Using React {white ${reactVersion}} linting rules.`); + +const commonRules = require('./eslint.rules.common'); +const reactRules = require('./eslint.rules.react'); + +module.exports = { + extends: [ + 'standard', + 'standard-react', + 'airbnb', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:promise/recommended', + 'plugin:jest/recommended', + // Prettier configuration comes last + 'plugin:prettier/recommended', + 'prettier/react', + 'prettier/standard' + ], + + env: { + es6: true, + browser: true, + jest: true + }, + + // use babel-eslint parser to ensure parity with Babel-supported syntax + parser: 'babel-eslint', + + settings: { + react: { + version: reactVersion + } + }, + + rules: { + ...commonRules, + ...reactRules, + 'react/destructuring-assignment': [ + 'error', + { + ignoreClassFields: true + } + ] + } +}; diff --git a/config/eslint.node.js b/config/eslint.node.js new file mode 100644 index 000000000..5eb541bfb --- /dev/null +++ b/config/eslint.node.js @@ -0,0 +1,35 @@ +// https://eslint.org/docs/user-guide/configuring +// This configuration covers code meant to execute in Node.js environment. + +const commonRules = require('./eslint.rules.common'); + +module.exports = { + extends: [ + 'standard', + 'airbnb', + 'plugin:node/recommended', + 'plugin:jest/recommended', + // Prettier configuration comes last + 'plugin:prettier/recommended', + 'prettier/standard' + ], + + env: { + es6: true, + node: true, + jest: true + }, + + parserOptions: { + // Node.js loads scripts as CommonJS modules + sourceType: 'script' + }, + + rules: { + ...commonRules, + 'no-console': 'off', + 'global-require': 'off', + 'import/no-dynamic-require': 'off', + 'import/newline-after-import': 'off' + } +}; diff --git a/config/eslint.rules.common.js b/config/eslint.rules.common.js new file mode 100644 index 000000000..79e14d5dc --- /dev/null +++ b/config/eslint.rules.common.js @@ -0,0 +1,33 @@ +// Common ESLint rule configuration extracted from patternfly-react/recommended. + +module.exports = { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true + } + ], + 'import/no-named-default': 'off', + 'import/prefer-default-export': 'off', + 'no-param-reassign': 'off', + 'no-plusplus': 'off', + 'no-prototype-builtins': 'off', + 'no-restricted-syntax': 'off', + 'no-underscore-dangle': 'off', + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTernary: true + } + ], + 'no-unused-vars': [ + 'error', + { + vars: 'all', + args: 'none', + ignoreRestSiblings: true + } + ], + 'no-use-before-define': 'off' +}; diff --git a/config/eslint.rules.react.js b/config/eslint.rules.react.js new file mode 100644 index 000000000..f48e880a0 --- /dev/null +++ b/config/eslint.rules.react.js @@ -0,0 +1,16 @@ +// React-specific ESLint rule configuration extracted from patternfly-react/recommended. + +module.exports = { + 'jsx-a11y/anchor-has-content': 'off', + 'jsx-a11y/anchor-is-valid': 'off', + 'jsx-a11y/label-has-for': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', + 'jsx-a11y/no-static-element-interactions': 'off', + 'jsx-a11y/no-noninteractive-element-interactions': 'off', + 'react/no-array-index-key': 'off', + 'react/forbid-prop-types': 'off', + 'react/jsx-filename-extension': 'off', + 'react/jsx-uses-vars': 'error', + 'react/no-danger': 'off', + 'react/sort-comp': 'off' +}; diff --git a/config/jest.config.js b/config/jest.config.js new file mode 100644 index 000000000..e186d235a --- /dev/null +++ b/config/jest.config.js @@ -0,0 +1,51 @@ +// https://jestjs.io/docs/en/configuration +// We use a single Jest configuration to cover all types of test environments. + +const paths = require('./paths'); +const testWatch = process.argv.slice(2).includes('--watch'); + +const commonProjectConfig = { + rootDir: paths.projectRoot, + + // by convention, all tests use the '.test.js' suffix + testMatch: ['**/*.test.js'] +}; + +module.exports = { + // each project represents a separate test configuration + projects: [ + { + displayName: 'jsdom-env', + ...commonProjectConfig, + roots: [paths.src], + testEnvironment: 'jsdom', + transform: { + '\\.js$': `${paths.config}/jest.transform.babel.js` + }, + setupFiles: [`${paths.src}/jest/setupTest.js`], + snapshotSerializers: ['enzyme-to-json/serializer'] + }, + + { + displayName: 'node-env', + ...commonProjectConfig, + roots: [paths.tools], + testEnvironment: 'node' + } + ], + + // don't collect coverage while in watch mode + collectCoverage: !testWatch, + + // files for which to collect coverage information + collectCoverageFrom: [ + 'src/components/**/*.js', + '!src/components/**/fixtures/**/*.js', + '!src/components/**/index.js', + 'tools/validations/*.js' + ], + + // coverage output settings + coverageDirectory: `${paths.coverage}`, + coverageReporters: ['lcov'] +}; diff --git a/config/jest.transform.babel.js b/config/jest.transform.babel.js new file mode 100644 index 000000000..b2636bfc7 --- /dev/null +++ b/config/jest.transform.babel.js @@ -0,0 +1,4 @@ +// Custom Jest transformer that wraps babel-jest and applies project-specific +// Babel options, avoiding the need to rely on .babelrc lookup semantics. + +module.exports = require('babel-jest').createTransformer(require('./babel.config')); diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js deleted file mode 100644 index 8f6511481..000000000 --- a/config/jest/cssTransform.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -// This is a custom Jest transformer turning style imports into empty objects. -// http://facebook.github.io/jest/docs/en/webpack.html - -module.exports = { - process() { - return 'module.exports = {};'; - }, - getCacheKey() { - // The output is always the same. - return 'cssTransform'; - }, -}; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js deleted file mode 100644 index 9e4047d35..000000000 --- a/config/jest/fileTransform.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const path = require('path'); - -// This is a custom Jest transformer turning file imports into filenames. -// http://facebook.github.io/jest/docs/en/webpack.html - -module.exports = { - process(src, filename) { - return `module.exports = ${JSON.stringify(path.basename(filename))};`; - }, -}; diff --git a/config/paths.js b/config/paths.js index 6d16efc99..f14068eeb 100644 --- a/config/paths.js +++ b/config/paths.js @@ -1,55 +1,14 @@ -'use strict'; - const path = require('path'); -const fs = require('fs'); -const url = require('url'); - -// Make sure any symlinks in the project folder are resolved: -// https://github.com/facebookincubator/create-react-app/issues/637 -const appDirectory = fs.realpathSync(process.cwd()); -const resolveApp = relativePath => path.resolve(appDirectory, relativePath); - -const envPublicUrl = process.env.PUBLIC_URL; - -function ensureSlash(path, needsSlash) { - const hasSlash = path.endsWith('/'); - if (hasSlash && !needsSlash) { - return path.substr(path, path.length - 1); - } else if (!hasSlash && needsSlash) { - return `${path}/`; - } else { - return path; - } -} - -const getPublicUrl = appPackageJson => - envPublicUrl || require(appPackageJson).homepage; - -// We use `PUBLIC_URL` environment variable or "homepage" field to infer -// "public path" at which the app is served. -// Webpack needs to know it to put the right