diff --git a/.circleci/config.yml b/.circleci/config.yml index 82ac89662b..aad442b2bf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,7 +33,9 @@ jobs: - run: *install_dependencies - save_cache: *save_cache - run: yarn test:unit + - run: yarn test:e2e - run: yarn lint + - run: yarn lint:style deploy-demos: <<: *defaults diff --git a/.eslintrc.js b/.eslintrc.js index 3c755d88d3..3cdb6594e9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,22 +2,24 @@ module.exports = { root: true, env: { node: true, - jest: true, + jest: true, // Optimal way to do this is through overrides, but they didn't work for me. }, extends: [ - '@vue/standard', 'plugin:vue/essential', - 'plugin:vue/strongly-recommended', + '@vue/standard', + '@vue/typescript/recommended', ], + parserOptions: { + ecmaVersion: 2020, + }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'comma-dangle': ['error', 'always-multiline'], curly: 'error', 'array-bracket-spacing': ['error', 'never'], + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/ban-ts-ignore': 0, + '@typescript-eslint/camelcase': 0, }, - parserOptions: { - parser: 'babel-eslint', - }, - } diff --git a/.gitignore b/.gitignore index a0dddc6fb8..2b53524947 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ node_modules /dist +/tests/e2e/videos/ +/tests/e2e/screenshots/ + # local env files .env.local .env.*.local diff --git a/README.md b/README.md index 0bd6f96a0b..4528060920 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,27 @@ yarn install ### Compiles and hot-reloads for development ``` -yarn run serve +yarn serve ``` ### Compiles and minifies for production ``` -yarn run build +yarn build ``` ### Run your tests ``` -yarn run test +yarn test ``` ### Lints and fixes files ``` -yarn run lint +yarn lint ``` ### Run your unit tests ``` -yarn run test:unit +yarn test:unit ``` ### Customize configuration diff --git a/babel.config.js b/babel.config.js index d39e57b42c..df195386eb 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,5 +1,5 @@ module.exports = { presets: [ - '@vue/app', + '@vue/cli-plugin-babel/preset', ], } diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000000..470c720199 --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "pluginsFile": "tests/e2e/plugins/index.js" +} diff --git a/docs/dropdown-popover.md b/docs/components/VaDropdown.md similarity index 100% rename from docs/dropdown-popover.md rename to docs/components/VaDropdown.md diff --git a/docs/tabs-docs.md b/docs/components/VaTabs.md similarity index 100% rename from docs/tabs-docs.md rename to docs/components/VaTabs.md diff --git a/docs/conventions.md b/docs/conventions.md index c7a34c4178..df172c934f 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -24,14 +24,22 @@ In future we also plan to be : * `prop="string-value"` is used instead of `:prop="'string-value'"`. * Short syntax is used for boolean props where possible (`hide` instead of `:hide="true"`). * Self closing tags are used where possible (`` instead of ``). +* No shortcuts: always `button` and not `btn`. Your naming should be well understood by all developers. + +## TypeScript usage +* You can use `@ts-nocheck` | `@ts-ignore` | `any` where it's appropriate. We want strict typing not to get in way of development. Also some mixins or plugins, such as context are fairly challenging to implement in a way to keep typing intact, and there is no much point given we will transition to Composition API sometime in the future. + +## Style * BEM is used for styles. * Rems are used for sizes (except for cases where it doesn't make sense). -* No shortcuts: always `button` and not `btn`. -* Use shortened function declaration:`buttonClass () {` instead of `buttonClass: function () {`. -## CSS -* We have both mixins (where that's possible) and global helpers. Mixins are prefixed with `va-`. Global classes are not prefixed. -* Html tags are unstyled. For example, `table`, `li`, `h1` etc won't give you any styling. Styles are applied either via classes or `.content` wrapper. +### Global classes +* Global helpers should be considered a separate library/plugin. Components should not rely on global classes! +* All vuestic global classes should be prefixed with `va-`. +* Html tags are unstyled. For example, `table`, `li`, `h1` etc won't give you any styling. Apply styles either via classes or `.content` wrapper. + +### Components +* Mixins should be used for style logic reuse inside components. ### Fonts diff --git a/docs/message-list.md b/docs/message-list.md deleted file mode 100644 index 1b5e46f3a2..0000000000 --- a/docs/message-list.md +++ /dev/null @@ -1,7 +0,0 @@ -## Gist - -Message list is intended as internal component for displaying consistently list of messages. - -It is used in various input components to show descriptions and error messages. - -This component can be used as standalone in form component to show form-specific messages. diff --git a/jest.config.js b/jest.config.js index 318270ed03..f8de7c1f49 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,30 +1,6 @@ module.exports = { - moduleFileExtensions: [ - 'js', - 'jsx', - 'json', - 'vue', - ], - transform: { - '^.+\\.vue$': 'vue-jest', - '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', - '^.+\\.jsx?$': 'babel-jest', - }, - transformIgnorePatterns: [ - '/node_modules/', - ], - moduleNameMapper: { - '^@/(.*)$': '/src/$1', - }, - snapshotSerializers: [ - 'jest-serializer-vue', - ], + preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', testMatch: [ '**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)', ], - testURL: 'http://localhost/', - watchPlugins: [ - 'jest-watch-typeahead/filename', - 'jest-watch-typeahead/testname', - ], } diff --git a/package.json b/package.json index ea5981caab..0565765a04 100644 --- a/package.json +++ b/package.json @@ -12,17 +12,21 @@ "test:unit": "vue-cli-service test:unit", "serve:docs": "vuepress dev docs", "build:docs": "vuepress build docs", + "test:e2e": "vue-cli-service test:e2e", "push": "npm publish --tag=next", "push-production": "npm publish --tag=latest" }, "dependencies": { - "@fortawesome/fontawesome-free": "^5.12.0", + "@fortawesome/fontawesome-free": "^5.13.0", "@popperjs/core": "^2.1.1", "approximate-number": "^2.0.0", "asva-executors": "^0.1.22", - "chart.js": "^2.6.0", - "colortranslator": "^1.1.1", - "core-js": "^3.4.1", + "babel-loader": "^8.1.0", + "chart.js": "^2.9.3", + "colortranslator": "^1.3.1", + "core-js": "^3.6.4", + "detect-browser": "^5.0.0", + "element-resize-detector": "^1.2.1", "flatpickr": "4.5.1", "font-awesome": "^4.7.0", "ionicons": "^3.0.0", @@ -31,43 +35,49 @@ "normalize.css": "^8.0.1", "stylelint": "^13.2.1", "stylelint-config-standard": "^20.0.0", - "v-tooltip": "^2.0.0-rc.30", + "v-tooltip": "^2.0.3", "vue": "^2.6.11", + "vue-class-component": "^7.2.2", "vue-clipboard2": "^0.3.0", "vue-color": "^2.7.1", "vue-epic-bus": "^0.1.5", "vue-flatpickr-component": "^8.0.0", + "vue-property-decorator": "^8.3.0", "vue-router": "^3.1.6", - "vue-toasted": "^1.1.25", - "vue-yandex-maps": "^0.8.14", - "vuelidate": "^0.7.4", + "vue-toasted": "^1.1.28", + "vue-yandex-maps": "^0.10.6", + "vuelidate": "^0.7.5", "vuetable-2": "1.7.5", - "webpack": "^4.42.0" + "webpack": "^4.42.1" }, "devDependencies": { - "@vue/cli-plugin-babel": "^4.2.3", - "@vue/cli-plugin-eslint": "^4.2.3", - "@vue/cli-plugin-unit-jest": "^4.2.3", - "@vue/cli-service": "^4.2.3", - "@vue/eslint-config-standard": "^5.1.2", + "@types/jest": "^25.1.4", + "@typescript-eslint/eslint-plugin": "^2.18.0", + "@typescript-eslint/parser": "^2.18.0", + "@vue/cli-plugin-babel": "~4.2.0", + "@vue/cli-plugin-e2e-cypress": "~4.2.0", + "@vue/cli-plugin-eslint": "~4.2.0", + "@vue/cli-plugin-router": "~4.2.0", + "@vue/cli-plugin-typescript": "~4.2.0", + "@vue/cli-plugin-unit-jest": "~4.2.0", + "@vue/cli-service": "~4.2.0", + "@vue/eslint-config-standard": "^5.1.0", + "@vue/eslint-config-typescript": "^5.0.1", "@vue/test-utils": "1.0.0-beta.32", - "babel-core": "7.0.0-bridge.0", - "babel-eslint": "^10.1.0", - "babel-jest": "^25.1.0", - "eslint": "^6.8.0", - "eslint-config-standard": "^14.1.0", - "eslint-plugin-import": "^2.20.1", - "eslint-plugin-node": "^11.0.0", + "eslint": "^6.7.2", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-standard": "^4.0.1", - "eslint-plugin-vue": "^6.2.2", - "lint-staged": "^10.0.8", - "node-sass": "^4.13.1", + "eslint-plugin-standard": "^4.0.0", + "eslint-plugin-vue": "^6.1.2", + "lint-staged": "^10.1.1", + "sass": "^1.25.0", "sass-loader": "^8.0.2", + "typescript": "~3.8.3", "vue-book": "0.1.0-alpha.31", - "vue-bulma-expanding": "0.0.1", "vue-template-compiler": "^2.6.11", - "vuepress": "^1.3.1" + "vue-bulma-expanding": "0.0.1", + "vuepress": "^1.4.0" }, "gitHooks": { "pre-commit": "lint-staged" diff --git a/src/components/context-test/ContextTest.demo.vue b/src/components/context-test/ContextTest.demo.vue index f204cbd491..4661307328 100644 --- a/src/components/context-test/ContextTest.demo.vue +++ b/src/components/context-test/ContextTest.demo.vue @@ -113,7 +113,7 @@ import VaTest from './ContextTest' import VaContext from './context-provide/VaContext' import VaButton from '../vuestic-components/va-button/VaButton' import VaBadge from '../vuestic-components/va-badge/VaBadge' -import { overrideContextConfig } from '../context-test/context-provide/ContextPlugin' +import { overrideContextConfig } from './context-provide/ContextPlugin' import { getContext } from '../context-test/context-provide/context' export default { diff --git a/src/components/context-test/context-provide/ContextPlugin.spec.js b/src/components/context-test/context-provide/ContextPlugin.spec.ts similarity index 100% rename from src/components/context-test/context-provide/ContextPlugin.spec.js rename to src/components/context-test/context-provide/ContextPlugin.spec.ts diff --git a/src/components/context-test/context-provide/ContextPlugin.js b/src/components/context-test/context-provide/ContextPlugin.ts similarity index 99% rename from src/components/context-test/context-provide/ContextPlugin.js rename to src/components/context-test/context-provide/ContextPlugin.ts index 141a63d5be..0cae9e0cf7 100644 --- a/src/components/context-test/context-provide/ContextPlugin.js +++ b/src/components/context-test/context-provide/ContextPlugin.ts @@ -1,3 +1,5 @@ +// @ts-nocheck + import flow from 'lodash/flow' import camelCase from 'lodash/camelCase' import upperFirst from 'lodash/upperFirst' @@ -38,44 +40,21 @@ export const ContextPluginMixin = { } /** - * Instead of getting component props from one config, we're getting a props and computed. - * So, for name "color" it will be: - * * prop `color` - just standard prop - * * computed `c_color` - computed (context-bound prop) + * Attempt to find a prop value from config chain. * - * @param componentProps Object - vue component object props - *``` - * { - * prop1: { type: Boolean, default: false }, - * prop2: { type: String, default: '' } + * @category String + * @param configs [object] config chain. + * @param componentName [string] + * @param propName [string] property name (pascal-case) * - * } - * ``` - * @param prefix - that prefix goes to contexted prop (that's intended for userland usage) - * @returns object - vue mixin with props and computed + * @returns {any} config object if found undefined means not found. */ -export const makeContextablePropsMixin = (componentProps, prefix = 'c_') => { - const computed = {} - - Object.entries(componentProps).forEach(([name, definition]) => { - computed[`${prefix}${name}`] = function () { - // We want to fallback to context in 2 cases: - // * prop value is undefined (allows user to dynamically enter/exit context). - // * prop value is not defined - if (!(name in this.$options.propsData) || this.$options.propsData[name] === undefined) { - return getContextPropValue(this, name, definition.default) - } - // In other cases we return the prop itself. - return this[name] - } - }) - - return { - // We attach mixin here for convenience - mixins: [ContextPluginMixin], - props: componentProps, - computed, - } +function getLocalConfigWithComponentProp (configs, componentName, propName) { + // Find prop value in config chain. + return configs.reverse().find(config => { + const componentConfig = config[componentName] + return componentConfig && hasOwnProperty(componentConfig, propName) + }) || undefined } /** @@ -145,24 +124,6 @@ export function getOriginalPropValue (key, context) { return context.$options.propsData[key] } -/** - * Attempt to find a prop value from config chain. - * - * @category String - * @param configs [object] config chain. - * @param componentName [string] - * @param propName [string] property name (pascal-case) - * - * @returns {any} config object if found undefined means not found. - */ -function getLocalConfigWithComponentProp (configs, componentName, propName) { - // Find prop value in config chain. - return configs.reverse().find(config => { - const componentConfig = config[componentName] - return componentConfig && hasOwnProperty(componentConfig, propName) - }) || undefined -} - // Just 2 levels deep merge. B has priority. export function mergeConfigs (configA, configB) { const result = {} @@ -178,3 +139,44 @@ export function mergeConfigs (configA, configB) { } return result } + +/** + * Instead of getting component props from one config, we're getting a props and computed. + * So, for name "color" it will be: + * * prop `color` - just standard prop + * * computed `c_color` - computed (context-bound prop) + * + * @param componentProps Object - vue component object props + *``` + * { + * prop1: { type: Boolean, default: false }, + * prop2: { type: String, default: '' } + * + * } + * ``` + * @param prefix - that prefix goes to contexted prop (that's intended for userland usage) + * @returns object - vue mixin with props and computed + */ +export const makeContextablePropsMixin = (componentProps, prefix = 'c_') => { + const computed = {} + + Object.entries(componentProps).forEach(([name, definition]) => { + computed[`${prefix}${name}`] = function () { + // We want to fallback to context in 2 cases: + // * prop value is undefined (allows user to dynamically enter/exit context). + // * prop value is not defined + if (!(name in this.$options.propsData) || this.$options.propsData[name] === undefined) { + return getContextPropValue(this, name, definition.default) + } + // In other cases we return the prop itself. + return this[name] + } + }) + + return { + // We attach mixin here for convenience + mixins: [ContextPluginMixin], + props: componentProps, + computed, + } +} diff --git a/src/components/vuestic-components/va-button-group/VaButtonGroup.vue b/src/components/vuestic-components/va-button-group/VaButtonGroup.vue index 921336c54e..6bc7497191 100644 --- a/src/components/vuestic-components/va-button-group/VaButtonGroup.vue +++ b/src/components/vuestic-components/va-button-group/VaButtonGroup.vue @@ -19,6 +19,7 @@ export default { }, }, provide () { + // eslint-disable-next-line @typescript-eslint/no-this-alias const parent = this return { va: new Vue({ diff --git a/src/components/vuestic-components/va-button/VaButton.vue b/src/components/vuestic-components/va-button/VaButton.vue index 0ea674e0da..964f87de92 100644 --- a/src/components/vuestic-components/va-button/VaButton.vue +++ b/src/components/vuestic-components/va-button/VaButton.vue @@ -222,6 +222,7 @@ export default { return 'button' }, inputListeners () { + // eslint-disable-next-line @typescript-eslint/no-this-alias const vm = this return Object.assign({}, this.$listeners, diff --git a/src/components/vuestic-components/va-card/VaCard.vue b/src/components/vuestic-components/va-card/VaCard.vue index b0a6ba753c..2b68bb864e 100644 --- a/src/components/vuestic-components/va-card/VaCard.vue +++ b/src/components/vuestic-components/va-card/VaCard.vue @@ -137,8 +137,8 @@ export default { left: 0; } - /deep/ #{&}__title, - /deep/ #{&}__content { + ::v-deep #{&}__title, + ::v-deep #{&}__content { padding: $card-padding; + .va-card__title, @@ -147,7 +147,7 @@ export default { } } - /deep/ #{&}__title { + ::v-deep #{&}__title { display: flex; align-items: center; diff --git a/src/components/vuestic-components/va-collapse/VaCollapse.vue b/src/components/vuestic-components/va-collapse/VaCollapse.vue index 672b6646c0..20062f0475 100644 --- a/src/components/vuestic-components/va-collapse/VaCollapse.vue +++ b/src/components/vuestic-components/va-collapse/VaCollapse.vue @@ -55,8 +55,7 @@ export default { inject: { accordion: { default: () => ({ - onChildChange: () => { - }, + onChildChange: () => undefined, }), }, }, diff --git a/src/components/vuestic-components/va-date-picker/VaDatePicker.vue b/src/components/vuestic-components/va-date-picker/VaDatePicker.vue index 0534274d55..688c8f11bd 100644 --- a/src/components/vuestic-components/va-date-picker/VaDatePicker.vue +++ b/src/components/vuestic-components/va-date-picker/VaDatePicker.vue @@ -82,7 +82,7 @@ export default { }, config: { type: Object, - default: () => {}, + default: () => undefined, }, }, ), diff --git a/src/components/vuestic-components/va-dropdown/VaDropdown.vue b/src/components/vuestic-components/va-dropdown/VaDropdown.vue index 8986130c0c..9f26ccb8cc 100644 --- a/src/components/vuestic-components/va-dropdown/VaDropdown.vue +++ b/src/components/vuestic-components/va-dropdown/VaDropdown.vue @@ -68,7 +68,7 @@ export default { }, watch: { showContent: { - handler (showContent) { + handler () { this.handlePopperInstance() }, }, diff --git a/src/components/vuestic-components/va-dropdown/dropdown-popover-subplugin.js b/src/components/vuestic-components/va-dropdown/dropdown-popover-subplugin.js index 18af99f35f..a62cfc5e90 100644 --- a/src/components/vuestic-components/va-dropdown/dropdown-popover-subplugin.js +++ b/src/components/vuestic-components/va-dropdown/dropdown-popover-subplugin.js @@ -1,6 +1,7 @@ export const DropdownPopperPlugin = { install (Vue) { Vue.prototype.$closeDropdown = function () { + // eslint-disable-next-line @typescript-eslint/no-this-alias let vm = this // Hide first parent dropdown. while ((vm = vm.$parent)) { diff --git a/src/components/vuestic-components/va-hover/VaHover.vue b/src/components/vuestic-components/va-hover/VaHover.vue index e0d16c8057..e0f8b0ae08 100644 --- a/src/components/vuestic-components/va-hover/VaHover.vue +++ b/src/components/vuestic-components/va-hover/VaHover.vue @@ -16,11 +16,11 @@ export default { }, }, methods: { - onMouseEnter (e) { + onMouseEnter () { this.active = true this.$emit('input', true) }, - onMouseLeave (e) { + onMouseLeave () { this.active = false this.$emit('input', false) }, diff --git a/src/components/vuestic-components/va-icon/VaIcon.vue b/src/components/vuestic-components/va-icon/VaIcon.vue index a8fc5446ec..871fd288ec 100644 --- a/src/components/vuestic-components/va-icon/VaIcon.vue +++ b/src/components/vuestic-components/va-icon/VaIcon.vue @@ -15,7 +15,7 @@