diff --git a/.eslintrc.js b/.eslintrc.js
index 4e7d8f7..86d2b2f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -53,6 +53,7 @@ module.exports = {
rules: {
'no-unused-vars': 2,
'@typescript-eslint/no-unused-vars': 'off',
+ '@typescript-eslint/no-var-requires': 'off',
},
},
],
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3e02ec4
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,12 @@
+###################################################
+# normalize line endings
+###################################################
+* text=auto
+
+# source files
+*.ts eol=lf
+*.vue eol=lf
+
+# windows files
+*.bat eol=crlf
+*.cmd eol=crlf
diff --git a/.github_changelog_generator b/.github_changelog_generator
new file mode 100644
index 0000000..0f4628d
--- /dev/null
+++ b/.github_changelog_generator
@@ -0,0 +1,4 @@
+user=eidng8
+project=vue-tree
+unreleased=false
+since-tag=0.0.25-2020.04.16-4b5ea82
diff --git a/.travis.yml b/.travis.yml
index a2c4a31..9c12f01 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,56 +1,86 @@
+os: linux
+dist: bionic
+language: node_js
+node_js: 12
branches:
only:
- master
- dev
-os:
- - linux
-language: node_js
-node_js:
- - 12
-addons:
- chrome: stable
+
cache:
directories:
- node_modules
-install:
- - npm install
-before_script:
- - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- - chmod +x ./cc-test-reporter
- - ./cc-test-reporter before-build
-script:
- - if [[ ${BUNDLE_ANALYZER_TOKEN} != "" ]]; then npm install --no-save @bundle-analyzer/webpack-plugin; fi
- - npm run test:unit -- --coverage
- - npm install coveralls
- - cat ./coverage/lcov.info | coveralls
- - npm run test:e2e
-after_script:
- - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT || echo code climate error is ignored.
+jobs:
+ include:
+ - stage: test
+ os: windows
+ addons:
+ chrome: stable
+ install:
+ - npm install
+ script:
+ - npm run test:unit
+
+ - stage: test
+ os: osx
+ addons:
+ chrome: stable
+ install:
+ - npm install
+ script:
+ - npm run test:unit
+
+ - stage: test
+ os: linux
+ env:
+ - secure: JZhvhE5uAXC1+WL47BFXREtbRJgaBSs7970cs1kSkpFv3cJ7SBGirjCg6xYmFvbqf3xmOYNbrd/iBt5Dyqs074cGxxccOpdwNgGF23euG3YgWzxcP7VUq1RrnxLALRtDRobxveNB7DctCPHxWuKlZgsZSAThEHUCdazH68/bCpBMAMaf/JO+MK/EndcDYWCGqY6TIeDAsmtNVNUI6pVnXkYJakiYODVEyzuSstT44eD8mZ3BXu36NN62d+uvSEw2+JVmC1yTHLgYllM5tiS4ScBaBq7/ih6cZunscZJSNaLBV9ifUeRlUyGTvt2GWBSuWuQMqxZldr5RqU6/APfhDNSzXROXy7hUCYXjgi6xbrHQPFc2yXLYxHX060NklhYn9+6UaPJig23MH2DY/lUjSOrec/jvPXSZ7F83DQwDlz9m1lWSWykO+PCodKBnRb9+75u4/I6h25ycgJ7iHHlCPMBCdiejajCfKgumS24bgN7/GKZHuo8XOlTzxg7wlt2fzO0Pmw79/+94vKMAPh8OrW1/L8zIzbLpfN7/y9MpKmoyMSJ4VSuq4Sp8cfPm3g6yfr9J0klZjX/ARG/dux6Yi650XsiUpZPsyT8YF9QdM/9Z76IvcfP/02RMSjg14tWn84jgtQ23kbAtsU4YtySG3OKNoJRDrMh+NSQxlHKlLs0=
+ - secure: sjd+Ru89jESG+RV8nEhl6ZmhgkdPsImFdW5uNuwrC3rCJXqjLATe1cJxUWDv4MIE3AOb0y5RisqtsRBbk8v9INJtuhZU3fLqbVOOfQXZz/FXynrM/vhRSrbeLTbiHRd/kqmyb/hQgqeoK8ZBjfDsGTz/w9eECLA98e2wOM2j2FLXAUm3jS1YR5rjUvAJuG8IYf0N+srlzpjKS7HE171BxRa7lrhNXUYxpP97XkDqHTxXbb1edGiLTesR+MG9x/yao4AT0lbuGVmFdf2xkib3uTlWX/FlZK3jCYivd2xuiBDhBNc0+g4azThSNwruxMng6pkna1Z9XNl6BF9bFJiU2/VTajMSE18zjJM6HHgvh/G6DINNkAejcNtHAAVoiX5Br54KC4LVnD5ZQkVrStyJTgAsz+Kquwc0Zb9PZMmKzLKAjlSZAQksj2F2XX+PDhEqAhmQZcCU2ln4yDIJrAzODYIZIYwS4J8vtiOkrLfiiHQQNLB69TB6/dOD5Byyvf1UMdbA62VC2mXto5osATZOIQP9hHoWuvFWXFSHh+WBghHXc4HeYMvefUV4BsJ+mKLLhBq07DEml5+YF63psp++MtgZAixdGP8pSe36R2UWC5MwP8m1XI9x+OEmb589gaJv+gd3dIx9l6wbWan6YKaXlhRgv+OLCWLYdlmMG2g1Brc=
+ addons:
+ chrome: stable
+ install:
+ - npm install
+ before_script:
+ - npm install --no-save coveralls
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
+ - chmod +x ./cc-test-reporter
+ - ./cc-test-reporter before-build
+ script:
+ - npm run test:unit -- --coverage
+ - npm run test:e2e
+ after_script:
+ - cat ./coverage/lcov.info | coveralls
+ - ./cc-test-reporter after-build --exit-code ${TRAVIS_TEST_RESULT}
+
+ - stage: deploy
+ os: linux
+ script: skip
+ before_deploy:
+ - npm install
+ - npm install --no-save @bundle-analyzer/webpack-plugin
+ - npm run build
+ - export PACKAGE=$(npm pack --silent)
+ deploy:
+ provider: releases
+ edge: true
+ file: ${PACKAGE}
+ token:
+ secure: Z4+zHdz07/2oO6QPIeSk/ZaS8+QwldjYPc0iRPIF/6N0Cww35EIMvtEm/tLMyZcw8kYJHAdh9v7q/bNrBwCdhmZ7/8d4qk8cFfqhxjWfA/8EnA8Juo+5+3gkUpYPukcNtDlaei3l++96k3sQ4FyYxuEpY3lWGzFwJggWA+LQGVebGKDreFnVytEr8DeXnOUhCamN39uv8xqrbJufvp+3vWNXu4PwRd5R7N+HqoZGPIQMa8rJwbPhW2cJF4bQoy7ZNzhh6/C0UbhEG9ykBWy/9VXIjxq46RJcWXVSq0dUEhhGWn34F5OZqDxOElPDzntXpunbNb1HnDFCw/hWy4BhDTlNI7DLhFc1R4+PRfd0c0lY1flHt/Xza5O4kV7P24bhZHuf8qVQyWjBg6mf+AOON88k7ewPyqKbkYphoeGdG0AktMcJ3CMbsKvgYtp/+K0mVtPsrgrFkJrXK9vgUazhBlkeq8PRxOrkTP9XeazZR6WzE7tJAmd612Q8Ij7zC1KNyXD1OCoFUs5KkjVD0vx7dM5jafWjhd3GWTegGJgQ/OOecXbfsigvwbSeaMN9Mh9RP8XrKXV8ajj7QeqhHOCzBtt4OukhjXXoU24BK3/Lak/WznKbrnVdtEORlU0jPcnXJvKrMrsRjeHQOeqZOTs4Mgt2GXcKGnzvTbsZVyceMZI=
+ on:
+ repo: eidng8/vue-tree
+ branch: master
+ tags: true
-before_deploy:
- - export TRAVIS_TAG=${TRAVIS_TAG:-$(node -p "require('./package.json').version")-$(date +'%Y.%m.%d')-$(git log --format=%h -1)}
- - echo ${TRAVIS_TAG}
- - 'if [[ ${TAGGED} != 1 ]]; then git tag ${TRAVIS_TAG} && export TAGGED=1; fi'
- - 'if [[ ! -e dist ]]; then npm run build && export PACKAGE=$(npm pack --silent); fi'
-deploy:
- - provider: releases
- skip_cleanup: true
- file: $PACKAGE
- api_key:
- secure: Z4+zHdz07/2oO6QPIeSk/ZaS8+QwldjYPc0iRPIF/6N0Cww35EIMvtEm/tLMyZcw8kYJHAdh9v7q/bNrBwCdhmZ7/8d4qk8cFfqhxjWfA/8EnA8Juo+5+3gkUpYPukcNtDlaei3l++96k3sQ4FyYxuEpY3lWGzFwJggWA+LQGVebGKDreFnVytEr8DeXnOUhCamN39uv8xqrbJufvp+3vWNXu4PwRd5R7N+HqoZGPIQMa8rJwbPhW2cJF4bQoy7ZNzhh6/C0UbhEG9ykBWy/9VXIjxq46RJcWXVSq0dUEhhGWn34F5OZqDxOElPDzntXpunbNb1HnDFCw/hWy4BhDTlNI7DLhFc1R4+PRfd0c0lY1flHt/Xza5O4kV7P24bhZHuf8qVQyWjBg6mf+AOON88k7ewPyqKbkYphoeGdG0AktMcJ3CMbsKvgYtp/+K0mVtPsrgrFkJrXK9vgUazhBlkeq8PRxOrkTP9XeazZR6WzE7tJAmd612Q8Ij7zC1KNyXD1OCoFUs5KkjVD0vx7dM5jafWjhd3GWTegGJgQ/OOecXbfsigvwbSeaMN9Mh9RP8XrKXV8ajj7QeqhHOCzBtt4OukhjXXoU24BK3/Lak/WznKbrnVdtEORlU0jPcnXJvKrMrsRjeHQOeqZOTs4Mgt2GXcKGnzvTbsZVyceMZI=
- on:
- repo: eidng8/vue-tree
- branch: master
- #tags: true
- condition: ${TRAVIS_EVENT_TYPE} == push
- - provider: npm
- skip_cleanup: true
- email: cheung.jackey@gmail.com
- api_key:
- secure: 2mIMc5dD88yzeeAhV1D8uTFJIJhYKgTMPH36K3HbLvMHUtSYRQBOob9H075iEsQ3Ta1Liuk27T93Q0atVsybUj1ufWo0Hzal0iYd4s1aj08hTsiIahD0EPf+dmSRaUELXqjFofXgboRugpxDeyTvOjCTjKT+eF3c1c+X7AFiPMmVzLJmx3eFRb8y3F9yUJ5nVz8NyvjJzv6Me6sf59O8a04awqL9okaSmJcdj79/7lkUzWAQ00LNKHXU+eP5C65VMciqu02PCRIk+C2nNOfw7IH5cRW3ZPzngPu6HAPgeUP0CloRU8yM8qU1LF9A/1IE09fRvctZnOCR2zt3EigmrmWVPPqiYBy2G95NQCJfGlAfynWVl+r/aMHmtABb818OBUyOXBxhEISrW9jJVm3z1N4gqYr+/Yfc62jkCGwUQHhDBOgOuNQv+G/8dN/FzRqRbCm/rXLcP4HaRfFu+CDkf+MFKu5jF5R7QFydRG3XVCOv73mpt/AFewJUJs7+IY/jPQ7J4JMeVhDYo3l31iC1mPE4+ylk9skq6Ss5v17N7/uahE8odV9tUyx0vDUTXfzjtIp6EH+y59HFNHn6dO2wAnQyozGvcDiQazt8jgcEcEAsRiVjrfVYO+UxBLTV2keXkvE4rzdAJZXJZblwRuyC5yFmBlUNGY38bpVBZZYTB+g=
- on:
- repo: eidng8/vue-tree
- branch: master
- condition: ${TRAVIS_EVENT_TYPE} == push
- #tags: true
+ - stage: deploy
+ os: linux
+ script: skip
+ deploy:
+ provider: npm
+ edge: true
+ email: cheung.jackey@gmail.com
+ api_token:
+ secure: 2mIMc5dD88yzeeAhV1D8uTFJIJhYKgTMPH36K3HbLvMHUtSYRQBOob9H075iEsQ3Ta1Liuk27T93Q0atVsybUj1ufWo0Hzal0iYd4s1aj08hTsiIahD0EPf+dmSRaUELXqjFofXgboRugpxDeyTvOjCTjKT+eF3c1c+X7AFiPMmVzLJmx3eFRb8y3F9yUJ5nVz8NyvjJzv6Me6sf59O8a04awqL9okaSmJcdj79/7lkUzWAQ00LNKHXU+eP5C65VMciqu02PCRIk+C2nNOfw7IH5cRW3ZPzngPu6HAPgeUP0CloRU8yM8qU1LF9A/1IE09fRvctZnOCR2zt3EigmrmWVPPqiYBy2G95NQCJfGlAfynWVl+r/aMHmtABb818OBUyOXBxhEISrW9jJVm3z1N4gqYr+/Yfc62jkCGwUQHhDBOgOuNQv+G/8dN/FzRqRbCm/rXLcP4HaRfFu+CDkf+MFKu5jF5R7QFydRG3XVCOv73mpt/AFewJUJs7+IY/jPQ7J4JMeVhDYo3l31iC1mPE4+ylk9skq6Ss5v17N7/uahE8odV9tUyx0vDUTXfzjtIp6EH+y59HFNHn6dO2wAnQyozGvcDiQazt8jgcEcEAsRiVjrfVYO+UxBLTV2keXkvE4rzdAJZXJZblwRuyC5yFmBlUNGY38bpVBZZYTB+g=
+ on:
+ repo: eidng8/vue-tree
+ branch: master
+ tags: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f8617ee
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Changelog
+
+\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
diff --git a/package-lock.json b/package-lock.json
index d81dbcd..e5d8544 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "g8-vue-tree",
- "version": "0.0.24",
+ "version": "0.0.26",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1560,6 +1560,16 @@
"integrity": "sha1-yz+fdBhp4gzOMw/765JxWQSDiC0=",
"dev": true
},
+ "@types/yauzl": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npm.taobao.org/@types/yauzl/download/@types/yauzl-2.9.1.tgz",
+ "integrity": "sha1-0Q9p+fUi7vPPmOMK+2hKHh7JI68=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@typescript-eslint/eslint-plugin": {
"version": "2.25.0",
"resolved": "https://registry.npm.taobao.org/@typescript-eslint/eslint-plugin/download/@typescript-eslint/eslint-plugin-2.25.0.tgz",
@@ -4016,16 +4026,16 @@
}
},
"chromedriver": {
- "version": "80.0.1",
- "resolved": "https://registry.npm.taobao.org/chromedriver/download/chromedriver-80.0.1.tgz?cache=0&sync_timestamp=1581477103156&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchromedriver%2Fdownload%2Fchromedriver-80.0.1.tgz",
- "integrity": "sha1-NcFkLi2GS56CYvKRAD5FWw5CKRc=",
+ "version": "81.0.0",
+ "resolved": "https://registry.npm.taobao.org/chromedriver/download/chromedriver-81.0.0.tgz?cache=0&sync_timestamp=1586531513275&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchromedriver%2Fdownload%2Fchromedriver-81.0.0.tgz",
+ "integrity": "sha1-aQujM67fK0xJM7ZZDDJC0+Xyjzw=",
"dev": true,
"requires": {
"@testim/chrome-version": "^1.0.7",
"axios": "^0.19.2",
"del": "^5.1.0",
- "extract-zip": "^1.6.7",
- "mkdirp": "^1.0.3",
+ "extract-zip": "^2.0.0",
+ "mkdirp": "^1.0.4",
"tcp-port-used": "^1.0.1"
},
"dependencies": {
@@ -4152,9 +4162,9 @@
}
},
"mkdirp": {
- "version": "1.0.3",
- "resolved": "https://registry.npm.taobao.org/mkdirp/download/mkdirp-1.0.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmkdirp%2Fdownload%2Fmkdirp-1.0.3.tgz",
- "integrity": "sha1-TPLjCtRZWd3epTrZfVGLbIIF4eo=",
+ "version": "1.0.4",
+ "resolved": "https://registry.npm.taobao.org/mkdirp/download/mkdirp-1.0.4.tgz",
+ "integrity": "sha1-PrXtYmInVteaXw4qIh3+utdcL34=",
"dev": true
},
"path-type": {
@@ -4165,7 +4175,7 @@
},
"rimraf": {
"version": "3.0.2",
- "resolved": "https://registry.npm.taobao.org/rimraf/download/rimraf-3.0.2.tgz?cache=0&sync_timestamp=1581229865753&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frimraf%2Fdownload%2Frimraf-3.0.2.tgz",
+ "resolved": "https://registry.npm.taobao.org/rimraf/download/rimraf-3.0.2.tgz",
"integrity": "sha1-8aVAK6YiCtUswSgrrBrjqkn9Bho=",
"dev": true,
"requires": {
@@ -7252,31 +7262,25 @@
}
},
"extract-zip": {
- "version": "1.7.0",
- "resolved": "https://registry.npm.taobao.org/extract-zip/download/extract-zip-1.7.0.tgz?cache=0&sync_timestamp=1585097922248&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fextract-zip%2Fdownload%2Fextract-zip-1.7.0.tgz",
- "integrity": "sha1-VWzDrp339FLEk6DPtRzDAneUCSc=",
+ "version": "2.0.0",
+ "resolved": "https://registry.npm.taobao.org/extract-zip/download/extract-zip-2.0.0.tgz",
+ "integrity": "sha1-9Ttx1E9P9aRSeiJZreAA+4swNJI=",
"dev": true,
"requires": {
- "concat-stream": "^1.6.2",
- "debug": "^2.6.9",
- "mkdirp": "^0.5.4",
+ "@types/yauzl": "^2.9.1",
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz",
- "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
+ "get-stream": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npm.taobao.org/get-stream/download/get-stream-5.1.0.tgz",
+ "integrity": "sha1-ASA83JJZf5uQkGfD5lbMH008Tck=",
"dev": true,
"requires": {
- "ms": "2.0.0"
+ "pump": "^3.0.0"
}
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
}
}
},
@@ -17300,9 +17304,9 @@
}
},
"webpack-bundle-analyzer": {
- "version": "3.6.1",
- "resolved": "https://registry.npm.taobao.org/webpack-bundle-analyzer/download/webpack-bundle-analyzer-3.6.1.tgz",
- "integrity": "sha1-vbY3wjBEJPL7/5qVDHvkKoOa5zs=",
+ "version": "3.7.0",
+ "resolved": "https://registry.npm.taobao.org/webpack-bundle-analyzer/download/webpack-bundle-analyzer-3.7.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack-bundle-analyzer%2Fdownload%2Fwebpack-bundle-analyzer-3.7.0.tgz",
+ "integrity": "sha1-hNpDTolEKJm4hNmtOORm0NsCpW8=",
"dev": true,
"requires": {
"acorn": "^7.1.1",
diff --git a/package.json b/package.json
index 5d38274..fc1f407 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "g8-vue-tree",
- "version": "0.0.25",
+ "version": "0.0.26",
"description": "A Vue.js tree view component with stable DOM tree.",
"repository": "git@github.com:eidng8/vue-tree.git",
"bugs": "git@github.com:eidng8/vue-tree/issues",
@@ -46,7 +46,7 @@
"@vue/eslint-config-standard": "^5.1.0",
"@vue/eslint-config-typescript": "^5.0.1",
"@vue/test-utils": "1.0.0-beta.31",
- "chromedriver": "80",
+ "chromedriver": "latest",
"core-js": "^3.6.4",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.10.1",
@@ -56,7 +56,7 @@
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
- "geckodriver": "^1.19.1",
+ "geckodriver": "latest",
"husky": "^4.2.5",
"prettier": "2.0.4",
"pretty-data": "^0.40.0",
@@ -68,7 +68,8 @@
"vue-class-component": "^7.2.2",
"vue-docgen-cli": "^4.16.0",
"vue-property-decorator": "^8.3.0",
- "vue-template-compiler": "^2.6.11"
+ "vue-template-compiler": "^2.6.11",
+ "webpack-bundle-analyzer": "^3.7.0"
},
"peerDependencies": {
"vue": "^2.6.11"
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100644
index 0000000..c12ae9a
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+# we have multiple deployment jobs
+# we just need to build the package only once
+[[ "${PACKAGE}" != '' ]] && exit 0
+
+# setup bundle-analyzer
+npm install --no-save @bundle-analyzer/webpack-plugin
+
+npm run build || exit 1
+
+# set's the environment variable to indicate the package has been built
+# this variable is also used by github deployment
+export PACKAGE=$(npm pack --silent)
+
diff --git a/scripts/make-release-note.js b/scripts/make-release-note.js
new file mode 100644
index 0000000..f508092
--- /dev/null
+++ b/scripts/make-release-note.js
@@ -0,0 +1,14 @@
+const fs = require('fs');
+const path = require('path');
+
+const log = path.resolve(path.join(__dirname, '../CHANGELOG.md'));
+const text = fs.readFileSync(log).toString('utf-8');
+const matches = /## \[.+?## \[/ms.exec(text);
+if (!matches || !matches.length || !matches[0] || !matches[0].length) return;
+
+const match = matches[0];
+const note = path.resolve(path.join(__dirname, '../RELEASE.md'));
+fs.writeFileSync(note, match.substr(0, match.length - 4), {
+ encoding: 'utf-8',
+ flag: 'w+',
+});
diff --git a/scripts/release.bat b/scripts/release.bat
new file mode 100644
index 0000000..ce19694
--- /dev/null
+++ b/scripts/release.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+SETLOCAL EnableDelayedExpansion
+
+cd /d "%~dp0"
+cd ..
+
+for /f %%i in ('git rev-parse --abbrev-ref HEAD') do set BRANCH=%%i
+if not "%BRANCH%"=="master" (
+ echo You are not on master branch.
+ goto ERR
+)
+
+git reset --hard --quiet
+git clean -fdx --quiet
+git pull
+bash.exe -lc github_changelog_generator
+node scripts\make-release-note.js
+git add CHANGELOG.md
+git add RELEASE.md
+git commit -m "update changelog [ci skip]"
+git push
+
+set RELEASE=%1
+if "%RELEASE%"=="" set RELEASE=patch
+npm version "%RELEASE%"
+git push --follow-tags
+
+goto END
+
+
+:ERR
+pause
+exit 1
+
+:END
diff --git a/scripts/test.sh b/scripts/test.sh
new file mode 100644
index 0000000..a91ded5
--- /dev/null
+++ b/scripts/test.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+# setup code climate
+setup_cc() {
+ # use only linux for analysis
+ [[ "${TRAVIS_OS_NAME}" != 'linux' ]] && return 0
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
+ chmod +x ./cc-test-reporter
+ ./cc-test-reporter before-build
+}
+
+# submit to code climate
+submit_cc() {
+ # use only linux for analysis
+ [[ "${TRAVIS_OS_NAME}" != 'linux' ]] && return 0
+ ./cc-test-reporter after-build --exit-code "${RES}"
+ # cc-test-reporter may report error, just ignore it
+ return 0
+}
+
+# setup coveralls
+setup_ca() {
+ # use only linux for analysis
+ [[ "${TRAVIS_OS_NAME}" != 'linux' ]] && return 0
+ npm install --no-save coveralls
+}
+
+# submit to coveralls
+submit_ca() {
+ # use only linux for analysis
+ [[ "${TRAVIS_OS_NAME}" != 'linux' ]] && return 0
+ cat ./coverage/lcov.info | coveralls
+}
+
+RES=0
+# unit test
+setup_ca
+setup_cc
+npm run test:unit -- --coverage
+RES=$?
+submit_cc
+submit_ca
+[[ ${RES} != '0' ]] && exit 1
+
+# e2e test
+npm run test:e2e
diff --git a/src/App.vue b/src/App.vue
index ae450a1..541905b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -11,13 +11,13 @@
@@ -52,18 +52,18 @@ export default class App extends Vue {
tagDblClicked = '';
populate() {
- const total = 1000;
+ const total = 100;
this.item = {
key: 'root',
name: 'root name',
- tags: [{ key: 'root tag', label: 'root label' }],
+ tags: [{ label: 'root label' }],
children: [],
};
for (let i = 1; i < total; i++) {
const child: G8TreeItem = {
key: `key-${i}`,
name: `name ${i}`,
- tags: [{ key: `tag-${i}`, label: `tag ${i}` }],
+ tags: [{ label: `tag ${i}` }],
children: [],
};
for (let j = 1; j < total; j++) {
@@ -71,7 +71,7 @@ export default class App extends Vue {
child.children!.push({
key: `key-${i}.${j}`,
name: `name ${i}.${j}`,
- tags: [{ key: `tag-${i}.${j}`, label: `tag ${i}.${j}` }],
+ tags: [{ label: `tag ${i}.${j}` }],
});
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
diff --git a/src/components/G8TreeView.vue b/src/components/G8TreeView.vue
index c6b6d6b..a07e2b5 100644
--- a/src/components/G8TreeView.vue
+++ b/src/components/G8TreeView.vue
@@ -14,30 +14,38 @@
>
- {{ item.name }}
+ {{ item[itemLabel] }}
{{ tag[tagLabel] }}
import { Component, Prop, Vue } from 'vue-property-decorator';
-import { G8StateChangeEvent, G8TreeItem } from './types';
+import { G8TreeItem, G8TreeItemTag } from './types';
/**
* A tree view component with stable DOM structure. Stable means its structure
@@ -64,15 +72,46 @@ import { G8StateChangeEvent, G8TreeItem } from './types';
@Component({ name: 'g8-tree-view' })
export default class G8TreeView extends Vue {
/**
- * The tree data to be rendered.
+ * Key of the field in `item` that holds node label.
*/
- @Prop() item!: G8TreeItem;
+ @Prop({ default: 'name' }) itemLabel!: string;
+
+ /**
+ * Key of the field in `item` that holds tags array.
+ */
+ @Prop({ default: 'tags' }) tagsKey!: string;
+
+ /**
+ * Key of the field in `item` that holds child nodes array.
+ */
+ @Prop({ default: 'children' }) childrenKey!: string;
+
+ /**
+ * Key of the field in tags list of `item` that holds tag label.
+ */
+ @Prop({ default: 'label' }) tagLabel!: string;
+
+ /**
+ * Key of the field in tags list of `item` that holds tag tooltip.
+ */
+ @Prop({ default: 'hint' }) tagHint!: string;
/**
* Whether to add a checkbox before each item, allowing multiple nodes to
* be checked.
*/
- @Prop() checker!: boolean;
+ @Prop({ default: false }) checker!: boolean;
+
+ /**
+ * The tree data to be rendered. Please note that data passed ***may*** be
+ * mutated by this component to reflect various state of tree nodes. Mutated
+ * fields include:
+ *
+ * * checked
+ * * intermediate
+ * * rendered
+ */
+ @Prop() item!: G8TreeItem;
/**
* Whether the node is expanded.
@@ -80,21 +119,33 @@ export default class G8TreeView extends Vue {
expanded = false;
/**
- * Whether the node is checked.
+ * Whether the node is checked. This must be a member field in order for
+ * binding to work.
*/
checked = false;
/**
* Intermediate check box state. Active while some of the children were
- * checked, but not all were checked.
+ * checked, but not all were checked. This must be a member field in order for
+ * binding to work.
*/
- ints = false;
+ intermediate = false;
/**
* Whether the current node has any child.
*/
- get hasChild() {
- return this.item.children && this.item.children.length;
+ get hasChild(): boolean {
+ const children = this.item[this.childrenKey] as G8TreeItem[] | null;
+ return children != null && children.length > 0;
+ }
+
+ // noinspection JSUnusedGlobalSymbols
+ /**
+ * Vue life cycle hook {@link https://vuejs.org/v2/api/#created}.
+ */
+ created() {
+ this.checked = true == this.item.checked;
+ this.intermediate = true == this.item.intermediate;
}
/**
@@ -104,13 +155,23 @@ export default class G8TreeView extends Vue {
* @param state
*/
setState(state: boolean) {
- this.item.checked = this.checked = state;
- this.$children.forEach(c => (c as G8TreeView).setState(state));
+ this.checked = this.item.checked = state;
+ if (this.$children && this.$children.length) {
+ // descend to all descendant sub-components and update their states,
+ // also triggers their `state-changed` event.
+ this.$children.forEach(c => {
+ (c as G8TreeView).setState(state);
+ });
+ } else if (this.item.children && this.item.children.length) {
+ // descend to all descendant's data and update their states,
+ // this is necessary because sub-components have not been created yet.
+ this.item.children.forEach(c => (c.checked = state));
+ }
/**
* Checkbox state of the node has changed.
* @param {G8StateChangeEvent} state
*/
- this.$emit('state-changed', { node: this.item.key, state: this.checked });
+ this.$emit('state-changed', this.item);
}
/**
@@ -126,7 +187,7 @@ export default class G8TreeView extends Vue {
* A tree node has been clicked.
* @param {G8ClickEvent} key the item's `key`
*/
- this.$emit('click', this.item.key);
+ this.$emit('click', this.item);
}
/**
@@ -137,7 +198,7 @@ export default class G8TreeView extends Vue {
* A tree node has been double clicked.
* @param {G8ClickEvent} key the item's `key`
*/
- this.$emit('dblclick', this.item.key);
+ this.$emit('dblclick', this.item);
}
/**
@@ -145,12 +206,12 @@ export default class G8TreeView extends Vue {
* @param tag
* @param index
*/
- tagClicked(tag: number | string, index: number) {
+ tagClicked(tag: G8TreeItemTag, index: number) {
/**
* A tree node tag has been clicked.
* @param {G8TagClickEvent} key
*/
- this.$emit('tag-clicked', { node: this.item.key, tag, index });
+ this.$emit('tag-clicked', { node: this.item, tag, index });
}
/**
@@ -159,28 +220,31 @@ export default class G8TreeView extends Vue {
* @param tag
* @param index
*/
- tagDblClicked(tag: number | string, index: number) {
+ tagDblClicked(tag: G8TreeItemTag, index: number) {
/**
* A tree node tag has been double clicked.
* @param {G8TagClickEvent} key
*/
- this.$emit('tag-dbl-clicked', { node: this.item.key, tag, index });
+ this.$emit('tag-dbl-clicked', {
+ node: this.item,
+ tag,
+ index,
+ });
}
/**
* Handles `state-changed` events emitted by children, updating the check
* state of current node according. This method also bubbles up the
* `state-changed` event.
- * @param evt
+ * @param node
*/
- childrenStateChanged(evt: G8StateChangeEvent) {
+ childrenStateChanged(node: G8TreeItem) {
let checked = 0;
const children: G8TreeItem[] = this.item.children as G8TreeItem[];
for (const child of children) {
if (child.intermediate) {
- this.item.intermediate = true;
- this.ints = true;
- this.$emit('state-changed', evt);
+ this.intermediate = this.item.intermediate = true;
+ this.$emit('state-changed', node);
return;
}
if (child.checked) {
@@ -188,20 +252,20 @@ export default class G8TreeView extends Vue {
}
}
if (children.length == checked) {
- this.ints = false;
- this.checked = true;
+ this.intermediate = false;
this.item.intermediate = false;
+ this.checked = true;
this.item.checked = true;
} else if (0 == checked) {
- this.ints = false;
- this.checked = false;
+ this.intermediate = false;
this.item.intermediate = false;
+ this.checked = false;
this.item.checked = false;
} else {
- this.ints = true;
+ this.intermediate = true;
this.item.intermediate = true;
}
- this.$emit('state-changed', evt);
+ this.$emit('state-changed', node);
}
}
diff --git a/src/components/types.ts b/src/components/types.ts
index 8dc74d8..46d87c0 100644
--- a/src/components/types.ts
+++ b/src/components/types.ts
@@ -4,43 +4,88 @@
* Author: eidng8
*/
+/**
+ * Tree item data
+ */
export interface G8TreeItem {
- key: number | string;
+ /**
+ * Just to allow accessing data via index syntax.
+ */
+ [key: string]: unknown;
+ /**
+ * Item name, serves as label, will be rendered as node label.
+ */
name: string;
+ /**
+ * Whether current node is checked.
+ */
checked?: boolean;
/**
* Intermediate check box state. Active while some of the children were
- * checked, but not all were checked.
+ * checked, but not all.
*/
intermediate?: boolean;
+ /**
+ * Whether the immediate sub-tree of this node has been rendered.
+ */
rendered?: boolean;
+ /**
+ * List of tags.
+ */
tags?: G8TreeItemTag[];
+ /**
+ * List of child nodes.
+ */
children?: G8TreeItem[];
}
+/**
+ * Node tag data
+ */
export interface G8TreeItemTag {
- key: number | string;
-
+ /**
+ * Tag label.
+ */
label: string;
+ /**
+ * Tag tooltip. Visible when mouse hovers on the tag.
+ */
hint?: string;
}
+/**
+ * Mouse click event of tree node
+ */
export type G8ClickEvent = string | number;
+/**
+ * Mouse click event of tag
+ */
export type G8TagClickEvent = {
+ /**
+ * Key of the node that triggered the event.
+ */
node: number | string;
+
+ /**
+ * Key of the tag that triggered the event.
+ */
tag: number | string;
+
+ /**
+ * Numeric index of the item in the tag list.
+ */
index: number;
};
-export type G8StateChangeEvent = {
- node: number | string;
- state: boolean;
-};
+/**
+ * Tree node state changed event
+ */
+export type G8StateChangeEvent = G8TreeItem;
diff --git a/tests/e2e/specs/test.js b/tests/e2e/specs/test.js
index 6f364e8..dc67f29 100644
--- a/tests/e2e/specs/test.js
+++ b/tests/e2e/specs/test.js
@@ -23,55 +23,79 @@ module.exports = {
'Click the button above to populate me.',
)
+ // this button generates test data
.click('button')
.assert // root node's text has changed
- .containsText(
- '.g8-tree__node_label_text',
- 'root name',
- )
+ .containsText('.g8-tree__node_label_text', 'root name')
+
+ // root node's check state propagates to descendants
+ .click('.g8-tree__checker')
+ .assert.cssClassPresent('.g8-tree__checker', 'g8-tree__checked')
+
+ // clicking the toggle expands the root
.click(rootLabel)
- .assert // clicking the toggle expands the root
- .cssClassPresent(root, 'g8-tree__node_expended')
+ .assert.cssClassPresent(root, 'g8-tree__node_expended')
.assert // click event is fired
- .value('#itemClicked', 'root')
+ .value('#itemClicked', 'root name')
+ .assert // check state is propagated from root node
+ .cssClassPresent(`${label2}>.g8-tree__checker`, 'g8-tree__checked')
+ // clicking the toggle expands the first branch
.click(label2)
- .assert // clicking the toggle expands the first branch
- .elementPresent('.g8-tree__branch .g8-tree__node_expended')
+ .assert.elementPresent('.g8-tree__branch .g8-tree__node_expended')
.assert // click event is fired
- .value('#itemClicked', 'key-2')
+ .value('#itemClicked', 'name 2')
+
+ // double clicking won't collapse the node
.moveToElement(rootLabel, 1, 1)
.doubleClick()
- .assert // double clicking won't collapse the node
- .cssClassPresent(root, 'g8-tree__node_expended')
+ .assert.cssClassPresent(root, 'g8-tree__node_expended')
.assert // double click event is fired
- .value('#itemDblClicked', 'root')
+ .value('#itemDblClicked', 'root name')
+
+ // root node's intermediate state changes to true, if child is unchecked
+ .click(`${label2}>.g8-tree__checker`)
+ .assert.cssClassPresent(
+ `${rootLabel}>.g8-tree__checker`,
+ 'g8-tree__checked_some',
+ )
+ // click event is fired on leaf node
.click(`${leaf} .g8-tree__node_label`)
- .assert // click event is fired
- .value('#itemClicked', 'key-2.1')
+ .assert.value('#itemClicked', 'name 2.1')
+
+ // double click event is fired on leaf node
.moveToElement(`${leaf} .g8-tree__node_label`, 1, 1)
.doubleClick()
.assert // click event is fired
- .value('#itemDblClicked', 'key-2.1')
+ .value('#itemDblClicked', 'name 2.1')
+
+ // click event is fired on leaf tag
.click(`${leaf} .g8-tree__node_tag:last-child`)
.assert // click event is fired
- .value('#tagClicked', 'key-2.1,tag-2.1,0')
+ .value('#tagClicked', 'name 2.1,tag 2.1,0')
+
+ // double click event is fired on leaf tag
.moveToElement(`${leaf} .g8-tree__node_tag`, 1, 1)
.doubleClick()
.assert // click event is fired
- .value('#tagDblClicked', 'key-2.1,tag-2.1,0')
+ .value('#tagDblClicked', 'name 2.1,tag 2.1,0')
+ // clicking the toggle collapses the node
.click(rootLabel)
- .assert.not // clicking the toggle collapses the node
- .cssClassPresent(root, 'g8-tree__node_expended')
+ .assert.not.cssClassPresent(root, 'g8-tree__node_expended')
+
+ // click event is fired on root tag
.click(`${rootLabel} .g8-tree__node_tag`)
.assert // tag-click event is fired
- .value('#tagClicked', 'root,root tag,0')
+ .value('#tagClicked', 'root name,root label,0')
+
+ // double click event is fired on root tag
.moveToElement(`${rootLabel} .g8-tree__node_tag`, 1, 1)
.doubleClick()
.assert // tag-dbl-click event is fired
- .value('#tagDblClicked', 'root,root tag,0')
+ .value('#tagDblClicked', 'root name,root label,0')
+
.end();
},
};
diff --git a/tests/unit/G8TreeView-events.spec.ts b/tests/unit/G8TreeView-events.spec.ts
new file mode 100644
index 0000000..8977974
--- /dev/null
+++ b/tests/unit/G8TreeView-events.spec.ts
@@ -0,0 +1,89 @@
+/*
+ * GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html
+ *
+ * Author: eidng8
+ */
+
+import { mount, Wrapper } from '@vue/test-utils';
+import { G8TreeItem, G8TreeView } from '../../src';
+
+const tree = {
+ item: {
+ name: 'root name',
+ tags: [{ label: 'root label' }],
+ children: [
+ {
+ name: 'item 1',
+ tags: [{ label: 'tag1.1' }, { label: 'tag1.2' }],
+ },
+ {
+ name: 'item 2',
+ tags: [{ label: 'tag1.1' }],
+ children: [
+ {
+ name: 'item 2.1',
+ tags: [{ label: 'tag2.1.1' }, { label: 'tag2.1.2' }],
+ },
+ {
+ name: 'item 2.2',
+ },
+ ],
+ },
+ ],
+ } as G8TreeItem,
+};
+
+let wrapper: Wrapper;
+let propsData: { item: G8TreeItem };
+
+describe('Tree View events', () => {
+ beforeEach(() => {
+ propsData = JSON.parse(JSON.stringify(tree));
+ wrapper = mount(G8TreeView, { propsData });
+ });
+
+ it('emits click events', async () => {
+ expect.assertions(3);
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick();
+ const emitted = wrapper.emitted('click');
+ expect(emitted).toBeInstanceOf(Array);
+ expect(emitted.length).toBe(1);
+ expect(emitted[0][0]).toStrictEqual(propsData.item);
+ });
+
+ it('emits double click events', async () => {
+ expect.assertions(3);
+ wrapper.find('.g8-tree__node_label').trigger('dblclick');
+ await wrapper.vm.$nextTick();
+ const emitted = wrapper.emitted('dblclick');
+ expect(emitted).toBeInstanceOf(Array);
+ expect(emitted.length).toBe(1);
+ expect(emitted[0][0]).toStrictEqual(propsData.item);
+ });
+
+ it('emits tag click events', async () => {
+ expect.assertions(5);
+ const tags = wrapper.findAll('.g8-tree__node_tag');
+ tags.trigger('click');
+ await wrapper.vm.$nextTick();
+ const emitted = wrapper.emitted('tag-clicked');
+ expect(emitted).toBeInstanceOf(Array);
+ expect(emitted.length).toBe(1);
+ expect(emitted[0][0].node).toStrictEqual(propsData.item);
+ expect(emitted[0][0].tag).toStrictEqual(propsData.item.tags![0]);
+ expect(emitted[0][0].index).toStrictEqual(0);
+ });
+
+ it('emits tag double click events', async () => {
+ expect.assertions(5);
+ wrapper.findAll('.g8-tree__node_tag').trigger('dblclick');
+ await wrapper.vm.$nextTick();
+ const emitted = wrapper.emitted('tag-dbl-clicked');
+ expect(emitted).toBeInstanceOf(Array);
+ expect(emitted.length).toBe(1);
+ expect(emitted[0][0].node).toStrictEqual(propsData.item);
+ expect(emitted[0][0].tag).toStrictEqual(propsData.item.tags![0]);
+ expect(emitted[0][0].index).toStrictEqual(0);
+ });
+});
diff --git a/tests/unit/G8TreeView-interactions.spec.ts b/tests/unit/G8TreeView-interactions.spec.ts
new file mode 100644
index 0000000..c2d2c3b
--- /dev/null
+++ b/tests/unit/G8TreeView-interactions.spec.ts
@@ -0,0 +1,156 @@
+/*
+ * GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html
+ *
+ * Author: eidng8
+ */
+
+import { mount, Wrapper } from '@vue/test-utils';
+import { G8TreeItem, G8TreeView } from '../../src';
+
+const tree = {
+ checker: true,
+ item: {
+ name: 'root name',
+ tags: [{ label: 'root label' }],
+ children: [
+ {
+ name: 'item 1',
+ tags: [{ label: 'tag1.1' }, { label: 'tag1.2' }],
+ },
+ {
+ name: 'item 2',
+ tags: [{ label: 'tag2.1' }],
+ children: [
+ {
+ name: 'item 2.1',
+ tags: [{ label: 'tag2.1.1' }, { label: 'tag2.1.2' }],
+ },
+ {
+ name: 'item 2.2',
+ },
+ ],
+ },
+ ],
+ } as G8TreeItem,
+};
+
+let wrapper: Wrapper;
+
+describe('Tree View interactions', () => {
+ beforeEach(
+ () =>
+ (wrapper = mount(G8TreeView, {
+ propsData: JSON.parse(JSON.stringify(tree)),
+ })),
+ );
+
+ it('expends and renders branches on click', async () => {
+ expect.assertions(10);
+ // initially no branch were expanded nor rendered
+ expect(wrapper.find('.g8-tree__node_expended').exists()).toBeFalsy();
+ expect(wrapper.findAll('.g8-tree__branch').length).toBe(0);
+ expect(wrapper.findAll('.g8-tree__node_label_text').length).toBe(1);
+ // click the first branch to expand it and render the sub-tree
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick(() => {
+ expect(wrapper.findAll('.g8-tree__node_expended').length).toBe(1);
+ const labels = wrapper.findAll('.g8-tree__node_label_text');
+ expect(labels.length).toBe(3);
+ expect(labels.at(0).text()).toBe('root name');
+ expect(labels.at(1).text()).toBe('item 1');
+ expect(labels.at(2).text()).toBe('item 2');
+ });
+ // click the second branch to expand it and render the sub-tree
+ wrapper.findAll('.g8-tree__branch .g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick(() => {
+ expect(wrapper.findAll('.g8-tree__node_expended').length).toBe(2);
+ expect(wrapper.findAll('.g8-tree__node_label_text').length).toBe(5);
+ });
+ });
+
+ it('collapses branch on click', async () => {
+ expect.assertions(1);
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ // click the top branch to collapse
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.g8-tree__node_expended').exists()).toBeFalsy();
+ });
+
+ it('toggles check state', async () => {
+ expect.assertions(1);
+ // check root node = checks all
+ wrapper.find('.g8-tree__checker').trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.findAll('.g8-tree__checked').length).toBe(1);
+ });
+
+ it('propagates checked state', async () => {
+ const checked = () => wrapper.findAll('.g8-tree__checker.g8-tree__checked');
+ expect.assertions(2);
+ wrapper.find('.g8-tree__checker').trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(checked().length).toBe(1);
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(checked().length).toBe(3);
+ });
+
+ it('sets parent intermediate state if any child is checked', async () => {
+ expect.assertions(1);
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick();
+ // check 2nd branch = set root to intermediate state
+ const checkers = wrapper.findAll('.g8-tree__checker');
+ checkers.at(1).trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.g8-tree__checker').classes()).toContain(
+ 'g8-tree__checked_some',
+ );
+ });
+
+ it('sets parent intermediate state if any child is unchecked', async () => {
+ expect.assertions(2);
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick();
+ // check root = check all descendant nodes
+ wrapper.find('.g8-tree__checker').trigger('click');
+ // uncheck 2nd branch = set root to intermediate state
+ const checkers = wrapper.findAll('.g8-tree__checker');
+ checkers.at(1).trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('.g8-tree__checker').classes()).toContain(
+ 'g8-tree__checked_some',
+ );
+ expect(wrapper.findAll('.g8-tree__checked').length).toBe(2);
+ });
+
+ it('toggles intermediate check state', async () => {
+ expect.assertions(6);
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick();
+ wrapper
+ .find('.g8-tree__branch .g8-tree__node:nth-child(2) .g8-tree__node_label')
+ .trigger('click');
+ await wrapper.vm.$nextTick();
+ const checkers = wrapper.findAll('.g8-tree__checker');
+ // check last node = set root & 2nd branch to intermediate
+ checkers.at(checkers.length - 1).trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.findAll('.g8-tree__checked_some').length).toBe(2);
+ // check root node = check all
+ checkers.at(0).trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.findAll('.g8-tree__checked').length).toBe(5);
+ // uncheck last node = set root & 2nd branch to intermediate
+ checkers.at(checkers.length - 1).trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.findAll('.g8-tree__checked_some').length).toBe(2);
+ expect(wrapper.findAll('.g8-tree__checked').length).toBe(4);
+ // uncheck 2nd last node = set root & 2nd branch to intermediate
+ checkers.at(checkers.length - 2).trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(wrapper.findAll('.g8-tree__checked_some').length).toBe(1);
+ expect(wrapper.findAll('.g8-tree__checked').length).toBe(2);
+ });
+});
diff --git a/tests/unit/G8TreeView-props.spec.ts b/tests/unit/G8TreeView-props.spec.ts
new file mode 100644
index 0000000..7717f9d
--- /dev/null
+++ b/tests/unit/G8TreeView-props.spec.ts
@@ -0,0 +1,94 @@
+/*
+ * GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html
+ *
+ * Author: eidng8
+ */
+
+import { mount, shallowMount } from '@vue/test-utils';
+import { G8TreeView } from '../../src';
+
+describe('Tree View props', () => {
+ it('renders using default props', () => {
+ expect.assertions(3);
+ const propsData = {
+ item: {
+ name: 'name',
+ tags: [{ label: 'tag1' }],
+ },
+ };
+ const wrapper = shallowMount(G8TreeView, { propsData });
+ expect(wrapper.props('item')).toEqual({
+ name: 'name',
+ tags: [{ label: 'tag1' }],
+ });
+ expect(wrapper.find('.g8-tree__node_label_text').text()).toBe('name');
+ expect(wrapper.find('.g8-tree__node_tag').text()).toBe('tag1');
+ });
+
+ it('defaults to have no checkbox', () => {
+ expect.assertions(1);
+ const propsData = { item: { name: 'signal' } };
+ const wrapper = shallowMount(G8TreeView, { propsData });
+ expect(wrapper.find('.g8-tree__checker').exists()).toBeFalsy();
+ });
+
+ it('turns on checkbox rendering', () => {
+ expect.assertions(1);
+ let propsData = {
+ checker: true,
+ item: { name: 'name' },
+ };
+ let wrapper = shallowMount(G8TreeView, { propsData });
+ expect(wrapper.find('.g8-tree__checker').exists()).toBeTruthy();
+ });
+
+ it('defaults to render only top level', () => {
+ expect.assertions(3);
+ const propsData = {
+ item: {
+ name: 'item 1',
+ tags: [{ label: 'tag 1' }],
+ children: [{ name: 'item 1.1', tags: [{ label: 'tag 2' }] }],
+ },
+ };
+ const wrapper = shallowMount(G8TreeView, { propsData });
+ expect(wrapper.findAll('.g8-tree__node').length).toBe(1);
+ expect(wrapper.findAll('.g8-tree__node_tag').length).toBe(1);
+ expect(wrapper.find('.g8-tree__branch').exists()).toBeFalsy();
+ });
+
+ it('renders using custom props', async () => {
+ expect.assertions(4);
+ const propsData = {
+ itemLabel: 'text',
+ tagsKey: 'badges',
+ childrenKey: 'sub',
+ tagLabel: 'text',
+ tagHint: 'tip',
+ item: {
+ text: 'node1',
+ badges: [{ text: 'tag1', tip: 'tip1' }],
+ sub: [
+ {
+ text: 'node1.1',
+ badges: [{ text: 'tag1.1' }],
+ sub: [{ text: 'node1.1.1' }],
+ },
+ {
+ text: 'node1.2',
+ badges: [{ text: 'tag1.2' }],
+ },
+ ],
+ },
+ };
+ const wrapper = mount(G8TreeView, { propsData });
+ expect(wrapper.props('item')).toEqual(propsData.item);
+ expect(wrapper.find('.g8-tree__node_label_text').text()).toBe('node1');
+ expect(wrapper.find('.g8-tree__node_tag').attributes('title')).toBe('tip1');
+ wrapper.find('.g8-tree__node_label').trigger('click');
+ await wrapper.vm.$nextTick();
+ expect(
+ wrapper.find('.g8-tree__branch .g8-tree__node_label_text').text(),
+ ).toBe('node1.1');
+ });
+});
diff --git a/tests/unit/G8TreeView.spec.ts b/tests/unit/G8TreeView.spec.ts
deleted file mode 100644
index a082daf..0000000
--- a/tests/unit/G8TreeView.spec.ts
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html
- *
- * Author: eidng8
- */
-
-import {mount, shallowMount} from '@vue/test-utils';
-import {G8TreeView} from '../../src';
-
-const tree = {
- item: {
- key: 'root',
- name: 'root name',
- tags: [{key: 'root tag', label: 'root label'}],
- children: [
- {
- key: 'item-1',
- name: 'item 1',
- tags: [
- {key: 1, label: 'tag1.1'},
- {key: 1, label: 'tag1.2'},
- ],
- },
- {
- key: 'item-2',
- name: 'item 2',
- tags: [{key: 2, label: 'tag1.1'}],
- children: [
- {
- key: 'item-2.1',
- name: 'item 2.1',
- tags: [
- {key: '2.1.1', label: 'tag2.1.1'},
- {key: '2.1.2', label: 'tag2.1.2'},
- ],
- },
- {
- key: 'item-2.2',
- name: 'item 2.2',
- },
- ],
- },
- ],
- },
-};
-
-describe('Tree View', () => {
- it('renders different props', () => {
- expect.assertions(6);
- let propsData = {
- item: {
- key: 'Hello',
- name: 'name',
- tags: [{key: 1, label: 'tag1'}],
- },
- };
- let wrapper = shallowMount(G8TreeView, {propsData});
- expect(wrapper.props('item'))
- .toEqual({key: 'Hello', name: 'name', tags: [{key: 1, label: 'tag1'}]});
- expect(wrapper.find('.g8-tree__node_label_text').text())
- .toBe('name');
- expect(wrapper.find('.g8-tree__node_tag').text())
- .toBe('tag1');
-
- propsData = {
- item: {
- key: 'wow',
- name: 'signal',
- tags: [{key: 2, label: 'tag2'}],
- },
- };
- wrapper = shallowMount(G8TreeView, {propsData});
- expect(wrapper.props('item'))
- .toEqual({key: 'wow', name: 'signal', tags: [{key: 2, label: 'tag2'}]});
- expect(wrapper.find('.g8-tree__node_label_text').text())
- .toBe('signal');
- expect(wrapper.find('.g8-tree__node_tag').text())
- .toBe('tag2');
- });
-
- it('expends/collapses and renders branches on click', async () => {
- expect.assertions(11);
- // initially no branch were expanded nor rendered
- const wrapper = mount(G8TreeView, {propsData: tree});
- expect(wrapper.find('.g8-tree__node_expended').exists()).toBeFalsy();
- expect(wrapper.findAll('.g8-tree__branch').length).toBe(0);
- expect(wrapper.findAll('.g8-tree__node_label_text').length).toBe(1);
- // click the first branch to expand it and render the sub-tree
- wrapper.find('.g8-tree__node_label').trigger('click');
- await wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('.g8-tree__node_expended').length).toBe(1);
- const labels = wrapper.findAll('.g8-tree__node_label_text');
- expect(labels.length).toBe(3);
- expect(labels.at(0).text()).toBe('root name');
- expect(labels.at(1).text()).toBe('item 1');
- });
- // click the second branch to expand it and render the sub-tree
- wrapper.findAll('.g8-tree__branch .g8-tree__node_label').trigger('click');
- await wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('.g8-tree__node_expended').length).toBe(2);
- expect(wrapper.findAll('.g8-tree__node_label_text').length).toBe(5);
- });
- // click branches to collapse them all
- wrapper.findAll('.g8-tree__node_label').trigger('click');
- await wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.g8-tree__node_expended').exists()).toBeFalsy();
- expect(wrapper.findAll('.g8-tree__node_label_text').length).toBe(5);
- });
- });
-
- it('emits click events', async () => {
- expect.assertions(1);
- const wrapper = mount(G8TreeView, {propsData: tree});
- wrapper.find('.g8-tree__node_label').trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.emitted('click')).toEqual([['root']]);
- });
-
- it('emits double click events', async () => {
- expect.assertions(1);
- const wrapper = mount(G8TreeView, {propsData: tree});
- wrapper.find('.g8-tree__node_label').trigger('dblclick');
- await wrapper.vm.$nextTick();
- expect(wrapper.emitted('dblclick')).toEqual([['root']]);
- });
-
- it('emits tag click events', async () => {
- expect.assertions(1);
- const wrapper = mount(G8TreeView, {propsData: tree});
- wrapper.findAll('.g8-tree__node_tag').trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.emitted('tag-clicked')).toEqual([
- [{node: 'root', tag: 'root tag', index: 0}],
- [{node: 'item-1', tag: 1, index: 0}],
- [{node: 'item-1', tag: 1, index: 1}],
- [{node: 'item-2', tag: 2, index: 0}],
- [{node: 'item-2.1', tag: '2.1.1', index: 0}],
- [{node: 'item-2.1', tag: '2.1.2', index: 1}],
- ]);
- });
-
- it('emits tag double click events', async () => {
- expect.assertions(1);
- const wrapper = mount(G8TreeView, {propsData: tree});
- wrapper.findAll('.g8-tree__node_tag').trigger('dblclick');
- await wrapper.vm.$nextTick();
- expect(wrapper.emitted('tag-dbl-clicked')).toEqual([
- [{node: 'root', tag: 'root tag', index: 0}],
- [{node: 'item-1', tag: 1, index: 0}],
- [{node: 'item-1', tag: 1, index: 1}],
- [{node: 'item-2', tag: 2, index: 0}],
- [{node: 'item-2.1', tag: '2.1.1', index: 0}],
- [{node: 'item-2.1', tag: '2.1.2', index: 1}],
- ]);
- });
-
- it('toggles check state', async () => {
- expect.assertions(1);
- const data = JSON.parse(JSON.stringify(tree));
- data.checker = true;
- const wrapper = mount(G8TreeView, {propsData: data});
- // check root node = checks all
- wrapper.find('.g8-tree__checker').trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.findAll('.g8-tree__checked').length).toBe(5);
- });
-
- it('toggles parent intermediate check state 1', async () => {
- expect.assertions(1);
- const data = JSON.parse(JSON.stringify(tree));
- data.checker = true;
- const wrapper = mount(G8TreeView, {propsData: data});
- // check 2nd branch = set root to intermediate state
- const checkers = wrapper.findAll('.g8-tree__checker');
- checkers.at(1).trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.find('.g8-tree__checker').classes())
- .toContain('g8-tree__checked_some');
- });
-
- it('toggles parent intermediate check state 2', async () => {
- expect.assertions(2);
- const data = JSON.parse(JSON.stringify(tree));
- data.checker = true;
- const wrapper = mount(G8TreeView, {propsData: data});
- wrapper.find('.g8-tree__checker').trigger('click');
- // uncheck 2nd branch = set root to intermediate state
- const checkers = wrapper.findAll('.g8-tree__checker');
- checkers.at(1).trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.find('.g8-tree__checker').classes())
- .toContain('g8-tree__checked_some');
- expect(wrapper.findAll('.g8-tree__checked').length).toBe(4);
- });
-
- it('toggles intermediate check state', async () => {
- expect.assertions(6);
- const data = JSON.parse(JSON.stringify(tree));
- data.checker = true;
- const wrapper = mount(G8TreeView, {propsData: data});
- const checkers = wrapper.findAll('.g8-tree__checker');
- // check last node = set root & 2nd branch to intermediate
- checkers.at(checkers.length - 1).trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.findAll('.g8-tree__checked_some').length).toBe(2);
- // check root node = check all
- checkers.at(0).trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.findAll('.g8-tree__checked').length).toBe(5);
- // uncheck last node = set root & 2nd branch to intermediate
- checkers.at(checkers.length - 1).trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.findAll('.g8-tree__checked_some').length).toBe(2);
- expect(wrapper.findAll('.g8-tree__checked').length).toBe(4);
- // uncheck 2nd last node = set root & 2nd branch to intermediate
- checkers.at(checkers.length - 2).trigger('click');
- await wrapper.vm.$nextTick();
- expect(wrapper.findAll('.g8-tree__checked_some').length).toBe(1);
- expect(wrapper.findAll('.g8-tree__checked').length).toBe(2);
- });
-});
diff --git a/vue.config.js b/vue.config.js
index 507c8b3..cfe86f4 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -6,15 +6,20 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const BundleAnalyzerPlugin = require('@bundle-analyzer/webpack-plugin');
+const WBA = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
configureWebpack: config => {
- if ('production' == process.env.NODE_ENV) {
+ if ('production' === process.env.NODE_ENV) {
config.devtool = undefined;
+ config.plugins.push(
+ new WBA({ analyzerMode: 'static', openAnalyzer: false }),
+ );
}
if (process.env.BUNDLE_ANALYZER_TOKEN) {
- config.plugins.push(new BundleAnalyzerPlugin(
- {token: process.env.BUNDLE_ANALYZER_TOKEN}));
+ config.plugins.push(
+ new BundleAnalyzerPlugin({ token: process.env.BUNDLE_ANALYZER_TOKEN }),
+ );
}
},
};