diff --git a/.eslintrc b/.eslintrc index d1e743ec..b2928aef 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,12 +10,12 @@ "plugin:@typescript-eslint/recommended", "plugin:react/recommended", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier + "prettier/@typescript-eslint", + "plugin:prettier/recommended", "plugin:jsx-a11y/recommended", "plugin:import/errors", "plugin:import/warnings", - "plugin:import/typescript", - "prettier/@typescript-eslint", - "plugin:prettier/recommended" + "plugin:import/typescript" ], "plugins": [ "import", @@ -39,6 +39,7 @@ "sourceType": "module" // Allows for the use of imports }, "rules": { + "@typescript-eslint/indent": ["error", 2], "@typescript-eslint/explicit-function-return-type": ["error", { // "allowExpressions": true, "allowTypedFunctionExpressions": true @@ -52,13 +53,6 @@ { "devDependencies": true } - ], - "import/extensions": [ - 2, - { - "scss": "always" - } - ], - "@typescript-eslint/no-empty-function": "off" + ] } } \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index d86a2d23..86f9d987 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,7 +13,7 @@ module.exports = { '/src/**/__tests__/**/*.{js,jsx,mjs,ts,tsx}', '/src/**/?(*.)(spec|test).{js,jsx,mjs,ts,tsx}', ], - testEnvironment: 'jsdom', + testEnvironment: 'node', testURL: 'http://localhost', modulePaths: ['src'], moduleNameMapper: { diff --git a/package.json b/package.json index 69ac51f3..d4789f5e 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@loadable/babel-plugin": "5.10.3", "@loadable/webpack-plugin": "5.7.1", "@testing-library/react": "9.3.0", - "@testing-library/react-hooks": "3.1.0", "@types/axios": "0.14.0", "@types/cors": "2.8.6", "@types/express": "4.17.1", @@ -60,15 +59,15 @@ "@types/loadable__server": "5.9.1", "@types/node": "12.7.12", "@types/ramda": "0.26.28", - "@types/react": "16.9.19", - "@types/react-dom": "16.9.5", + "@types/react": "16.9.5", + "@types/react-dom": "16.9.1", "@types/react-helmet": "5.0.11", - "@types/react-redux": "7.1.7", - "@types/react-router-dom": "5.1.3", + "@types/react-redux": "7.1.4", + "@types/react-router-dom": "5.1.0", "@types/redux-mock-store": "1.0.1", "@types/testing-library__react": "9.1.2", - "@typescript-eslint/eslint-plugin": "2.14.0", - "@typescript-eslint/parser": "2.14.0", + "@typescript-eslint/eslint-plugin": "2.3.3", + "@typescript-eslint/parser": "2.3.3", "autoprefixer": "9.6.4", "babel-core": "7.0.0-bridge.0", "babel-eslint": "10.0.3", @@ -87,17 +86,18 @@ "cz-conventional-changelog": "3.0.2", "dotenv": "8.1.0", "escape-string-regexp": "2.0.0", - "eslint": "6.8.0", + "eslint": "6.5.1", "eslint-config-airbnb-base": "14.0.0", - "eslint-config-prettier": "6.10.0", - "eslint-import-resolver-webpack": "0.12.0", - "eslint-loader": "3.0.3", + "eslint-config-prettier": "6.3.0", + "eslint-import-resolver-webpack": "0.11.1", + "eslint-loader": "3.0.2", "eslint-plugin-class-property": "1.1.0", - "eslint-plugin-import": "2.19.1", + "eslint-plugin-flowtype": "4.3.0", + "eslint-plugin-import": "2.18.2", "eslint-plugin-jsx-a11y": "6.2.3", - "eslint-plugin-prettier": "3.1.2", - "eslint-plugin-react": "7.17.0", - "eslint-plugin-react-hooks": "2.3.0", + "eslint-plugin-prettier": "3.1.1", + "eslint-plugin-react": "7.16.0", + "eslint-plugin-react-hooks": "2.1.2", "express": "4.17.1", "express-manifest-helpers": "0.6.0", "file-loader": "4.2.0", @@ -126,7 +126,7 @@ "sass-loader": "8.0.0", "semantic-release": "15.13.24", "style-loader": "1.0.0", - "typescript": "3.7.4", + "typescript": "3.6.3", "url-loader": "2.2.0", "webpack": "4.41.0", "webpack-bundle-analyzer": "3.5.2", @@ -149,13 +149,13 @@ "lodash.throttle": "4.1.1", "node-uuid": "1.4.8", "ramda": "0.26.1", - "react": "16.12.0", - "react-dom": "16.12.0", + "react": "16.10.1", + "react-dom": "16.10.1", "react-helmet": "5.2.1", - "react-redux": "7.1.3", + "react-redux": "7.1.1", "react-router": "5.1.2", "react-router-dom": "5.1.2", - "redux": "4.0.5", + "redux": "4.0.4", "redux-thunk": "2.3.0" } } diff --git a/src/app/App.tsx b/src/app/App.tsx index 1c6f587b..5e39a708 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,9 +1,9 @@ -import React, { ReactElement } from 'react'; +import React from 'react'; import Routing from '~/components/routing'; import './assets/scss/styles.scss'; -const App = (): ReactElement => ; +const App = (): React.ReactElement => ; export default App; diff --git a/src/app/components/base/Icon/Icon.test.tsx b/src/app/components/base/Icon/Icon.test.tsx index ea100f72..35a3a1ce 100644 --- a/src/app/components/base/Icon/Icon.test.tsx +++ b/src/app/components/base/Icon/Icon.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-ignore */ import React from 'react'; -import { act } from 'react-test-renderer'; +import { act } from 'react-dom/test-utils'; import { render, cleanup, RenderResult } from '@testing-library/react'; import Icon from './Icon'; diff --git a/src/app/components/base/Icon/Icon.tsx b/src/app/components/base/Icon/Icon.tsx index e669bb06..4f0eb10c 100644 --- a/src/app/components/base/Icon/Icon.tsx +++ b/src/app/components/base/Icon/Icon.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, FunctionComponent } from 'react'; +import React, { useState, useEffect } from 'react'; import './Icon.scss'; @@ -8,14 +8,13 @@ type PropsType = { size: number | string; }; -const Icon: FunctionComponent = ({ path, size }: PropsType) => { +const Icon = ({ path, size }: PropsType): JSX.Element => { const [iconPath, setIconPath] = useState(''); useEffect(() => { (async (): Promise => { try { - // eslint-disable-next-line prettier/prettier - const icon = await import(/* webpackChunkName: "ShopIcon" */ '~/assets/svg/' + path + '.svg'); // eslint-disable-line prefer-template + const icon = await import('~/assets/svg/' + path + '.svg'); // eslint-disable-line prefer-template setIconPath(icon.default); // eslint-disable-next-line no-empty } catch (err) {} diff --git a/src/app/components/common/ImgLoader/ImgLoader.test.tsx b/src/app/components/common/ImgLoader/ImgLoader.test.tsx index bead723e..84ef313b 100644 --- a/src/app/components/common/ImgLoader/ImgLoader.test.tsx +++ b/src/app/components/common/ImgLoader/ImgLoader.test.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import React from 'react'; -import { cleanup, render, RenderResult, act } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; +import { cleanup, render, RenderResult } from '@testing-library/react'; import ImgLoader from './ImgLoader'; diff --git a/src/app/components/common/ImgLoader/ImgLoader.tsx b/src/app/components/common/ImgLoader/ImgLoader.tsx index 18f60ee4..63723f35 100644 --- a/src/app/components/common/ImgLoader/ImgLoader.tsx +++ b/src/app/components/common/ImgLoader/ImgLoader.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, memo, ReactElement } from 'react'; +import React, { useState, useEffect, memo } from 'react'; import Spinner from '~/components/base/Spinner'; @@ -11,7 +11,7 @@ type PropsType = { onError?: (error: string | Event) => void; }; -function ImgLoader({ src, onError }: PropsType): ReactElement { +function ImgLoader({ src, onError }: PropsType): React.ReactElement { const [imgObj, setImg] = useState({ img: '', isLoading: true }); const image = new Image(); diff --git a/src/app/components/common/LazyComponent/LazyComponent.test.tsx b/src/app/components/common/LazyComponent/LazyComponent.test.tsx new file mode 100644 index 00000000..b41c8809 --- /dev/null +++ b/src/app/components/common/LazyComponent/LazyComponent.test.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { render, RenderResult } from '@testing-library/react'; + +import LazyComponent from './LazyComponent'; + +const Example = (): React.ReactElement =>
Some Component
; + +describe('LazyComponent', () => { + // eslint-disable-next-line prettier/prettier + it('should have component\'s name as className', async () => { + let testRenderer = {} as RenderResult; + + await act(async () => { + testRenderer = render( + React.ReactElement }> => + Promise.resolve({ default: Example }) + } + />, + ); + }); + const { container } = testRenderer; + const div = container.firstChild as HTMLDivElement; + + expect(div.textContent).toEqual('Some Component'); + }); +}); diff --git a/src/app/components/common/LazyComponent/LazyComponent.tsx b/src/app/components/common/LazyComponent/LazyComponent.tsx new file mode 100644 index 00000000..e136fb7b --- /dev/null +++ b/src/app/components/common/LazyComponent/LazyComponent.tsx @@ -0,0 +1,32 @@ +import React, { useState, useEffect } from 'react'; + +type GetModule = () => Promise<{ default: () => React.ReactElement }>; + +type PropsType = { + getModule: GetModule; + children?: React.ElementType; +}; + +const LazyComponent = ({ getModule, ...rest }: PropsType): React.ReactElement | null => { + const [AsyncModule, setAsyncModule] = useState<(() => React.ReactElement) | null>(null); + + useEffect(() => { + (async (): Promise => { + try { + const module = await getModule(); + setAsyncModule(() => module.default); + } catch (err) { + throw new Error(`LazyComponent error: ${err}`); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (AsyncModule) { + return ; + } + + return null; +}; + +export default LazyComponent; diff --git a/src/app/components/common/LazyComponent/index.tsx b/src/app/components/common/LazyComponent/index.tsx new file mode 100644 index 00000000..872994e6 --- /dev/null +++ b/src/app/components/common/LazyComponent/index.tsx @@ -0,0 +1 @@ +export { default } from './LazyComponent'; diff --git a/src/app/components/common/ProductCard/ProductCard.test.tsx b/src/app/components/common/ProductCard/ProductCard.test.tsx index ef05c48e..e634f9c4 100644 --- a/src/app/components/common/ProductCard/ProductCard.test.tsx +++ b/src/app/components/common/ProductCard/ProductCard.test.tsx @@ -3,12 +3,12 @@ import React from 'react'; import { render, cleanup } from '@testing-library/react'; import ProductCard from './ProductCard'; -import { Product } from '~/store/modules/products/types'; +import { ProductType } from '~/store/modules/products/types'; afterEach(cleanup); describe('ProductCard', () => { - const product: Product = { + const product: ProductType = { _id: '5cc2def690118411e1311e92', name: 'Nike Air Jordan', image: diff --git a/src/app/components/common/ProductCard/ProductCard.tsx b/src/app/components/common/ProductCard/ProductCard.tsx index 5639ec8d..ee80b070 100644 --- a/src/app/components/common/ProductCard/ProductCard.tsx +++ b/src/app/components/common/ProductCard/ProductCard.tsx @@ -2,12 +2,12 @@ import React, { ReactElement } from 'react'; import ImgLoader from '~/components/common/ImgLoader'; -import { Product } from '~/store/modules/products/types'; +import { ProductType } from '~/store/modules/products/types'; import './ProductCard.scss'; type PropsType = { - product: Product; + product: ProductType; hasOverlay: boolean; hasHover: boolean; width: string; diff --git a/src/app/components/common/Sidebar/Sidebar.test.tsx b/src/app/components/common/Sidebar/Sidebar.test.tsx index 09823748..49f8d8fd 100644 --- a/src/app/components/common/Sidebar/Sidebar.test.tsx +++ b/src/app/components/common/Sidebar/Sidebar.test.tsx @@ -1,5 +1,5 @@ import React, { ReactElement } from 'react'; -import { act } from 'react-test-renderer'; +import { act } from 'react-dom/test-utils'; import { render, cleanup, fireEvent, RenderResult } from '@testing-library/react'; import Sidebar from './Sidebar'; diff --git a/src/app/components/core/Footer/Footer.test.tsx b/src/app/components/core/Footer/Footer.test.tsx index a9347137..5b0984a8 100644 --- a/src/app/components/core/Footer/Footer.test.tsx +++ b/src/app/components/core/Footer/Footer.test.tsx @@ -1,29 +1,33 @@ -import { cleanup, render, RenderResult } from '@testing-library/react'; +import React from 'react'; -import renderFromAlien from '~/shared/utils/testing/renderFromAlien'; -import Footer from './index'; +import { cleanup } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; -afterEach(cleanup); - -let testRenderer = {} as RenderResult; +import renderWithRedux from '~/shared/utils/testing/renderWithRedux'; -beforeEach(async () => { - const { result, wrapper } = await renderFromAlien(Footer); - const FooterComponent = result.current; +import Footer from './Footer'; - testRenderer = render(FooterComponent, { wrapper }); -}); +afterEach(cleanup); describe('Footer test', () => { it('should have only one social network', async () => { + let testRenderer = {}; + await act(async () => { + testRenderer = renderWithRedux(