React starter (Webpack + Typescript + Css modules + Jest)

от автора

Webpack 5

Webpack

Приветствую уважаемое сообщество.

Хочу поделиться своим видением сборки для быстрого старта разработки на React.

Помогает быстро запуститься, когда нужно «на скорую» войти в разработку.

Что-то я подглядел здесь же, на Хабре, к чему-то пришёл сам, ну и «ангажировал» немного на просторах «необъятного».

Цель поста — поделиться полезным, услышать мнения, прознать про брешь.

Поехали.

Что «под капотом»

  1. Webpack 5

  2. React v.18

  3. Redux (Redux Toolkit)

  4. Typescript

  5. Css modules

  6. Jest

  7. VS Code

Структура директорий

Кратко опишу некоторые из представленных:

__jest__

Файлы конфигураций Jest

__tests__

Папка, в которой лежат тесты. Да, я знаю что рекомендуется (удобно) размещать файлы тестов на одном уровне с компонентом. Конфиг позволяет это делать. Подход с выносом тестов в отдельную директорию тоже имеет все шансы быть.

__webpack__

Файлы конфигураций Webpack

build

Output папка c бандлом.

.vscode

Папка, где хранятся настройки VS Code. Весьма полезно иногда в них поковыряться.

public

Каталог, в котором лежат файлы, изменяемые и перемещаемые бандлером.

static

Каталог, в котором лежат файлы, не изменяемые и перемещаемые бандлером.

src

Корневая папка с кодом. Здесь творится волшебство.

src/core

Тут размещаются файлы с запросами, state-менеджер.

src/pages

Компоненты страниц

src/routes

Файлы роутов (ендпоинты)

Конфигурируем Webpack

Для разделения на dev и prod используется (если можно так сказать) принцип расширения базовой конфигурации. Если кратко, есть файл большинства настроек, который дополняется другим файлом, в зависимости от среды разработки (dev, либо prod).

Тут можно изучить подробнее

В папке __webpack__ были созданы 3 файла:

  • common.config.js

  • dev.config.js

  • prod.config.js

Два последних дополняют первый.

common.config.js

Скачиваем и подключаем webpack

npm i —save -D webpack webpack-cli webpack-dev-server

Выносим нужные пути в константы:

const webpack = require("webpack"); const path = require("path");  const BUILD_DIR = path.resolve(__dirname, "..", "build"); const PUBLIC_DIR = path.resolve(__dirname, "..", "public"); const STATIC_DIR = path.resolve(__dirname, "..", "static");
Устанавливаем плагины в качестве Dev-зависимостей:
npm i --save -D mini-css-extract-plugin html-webpack-plugin favicons-webpack-plugin @pmmmwh/react-refresh-webpack-plugin filemanager-webpack-plugin

mini-css-extract-plugin

Создает файл CSS для каждого файла JS, содержащего CSS

https://www.npmjs.com/package/mini-css-extract-plugin

html-webpack-plugin

Плагин подключит создаваемые бандлером файлы в указанный в качестве шаблона index.html

https://webpack.js.org/plugins/html-webpack-plugin/

favicons-webpack-plugin

Плагин для генерации фавиконок.

https://www.npmjs.com/package/favicons-webpack-plugin

favicons

Пакет необходим для favicons-webpack-plugin

https://www.npmjs.com/package/favicons

@pmmmwh/react-refresh-webpack-plugin

Осуществляет быстрое обновление компонентов React при изменении кода. Hot Reload.

https://www.npmjs.com/package@pmmmwhh/react-refresh-webpack-plugin

react-refresh

Пакет также необходим Webpack для «Горячей перезагрузки»

https://www.npmjs.com/package/react-refresh

filemanager-webpack-plugin

Позволяет копировать, архивировать перемещать, удалять файлы и каталоги до и после сборки.

https://www.npmjs.com/package/filemanager-webpack-plugin

и подключаем
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const FaviconsWebpackPlugin = require("favicons-webpack-plugin"); const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin"); const FileManagerPlugin = require("filemanager-webpack-plugin");  const plugins = [   new FileManagerPlugin({     events: {       // Remove build dir       onStart: {         delete: [BUILD_DIR],       },       onEnd: {         // Copy static files         copy: [           {             source: STATIC_DIR,             destination: BUILD_DIR,           },         ],       },     },   }),   new HtmlWebpackPlugin({     template: path.join(PUBLIC_DIR, "index.html"),     filename: "index.html",   }),   //   new FaviconsWebpackPlugin({     logo: path.resolve(PUBLIC_DIR, "favicon.svg"),     prefix: "/favicons/",     outputPath: path.resolve(BUILD_DIR, "favicons"),     mode: "webapp",     // Injecting into all HTML Files or separately (for an every instance of HtmlWebpackPlugin)     // inject: true,     inject: (htmlPlugin) =>       path.basename(htmlPlugin.options.filename) === "index.html",     favicons: {       icons: {         appleIcon: false, // Apple touch icons.         appleStartup: false, // Apple startup images.         android: false, // Android homescreen icon.         favicons: true, // Regular favicons.         coast: false, // Opera Coast icon.         firefox: false, // Firefox OS icons.         windows: false, // Windows 8 tile icons.         yandex: false, // Yandex browser icon.       },     },     cache: false, // Disallow caching the assets across webpack builds.   }),   new webpack.HotModuleReplacementPlugin(), // For page reloading ];  if (process.env.SERVE) {   plugins.push(new ReactRefreshWebpackPlugin()); }


Добавляем Dev Server

const devServer = {   historyApiFallback: true, // Apply HTML5 History API if routes are used   open: true,   compress: true,   allowedHosts: "all",   hot: true, // Reload the page after changes saved (HotModuleReplacementPlugin)   client: {     // Shows a full-screen overlay in the browser when there are compiler errors or warnings     overlay: {       errors: true,       warnings: true,     },     progress: true, // Prints compilation progress in percentage in the browser.   },    port: 3000,   /**    * Writes files to output path (default: false)    * Build dir is not cleared using <output: {clean:true}>    * To resolve should use FileManager    */   devMiddleware: {     writeToDisk: true,   },   static: [     // Required to use favicons located in a separate directory as assets     // Should use with historyApiFallback, to avoid of 404 for routes     {       directory: path.join(BUILD_DIR, "favicons"),     },   ], };

Здесь стоит отметить используемое devMiddleware. А именно его свойство:

writeToDisk: true

Оно позволяет записывать выходные файлы, с которым работает dev server на диск. Это может быть полезно, если хочется просмотреть получаемое.

Но тут же возникает проблема. Билд не будет удаляться при повторном использовании.

Для этого нам и пригодится File Manager плагин, подключенный ранее.

Вернее, его свойство:

events: {       // Remove build dir       onStart: {         delete: [BUILD_DIR],       }, }

Дополнительно, хочу отметить:

static: [     {       directory: path.join(BUILD_DIR, "favicons"),     }, ],

Здесь мы указываем серверу, где искать фавиконки.


Подключили плагины, поставили Dev-сервер, прописали параметры сборки.

Указываем правила формирования модулей:

Добавляем рулзы и экспортируем:
module.exports = {   devServer,   plugins,   entry: path.join(__dirname, "..", "src", "index.tsx"),   output: {     path: BUILD_DIR,     /**      * Helps to avoid of MIME type ('text/html') is not a supported stylesheet      * And sets address in html imports      */     publicPath: "/",   },   // Checking the maximum weight of the bundle is disabled   performance: {     hints: false,   },   // Modules resolved   resolve: {     extensions: [".tsx", ".ts", ".js"],   },   module: {     strictExportPresence: true, // Strict mod to avoid of importing non-existent objects     rules: [       // --- JS | TS USING BABEL       {         test: /\.[jt]sx?$/,         exclude: /node_modules/,         use: {           loader: "babel-loader",           options: {             cacheDirectory: true, // Using a cache to avoid of recompilation           },         },       },       // --- HTML       { test: /\.(html)$/, use: ["html-loader"] },       // --- S/A/C/SS       {         test: /\.(s[ac]|c)ss$/i,         use: [           MiniCssExtractPlugin.loader,           {             loader: "css-loader", // translates css into CommonJS             options: {               esModule: true,               // css modules               modules: {                 localIdentName: "[name]__[local]__[hash:base64:5]", // format of output                 namedExport: true, // named exports instead of default               },             },           },           {             // autoprefixer             loader: "postcss-loader",             options: {               postcssOptions: {                 plugins: [                   [                     "postcss-preset-env",                     {                       // Options                     },                   ],                 ],               },             },           },         ],       },       // --- S/A/SS       {         test: /\.(s[ac])ss$/i,         use: ["sass-loader"],       },       // --- IMG       {         test: /\.(png|jpe?g|gif|svg|webp|ico)$/i,         type: "asset/resource",         generator: {           filename: "assets/img/[hash][ext]",         },       },       // --- FONTS       {         test: /\.(woff2?|eot|ttf|otf)$/i,         exclude: /node_modules/,         type: "asset/resource",         generator: {           filename: "assets/fonts/[hash][ext]",         },       },     ],   }, };

Коротко об используемых плагинах:

Установка в Dev
npm i --save -D babel-loader html-loader css-loader postcss-loader postcss-preset-env sass-loader

babel-loader

Транспайлер для js/ts

https://www.npmjs.com/package/babel-loader

html-loader

Необходим для экспорта html файлов. Плагин не официальный, написан сообществом.

https://www.npmjs.com/package/html-loader

css-loader

Позволяет использовать @import и url() как import/require() в JS

https://www.npmjs.com/package/css-loader

postcss-loader

Плагин для работы с css

https://www.npmjs.com/package/postcss-loader

postcss-preset-env

Плагин (надстройка) для postcss. Позволяет преобразовывать современный CSS во что-то, понятное большинству браузеров, определяя нужные полифилы на основе целевых браузеров или сред выполнения.

https://www.npmjs.com/package/postcss-preset-env

sass-loader

Преобразует CSS-препроцессоры в CSS

https://www.npmjs.com/package/sass-loader

dev.config.js

dev.config.js
const { merge } = require("webpack-merge"); const common = require("./common.config.js"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");  const plugins = [   new MiniCssExtractPlugin({     filename: "[name].[contenthash].css",   }), ];  module.exports = merge(common, {   mode: "development",   target: "web",   plugins,   devtool: "inline-source-map",   output: {     filename: "[name].[contenthash].js",   }, });

prod.config.js

prod.config.js
const { merge } = require("webpack-merge"); const common = require("./common.config.js"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");  const plugins = [   new MiniCssExtractPlugin({     filename: "[contenthash].css",   }),   // Compress images   new ImageMinimizerPlugin({     minimizer: {       implementation: ImageMinimizerPlugin.imageminMinify,       options: {         plugins: [           ["gifsicle", { interlaced: true }],           ["jpegtran", { progressive: true }],           ["optipng", { optimizationLevel: 8 }],           [             "svgo",             {               plugins: [                 {                   name: "preset-default",                   params: {                     overrides: {                       removeViewBox: false,                       addAttributesToSVGElement: {                         params: {                           attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],                         },                       },                     },                   },                 },               ],             },           ],         ],       },     },   }), ];  module.exports = merge(common, {   mode: "production",   target: "browserslist",   plugins,   devtool: false,   output: {     filename: "[fullhash].js",   },   optimization: {     usedExports: false,     minimize: true, // Affects Terser Plugin     minimizer: [       new TerserPlugin({         terserOptions: {           mangle: false,           compress: true,           output: {             beautify: true,             comments: false,           },         },         extractComments: false,       }),     ],   }, }); 

Различия между dev и prod версиями данной сборки заключаются в разном именовании output-файлов. Конечно же могут быть расширены.

Плюс в проде добавлены сжатие изображений и минификация кода в bundle.js.

Здесь чуть задержусь.

За сжатие изображений отвечает image-minimizer-webpack-plugin, а также другие плагины (можно назвать их — плагины частного случая :D), с которыми он взаимодействует.

Устанавливаем их
npm i --save -D image-minimizer-webpack-plugin imagemin imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo

За «сжатие» js бандла отвечает terser-webpack-plugin.

Устанавливать его не нужно, т.к. идёт с webpack 5 из коробки.

Подробнее тут

с конфигом webpack — всё.

Добавляем скрипты в package.json

package.json
"config": {     "dev": "--config __webpack__/dev.config.js",     "prod": "--config __webpack__/prod.config.js"   }, "scripts": {     "webpack-config-dev": "nodemon --watch \"./__webpack__/*\" --exec npm run start-dev",     "webpack-config-prod": "nodemon --watch \"./__webpack__/*\" --exec npm run start-prod",     "start-dev": "cross-env-shell webpack serve ${npm_package_config_dev}",     "start-prod": "cross-env-shell webpack serve ${npm_package_config_prod}",     "build-dev": "cross-env-shell webpack ${npm_package_config_dev}",     "build-prod": "cross-env-shell webpack ${npm_package_config_prod} --stats-children",     "clean": "rd /s /q build",     "lint": "eslint src --ext .js --ext .ts",     "lint-fix": "eslint src --ext .js --ext .ts --fix",     "test": "cross-env jest --config __jest__/jest.config.js",     "test-watch": "jest --watch --config __jest__/jest.config.js",     "test-coverage": "jest --coverage --config __jest__/jest.config.js"   },

webpack-config- [dev|prod]

Во время правки конфигурации часто приходится останавливать запущенный скрипт и затем заново запускать его. Каждый раз это делать надоедает, поэтому на помощь приходит nodemon с флагом —watch.

start-[dev|prod]

Собственно запуск сборки в соответствующих режимах. С ребилдингом при изменениях в коде.

Здесь используются переменные среды (адрес файлов конфигураций webpack), вынесенные в config, для удобства.

Подробнее тут

Также использован cross-env-shell для ухода от ошибок, связанных с привязкой к конкретной операционной системе.

Подробнее тут

build-[dev|prod]

Также сборка проекта, с единственным отличием. Скрипт собрал бандл и прекратил свою деятельность. Разница с предыдущим — это отсутствие флага serve.

clean

Просто удаление получаемой папки со сборкой.

lint и lint-fix

Найти, либо найти и исправить ошибки, выдаваемые линтером.

Оставшиеся 3 скрипта отвечают за тестирование.

Ставим необходимые пакеты
npm i --save -D nodemon cross-env eslint jest

nodemon

Автоматически перезапускает приложение при обнаружении изменений файлов в каталоге

https://www.npmjs.com/package/nodemon

cross-env

Необходим для кроссплатформенного выполнения CLI команд

https://www.npmjs.com/package/cross-env

eslint

Инструмент для шаблонного проектирования кода

https://www.npmjs.com/package/eslint

jest

JS фреймворк для написания и исполнения тестов

https://jestjs.io/

Установка React и полезных фронтовых штук

Нам потребуются (могут пригодиться):

react

Библиотека пользовательских интерфейсов

https://react.dev/

react-dom

Пакет для работы с DOM

https://www.npmjs.com/package/react-dom

react-redux

Позволяет компонентам React считывать данные из хранилища Redux

https://react-redux.js.org/introduction/getting-started

react-router-dom

Необходим для работы с React Router

https://www.npmjs.com/package/react-router-dom

@reduxjs/toolkit

Пакет для работы со стейт-менеджером Redux

https://redux-toolkit.js.org/

@emotion

Библиотека для написания CSS стилей, используя Javascript

https://emotion.sh/docs/introduction

@mui

Всем известная библиотека для написания компонентов

https://mui.com/

redux-logger

Как следует из названия, — это логгер изменения состояний для Redux

https://www.npmjs.com/package/redux-logger

@types/redux-logger

Пакет с типизацией для redux-logger

https://www.npmjs.com/package/@types/redux-logger

Устанавливаем как Prod зависимости
npm i react react-dom react-redux react-router-dom @reduxjs/toolkit @emotion/react @emotion/styled @mui/icons-material @mui/material

redux-logger и типизацию к нему отправляем в Dev
npm i --save -D redux-logger @types/redux-logger

Добавляем ESLint

Создаём в корне проекта файл и наполняем правилами, включая поддержку Typescript:

.eslintrc
{     "root": true,     "extends": [         "plugin:react/recommended",         "plugin:@typescript-eslint/recommended"     ],     "parser": "@typescript-eslint/parser",     "parserOptions": {         "sourceType": "module",         "ecmaVersion": 2015,         "ecmaFeatures": {             "jsx": true // JSX-compatible         }     },     "env": {         "es6": true,         "browser": true,         "node": true     },     "plugins": [         "@typescript-eslint",         "react"     ],     "rules": {         "@typescript-eslint/no-var-requires": "off", // To avoid of error: "Require statement not part of import statement", if ES modules are used          "semi": [             "error",             "always"         ],         "quotes": [             "error",             "double"         ],         "indent": "off",         "no-fallthrough": "off", // disallow fallthrough of case statements         "no-multiple-empty-lines": [             1,             {                 "max": 2             }         ], // disallow multiple empty lines (off by default)         "no-nested-ternary": 1, // disallow nested ternary expressions (off by default)         "eqeqeq": 2, // require the use of === and !==         "react/prop-types": "off" // Prevent missing props validation in a React component definition     },     "settings": {         "react": {             "version": "detect" // Tell eslint-plugin-react to automatically detect the latest version of react.         }     } }

Ставим, указанные в конфиге линтера плагины для работы с React и Typescript:

Установка в качестве Dev-зависимости
npm i --save -D eslint-plugin-react @typescript-eslint/eslint-plugin @typescript-eslint/parser

eslint-plugin-react

Необходим для правил, применимых к компонентам React

https://www.npmjs.com/package/eslint-plugin-react

@typescript-eslint/eslint-plugin

Плагин для написания правил под Typescript

https://www.npmjs.com/package/eslint-plugin

@typescript-eslint/parser

Необходим для чтения и анализа исходного кода Typescript

https://www.npmjs.com/package@typescript-eslint/parser

Добавляем конфигурацию Babel

В корне проекта создать файл конфигурации Babel:

babel.config.js
const plugins = [];  module.exports = {   presets: [     "@babel/preset-env",     "@babel/preset-react",     "@babel/preset-typescript",   ],   plugins, };

Ставим, указанные в конфиге плагины поддержки:

Установка в Dev
npm i --save -D @babel/preset-env @babel/preset-react @babel/preset-typescript

@babel/preset-env

Пресет, позволяющий использовать последнюю версию JavaScript без необходимости преобразования синтаксиса

https://babeljs.io/docs/babel-preset-env

@babel/preset-react

Пресет для плагинов React

https://babeljs.io/docs/babel-preset-react

@babel/preset-typescript

Пресет для Typescript

https://babeljs.io/docs/babel-preset-typescript

«Прикручиваем» Typescript

В корне создаём файл конфигурации:

tsconfig.json
{     "compilerOptions": {         "rootDirs": [             "src",             "__jest__"         ],         "outDir": "build",         "lib": [             "dom",             "esnext"         ],         // This will include all packages from array only         // node_modules/@types - is default path. Required, otherwise it will be ignored.         "typeRoots": [             "node_modules/@types",             "src/types"         ],         "target": "es5",         "skipLibCheck": true, // Skip type checking of declaration files (.d.ts)         "esModuleInterop": true, // Creates __importStar and __importDefault helpers for compatibility with the Babel         "allowSyntheticDefaultImports": true, // allows import w/o default prop         "strict": true, // Еnabling all of the strict mode family options         "forceConsistentCasingInFileNames": true, // Force consistent casing in file names         "noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statements         "module": "esnext", // Sets the module system for the program. Also it's required when use outFile option.          "moduleResolution": "node", // Specify the module resolution strategy         "resolveJsonModule": true, // Allows importing modules with a ‘.json’ extension, which is a common practice in node projects         "isolatedModules": true, // all implementation files must be modules (which means it has some form of import/export)         "noImplicitAny": true, // Raise error if the type "any" is specified somewhere         "noImplicitThis": true, // Raise error on "this" expressions with an implied "any" type         "noUnusedLocals": true, // Raise errors on unused local variables         "noEmit": true, // Do not emit compiler output files like JavaScript source code, source-maps or declarations         "jsx": "react",         "plugins": [             {                 "name": "typescript-plugin-css-modules", // auto-genertes virtual .d.ts for an every css file                 "options": {                     "customTemplate": "./customTemplate.js"                 }             }         ]     },     "exclude": [         "node_modules",         "build",         "coverage",         "webpack.*.js",         "*.config.js",         "**/*.test.ts*"     ] }

Ставим требуемые пакеты:

Естесственно в devDependencies
npm i --save -D typescript typescript-plugin-css-modules

typescript

Typescript

https://www.npmjs.com/package/typescript

typescript-plugin-css-modules

Плагин для работы с CSS-модулями в TS

https://www.npmjs.com/package/typescript-plugin-css-modules

В корне проекта остаётся создать файлик customTemplate.js, указанный в конфиге:

customTemplate.js
module.exports = (dts, { classes }) => {   return Object.keys(classes)     .map((key) => `export const ${key}: string`)     .join("\n"); };

Спасибо за рецепт

Последние штрихи — Jest

В созданной ранее, в корне проекта, папке __jest__, создаём файл конфигурации:

jest.config.js
module.exports = {   roots: ["../__tests__", "../src"],   setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], // Modules are meant for code which is repeating in each test file   moduleFileExtensions: ["js", "jsx", "ts", "tsx"],   moduleNameMapper: {     "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":       "<rootDir>/filesMock.js",   },   transform: {     "^.+\\.jsx?$": "babel-jest",     "^.+\\.tsx?$": "ts-jest",     ".+\\.(css|styl|less|sass|scss)$": "jest-css-modules-transform",   },   testMatch: ["**/?(*.)(spec|test).[jt]s?(x)"], // Finds test files named like abc.test|spec.ts?tsx|js|jsx in roots:[] prop.   testEnvironment: "jsdom", // To avoid of js DOM errors };

Кратко про параметры

Здесь указываются корневые пути хранения тестов, расширения и имена файлов с тестами.

Свойство transform содержит правила для преобразования «непонятного» для Nodejs синтаксиса в Javascript. Например компоненты React.

Почитать можно тут

Чуть подробнее о параметре setupFilesAfterEnv.

Здесь указывается список модулей, необходимых для выполнения каждого тестового файла.

Это полезно когда не нужно прописывать одни и те же импорты в каждом тесте.

Приведу пример.

Для тестирования используется библиотека Testing Library.

В данном случае, без настройки setupFilesAfterEnv, бросается исключение:

 FAIL  __tests__/components/homePage.test.tsx   × Home page shows the text (57 ms)    ● Home page shows the text      TypeError: expect(...).toBeInTheDocument is not a function        11 | it("Home page shows the text", () => {       12 |   renderWithProviders(<Home />);     > 13 |   expect(screen.getByText<HTMLHeadingElement>("Home page")).toBeInTheDocument();          |                                                             ^       14 | });       15 |

Так происходит, потому что метод toBeInTheDocument() не является частью/методом React Testing Library. Для решения проблемы необходимо установить пакет @testing-library/jest-dom , и затем импортировать его в файл с тестом.

Очевидно, что такой импорт придётся выполнять для каждого файла, содержащего подобный тест.

Соответственно, был создан файлик указанный в конфиге, содержащий вышеуказанный пакет.

jest.setup.ts
import "@testing-library/jest-dom";

Теперь, все импорты, указанные в нём, будут подгружаться непосредственно перед выполнением каждого файла с тестом.

Узнать больше можно здесь

В папке utils был создан (как говорит нам делать Официальная документация) вспомогательный файл:

utils/testUtils.tsx
import React, { PropsWithChildren } from "react"; import { render } from "@testing-library/react"; import type { RenderOptions } from "@testing-library/react"; import { configureStore } from "@reduxjs/toolkit"; import type { PreloadedState } from "@reduxjs/toolkit"; import { Provider } from "react-redux";  import type { store, RootState } from "../../src/core/redux/store"; // As a basic setup, import your same slice reducers import { postSlice } from "../../src/core/redux/slices/postSlice"; import { thunkSlice } from "../../src/core/redux/slices/thunkSlice";  // This type interface extends the default options for render from RTL, as well // as allows the user to specify other things such as initialState, store. interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {   preloadedState?: PreloadedState<RootState>;   // store?: AppStore;   store?: typeof store; }  export function renderWithProviders(   ui: React.ReactElement,   {     preloadedState = {},     // Automatically create a store instance if no store was passed in     store = configureStore({       reducer: {         postReducer: postSlice.reducer,         thunkReducer: thunkSlice.reducer,       },       preloadedState,     }),     ...renderOptions   }: ExtendedRenderOptions = {} ) {   function Wrapper({ children }: PropsWithChildren<object>): JSX.Element {     return <Provider store={store}>{children}</Provider>;   }    // Return an object with the store and all of RTL's query functions   return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }; } 

, потому что:

Тестовый код должен создавать отдельный экземпляр хранилища Redux для каждого теста, а не повторно использовать один и тот же экземпляр хранилища и сбрасывать его состояние . Это гарантирует отсутствие случайной утечки значений между тестами.

Не забываем установить все необходимые для тестирования пакеты:

Установка в Dev
npm i --save -D ts-jest jest-environment-jsdom jest-css-modules-transform babel-jest @types/jest @testing-library/react @testing-library/jest-dom

ts-jest

Jest трансформер, позволяющий тестировать проекты на Typescript

https://www.npmjs.com/package/ts-jest

jest-environment-jsdom

Пакет для работы с JSDOM

https://jestjs.io/docs/next/tutorial-jquery

jest-css-modules-transform

Конвертирует CSS файлы в JS модули

https://www.npmjs.com/package/jest-css-modules-transform

babel-jest

Jest трансформер для транспиляции

https://www.npmjs.com/package/babel-jest

@types/jest

Типизация для Jest

https://www.npmjs.com/package/@types/jest

@testing‑library/react

Библиотека тестов React Testing Library

https://www.npmjs.com/package/@testing-library/react

@testing-library/jest-dom

Пакет, расширяющий возможности Jest

https://www.npmjs.com/package/@testing-library/jest-dom

Осталось сказать про настройки VS Code

Открыть файл .vscode/settings.json и привести его к виду:

settings.json
{     "editor.formatOnSave": true,     "[typescriptreact]": {         "editor.defaultFormatter": "esbenp.prettier-vscode"     },     "[json]": {         "editor.defaultFormatter": "esbenp.prettier-vscode"     },     "typescript.updateImportsOnFileMove.enabled": "always",     "[javascript]": {         "editor.defaultFormatter": "esbenp.prettier-vscode"     },     "editor.codeActionsOnSave": {         "source.fixAll.eslint": true     },     "eslint.validate": [         "javascript",         "typescript"     ],     "typescript.tsdk": "node_modules\\typescript\\lib", // Use Workspace Version for using plugins from tsconfig     "typescript.enablePromptUseWorkspaceTsdk": true }

Здесь содержатся событийные правила для редактора.

Такие как, указание форматтера, действия при перемещении файла, действия при сохранении, и.т.п.

На этом, всё.

В качестве Конклюжна

Вышло сумбурно. Понимаю. Старался «лить меньше воды». Местами, где прям ну вообще засуха, можно смотреть комментарии в коде.

О многом не сказал, но готов обсудить.

Надеюсь кто-то найдёт здесь что-либо полезное.

Код сборки можно глянуть здесь

Спасибо за уделённое прочтению время.


ссылка на оригинал статьи https://habr.com/ru/articles/754560/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *