├── .browserslistrc ├── .dockerignore ├── .editorconfig ├── .env.example ├── .env.test ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .gitlab-ci.yml ├── .prettierrc ├── .sass-lint.yml ├── README-RU.md ├── README.md ├── babel.config.js ├── cli ├── core │ ├── cleaner │ │ ├── index.js │ │ ├── packageJson.js │ │ └── rm.js │ ├── inquirer.js │ ├── prompts.js │ └── ui.js ├── index.js └── package.json ├── cy-open.yml ├── cypress.json ├── docker-compose.dev.yml ├── docker-compose.e2e.yml ├── docker-compose.prod.yml ├── docker ├── Dockerfile.dev ├── Dockerfile.e2e └── Dockerfile.prod ├── docs ├── How_to_run_and_cofugure_tests.md └── components │ └── UIButton.md ├── jest-coverage-badges.js ├── jest.config.js ├── nginx └── nginx.conf ├── package.json ├── public ├── badge-branches.svg ├── badge-functions.svg ├── badge-lines.svg ├── badge-statements.svg ├── cli.png ├── favicon.ico ├── index.html └── preview.png ├── src ├── App.vue ├── api │ └── config.js ├── assets │ ├── fonts │ │ ├── OpenSansRegular.woff │ │ ├── OpenSansRegular.woff2 │ │ └── Poppins │ │ │ ├── Poppins-Bold.woff │ │ │ ├── Poppins-Bold.woff2 │ │ │ ├── Poppins-Medium.woff │ │ │ ├── Poppins-Medium.woff2 │ │ │ ├── Poppins-Regular.woff │ │ │ └── Poppins-Regular.woff2 │ ├── icons │ │ ├── css │ │ │ ├── materialdesignicons.min.css │ │ │ └── materialdesignicons.min.css.map │ │ ├── fonts │ │ │ ├── materialdesignicons-webfont.eot │ │ │ ├── materialdesignicons-webfont.ttf │ │ │ ├── materialdesignicons-webfont.woff │ │ │ └── materialdesignicons-webfont.woff2 │ │ └── preview.html │ ├── img │ │ └── md.svg │ └── scss │ │ ├── _base.scss │ │ ├── _fonts.scss │ │ ├── _vars.scss │ │ └── index.scss ├── axios.js ├── components │ ├── BG.vue │ ├── HelloWorld.vue │ └── ui │ │ └── UIButton.vue ├── config.js ├── directives │ └── WaveAnimation.js ├── locales │ ├── en.js │ ├── index.js │ └── ru.js ├── main.js ├── mixins.js ├── pages │ ├── Home.vue │ ├── Signin.vue │ └── Signup.vue ├── router │ ├── index.js │ ├── middlewarePipeline.js │ └── middlewares │ │ └── auth.js ├── services │ └── localStorageService.js └── store │ ├── index.js │ └── modules │ ├── auth │ ├── actions.js │ ├── mutations.js │ └── types.js │ └── likes │ ├── actions.js │ ├── getters.js │ ├── mutations.js │ └── types.js ├── tests ├── .eslintrc.js ├── e2e │ ├── components │ │ └── UIButton.spec.js │ ├── fixtures │ │ └── example.json │ ├── integrations │ │ └── Home.spec.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ └── index.js ├── server │ ├── config.js │ ├── data │ │ └── hello.js │ └── server.js └── unit │ ├── axios.spec.js │ └── test.spec.js └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=*** 2 | VUE_APP_SENTRY_DNS=*** 3 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_API_URL=http://localhost:8888 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /dist/ 3 | /coverage/ 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/airbnb', 9 | 'prettier' 10 | ], 11 | rules: { 12 | 'no-console': 'off', 13 | 'no-debugger': 'off', 14 | "no-param-reassign": 0, 15 | "no-lonely-if": 0, 16 | 'no-new': 0, 17 | 'linebreak-style': 0, 18 | 'no-bitwise': 0, 19 | 'indent': ['error', 2], 20 | 'comma-dangle': ['error', 'never'], 21 | 'arrow-parens': ['error', 'as-needed'], 22 | 'max-len': 'off', 23 | 'no-plusplus': 'off', 24 | 'prefer-destructuring': 'off', 25 | "import/no-unresolved": 'off', 26 | 'import/extensions': ['error', 'never', { 27 | 'packages': 'never', 28 | 'json': 'always' 29 | }], 30 | 'no-floating-decimal': 'off', 31 | 'no-underscore-dangle': 'off', 32 | 'import/no-extraneous-dependencies': 'off', 33 | 'semi': ['error', 'always'], 34 | 'space-before-function-paren': 0, 35 | 'quotes': ['error', 'single'], 36 | 'vue/max-attributes-per-line': 'off', 37 | 'no-extra-semi': 'error', 38 | 'import/no-dynamic-require': 0, 39 | 'new-cap': ['error', { 40 | 'newIsCap': false, 41 | 'properties': false 42 | }] 43 | }, 44 | 'parserOptions': { 45 | 'parser': 'babel-eslint' 46 | }, 47 | 'overrides': [ 48 | { 49 | 'files': [ 50 | '**/__tests__/*.{j,t}s?(x)', 51 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 52 | ], 53 | 'env': { 54 | 'jest': true 55 | } 56 | } 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /coverage 5 | /cypress 6 | /.nyc_output 7 | 8 | # local env files 9 | .env 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:14.15.4 2 | 3 | cache: 4 | paths: 5 | - node_modules/ 6 | 7 | stages: 8 | - build 9 | - lint 10 | - test 11 | - deploy 12 | - pages 13 | 14 | install_dependencies: 15 | stage: build 16 | script: 17 | - npm install 18 | artifacts: 19 | paths: 20 | - node_modules/ 21 | 22 | lint: 23 | stage: lint 24 | script: 25 | - npm run lint 26 | 27 | test: 28 | stage: test 29 | script: 30 | - npm run test 31 | 32 | pages: 33 | image: node:14.15.4 34 | stage: deploy 35 | script: 36 | - npm run build 37 | - mv public public-vue # GitLab Pages hooks on the public folder 38 | - mv dist public # rename the dist folder (result of npm run build) 39 | # optionally, you can activate gzip support with the following line: 40 | - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec gzip -f -k {} \; 41 | artifacts: 42 | paths: 43 | - public # artifact path must be /public for GitLab Pages to pick it up 44 | only: 45 | - master 46 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "proseWrap": "never", 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "trailingComma": "none", 7 | "bracketSpacing": true, 8 | "semi": true, 9 | "arrowParens": "avoid" 10 | } 11 | -------------------------------------------------------------------------------- /.sass-lint.yml: -------------------------------------------------------------------------------- 1 | files: 2 | ignore: 3 | - 'src/assets/scss/_vars.scss' 4 | rules: 5 | brace-style: 6 | - 2 7 | - 8 | allow-single-line: true 9 | 10 | class-name-format: 0 11 | declarations-before-nesting: 2 12 | empty-line-between-blocks: 2 13 | force-attribute-nesting: 0 14 | force-element-nesting: 0 15 | force-pseudo-nesting: 0 16 | 17 | indentation: 0 18 | leading-zero: 0 19 | nesting-depth: 20 | - 2 21 | - 22 | max-depth: 4 23 | 24 | no-color-keywords: 2 25 | no-color-literals: 0 26 | no-debug: 2 27 | no-duplicate-properties: 2 28 | no-empty-rulesets: 2 29 | empty-args: 0 30 | no-extends: 2 31 | no-ids: 0 32 | no-important: 0 33 | no-invalid-hex: 2 34 | no-mergeable-selectors: 2 35 | no-misspelled-properties: 36 | - 2 37 | - 38 | 'extra-properties': 39 | - 'scrollbar-width' 40 | no-trailing-whitespace: 2 41 | no-trailing-zero: 2 42 | no-transition-all: 0 43 | no-vendor-prefixes: 0 44 | no-qualifying-elements: 0 45 | no-warn: 2 46 | one-declaration-per-line: 2 47 | property-sort-order: 0 48 | pseudo-element: 2 49 | quotes: 50 | - 2 51 | - 52 | style: single 53 | single-line-per-selector: 2 54 | space-after-colon: 2 55 | space-before-brace: 2 56 | trailing-semicolon: 2 57 | zero-unit: 0 58 | -------------------------------------------------------------------------------- /README-RU.md: -------------------------------------------------------------------------------- 1 | # Vue 3 Mad Boiler 2 | 3 | [](https://maddevs.io?utm_source=github&utm_medium=madboiler) 4 | [](https://opensource.org/licenses/MIT) 5 |  6 | 7 |  8 | 9 | ## Переводы документации 10 | 11 | - [English](./README.md) 12 | - Russian 13 | 14 | ## Почему вы должны использовать этот бойлер 15 | 16 | **Vue Mad Boiler - поможет сохранить ваше время, дав готовый, настроенный проект.** 17 | 18 | При старте проекта на vue, я постоянно сталкивался с тем, что приходилось снова и снова настраивать стор, добавлять базовую структуру стилей, искать пакет иконок, настраивать нормально линтер и тд. Это отнимало достаточно времени. 19 | Что уж говорить о человеке, который только начал копаться во всем в этом, там бывает и недели мало, чтобы собрать это всё. 20 | 21 | ## Преимущества 22 | 23 | * Пример настроенного стора, где применяется модульный подход 24 | * Очень злой линтер. Поругает за стили, верстку и скрипты 25 | * Само собой тесты, которые генерят отчет о покрытии кода 26 | * Готовая структура папок. Не нужно ничего придумывать 27 | * Очень крутой пакет Material иконок 28 | * Material Design Bootstrap - фреймворк готовых ui элементов 29 | * Базовая структура scss стилей 30 | * Пример директивы, которая добавляет анимацию волны для кнопок 31 | * Настройка мультиязычности. 32 | * Пример сервиса для работы с localstorage 33 | * Настройки роутера, + middleware 34 | * Функционал JWT Token 35 | 36 | ## Содержание 37 | 38 | **[1. Фичи](#фичи)** 39 | 40 | * [Функционал JWT Token](#функционал-jwt-token) 41 | * [Sentry](#sentry) 42 | * [Первым делом](#первым-делом) 43 | * [Параметры](#параметры) 44 | * [Gitlab pages](#gitlab-pages) 45 | * [Генерация ковредж баджей для юнит тестов](#генерация-ковредж-баджей-для-юнит-тестов) 46 | 47 | **[2. Запуск проекта](#запуск-проекта)** 48 | 49 | * [Инициализация](#инициализация) 50 | * [Запуск через докер](#запуск-через-докер) 51 | * [Запуск без докера](#запуск-без-докера) 52 | 53 | **[3. Документирование компонентов](#документирование-компонентов)** 54 | 55 | * [Установка vuedoc](#установка-vuedoc) 56 | * [Пробуем](#пробуем) 57 | 58 | **[4. Тестирование проекта](#тестирование-проекта)** 59 | 60 | * [Mock server](#mock-server) 61 | * [Структура папок](#структура-папок) 62 | * [Запуск сервера](#запуск-сервера) 63 | * [Unit тесты](#unit-тесты) 64 | * [Интеграционные и Компонентные тесты](#интеграционные-и-компонентные-тесты) 65 | * [Папки и файлы](#папки-и-файлы) 66 | * [Настройка](#настройка) 67 | * [Запуск тестов в докер контейнере](#запуск-тестов-в-докер-контейнере) 68 | 69 | **[5. Проверка, форматирование кода](#проверка,-форматирование-кода)** 70 | 71 | * [Линтер](#линтер) 72 | * [Форматирование кода](#форматирование-кода) 73 | 74 | **[6. Запуск проекта на продакшне](#запуск-проекта-на-продакшне)** 75 | 76 | ## Фичи 77 | 78 | ### Функционал JWT Token 79 | 80 | Для работы с JWT Token использован HTTP client axios (https://www.npmjs.com/package/axios). 81 | 82 | Функционал реализован в файле /api/config.js 83 | 84 | **REFRESH_URL** - эндпоинт по которому будет происходить запрос на обновление Access-token 85 | **RESPONSE_ACCESS_PARAM** - название ключа по которому Access token будет сохраняться в local storage 86 | **RESPONSE_REFRESH_PARAM** - название ключа по которому Refresh token будет сохраняться в local storage 87 | **DEFAULT_URL** - Дефолтный URL по которому будет производиться запрос в случае если process.env.REACT_APP_API_URL будет пустой 88 | 89 | #### Описание: 90 | 91 | **Access-token** — это токен, который предоставляет доступ его владельцу к защищенным ресурсам сервера 92 | **Refresh-token** — это токен, позволяющий клиентам запрашивать новые access-токены по истечении их времени жизни. 93 | 94 | 1. Клиент проходит аутентификацию в приложении 95 | 2. В случае успешной аутентификации сервер отправляет клиенту access- и refresh-токены, которые сохраняются в Local Storage по ключам RESPONSE_ACCESS_PARAM и RESPONSE_REFRESH_PARAM 96 | 3. При дальнейшем обращении к серверу клиент использует access-токен. Сервер проверяет токен на валидность и предоставляет клиенту доступ к ресурсам 97 | 4. В случае, если access-токен становится не валидным, клиент отправляет refresh-токен (по URL указанному в REFRESH_URL), в ответ на который сервер предоставляет два обновленных токена. (Которые обновляются в Local Storage) 98 | 5. В случае, если refresh-токен становится не валидным, то происходит удаление токенов из Local Storage и клиент опять должен пройти процесс аутентификации 99 | 100 | 101 | #### Имеются методы: 102 | 1. **queryGet** - Используется для get запросов к серверу, где первым параметром отправляется URL, вторым параметры запроса 103 | 104 | ```bash 105 | queryGet('/some-url', {query: 1}) 106 | ``` 107 | 108 | 2. **queryPost** - Используется для post запросов к серверу, где первым параметром отправляется URL, вторым данные передаваемые на сервер, третьим параметры запроса 109 | 110 | ```bash 111 | queryPost = ('/some-url', {data: 'some_data'}, {query: 1}) 112 | ``` 113 | 114 | Возможно добавление собственных запросов, либо создать новый instance axios.create. 115 | 116 | ### Sentry 117 | 118 | Sentry - сервис для удаленного мониторинга ошибок в веб приложениях. 119 | 120 | #### Первым делом 121 | 122 | 1. Идём на сайт https://sentry.io и авторизуемся 123 | 2. Создаём новый проект https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/ 124 | 3. После этого шага, у вас в распоряжении окажется `dns url`, который добавим в настройках в файле [main.js](./src/main.js) 125 | 4. Перезапускаем проект. По идеи все настроено и готово к отлову ошибок. 126 | 127 | #### Параметры 128 | 129 | * **dns** - урл на который будут отправляться ошибки. Его получите при создании нового проекта в Sentry 130 | * **tracesSampleRate** - количество отправляемых ошибок в процентном соотношении от 0 до 1. Если нужно отправлять 40% транзакций - укажите 0.4 131 | 132 | ### Gitlab pages 133 | 134 | В начале разработки проекта, было бы хорошо иметь сервер, на котором бы крутился ваш сайт. чтобы иметь возможность демонстрировать его заказчику или еще кому-то. 135 | 136 | Конечно, есть куча разных вариантов, но мы остановились на Giblab pages. 137 | 138 | https://madboiler.gitlab.io/frontend/vue-madboiler/ 139 | 140 | В файле [vue.config.js](./vue.config.js) добавили функцию, которая определяет правильный путь к файлам в gitlab. Но вам нужно обязательно переписать его под свой проект, так как пути могут быть разные. 141 | 142 | Ну или использовать другой вариант для хостинка вашего проекта. 143 | 144 | Чтобы это всё завелось на gitlab, был добавлен файл [.gitlab-ci.yml](./.gitlab-ci.yml). В нём можно найти блок кода, который отвечает за разветрывание страницы 145 | 146 | ```bash 147 | pages: 148 | image: node:14.15.4 149 | stage: deploy 150 | script: 151 | - npm run build 152 | - mv public public-vue # GitLab Pages hooks on the public folder 153 | - mv dist public # rename the dist folder (result of npm run build) 154 | # optionally, you can activate gzip support with the following line: 155 | - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec gzip -f -k {} \; 156 | artifacts: 157 | paths: 158 | - public # artifact path must be /public for GitLab Pages to pick it up 159 | only: 160 | - master 161 | ``` 162 | 163 | Последняя строчка кода, говорит о том, что страница будет обновлена только в том случае, если изменения будет залиты в ветку master. 164 | 165 | ### Генерация ковредж баджей для юнит тестов 166 | 167 | Понимать какой процент кода покрыт тестами всегда хорошо. Это как минимум показывает на сколько проект стабилен. 168 | 169 | Чтобы выдеть это наглядно и без особо труда, был добален скрипт для генерации баджей, который отображают процент покрытия. 170 | 171 |  172 |  173 |  174 |  175 | 176 | Команда для генерации баджей 177 | 178 | ```bash 179 | node ./jest-coverage-badges.js -output './public' 180 | ``` 181 | 182 | Но нужно понимать, что для генерации баджей нужно обязательно запустить команду, которая создаст папку `coverage` с нужными файлами. 183 | 184 | Для этого в файле [package.json](./package.json) добавили скрипт, который запускает и то и другое 185 | 186 | ```bash 187 | npm run test:unit:coverage 188 | ``` 189 | 190 | После запуска команды, баджи будет лежать в папке `public`. 191 | 192 | ## Запуск проекта 193 | 194 | ### Инициализация 195 | 196 | При первом старте проекта, вам нужно установить необходимые пакеты 197 | 198 | ```bash 199 | npm install 200 | ``` 201 | 202 | После чего запкстить команду инициализации 203 | 204 | ```bash 205 | npm run init 206 | ``` 207 | 208 | Эта команда поможет настроить проект и удалить не нужные фичи. 209 | 210 | [](https://asciinema.org/a/oHRoVQMhlUZzvYuLuZ9kXyKUN) 211 | 212 | После успешной настройки, останется лишь запустить проект и начинать разработку. 213 | 214 | ### Запуск через докер 215 | 216 | Этот вариант хорош тем, что не нужно устанавливать на вашу рабочую машину кучу npm зависимостей. Докер инкапсулирует весь этот мусор и не позволит загадить вашу систему. 217 | 218 | Для этого потребуется установить только [Docker](https://docs.docker.com/get-docker/) и [Docker compose](https://docs.docker.com/compose/install/). 219 | 220 | Запускаем проект для разработки 221 | 222 | ```bash 223 | npm run docker:dev 224 | ``` 225 | После того как докер соберет контейнер, сайт будет доступен по ссылке http://localhost:8080 226 | 227 | Настройки докера для разработки можно посмотреть в [Dockerfile.dev](./docker/Dockerfile.dev) и в [docker-compose.dev.yml](./docker-compose.dev.yml) 228 | 229 | ### Запуск без докера 230 | 231 | Если уж не хочется ставить докер, то запускаем без него. 232 | 233 | Запускаем сервер 234 | 235 | ```bash 236 | npm run serve 237 | ``` 238 | 239 | Если всё установилось и нормально запустилось, то в консоле будет такая картина 240 | 241 | ```bash 242 | DONE Compiled successfully in 9320ms 243 | 244 | App running at: 245 | - Local: http://localhost:8080/ 246 | - Network: http://192.168.20.242:8080/ 247 | 248 | Note that the development build is not optimized. 249 | To create a production build, run npm run build. 250 | ``` 251 | 252 | Там можно заметить две ссылки: 253 | 254 | 1. http://localhost:8080 - ссылка по которой будет доступен наш сайт 255 | 2. http://192.168.20.242:8080 - по этой ссылке тоже будет доступен сайт, но так им можно делиться внутри сети(под одним wifi), например, для того, чтобы тестировать на телефоне или у друга на ноуте. Первая же ссылка будет работать только на вашем ПК 256 | 257 | ## Документирование компонентов 258 | 259 | Проект с хорошо задокументированным кодом, в дальнейшем обеспечит более низкий порог входа для новых разработчиков. 260 | 261 | [@vuedoc/md](https://www.npmjs.com/package/@vuedoc/md) библиотека, которую будем использовать для документирования компонентов. 262 | 263 | ### Установка vuedoc 264 | 265 | Чтобы можно было вызвать команду `vuedoc.md`, нужно обязательно установить этот пакет глобально. 266 | 267 | Возможно понадобится использовать команду `sudo`, чтобы дать права на установку пакета глобально. 268 | 269 | ```bash 270 | sudo npm install --global @vuedoc/parser @vuedoc/md 271 | ``` 272 | 273 | ### Пробуем 274 | 275 | Теперь, мы можем документировать компоненты. 276 | 277 | Вот несколько примеров https://gitlab.com/vuedoc/md/-/tree/master/test/fixtures 278 | 279 | После того, как описали один и компонентов, можно запускать команду и смореть результат. Только не забудьте поправить команду под вашу структуру файлов и папок. 280 | 281 | ```bash 282 | vuedoc.md src/components/COMPONENT_NAME.vue --output docs/components 283 | ``` 284 | 285 | ## Тестирование проекта 286 | 287 | В проекте доступно три вида тестов 288 | 289 | 1. **Unit** - ими будут тестироваться конкретные функции в коде, чтобы понимать, что они работают так, как ожидается 290 | 2. **Component** - тестирование отдельных компонентов, например, дропдаун. Можно проверить, что при нажатии на него выпадает список или при нажатии на элемент списка он выделяется и тд 291 | 3. **Integration** - этими тестами уже проверяется вся связка, то как оно работает вместе. 292 | 293 | ### Mock server 294 | 295 | Чтобы тесты не зависели от реального сервера, который может отказать в любую секунду, был добавлен mock server, с возможностью подмены запросов. 296 | Таким образом мы сможет тестровать проект даже без доступа в интернет. 297 | 298 | > В этом нас поможет > `https://github.com/typicode/json-server` 299 | 300 | #### Структура папок 301 | 302 | Файлы для сервера находятся в папке `/tests/server`. 303 | 304 | * Файл [server.js](./tests/server/server.js) - это основной файл для запуска сервера. 305 | * Файл [config.js](./tests/server/config.js) - файл с опциями для сервера. 306 | * Папка **data** - хранит файлы с тестовыми данными, которые будут возвращены json-сервером. Все данные в файлах могут быть изменены. 307 | 308 | #### Запуск сервера 309 | 310 | > Оригинальная команда для запуска `json-server --watch ./tests/server/server.js` 311 | 312 | ```bash 313 | npm run test:server 314 | ``` 315 | 316 | Сервер запустится на локальном хосте и будет доступен по адресу [http://localhost:8888](http://localhost:8888). 317 | 318 | В консоли должен быть виден следующий результат: 319 | 320 | ```bash 321 | $ npm run test:server 322 | 323 | > vue-madboiler@1.0.0 test:server 324 | > node ./tests/server/server.js 325 | 326 | JSON Server is running on port: 8888 327 | ... 328 | ``` 329 | 330 | ### Unit тесты 331 | 332 | Для запуска unit тестов используется [vue-cli-service](https://cli.vuejs.org/guide/cli-service.html), который уже полностью настроен и готов к работе. 333 | 334 | Имеем две команды 335 | 336 | 1. Запуск тестов. 337 | 338 | ```bash 339 | npm run test:unit 340 | ``` 341 | 342 | Тесты располагаются в папке `/tests/unit`. 343 | 344 | 345 | 2. Генерация отчета о покрытии кода тестами 346 | 347 | ```bash 348 | npm run test:unit:coverage 349 | ``` 350 | 351 | После запуска команды, в корне проекта создастся папка `/coverage`, в которой будет лежать отчет. При этот будут сгенерированны баджи, о которых можно почитать [тут](#генерация-ковредж-баджей-для-юнит-тестов) 352 | 353 | Чтобы посмотреть отчёт, перейдем в папку `/coverage/lcov-report` и найдем там файл [index.html](./coverage/lcov-report/index.html). Этот файл нужно запустить в баузере. Откроется страница с подробной инфой о покрытии кода тестами. 354 | 355 | ### Интеграционные и Компонентные тесты 356 | 357 | Для данного вида тестов используем фреймворк [cypress](https://www.cypress.io/). 358 | 359 | Для тестирования конкретных компонентов используем экспериментальную библиотеку `https://docs.cypress.io/guides/component-testing/introduction.html#What-is-Cypress-Component-Testing`. 360 | 361 | Команда для запуска: 362 | 363 | ```bash 364 | npm run test:e2e 365 | ``` 366 | 367 | После выполнения этой команды: 368 | 369 | 1. Запустится mock server 370 | 2. Запустится сервер для интеграционных и компонентных тестов 371 | 3. Открывается окно со списком всех тестов, которые можно запустить и видить процесс. 372 | 4. Далее можно приступать к написанию тестов. 373 | 374 | #### Папки и файлы 375 | 376 | Интеграционные и Компонентные тесты находятся в папке `/tests/e2e`. 377 | 378 | ```bash 379 | tests 380 | e2e 381 | components 382 | - UIButton.spec.js 383 | - ... 384 | fixtures 385 | - example.json 386 | - ... 387 | integrations 388 | - Home.spec.js 389 | - ... 390 | plugins 391 | - index.js 392 | support 393 | - commands.js 394 | - index.js 395 | unit 396 | - ... 397 | server 398 | - ... 399 | ``` 400 | 401 | * `/tests/e2e/components` - эта папка предназначена для компонентных тестов. 402 | * `/tests/e2e/integrations` - эта для интеграционных. 403 | * Больше информации о папках и файлах можно найти здесь `https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Plugin-files`. 404 | 405 | #### Настройка 406 | 407 | Файл настроек cypress.json находится в корне проекта. 408 | 409 | ```bash 410 | { 411 | "baseUrl": "http://localhost:3000", 412 | "chromeWebSecurity": false, 413 | "pluginsFile": "tests/e2e/plugins/index.js", 414 | "supportFile": "tests/e2e/support/index.js", 415 | "fixturesFolder": "tests/e2e/fixtures", 416 | "integrationFolder": "tests/e2e/integrations", 417 | "testFiles": "**/*.spec.js*", 418 | "experimentalComponentTesting": true, 419 | "componentFolder": "tests/e2e/components", 420 | "nodeVersion":"system", 421 | "video": false, 422 | "viewportWidth": 1366, 423 | "viewportHeight": 768 424 | } 425 | ``` 426 | 427 | Обо всех доступных настройках можно прочитать здесь `https://docs.cypress.io/guides/references/configuration.html#Options`. 428 | 429 | #### Запуск тестов в докер контейнере 430 | 431 | ```bash 432 | npm run docker:test:e2e 433 | ``` 434 | 435 | Если получаем такую ошибку, то 436 | 437 | ```bash 438 | cypress_1 | ---------- 439 | cypress_1 | 440 | cypress_1 | No protocol specified 441 | cypress_1 | [101:0208/050449.746174:ERROR:browser_main_loop.cc(1434)] Unable to open X display. 442 | cypress_1 | The futex facility returned an unexpected error code. 443 | cypress_1 | No protocol specified 444 | cypress_1 | [115:0208/050450.882329:ERROR:browser_main_loop.cc(1434)] Unable to open X display. 445 | cypress_1 | 446 | cypress_1 | undefined:0 447 | cypress_1 | 448 | cypress_1 | 449 | cypress_1 | illegal access 450 | cypress_1 | (Use `Cypress --trace-uncaught ...` to show where the exception was thrown) 451 | cypress_1 | 452 | cypress_1 | ---------- 453 | ``` 454 | 455 | 1. В консоле запустим эту команду 456 | 457 | ```bash 458 | xhost +si:localuser:root 459 | ``` 460 | 461 | Результат должен быть таким 462 | 463 | ```bash 464 | localuser:root being added to access control list 465 | ``` 466 | 467 | 2. По идеи дальше всё дожно запустить 468 | 469 | ```bash 470 | npm run docker:test:e2e 471 | ``` 472 | 473 | ## Проверка, форматирование кода 474 | 475 | ### Линтер 476 | 477 | Для того, чтобы проект был всегда всегда написан так сказать "одним почерком", в проекте необходимо использовать линтер. 478 | Это позволит сделать код единообразным, удобным для восприятия вам и другим разработчиками. 479 | 480 | В качестве линтера используется [eslint](https://eslint.org/) в пресетом [airbnb](https://github.com/airbnb/javascript). 481 | 482 | Команда ниже, запустит проверку `.vue, .js, .scss` файлов. Так же во `.vue` файлах будет проверен блок ``. 483 | 484 | ```bash 485 | npm run lint 486 | ``` 487 | 488 | Настройки для `.vue, .js` файлов можно посмотреть в [.eslintrc.js](./.eslintrc.js) 489 | Настройки для `.scss` файлов в [.sass-lint.yml](./.sass-lint.yml) 490 | 491 | ### Форматирование кода 492 | 493 | Не всегда получается писать код аккуратно и так как требует линтер. Чтобы упростить себе жизнь, в проект был добавлен [Prettier](https://prettier.io/) 494 | 495 | Он поможет автоматически поправить код, максимально привести его к той форме, которую требует линтер 496 | 497 | ```bash 498 | npm run format 499 | ``` 500 | 501 | Настройки можно посмотреть тут [.prettierrc](./.prettierrc) 502 | 503 | ## Запуск проекта на продакшне 504 | 505 | После того, как проект готов к продакшену его нужно правильно собрать. 506 | 507 | Для этого подготовили докер команду 508 | 509 | ```bash 510 | npm run docker:prod 511 | ``` 512 | 513 | После запуска, докер сбилдит файлы, запустит nginx, который будет проксировать наш index.html 514 | 515 | Страница будет доступна по ссылке http://localhost/ 516 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 Mad Boiler 2 | 3 | [](https://maddevs.io?utm_source=github&utm_medium=madboiler) 4 | [](https://opensource.org/licenses/MIT) 5 |  6 | 7 |  8 | 9 | ## Translations of documentation 10 | 11 | - English 12 | - [Russian](./README-RU.md) 13 | 14 | ## Why you should use this boilerplate 15 | 16 | **Vue Mad Boiler will save you time by giving you a ready-made, customized project.** 17 | 18 | When starting a project in vue, I constantly faced the fact that I had to set up the store again and again, add the basic structure of styles, look for a package of icons, configure the linter properly, etc. This was quite time-consuming. 19 | And what if a person just starts digging into all this?! Even a week won't be enough to put it all together. 20 | 21 | ## Benefits 22 | 23 | * JWT tokens 24 | * An example of a configured Vuex Store that uses a modular approach 25 | * A very angry linter. Scolds you for styles, layout and scripts. 26 | * Tests that generate a code coverage report 27 | * Ready-made folder structure 28 | * Very cool Material icon pack 29 | * Material Design Bootstrap is a framework of ready-made ui elements 30 | * Basic scss style structure 31 | * Example directive that adds wave animation for buttons 32 | * Multilanguage functionality. 33 | * Example of a service for working with localstorage 34 | * Router settings + middleware 35 | 36 | ## Contents 37 | 38 | **[1. Features](#features)** 39 | 40 | * [JWT functional](#jwt-functional) 41 | * [Sentry](#sentry) 42 | * [Init](#init) 43 | * [Params](#params) 44 | * [Gitlab pages](#gitlab-pages) 45 | * [Generate coverage badges for unit tests](#generate-coverage-badges-for-unit-tests) 46 | 47 | **[2. Run project](#run-project)** 48 | 49 | * [Initialization](#initialization) 50 | * [Run with docker](#run-with-docker) 51 | * [Run without docker](#run-without-docker) 52 | 53 | **[3. Component documentation](#component-documentation)** 54 | 55 | * [Install vuedoc](#install-vuedoc) 56 | * [Using](#using) 57 | 58 | **[4. Project testing](#project-testing)** 59 | 60 | * [Mock server](#mock-server) 61 | * [Folder structure](#folder-structure) 62 | * [Run server](#run-server) 63 | * [Unit tests](#unit-tests) 64 | * [Integration and component tests](#integration-and-component-tests) 65 | * [Folders and files](#folders-and-files) 66 | * [Settings](#settings) 67 | * [Run tests in docker container](#run-tests-in-docker-container) 68 | 69 | **[5. Checking, formatting code](#checking,-formatting-code)** 70 | 71 | * [Linter](#linter) 72 | * [Formatting code](#formatting-code) 73 | 74 | **[6. Run project in production](#run-project-in-production)** 75 | 76 | ## Features 77 | 78 | ### JWT functional 79 | 80 | To work with JWT Token, HTTP client axios (https://www.npmjs.com/package/axios) is used. 81 | 82 | The functionality is implemented in the file src/api/config.js 83 | 84 | REFRESH_URL - endpoint on which the request to update the access-token will be made 85 | RESPONSE_ACCESS_PARAM - name of the key by which the access-token will be saved in the local storage 86 | RESPONSE_REFRESH_PARAM - the name of the key by which the Refresh token will be saved in the local storage 87 | DEFAULT_URL - Default URL for the request in case process.env.REACT_APP_API_URL will be empty 88 | 89 | **Description:** 90 | 91 | An access-token is a token that gives its owner access to secure server resources. 92 | Refresh-token is a token that allows clients to request new access-tokens when their lifetime expires. 93 | 94 | 1. The client is authenticated in the application 95 | 2. If authentication is successful, server sends access and refresh tokens to client, which are stored in Local Storage by RESPONSE_ACCESS_PARAM and RESPONSE_REFRESH_PARAM keys 96 | 3. The client uses the access token to further access the server. Server checks token for validity and gives client access to resources 97 | 4. In case the access token becomes invalid, the client sends a refresh token (at the URL specified in REFRESH_URL), in response to which the server provides two updated tokens. (Which are updated in Local Storage). 98 | 5. If the refresh token expires, the tokens are removed from Local Storage and the client has to authenticate again 99 | 100 | 101 | **There are these methods:** 102 | 103 | 1. queryGet - Used for get requests to the server, where the first parameter is the URL, and the second is request parameters 104 | 105 | ```bash 106 | queryGet('/some-url', {query: 1}) 107 | ``` 108 | 109 | 2. queryPost - used for request post to the server, where the first parameter is the URL, the second one is data transferred to the server, and the third one is query parameters 110 | 111 | ```bash 112 | queryPost = ('/some-url', {data: 'some_data'}, {query: 1}) 113 | ``` 114 | 115 | It's possible to add your own queries or create a new instance axios.create. 116 | 117 | ### Sentry 118 | 119 | Sentry is a service for remote error monitoring in web applications. 120 | 121 | #### Init 122 | 123 | 1. Go to https://sentry.io and log in there 124 | 2. Create a new project https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/ 125 | 3. After this step, you will have a dns url, which will be added to the settings in the file [main.js](./src/main.js) 126 | 4. Restart the project. Everything is set up and ready to catch errors. 127 | 128 | #### Params 129 | 130 | * **dns** - the url to which errors will be sent. You will get it when creating a new project in Sentry 131 | * **tracesSampleRate** - number of sent errors as a percentage from 0 to 1. If you need to send 40% of transactions - put 0.4 132 | ### Gitlab pages 133 | 134 | At the beginning of project development, it would be good to have a server to run your site on so that you can show it to the customer or someone else. 135 | 136 | Of course, there are a bunch of different options, but we settled on Giblab pages. 137 | 138 | https://madboiler.gitlab.io/frontend/vue-madboiler/ 139 | 140 | In the file [vue.config.js](./vue.config.js), we added a function that determines the correct path to the files in gitlab. But you need to make sure to rewrite it for your project because paths can be different. 141 | 142 | Or use another option for hosting your project. 143 | 144 | To make it all work on gitlab, the file [.gitlab-ci.yml](./.gitlab-ci.yml) was added. Here you can find a block of code that is to deploy the page 145 | 146 | ```bash 147 | pages: 148 | image: node:14.15.4 149 | stage: deploy 150 | script: 151 | - npm run build 152 | - mv public public-vue # GitLab Pages hooks on the public folder 153 | - mv dist public # rename the dist folder (result of npm run build) 154 | # optionally, you can activate gzip support with the following line: 155 | - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec gzip -f -k {} \; 156 | artifacts: 157 | paths: 158 | - public # artifact path must be /public for GitLab Pages to pick it up 159 | only: 160 | - master 161 | ``` 162 | 163 | The last line of code says that the page will be updated only if changes are sent to the master branch. 164 | 165 | ### Generate coverage badges for unit tests 166 | 167 | Understanding what percentage of the code is covered by tests is always good. This at least shows us how stable the project is. 168 | 169 | To visualize it, a script has been added to generate badges that display the percentage of code coverage. 170 | 171 |  172 |  173 |  174 |  175 | 176 | Command for generating badges 177 | 178 | ```bash 179 | node ./jest-coverage-badges.js -output './public' 180 | ``` 181 | 182 | But it should be understood that in order to generate badges it is necessary to run a command that creates a folder `coverage` with the necessary files. 183 | 184 | To do this, we added a script in the [package.json](./package.json) file that runs both 185 | 186 | ```bash 187 | npm run test:unit:coverage 188 | ``` 189 | 190 | After running the command, the badge will sit in the `public` folder. 191 | 192 | ## Run project 193 | 194 | ### Initialization 195 | 196 | The first time you start the project, you need to install the necessary packages 197 | 198 | ```bash 199 | npm install 200 | ``` 201 | 202 | After that, execute the initialization command 203 | 204 | ```bash 205 | npm run init 206 | ``` 207 | 208 | This command helps to configure the project and to remove unnecessary features. 209 | 210 | [](https://asciinema.org/a/oHRoVQMhlUZzvYuLuZ9kXyKUN) 211 | 212 | After successful setup, the only thing left to do is to run the project and start development. 213 | 214 | ### With docker 215 | 216 | The good thing about this option is that you don't need to install a bunch of npm dependencies on your working machine. The docker encapsulates all this and prevents your system from getting trashed. 217 | 218 | You only need to install [Docker](https://docs.docker.com/get-docker/) and [Docker compose](https://docs.docker.com/compose/install/). 219 | 220 | Run a project for development 221 | 222 | ```bash 223 | npm run docker:dev 224 | ``` 225 | After docker builds the container, the site will be available at http://localhost:8080 226 | 227 | You can see the docker setup for development in [dockerfile.dev](./docker/Dockerfile.dev) and in [docker-compose.dev.yml](./docker-compose.dev.yml) 228 | 229 | ### Without docker 230 | 231 | If you don't want to install docker, you can run it without it. 232 | 233 | Run server 234 | 235 | ```bash 236 | npm run serve 237 | ``` 238 | 239 | If everything is installed and started correctly, the console will show the result 240 | 241 | ```bash 242 | DONE Compiled successfully in 9320ms 243 | 244 | App running at: 245 | - Local: http://localhost:8080/ 246 | - Network: http://192.168.20.242:8080/ 247 | 248 | Note that the development build is not optimized. 249 | To create a production build, run npm run build. 250 | ``` 251 | 252 | You can spot two references there 253 | 254 | 1. http://localhost:8080 - the link where our site will be available 255 | 2. http://192.168.20.242:8080 - the site will be available at this link, too, so it can be shared internally, for example, to test on your phone or a friend's laptop. The first link will only work on your PC 256 | 257 | ## Component documentation 258 | 259 | A project with well-documented code, in the future, will provide a lower entry threshold for new developers. 260 | 261 | [@vuedoc/md](https://www.npmjs.com/package/@vuedoc/md) the library we will use to document components. 262 | 263 | ### Install vuedoc 264 | 265 | To be able to call the `vuedoc.md` command, it must be installed globally. 266 | 267 | You may need to use the `sudo` command to give global permissions to install the package. 268 | 269 | ```bash 270 | sudo npm install --global @vuedoc/parser @vuedoc/md 271 | ``` 272 | 273 | ### Using 274 | 275 | Now, we can document the components. 276 | 277 | Here are a few examples https://gitlab.com/vuedoc/md/-/tree/master/test/fixtures 278 | 279 | After you describe one of components, you can run the command and see the result. Just don't forget to adjust the command to your file and folder structure. 280 | 281 | ```bash 282 | vuedoc.md src/components/COMPONENT_NAME.vue --output docs/components 283 | ``` 284 | 285 | ## Project testing 286 | 287 | There are three types of tests available in the project 288 | 289 | 1. **Unit** - they will test specific functions in the code to understand that they work as expected 290 | 2. **Component** - testing individual components. For example, dropdown. You may verify that when you click on it, the list will drop down; when you click on a list item, it will be highlighted, etc. 291 | 3. **Integration** - these tests already test the whole bundle, how it works together. 292 | 293 | ### Mock server 294 | 295 | In order to avoid dependence on the real server, which can fail at any second, we added a mock server, with request spoofing enabled. 296 | This way, we can test the project even without internet access. 297 | 298 | > For this we will use > `https://github.com/typicode/json-server` 299 | 300 | #### Folder structure 301 | 302 | The files for the server are in the `/tests/server` folder. 303 | 304 | * File [server.js](./tests/server/server.js) is the main file for starting the server. 305 | * File [config.js](./tests/server/config.js) - file with options for the server. 306 | * **data** folder - stores files with test data, which will be returned by the json server. All data in the files can be changed. 307 | 308 | #### Run server 309 | 310 | > Original command to run `json-server --watch ./tests/server/server.js` 311 | 312 | ```bash 313 | npm run test:server 314 | ``` 315 | 316 | The server will start on the local host and will be available at [http://localhost:8888](http://localhost:8888). 317 | 318 | You should see the following result in the console: 319 | 320 | ```bash 321 | $ npm run test:server 322 | 323 | > vue-madboiler@1.0.0 test:server 324 | > node ./tests/server/server.js 325 | 326 | JSON Server is running on port: 8888 327 | ... 328 | ``` 329 | 330 | ### Unit tests 331 | 332 | To run these tests, use [vue-cli-service](https://cli.vuejs.org/guide/cli-service.html), which is already fully configured and ready to go. 333 | 334 | We have two commands 335 | 336 | 1. Running tests. 337 | 338 | ```bash 339 | npm run test:unit 340 | ``` 341 | 342 | The tests are located in the `/tests/unit` folder. 343 | 344 | 345 | 2. Generating a report on code coverage by tests 346 | 347 | ```bash 348 | npm run test:unit:coverage 349 | ``` 350 | 351 | After running the command, a folder `/coverage` will be created in the root of the project, where the report will be located. This will generate badges, which you can read about [here](#generation-covreage-badges-for-unit-tests) 352 | 353 | To see the report, go to the folder `/coverage/lcov-report` and find the file [index.html](./coverage/lcov-report/index.html) there. This file must be run in the browser. This will open a page with detailed information about code coverage by tests. 354 | 355 | ### Integration and component tests 356 | 357 | For this kind of tests, we use the [cypress](https://www.cypress.io/) framework. 358 | 359 | To test specific components, we use the experimental library `https://docs.cypress.io/guides/component-testing/introduction.html#What-is-Cypress-Component-Testing`. 360 | 361 | The command to start: 362 | 363 | ```bash 364 | npm run test:e2e 365 | ``` 366 | 367 | After executing this command: 368 | 369 | 1. The mock server will start 370 | 2. The server for integration and component tests starts 371 | 3. Opens a window with a list of all the tests that you can run and see the process 372 | 4. Then you can start writing tests 373 | 374 | #### Folders and files 375 | 376 | Integration and Component tests are located in the `/tests/e2e` folder. 377 | 378 | ```bash 379 | tests 380 | e2e 381 | components 382 | - UIButton.spec.js 383 | - ... 384 | fixtures 385 | - example.json 386 | - ... 387 | integrations 388 | - Home.spec.js 389 | - ... 390 | plugins 391 | - index.js 392 | support 393 | - commands.js 394 | - index.js 395 | unit 396 | - ... 397 | server 398 | - ... 399 | ``` 400 | 401 | * `/tests/e2e/components` - this folder is for component tests. 402 | * `/tests/e2e/integrations` - this is for integration tests. 403 | * More information about folders and files can be found here `https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Plugin-files`. 404 | 405 | #### Settings 406 | 407 | The settings file cypress.json is in the root of the project. 408 | 409 | ```bash 410 | { 411 | "baseUrl": "http://localhost:3000", 412 | "chromeWebSecurity": false, 413 | "pluginsFile": "tests/e2e/plugins/index.js", 414 | "supportFile": "tests/e2e/support/index.js", 415 | "fixturesFolder": "tests/e2e/fixtures", 416 | "integrationFolder": "tests/e2e/integrations", 417 | "testFiles": "**/*.spec.js*", 418 | "experimentalComponentTesting": true, 419 | "componentFolder": "tests/e2e/components", 420 | "nodeVersion":"system", 421 | "video": false, 422 | "viewportWidth": 1366, 423 | "viewportHeight": 768 424 | } 425 | ``` 426 | 427 | You can read about all the available settings here `https://docs.cypress.io/guides/references/configuration.html#Options`. 428 | 429 | #### Run tests in docker container 430 | 431 | ```bash 432 | npm run docker:test:e2e 433 | ``` 434 | 435 | If we get an error like this, then 436 | 437 | ```bash 438 | cypress_1 | ---------- 439 | cypress_1 | 440 | cypress_1 | No protocol specified 441 | cypress_1 | [101:0208/050449.746174:ERROR:browser_main_loop.cc(1434)] Unable to open X display. 442 | cypress_1 | The futex facility returned an unexpected error code. 443 | cypress_1 | No protocol specified 444 | cypress_1 | [115:0208/050450.882329:ERROR:browser_main_loop.cc(1434)] Unable to open X display. 445 | cypress_1 | 446 | cypress_1 | undefined:0 447 | cypress_1 | 448 | cypress_1 | 449 | cypress_1 | illegal access 450 | cypress_1 | (Use `Cypress --trace-uncaught ...` to show where the exception was thrown) 451 | cypress_1 | 452 | cypress_1 | ---------- 453 | ``` 454 | 455 | 1. In the console run this command 456 | 457 | ```bash 458 | xhost +si:localuser:root 459 | ``` 460 | 461 | The result should be 462 | 463 | ```bash 464 | localuser:root being added to access control list 465 | ``` 466 | 467 | 2. Everything should now run 468 | 469 | ```bash 470 | npm run docker:test:e2e 471 | ``` 472 | 473 | ## Checking, formatting code 474 | 475 | ### Linter 476 | 477 | In order to ensure that the project is always written in "one handwriting" so to speak, it is necessary to use a linter in the project. 478 | This will make the code uniform and easy to understand for you and other developers. 479 | 480 | The linter is [eslint](https://eslint.org/) with preset [airbnb](https://github.com/airbnb/javascript). 481 | 482 | The command below will check the `.vue, .js, .scss` files. Also, in the `.vue` files, the block `` will be checked. 483 | 484 | ```bash 485 | npm run lint 486 | ``` 487 | 488 | Settings for `.vue, .js` files can be found in [.eslintrc.js](./.eslintrc.js) 489 | Settings for `.scss` files are in [.sass-lint.yml](./.sass-lint.yml) 490 | 491 | ### Formatting code 492 | 493 | It's not always possible to write code neatly and the way the linter requires. To make life easier, [Prettier](https://prettier.io/) was added to the project 494 | 495 | It helps to automatically correct the code and to bring it as close as possible to the form required by the linter 496 | 497 | ```bash 498 | npm run format 499 | ``` 500 | 501 | You can see the settings here [.prettierrc](./.prettierrc) 502 | 503 | ## Run project in production 504 | 505 | Once the project is ready for production, it must be properly assembled. 506 | 507 | For this purpose, a docker command was prepared. 508 | 509 | ```bash 510 | npm run docker:prod 511 | ``` 512 | 513 | Upon its launch, docker will build the files and launch nginx, which will proxy our index.html 514 | 515 | The page will be available at http://localhost/ 516 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /cli/core/cleaner/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require('rimraf'); 4 | const rm = require('./rm'); 5 | const packageJson = require('./packageJson'); 6 | 7 | const root = path.resolve(); 8 | 9 | function removeFolder(folder) { 10 | rimraf.sync(folder); 11 | } 12 | 13 | function removeFile(file) { 14 | return new Promise(resolve => { 15 | fs.unlink(file, () => { 16 | resolve(); 17 | }); 18 | }); 19 | } 20 | 21 | function writeFile(data, file) { 22 | return new Promise(resolve => { 23 | fs.writeFileSync(file, data, err => { 24 | console.error(err); 25 | resolve(); 26 | }); 27 | }); 28 | } 29 | 30 | async function removeFiles(files) { 31 | const promises = []; 32 | /* eslint-disable-next-line */ 33 | for (const file of files) { 34 | promises.push(removeFile(file)); 35 | } 36 | await Promise.all(promises); 37 | } 38 | 39 | async function removeFolders(folders) { 40 | const promises = []; 41 | /* eslint-disable-next-line */ 42 | for (const folder of folders) { 43 | promises.push(removeFolder(folder)); 44 | } 45 | await Promise.all(promises); 46 | } 47 | 48 | async function run(options) { 49 | // Update package.json 50 | const newPackageJson = packageJson.update(options); 51 | writeFile(JSON.stringify(newPackageJson, null, 2), `${root}/package.json`); 52 | 53 | await removeFiles([`${root}/package-lock.json`]); 54 | await removeFolders([`${root}/node_modules`]); 55 | 56 | // Remove sentry 57 | if (options.sentry) { 58 | await rm.removeLines(`${root}/src/main.js`, [2, 3, 8, 9, [13, 21]]); 59 | } 60 | 61 | // Remove cypress 62 | if (options.cypress) { 63 | await removeFiles([ 64 | `${root}/docker-compose.e2e.yml`, 65 | `${root}/cy-open.yml`, 66 | `${root}/cypress.json`, 67 | `${root}/docker/Dockerfile.e2e`, 68 | `${root}/tests/.eslintrc.js` 69 | ]); 70 | await removeFolders([`${root}/tests/e2e`, `${root}/tests/server`]); 71 | } 72 | 73 | // Remove jest 74 | if (options.jest) { 75 | await removeFiles([`${root}/jest.config.js`, `${root}/jest-coverage-badges.js`]); 76 | await removeFolders([`${root}/tests/unit`]); 77 | } 78 | 79 | // Remove linter 80 | if (options.linter) { 81 | await removeFiles([`${root}/.eslintignore`, `${root}/.eslintrc.js`]); 82 | } 83 | 84 | // Remove gitlab pages 85 | if (options.gitlabPage) { 86 | await removeFiles([`${root}/vue.config.js`]); 87 | } 88 | 89 | // Remove vue Doc 90 | if (options.vueDoc) { 91 | await removeFolders([`${root}/docs/components`]); 92 | } 93 | 94 | // Remove i18n 95 | if (options.multiLanguage) { 96 | await removeFolders([`${root}/src/locales`]); 97 | if (options.sentry) { 98 | await rm.removeLines(`${root}/src/main.js`, [5, 12]); 99 | } else { 100 | await rm.removeLines(`${root}/src/main.js`, [7, 25]); 101 | } 102 | } 103 | 104 | // Remove mdb 105 | if (options.mdb) { 106 | await rm.removeLines(`${root}/public/index.html`, [8]); 107 | await rm.removeLines(`${root}/src/assets/scss/index.scss`, [2]); 108 | } 109 | 110 | // Remove prettier 111 | if (options.prettier) { 112 | await removeFiles([`${root}/.prettierrc`]); 113 | } 114 | 115 | return true; 116 | } 117 | 118 | module.exports = { run }; 119 | -------------------------------------------------------------------------------- /cli/core/cleaner/packageJson.js: -------------------------------------------------------------------------------- 1 | function cypress(remove, key) { 2 | if (remove) return null; 3 | switch (key) { 4 | case 'scripts': 5 | return { 6 | 'docker:test:e2e': 7 | 'docker-compose -f docker-compose.e2e.yml -f cy-open.yml up --exit-code-from cypress', 8 | 'test:e2e': 'npm run test:server & vue-cli-service test:e2e --mode test', 9 | 'test:server': 'node ./tests/server/server.js' 10 | }; 11 | case 'info': 12 | return { 13 | 'docker:test:e2e': 'Run e2e tests in docker', 14 | 'test:e2e': 'Run integration tests', 15 | 'test:server': 'Run mock server for tests' 16 | }; 17 | case 'dep': 18 | return { 19 | cypress: '^6.4.0', 20 | 'json-server': '^0.16.3' 21 | }; 22 | case 'devDep': 23 | return { 24 | '@cypress/vue': '^1.0.0-alpha.4', 25 | '@vue/cli-plugin-e2e-cypress': '^4.5.11', 26 | 'vue-cli-plugin-cypress-experimental': '~1.2.0' 27 | }; 28 | default: 29 | return null; 30 | } 31 | } 32 | 33 | function linter(remove, key) { 34 | if (remove) return null; 35 | switch (key) { 36 | case 'scripts': 37 | return { 38 | lint: 'npm run lint-es-vue & npm run lint-es & npm run lint-vue-scss & npm run lint-scss', 39 | 'lint-es': 'eslint --ext .js,.vue .', 40 | 'lint-es-vue': 'vue-cli-service lint --no-fix', 41 | 'lint-scss': 'sass-lint src/assets/scss/*.scss --verbose', 42 | 'lint-vue-scss': 'sass-lint-vue src' 43 | }; 44 | case 'info': 45 | return { 46 | lint: 'Run linters for vue, js, scss files', 47 | 'lint-es': 'Run es linter', 48 | 'lint-es-vue': 'Run es-vue linter', 49 | 'lint-scss': 'Run linter for check scss files', 50 | 'lint-vue-scss': 'Run linter for check scss styles in vue files' 51 | }; 52 | case 'dep': 53 | return { 54 | 'eslint-config-prettier': '^6.15.0', 55 | 'eslint-plugin-prettier': '^3.1.4' 56 | }; 57 | case 'devDep': 58 | return { 59 | '@vue/cli-plugin-eslint': '^4.5.11', 60 | '@vue/eslint-config-airbnb': '^4.0.0', 61 | 'babel-eslint': '^10.1.0', 62 | eslint: '^5.16.0', 63 | 'eslint-plugin-vue': '^5.0.0', 64 | 'sass-lint': '^1.13.1', 65 | 'sass-lint-vue': '^0.4.0' 66 | }; 67 | default: 68 | return null; 69 | } 70 | } 71 | 72 | function jest(remove, key) { 73 | if (remove) return null; 74 | switch (key) { 75 | case 'scripts': 76 | return { 77 | 'test:unit': 'vue-cli-service test:unit --watch --coverage=false', 78 | 'test:unit:coverage': 79 | 'vue-cli-service test:unit --coverage && node ./jest-coverage-badges.js -output "./public"' 80 | }; 81 | case 'info': 82 | return { 83 | 'test:unit': 'Run functional tests', 84 | 'test:unit:coverage': 'Generate code coverage for functional tests' 85 | }; 86 | case 'devDep': 87 | return { 88 | '@vue/test-utils': '1.0.0-beta.29', 89 | '@vue/cli-plugin-unit-jest': '^4.5.11' 90 | }; 91 | default: 92 | return null; 93 | } 94 | } 95 | 96 | function prettier(remove, key) { 97 | if (remove) return null; 98 | switch (key) { 99 | case 'scripts': 100 | return { 101 | format: 'prettier --write "{tests,src,.}/**/*.{js,vue}"' 102 | }; 103 | case 'info': 104 | return { 105 | format: 'Run code formatting' 106 | }; 107 | case 'devDep': 108 | return { 109 | prettier: '^2.2.1' 110 | }; 111 | default: 112 | return null; 113 | } 114 | } 115 | 116 | function vueDoc(remove, key) { 117 | if (remove) return null; 118 | switch (key) { 119 | case 'dep': 120 | return { 121 | '@vuedoc/md': '^3.0.0', 122 | '@vuedoc/parser': '^3.3.0' 123 | }; 124 | default: 125 | return null; 126 | } 127 | } 128 | 129 | function multiLanguage(remove, key) { 130 | if (remove) return null; 131 | switch (key) { 132 | case 'devDep': 133 | return { 134 | 'vue-i18n': '^9.1.6' 135 | }; 136 | default: 137 | return null; 138 | } 139 | } 140 | 141 | function sentry(remove, key) { 142 | if (remove) return null; 143 | switch (key) { 144 | case 'dep': 145 | return { 146 | '@sentry/tracing': '^6.0.4', 147 | '@sentry/vue': '^6.0.4' 148 | }; 149 | default: 150 | return null; 151 | } 152 | } 153 | 154 | function mdb(remove, key) { 155 | if (remove) return null; 156 | switch (key) { 157 | case 'dep': 158 | return { 159 | 'mdb-vue-ui-kit': '^1.0.0-beta6' 160 | }; 161 | case 'devDep': 162 | return { 163 | 'vue-cli-plugin-mdb5': '~1.2.0' 164 | }; 165 | default: 166 | return null; 167 | } 168 | } 169 | 170 | function tests(options, key) { 171 | if (!options.jest && !options.cypress) return null; 172 | switch (key) { 173 | case 'scripts': 174 | if (options.jest && options.cypress) 175 | return { 176 | tests: 177 | 'npm run test:server & vue-cli-service test:unit --coverage=false && vue-cli-service test:e2e --headless --mode test' 178 | }; 179 | if (options.jest && !options.cypress) 180 | return { tests: 'vue-cli-service test:unit --coverage=false' }; 181 | if (!options.jest && options.cypress) 182 | return { tests: 'npm run test:server & vue-cli-service test:e2e --headless --mode test' }; 183 | return null; 184 | case 'info': 185 | if (options.jest && options.cypress) return { tests: 'Run integration and functional tests' }; 186 | if (options.jest && !options.cypress) return { tests: 'Run functional tests' }; 187 | if (!options.jest && options.cypress) return { tests: 'Run integration tests' }; 188 | return null; 189 | default: 190 | return null; 191 | } 192 | } 193 | 194 | module.exports = { 195 | update: options => ({ 196 | name: options.name, 197 | version: '1.0.0', 198 | private: true, 199 | author: options.author, 200 | engines: { 201 | node: 'v14.15.4', 202 | npm: '6.14.4' 203 | }, 204 | scripts: { 205 | ...tests(options, 'scripts'), 206 | ...linter(options.linter, 'scripts'), 207 | ...jest(options.jest, 'scripts'), 208 | ...cypress(options.cypress, 'scripts'), 209 | ...prettier(options.prettier, 'scripts'), 210 | 'docker:dev': 'docker-compose -f docker-compose.dev.yml up', 211 | 'docker:prod': 'docker-compose -f docker-compose.prod.yml up', 212 | serve: 'vue-cli-service serve', 213 | build: 'vue-cli-service build' 214 | }, 215 | 'scripts-info': { 216 | ...tests(options, 'info'), 217 | ...linter(options.linter, 'info'), 218 | ...jest(options.jest, 'info'), 219 | ...cypress(options.cypress, 'info'), 220 | 'docker:dev': 'Develop via Docker compose', 221 | 'docker:prod': 'Run project on server production', 222 | serve: 'Run develop server', 223 | build: 'Build project for production' 224 | }, 225 | dependencies: { 226 | ...sentry(options.sentry, 'dep'), 227 | ...linter(options.linter, 'dep'), 228 | ...cypress(options.cypress, 'dep'), 229 | ...vueDoc(options.vueDoc, 'dep'), 230 | ...mdb(options.mdb, 'dep'), 231 | axios: '^0.21.1', 232 | 'axios-mock-adapter': '^1.19.0', 233 | 'core-js': '^3.8.0', 234 | "cytoscape": "^3.19.0", 235 | 'cytoscape-cola': '^2.4.0', 236 | 'document-register-element': '^1.14.10', 237 | save: '^2.4.0', 238 | vue: '^3.0.0-beta.1', 239 | 'vue-router': '^4.0.8', 240 | vuex: '^4.0.1', 241 | webpack: '^4.44.2' 242 | }, 243 | devDependencies: { 244 | ...linter(options.linter, 'devDep'), 245 | ...jest(options.jest, 'devDep'), 246 | ...prettier(options.prettier, 'devDep'), 247 | ...cypress(options.cypress, 'devDep'), 248 | ...multiLanguage(options.multiLanguage, 'devDep'), 249 | ...mdb(options.mdb, 'devDep'), 250 | '@vue/compiler-sfc': '^3.0.0-beta.1', 251 | '@babel/plugin-proposal-nullish-coalescing-operator': '^7.12.13', 252 | '@babel/plugin-proposal-optional-chaining': '^7.12.13', 253 | '@babel/preset-env': '^7.12.13', 254 | '@vue/cli-plugin-babel': '^4.5.11', 255 | '@vue/cli-plugin-router': '^4.5.11', 256 | '@vue/cli-plugin-vuex': '^4.5.11', 257 | '@vue/cli-service': '^4.5.13', 258 | 'node-sass': '^4.14.1', 259 | 'sass-loader': '^8.0.2', 260 | 'vue-template-compiler': '^2.6.12' 261 | } 262 | }) 263 | }; 264 | -------------------------------------------------------------------------------- /cli/core/cleaner/rm.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | /** 4 | * Generating a one-dimensional array and adding a range. 5 | * @param {Multidimensional array} lines = [3, [8, 11]] 6 | * @returns [3, 8, 9, 10, 11] 7 | */ 8 | const generateRange = (lines = []) => { 9 | const result = []; 10 | for (const line of lines) { 11 | if (Array.isArray(line)) { 12 | for (let i = line[0]; i <= line[1]; i++){ 13 | result.push(i); 14 | } 15 | } else { 16 | result.push(line); 17 | } 18 | } 19 | return result; 20 | } 21 | 22 | const remove = (data, lines = []) => { 23 | const resultLines = generateRange(lines); 24 | return data 25 | .split('\n') 26 | .filter((val, idx) => resultLines.indexOf(idx + 1) === -1) 27 | .join('\n'); 28 | } 29 | 30 | const removeLines = async (file, lines) => { 31 | return new Promise((resolve, reject) => { 32 | fs.readFile(file, 'utf8', async (err, data) => { 33 | if (err) reject(err); 34 | 35 | fs.writeFile(file, remove(data, lines), 'utf8', (err) => { 36 | if (err) reject(err); 37 | resolve(); 38 | }); 39 | }) 40 | }) 41 | } 42 | 43 | module.exports = { removeLines }; 44 | -------------------------------------------------------------------------------- /cli/core/inquirer.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const { initPrompts } = require('./prompts'); 3 | 4 | module.exports = { 5 | // sign in prompts 6 | askSignInCredentials: async () => inquirer.prompt(initPrompts) 7 | }; 8 | -------------------------------------------------------------------------------- /cli/core/prompts.js: -------------------------------------------------------------------------------- 1 | const logSymbols = require('log-symbols'); 2 | 3 | module.exports = { 4 | initPrompts: [ 5 | { 6 | name: 'name', 7 | type: 'input', 8 | message: 'Project name:', 9 | filter(val) { 10 | return val 11 | .trim() 12 | .toLowerCase() 13 | .replace(/[^+\w]/g, ' ') // Change all symbols to space 14 | .trim() // Remove spaces from start & end string 15 | .replace(/\s+/g, '-') // Change spaces to "-"; 16 | }, 17 | validate: value => 18 | value.length ? true : `${logSymbols.warning} This field should not be empty` 19 | }, 20 | { 21 | name: 'author', 22 | type: 'input', 23 | message: 'Author:', 24 | validate: value => 25 | value.length ? true : `${logSymbols.warning} This field should not be empty` 26 | }, 27 | { 28 | name: 'cypress', 29 | type: 'list', 30 | message: 'Remove cypress?', 31 | validate: value => 32 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 33 | filter(val) { 34 | return val === 'Yes'; 35 | }, 36 | choices: ['No', 'Yes'] 37 | }, 38 | { 39 | name: 'jest', 40 | type: 'list', 41 | message: 'Remove unit tests?', 42 | validate: value => 43 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 44 | filter(val) { 45 | return val === 'Yes'; 46 | }, 47 | choices: ['No', 'Yes'] 48 | }, 49 | { 50 | name: 'linter', 51 | type: 'list', 52 | message: 'Remove linter?', 53 | validate: value => 54 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 55 | filter(val) { 56 | return val === 'Yes'; 57 | }, 58 | choices: ['No', 'Yes'] 59 | }, 60 | { 61 | name: 'prettier', 62 | type: 'list', 63 | message: 'Remove formatter?', 64 | validate: value => 65 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 66 | filter(val) { 67 | return val === 'Yes'; 68 | }, 69 | choices: ['No', 'Yes'] 70 | }, 71 | { 72 | name: 'gitlabPage', 73 | type: 'list', 74 | message: 'Remove gitlab page?', 75 | validate: value => 76 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 77 | filter(val) { 78 | return val === 'Yes'; 79 | }, 80 | choices: ['No', 'Yes'] 81 | }, 82 | { 83 | name: 'mdb', 84 | type: 'list', 85 | message: 'Remove Material Design Bootstrap?', 86 | validate: value => 87 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 88 | filter(val) { 89 | return val === 'Yes'; 90 | }, 91 | choices: ['No', 'Yes'] 92 | }, 93 | { 94 | name: 'vueDoc', 95 | type: 'list', 96 | message: 'Remove component documentation?', 97 | validate: value => 98 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 99 | filter(val) { 100 | return val === 'Yes'; 101 | }, 102 | choices: ['No', 'Yes'] 103 | }, 104 | { 105 | name: 'multiLanguage', 106 | type: 'list', 107 | message: 'Remove multi language settings?', 108 | validate: value => 109 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 110 | filter(val) { 111 | return val === 'Yes'; 112 | }, 113 | choices: ['No', 'Yes'] 114 | }, 115 | { 116 | name: 'sentry', 117 | type: 'list', 118 | message: 'Remove sentry?', 119 | validate: value => 120 | value.length ? true : `${logSymbols.warning} This field should not be empty`, 121 | filter(val) { 122 | return val === 'Yes'; 123 | }, 124 | choices: ['No', 'Yes'] 125 | } 126 | ] 127 | }; 128 | -------------------------------------------------------------------------------- /cli/core/ui.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const figlet = require('figlet'); 3 | 4 | module.exports = { 5 | title: () => 6 | console.log( 7 | `${chalk.red( 8 | figlet.textSync(' VUE MADBOILER ', { 9 | horizontalLayout: 'full' 10 | }) 11 | )}\n` 12 | ) 13 | }; 14 | -------------------------------------------------------------------------------- /cli/index.js: -------------------------------------------------------------------------------- 1 | const ora = require('ora'); 2 | const chalk = require('chalk'); 3 | const clear = require('clear'); 4 | const { exec } = require('child_process'); 5 | const ui = require('./core/ui'); 6 | const inquirer = require('./core/inquirer'); 7 | const cleaner = require('./core/cleaner'); 8 | 9 | // clear console 10 | clear(); 11 | 12 | // display title 13 | ui.title(); 14 | 15 | // main execution 16 | const run = async () => { 17 | // prompt credentials 18 | const credentials = await inquirer.askSignInCredentials(); 19 | 20 | // invoke spinner 21 | const spinner = ora({ 22 | text: `${chalk.green('Cleaning...\n')}`, 23 | color: 'yellow' 24 | }).start(); 25 | 26 | // Run cleaner 27 | await cleaner.run(credentials); 28 | 29 | spinner.stop(); 30 | 31 | console.log(`\n ${chalk.green('Successful setup 👍\n')}`); 32 | 33 | const install = ora({ 34 | text: `${chalk.green('Installing packages...\n')}`, 35 | color: 'yellow' 36 | }).start(); 37 | 38 | exec('npm install --legacy-peer-deps', (error, stdout, stderr) => { 39 | install.stop(); 40 | if (error) { 41 | console.log(`\n ${chalk.yellow(`error: ${error.message}`)}`); 42 | } 43 | if (stderr) { 44 | console.log(`\n ${chalk.yellow(`stderr: ${stderr}`)}`); 45 | } 46 | console.log(`\n ${chalk.yellow(`stdout: ${stdout}`)}`); 47 | console.log(`\n ${chalk.green('Installation completed')}`); 48 | console.log(`\n ${chalk.green('The command to start the project is: npm run serve\n\n')}`); 49 | }); 50 | }; 51 | 52 | run(); 53 | -------------------------------------------------------------------------------- /cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chinese-review-cli", 3 | "version": "1.0.0", 4 | "description": "Chinese Review CLI", 5 | "main": "index.js", 6 | "author": "Ross Bulat", 7 | "license": "MIT", 8 | "private": true, 9 | "bin": { 10 | "ccli": "./index.js" 11 | }, 12 | "scripts": { 13 | "debug": "nodemon --no-stdin index.js", 14 | "start": "node index.js" 15 | }, 16 | "dependencies": { 17 | "boxen": "^4.2.0", 18 | "chalk": "^4.1.0", 19 | "clear": "^0.1.0", 20 | "configstore": "^5.0.1", 21 | "figlet": "^1.5.0", 22 | "inquirer": "^7.3.3", 23 | "log-symbols": "^4.0.0", 24 | "meow": "^7.0.1", 25 | "minimist": "^1.2.5", 26 | "moment": "^2.27.0", 27 | "node-fetch": "^2.6.1", 28 | "ora": "^4.0.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cy-open.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | # run web application and open Cypress Test Runner in a Docker container 4 | # but send the X11 output to the server running on the host machine 5 | # so you can see and interact with the tests. Most services are configured 6 | # in the file docker-compose.yml, this file only overrides variables 7 | # necessary for "cypress open" to work with X11. 8 | # We need to use both file names to run: 9 | # 10 | # docker-compose -f docker-compose.e2e.yml -f cy-open.yml up --exit-code-from cypress 11 | # 12 | services: 13 | cypress: 14 | # pass custom command to start Cypress otherwise it will use the entrypoint 15 | # specified in the Cypress Docker image. 16 | # it can find file "cypress.json" and show integration specs 17 | # https://on.cypress.io/command-line#cypress-open 18 | image: cypress 19 | entrypoint: npm run test:e2e 20 | environment: 21 | # get the IP address of the host machine and allow X11 to accept 22 | # incoming connections from that IP address 23 | # IP=$(ipconfig getifaddr en0) 24 | # /usr/X11/bin/xhost + $IP 25 | # then pass the environment variable DISPLAY to show Cypress GUI on the host system 26 | # DISPLAY=$IP:0 27 | - DISPLAY 28 | volumes: 29 | # for Cypress to communicate with the X11 server pass this socket file 30 | # in addition to any other mapped volumes 31 | - /tmp/.X11-unix:/tmp/.X11-unix 32 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "chromeWebSecurity": false, 4 | "pluginsFile": "tests/e2e/plugins/index.js", 5 | "supportFile": "tests/e2e/support/index.js", 6 | "fixturesFolder": "tests/e2e/fixtures", 7 | "integrationFolder": "tests/e2e/integrations", 8 | "testFiles": "**/*.spec.js*", 9 | "experimentalComponentTesting": true, 10 | "componentFolder": "tests/e2e/components", 11 | "nodeVersion":"system", 12 | "video": false, 13 | "viewportWidth": 1366, 14 | "viewportHeight": 768 15 | } 16 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web-dev: 5 | build: 6 | context: . 7 | dockerfile: ./docker/Dockerfile.dev 8 | ports: 9 | - '8080:8080' 10 | environment: 11 | NODE_ENV: development 12 | volumes: 13 | - ./:/app 14 | - /app/node_modules 15 | -------------------------------------------------------------------------------- /docker-compose.e2e.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | cypress: 5 | image: cypress 6 | build: 7 | context: . 8 | dockerfile: ./docker/Dockerfile.e2e 9 | ports: 10 | - '3000:3000' 11 | environment: 12 | - CYPRESS_baseUrl=http://localhost:3000 13 | volumes: 14 | - ./tests/e2e/:/app/tests/e2e 15 | - ./tests/server:/app/tests/server 16 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web-prod: 5 | build: 6 | context: . 7 | dockerfile: ./docker/Dockerfile.prod 8 | ports: 9 | - '80:80' 10 | environment: 11 | NODE_ENV: production 12 | volumes: 13 | - ./:/app 14 | - /app/node_modules 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm ci 8 | RUN npm rebuild node-sass 9 | 10 | COPY . . 11 | 12 | EXPOSE 8080 13 | 14 | CMD ["npm", "run", "serve"] 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.e2e: -------------------------------------------------------------------------------- 1 | 2 | FROM cypress/browsers:latest 3 | 4 | WORKDIR /app 5 | 6 | COPY package*.json ./ 7 | 8 | RUN npm ci 9 | 10 | COPY . . 11 | 12 | EXPOSE 3000 13 | -------------------------------------------------------------------------------- /docker/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM node:14.15.4-alpine as build 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm ci 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | FROM nginx:1.18.0-alpine 14 | 15 | COPY --from=build /app/dist /usr/share/nginx/html 16 | 17 | RUN rm /etc/nginx/conf.d/default.conf 18 | 19 | COPY nginx/nginx.conf /etc/nginx/conf.d 20 | 21 | EXPOSE 80 22 | 23 | CMD ["nginx", "-g", "daemon off;"] 24 | -------------------------------------------------------------------------------- /docs/How_to_run_and_cofugure_tests.md: -------------------------------------------------------------------------------- 1 | # How to run and configure tests 2 | 3 | To test the widget we have a type of test: 4 | 1. [Functional](#unit-tests) 5 | 2. [Component](#integration-and-component-tests) 6 | 3. [Integration](#integration-and-component-tests) 7 | 8 | ## Run all tests with mock json server on production 9 | 10 | Before running the tests, you need to run the `npm i` command to install all dependencies. 11 | 12 | ```bash 13 | npm run tests 14 | ``` 15 | 16 | After running this command: 17 | 18 | 1. The Mock server will start 19 | 2. The unit test will start checking 20 | 3. Server for integration tests will start 21 | 4. Integration and component tests will start checking 22 | 5. Finish. A table of results will be displayed 23 | 24 | ``` 25 | (Run Finished) 26 | 27 | Spec Tests Passing Failing Pending Skipped 28 | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ 29 | │ ✔ Home.spec.js 00:05 5 5 - - - │ 30 | ├────────────────────────────────────────────────────────────────────────────────────────────────┤ 31 | │ ✔ Search.spec.js 00:03 5 5 - - - │ 32 | ├────────────────────────────────────────────────────────────────────────────────────────────────┤ 33 | │ ✔ VehiclesList.spec.js 642ms 1 1 - - - │ 34 | └────────────────────────────────────────────────────────────────────────────────────────────────┘ 35 | ✔ All specs passed! 00:14 11 11 - - - 36 | ``` 37 | 38 | **IMPORTANT**: Tests should be run before the project build to warn of bugs. The `npm run build` command should be run if the tests pass. 39 | 40 | Example: 41 | 42 | ``` 43 | npm i 44 | npm run lint 45 | npm run tests 46 | npm run build 47 | ``` 48 | 49 | ## Server for tests 50 | 51 | A special Mock server for tests has been added so that the tests do not depend on a real server. 52 | 53 | > We used this server > `https://github.com/typicode/json-server` 54 | 55 | ### Run server 56 | 57 | > Original command to run server `json-server --watch ./tests/server/server.js` 58 | 59 | ```bash 60 | npm run test:server 61 | ``` 62 | 63 | The server will start on the localhost and will be available at [http://localhost:8888](http://localhost:8888) 64 | 65 | In the console you should see the following result: 66 | 67 | ```bash 68 | $ npm run test:server 69 | 70 | $ vms-showroom@0.0.1 test:server /home/denisoed/projects/XXX/vms-frontend 71 | $ json-server --port 8888 --watch ./tests/server/server.js --routes ./tests/server/router.json --middlewares ./tests/server/middleware.js 72 | 73 | 74 | \{^_^}/ hi! 75 | 76 | Loading ./tests/server/server.js 77 | Loading ./tests/server/router.json 78 | Loading ./tests/server/middleware.js 79 | Done 80 | 81 | Resources 82 | http://localhost:8888/cultures 83 | http://localhost:8888/instance 84 | http://localhost:8888/showroom 85 | http://localhost:8888/vehicles 86 | http://localhost:8888/vehicle 87 | http://localhost:8888/bestDeals 88 | http://localhost:8888/filtered 89 | http://localhost:8888/availableFilters 90 | http://localhost:8888/byIds 91 | 92 | Other routes 93 | /api/v1/CultureSections/json\?* -> /cultures 94 | /api/v2/Instances/:id -> /instance 95 | /api/v2/Showroom/public/:uid\?* -> /showroom 96 | /api/v2/Showroom/public/:uid/AvailableFilters\?* -> /availableFilters 97 | /api/v2/Showroom/public/:uid/vehicles/byIds\?* -> /byIds 98 | /api/v2/Showroom/public/:uid/vehicles\?* -> /vehicles 99 | /api/v2/Showroom/public/:uid/vehicles/best-deals\?* -> /bestDeals 100 | /api/v2/Showroom/public/:uid/vehicles/filtered\?* -> /filtered 101 | /api/v2/Showroom/public/:uid/vehicles/:uid\?* -> /vehicle 102 | 103 | Home 104 | http://localhost:8888 105 | 106 | ... 107 | ``` 108 | 109 | ### Configure server 110 | 111 | In the root of the project there is a configuration [file](./json-server.json) for json-server, in it you can change the port, add new middlewares, etc. 112 | 113 | ```json 114 | { 115 | "port": "8888", 116 | "routes": "./tests/server/router.json", 117 | "middlewares": "./tests/server/middleware.js" 118 | } 119 | ``` 120 | 121 | ### Structure folders 122 | 123 | The files for the server are located in the `/tests/server` folder. 124 | 125 | * File [server.js](./tests/server/server.js) - is the main file for starting the server. 126 | * File [router.json](./tests/server/router.json) - needed to create custom api urls. 127 | * File [middleware.js](./tests/server/middleware.js) - needed to change the behavior of the json-server. Example change all POST requests to GET requests 128 | * Folder **data** - stores files with test data, which will be returned by json-server. All data in the files can be changed. 129 | 130 | ## Unit tests 131 | 132 | Functional testing allows you to test individual functions in your code to identify logical errors 133 | 134 | > The jest library is used for unit tests > `https://vue-test-utils.vuejs.org/ru/guides` 135 | 136 | Run unit tests 137 | 138 | ```bash 139 | npm run test:unit 140 | ``` 141 | 142 | Generate code coverage report 143 | 144 | ```bash 145 | npm run test:unit:coverage 146 | ``` 147 | 148 | ## Integration and Component tests 149 | 150 | The [cypress](https://www.cypress.io/) framework is used for integration and component testing. 151 | 152 | For the component tests we use experimental lib `https://docs.cypress.io/guides/component-testing/introduction.html#What-is-Cypress-Component-Testing` 153 | 154 | Command for run: 155 | 156 | ```bash 157 | npm run test:e2e 158 | ``` 159 | 160 | After running this command: 161 | 162 | 1. The Mock server will start 163 | 2. Server for integration tests will start 164 | 3. A window opens with a list of all tests that can be run and monitored. 165 |  166 | 4. Now you can start writing tests 167 | 168 | 169 | ### Folders and files 170 | 171 | The integration tests are located in the `/tests/e2e` folder. 172 | 173 | ```bash 174 | tests 175 | e2e 176 | components 177 | - example.spec.js 178 | - ... 179 | fixtures 180 | - example.json 181 | - ... 182 | integration 183 | - example.spec.js 184 | - ... 185 | plugins 186 | - index.js 187 | support 188 | - commands.js 189 | - index.js 190 | - constants.js 191 | unit 192 | - ... 193 | server 194 | - ... 195 | ``` 196 | 197 | * Add tests for components to the `/tests/e2e/components` folder 198 | * Add tests for pages to the `/tests/e2e/integrations` folder 199 | * *More information about folders and files can be found here* `https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Plugin-files` 200 | 201 | > Please, use fixtures from this folder `/tests/server/data/*.js`. Example [VehiclesList](../tests/e2e/components/VehiclesList.spec.js) 202 | 203 | ### Settings 204 | 205 | The settings file cypress.json for integration tests is located in the root of the project. 206 | 207 | ```bash 208 | { 209 | "baseUrl": "http://localhost:3000", 210 | "chromeWebSecurity": false, 211 | "pluginsFile": "tests/e2e/plugins/index.js", 212 | "supportFile": "tests/e2e/support/index.js", 213 | "fixturesFolder": "tests/e2e/fixtures", 214 | "integrationFolder": "tests/e2e/integrations", 215 | "testFiles": "**/*.spec.js*", 216 | "experimentalComponentTesting": true, 217 | "componentFolder": "tests/e2e/components", 218 | "nodeVersion":"system", 219 | "video": false, 220 | "viewportWidth": 1366, 221 | "viewportHeight": 768 222 | } 223 | ``` 224 | 225 | You can read about all available settings here `https://docs.cypress.io/guides/references/configuration.html#Options` 226 | 227 | ### Run tests in docker container 228 | 229 | If you get this error 230 | 231 | ```bash 232 | cypress_1 | ---------- 233 | cypress_1 | 234 | cypress_1 | No protocol specified 235 | cypress_1 | [101:0208/050449.746174:ERROR:browser_main_loop.cc(1434)] Unable to open X display. 236 | cypress_1 | The futex facility returned an unexpected error code. 237 | cypress_1 | No protocol specified 238 | cypress_1 | [115:0208/050450.882329:ERROR:browser_main_loop.cc(1434)] Unable to open X display. 239 | cypress_1 | 240 | cypress_1 | undefined:0 241 | cypress_1 | 242 | cypress_1 | 243 | cypress_1 | illegal access 244 | cypress_1 | (Use `Cypress --trace-uncaught ...` to show where the exception was thrown) 245 | cypress_1 | 246 | cypress_1 | ---------- 247 | ``` 248 | 249 | 1. Please, run this command 250 | 251 | ```bash 252 | xhost +si:localuser:root 253 | ``` 254 | 255 | Result 256 | 257 | ```bash 258 | localuser:root being added to access control list 259 | ``` 260 | 261 | 2. Run e2e tests 262 | ```bash 263 | docker-compose -f docker-compose.e2e.yml -f cy-open.yml up --exit-code-from cypress 264 | ``` 265 | -------------------------------------------------------------------------------- /docs/components/UIButton.md: -------------------------------------------------------------------------------- 1 | # UIButton 2 | 3 | ## Slots 4 | 5 | | Name | Description | 6 | | --------- | ----------- | 7 | | `default` | | 8 | 9 | ## Events 10 | 11 | | Name | Description | 12 | | ------- | ----------- | 13 | | `click` | | 14 | 15 | ## Methods 16 | 17 | ### onClick() 18 | 19 | **Syntax** 20 | 21 | ```typescript 22 | onClick(): void 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /jest-coverage-badges.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable semi */ 4 | const mkdirp = require('mkdirp'); 5 | const { 6 | get 7 | } = require('https'); 8 | const { 9 | readFile, 10 | writeFile 11 | } = require('fs'); 12 | 13 | /** 14 | * Will lookup the argument in the cli arguments list and will return a 15 | * value passed as CLI arg (if found) 16 | * Otherwise will return default value passed 17 | * @param argName - name of hte argument to look for 18 | * @param defaultOutput - default value to return if could not find argument in cli command 19 | * @private 20 | */ 21 | const findArgument = (argName, defaultOutput) => { 22 | if (!argName) { 23 | return defaultOutput; 24 | } 25 | 26 | const index = process.argv.findIndex(a => a.match(argName)) 27 | if (index < 0) { 28 | return defaultOutput; 29 | } 30 | 31 | try { 32 | return process.argv[index + 1]; 33 | } catch (e) { 34 | return defaultOutput; 35 | } 36 | } 37 | 38 | 39 | const outputPath = findArgument('output', './coverage'); 40 | const inputPath = findArgument('input', './coverage/coverage-summary.json'); 41 | 42 | const getColour = coverage => { 43 | if (coverage < 80) { 44 | return 'red'; 45 | } 46 | 47 | if (coverage < 90) { 48 | return 'yellow'; 49 | } 50 | 51 | return 'brightgreen'; 52 | }; 53 | 54 | const reportKeys = ['lines', 'statements', 'functions', 'branches']; 55 | 56 | const getBadge = (report, key) => { 57 | if (!(report && report.total && report.total[key])) { 58 | throw new Error('malformed coverage report'); 59 | } 60 | 61 | const coverage = report.total[key].pct; 62 | const colour = getColour(coverage); 63 | 64 | return `https://img.shields.io/badge/Coverage${encodeURI(':')}${key}-${coverage}${encodeURI('%')}-${colour}.svg`; 65 | } 66 | 67 | const download = (url, cb) => { 68 | get(url, res => { 69 | let file = ''; 70 | res.on('data', chunk => { 71 | file += chunk; 72 | }); 73 | res.on('end', () => cb(null, file)); 74 | }).on('error', err => cb(err)); 75 | } 76 | 77 | const writeBadgeInFolder = (key, res) => { 78 | writeFile(`${outputPath}/badge-${key}.svg`, res, 'utf8', writeError => { 79 | if (writeError) { 80 | throw writeError; 81 | } 82 | }); 83 | } 84 | 85 | const getBadgeByKey = report => key => { 86 | const url = getBadge(report, key); 87 | 88 | download(url, (err, res) => { 89 | if (err) { 90 | throw err; 91 | } 92 | mkdirp(outputPath, folderError => { 93 | if (folderError) { 94 | console.error(`Could not create output directory ${folderError}`); 95 | } else { 96 | writeBadgeInFolder(key, res); 97 | } 98 | }) 99 | }) 100 | } 101 | 102 | readFile(`${inputPath}`, 'utf8', (err, res) => { 103 | if (err) { 104 | throw err; 105 | } 106 | 107 | const report = JSON.parse(res); 108 | reportKeys.forEach(getBadgeByKey(report)); 109 | }); 110 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest', 3 | collectCoverage: true, 4 | collectCoverageFrom: [ 5 | 'src/**/*.{js,vue}', 6 | '!src/main.js' 7 | ], 8 | coverageReporters: [ 9 | 'json-summary', 10 | 'text', 11 | 'lcov' 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 80; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html; 9 | } 10 | 11 | error_page 500 502 503 504 /50x.html; 12 | 13 | location = /50x.html { 14 | root /usr/share/nginx/html; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-madboiler", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "Mad Devs", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "init": "node ./cli/index.js" 10 | }, 11 | "dependencies": { 12 | "boxen": "^4.2.0", 13 | "chalk": "^4.1.0", 14 | "clear": "^0.1.0", 15 | "configstore": "^5.0.1", 16 | "figlet": "^1.5.0", 17 | "inquirer": "^7.3.3", 18 | "log-symbols": "^4.0.0", 19 | "meow": "^7.0.1", 20 | "minimist": "^1.2.5", 21 | "moment": "^2.27.0", 22 | "node-fetch": "^2.6.1", 23 | "ora": "^4.0.5", 24 | "rimraf": "^3.0.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/badge-branches.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/badge-functions.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/badge-lines.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/badge-statements.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maddevsio/vue-madboiler/dd755a69a8a2a7a6a554a0f4d2f9aec26bad6ac7/public/cli.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maddevsio/vue-madboiler/dd755a69a8a2a7a6a554a0f4d2f9aec26bad6ac7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |11 | Simple boilerplate for 12 | Vue 3 13 | from 14 | Mad Devs 15 |
16 |{{ desctiption }} {{ countFromStore }}
17 |Sign in
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |Not a member?
36 |
or sign in with:
41 | 42 | 43 | 44 | 45 | 46 | 47 |Sign up
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | At least 8 characters and 1 digit 24 | 25 | 26 | 27 | 28 | 29 | 30 |or sign up with:
31 | 32 | 33 | 34 | 35 | 36 | 37 |By clicking 41 | Sign up you agree to our 42 | terms of service 43 |
44 | 45 |