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 @@