Compare commits

...

31 Commits

Author SHA1 Message Date
Binbin Sun
d8a5da1e0e feat: 🎸 extracted css file
Closes: #55
2020-01-07 10:59:06 +08:00
youxingzhi
780d42c6ea 1.4.2 2020-01-06 17:40:23 +08:00
ayou
9c2d25e313 Merge pull request #54 from ParadeTo/feature-#47
feat: 🎸 #47
2020-01-06 17:37:43 +08:00
youxingzhi
fbd370e9e5 feat: 🎸 #47 2020-01-03 23:19:55 +08:00
ayou
332402dee6 Merge pull request #53 from weiqian93/master
feat: support different icon for opened node
2020-01-03 21:33:58 +08:00
qian.wei
61ae848898 feat: 🎸 support different icon for opened node
Closes: #47
2020-01-03 19:44:42 +08:00
ayou
82e87e493a Merge pull request #51 from ParadeTo/feature-add-tests
Feature add tests
2019-12-31 09:18:31 +08:00
ayou
2427f47201 Merge pull request #52 from ParadeTo/add-license-1
Create LICENSE
2019-12-30 22:48:45 +08:00
ayou
fab494cedf Create LICENSE 2019-12-30 22:46:31 +08:00
youxingzhi
4e37591f10 test: 💍 update snapshot 2019-12-30 22:45:07 +08:00
youxingzhi
8b23327cee ci: 🎡 change test:unit to test:coverage 2019-12-30 22:39:13 +08:00
youxingzhi
ad6ad1b255 1.4.1 2019-12-30 21:51:20 +08:00
youxingzhi
2fe4da8605 docs: ✏️ add action badge 2019-12-30 21:48:06 +08:00
youxingzhi
d545e6bdbb Merge remote-tracking branch 'origin/master' into feature-add-tests 2019-12-30 21:35:46 +08:00
youxingzhi
d4f911b7b5 test: 💍 add some tests 2019-12-30 12:17:51 +08:00
ayou
9ded7fe914 Merge pull request #50 from ParadeTo/feature/github-actions
Create test.yml
2019-12-29 22:45:39 +08:00
Binbin Sun
b07e14b9ff chore: 🤖 update test.yml 2019-12-29 22:40:08 +08:00
Binbin Sun
fa2b7cb9dd Create test.yml 2019-12-29 22:31:23 +08:00
youxingzhi
57be520b99 1.4.1 2019-12-29 21:25:47 +08:00
ayou
7b70e8dbf2 Merge pull request #49 from beyoursun/master
Add unit test
2019-12-29 21:01:36 +08:00
Binbin Sun
b9402076ec chore: 🤖 change lock registry to official npm 2019-12-29 20:46:21 +08:00
Binbin Sun
669e84fdc5 chore: 🤖 add snapshot testing 2019-12-29 11:23:46 +08:00
Binbin Sun
146b61e70d chore: 🤖 add unit test 2019-12-29 11:14:03 +08:00
ayou
b3e238208b Merge pull request #48 from beyoursun/master
Migrate to vue cli 3
2019-12-27 14:26:25 +08:00
Binbin Sun
4b8bbcbfde chore: 🤖 migrate to vue cli 3 2019-12-27 14:14:34 +08:00
youxingzhi
0723294c7b 1.4.0 2019-12-08 17:41:49 +08:00
youxingzhi
22988f0c81 refactor: 💡 rename the slot name 2019-12-08 17:41:41 +08:00
ayou
c46c570c30 Merge pull request #46 from Lwtsde/slot-fix
fix(slots): fixed slots in childrens
2019-12-08 17:22:13 +08:00
Lucas Wassenhoven
cfe90c7f81 fix(slots): fixed slots in childrens 2019-12-06 11:50:33 +01:00
ayou
7e0184f7f5 Merge pull request #42 from evillt-bot/master
chore(readme): format code
2019-08-30 22:07:09 +08:00
EVILLT
a78ff3f904 chore(readme): update 2019-08-30 16:56:01 +08:00
27 changed files with 15179 additions and 803 deletions

View File

@@ -1,10 +0,0 @@
{
"presets": ["es2015", "stage-2"],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"plugins": [ "istanbul" ]
}
}
}

15
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Test
on: [pull_request, push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: npm install, lint and test
run: |
npm install
npm run lint
npm run test:coverage

22
.gitignore vendored
View File

@@ -1,5 +1,23 @@
.DS_Store
node_modules
npm-debug.log
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
coverage
package-lock.json

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 ayou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -1,38 +0,0 @@
var path = require('path')
var projectRoot = path.resolve(__dirname, '../')
module.exports = {
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel',
include: [path.join(projectRoot, 'src'), path.join(projectRoot, 'dev')],
exclude: /node_modules/
},
{
test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
]
},
vue: {
loaders: {
less: 'vue-style!css!less'
},
postcss: [
require('autoprefixer')({
browsers: ['iOS >= 7', 'Android >= 4.1']
})
]
}
}

View File

@@ -1,23 +0,0 @@
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseConfig = require('./webpack.base.conf')
var path = require('path')
var projectRoot = path.resolve(__dirname, '../')
module.exports = merge(baseConfig, {
entry: './src/index.js',
output: {
path: path.resolve(projectRoot, 'dist'),
filename: 'vue-tree-list.min.js',
library: 'VueTreeList',
libraryTarget: 'umd'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
})

View File

@@ -1,25 +0,0 @@
var webpack = require('webpack')
var merge = require('webpack-merge')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var baseConfig = require('./webpack.base.conf')
module.exports = merge(baseConfig, {
entry: './dev/index.js',
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true,
open: true,
hot: true,
inline: true
},
devtool: '#eval-source-map',
plugins: [
new HtmlWebpackPlugin({
template: './dev/index.html',
filename: 'index.html',
inject: true
}),
new webpack.HotModuleReplacementPlugin()
]
})

View File

@@ -12,25 +12,42 @@
:model="data"
default-tree-node-name="new node"
default-leaf-node-name="new leaf"
v-bind:default-expanded="false">
<span class="icon" slot="addTreeNode">addTreeNode</span>
<span class="icon" slot="addLeafNode">addLeafNode</span>
<span class="icon" slot="editNode">editNode</span>
<span class="icon" slot="delNode">delNode</span>
v-bind:default-expanded="false"
>
<template v-slot:addTreeNodeIcon="slotProps">
<span class="icon">📂</span>
</template>
<template v-slot:addLeafNodeIcon="slotProps">
<span class="icon"></span>
</template>
<template v-slot:editNodeIcon="slotProps">
<span class="icon">📃</span>
</template>
<template v-slot:delNodeIcon="slotProps">
<span class="icon"></span>
</template>
<template v-slot:leafNodeIcon="slotProps">
<span class="icon">🍃</span>
</template>
<template v-slot:treeNodeIcon="slotProps">
<span class="icon">
{{ (slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded) ? '🌲' : '' }}</span>
</template>
</vue-tree-list>
<button @click="getNewTree">Get new tree</button>
<pre>
{{newTree}}
</pre>
{{ newTree }}
</pre
>
</div>
</template>
<script>
import { VueTreeList, Tree, TreeNode } from '../src'
export default {
import {VueTreeList, Tree, TreeNode} from '../src'
export default {
components: {
VueTreeList
},
data () {
data() {
return {
newTree: {},
data: new Tree([
@@ -67,44 +84,51 @@
}
},
methods: {
onDel (node) {
onDel(node) {
// eslint-disable-next-line no-console
console.log(node)
node.remove()
},
onChangeName (params) {
onChangeName(params) {
// eslint-disable-next-line no-console
console.log(params)
},
onAddNode (params) {
onAddNode(params) {
// eslint-disable-next-line no-console
console.log(params)
},
onClick (params) {
onClick(params) {
// eslint-disable-next-line no-console
console.log(params)
},
drop: function ({node, src, target}) {
drop: function({node, src, target}) {
// eslint-disable-next-line no-console
console.log('drop', node, src, target)
},
dropBefore: function ({node, src, target}) {
dropBefore: function({node, src, target}) {
// eslint-disable-next-line no-console
console.log('drop-before', node, src, target)
},
dropAfter: function ({node, src, target}) {
dropAfter: function({node, src, target}) {
// eslint-disable-next-line no-console
console.log('drop-after', node, src, target)
},
addNode () {
var node = new TreeNode({ name: 'new node', isLeaf: false })
addNode() {
var node = new TreeNode({name: 'new node', isLeaf: false})
if (!this.data.children) this.data.children = []
this.data.addChildren(node)
},
getNewTree () {
getNewTree() {
var vm = this
function _dfs (oldNode) {
function _dfs(oldNode) {
var newNode = {}
for (var k in oldNode) {
@@ -123,16 +147,12 @@
}
vm.newTree = _dfs(vm.data)
},
onClick(model) {
console.log(model)
}
}
}
}
</script>
<style lang="less" rel="stylesheet/less">
.vtl {
.vtl {
.vtl-drag-disabled {
background-color: #d0cfcf;
&:hover {
@@ -142,13 +162,13 @@
.vtl-disabled {
background-color: #d0cfcf;
}
}
}
</style>
<style lang="less" rel="stylesheet/less" scoped>
.icon {
.icon {
&:hover {
cursor: pointer;
}
}
}
</style>

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="with=device-width,initial-scale=1.0,
maximum-scale=1.0,minimum-scale=1.0,uses-scalable=no">
<title>vue-tree-list</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

6
jest.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
snapshotSerializers: ['jest-serializer-vue'],
collectCoverageFrom: ['src/**/*.{js,vue}'],
coveragePathIgnorePatterns: ['src/index.js']
}

13806
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,68 +1,78 @@
{
"name": "vue-tree-list",
"version": "1.3.2",
"version": "1.4.2",
"description": "A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.",
"main": "dist/vue-tree-list.min.js",
"scripts": {
"commit": "npx git-cz",
"standard": "standard",
"test": "karma start --single-run",
"unit": "karma start --watch",
"coveralls": "npm run test -- --report lcovonly && cat ./coverage/lcov.info | coveralls",
"build": "webpack --config build/webpack.build.conf.js",
"dev": "webpack-dev-server --config build/webpack.dev.conf.js",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ParadeTo/vue-tree-list.git"
},
"keywords": [
"vue",
"tree"
],
"author": "ayou",
"license": "ISC",
"scripts": {
"serve": "vue-cli-service serve dev",
"build": "vue-cli-service build --target lib src/index.js",
"test:unit": "vue-cli-service test:unit --watch",
"test:coverage": "vue-cli-service test:unit --coverage",
"lint": "vue-cli-service lint",
"commit": "npx git-cz",
"prepublish": "npm run build"
},
"main": "dist/vue-tree-list.umd.min.js",
"dependencies": {
"core-js": "^3.4.3",
"vue": "^2.6.10"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-unit-jest": "^4.1.1",
"@vue/cli-service": "^4.1.0",
"@vue/test-utils": "1.0.0-beta.29",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"jest-serializer-vue": "^2.0.2",
"less": "^3.10.3",
"less-loader": "^5.0.0",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {
"no-console": "warn"
},
"parserOptions": {
"parser": "babel-eslint"
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"jest": true
}
}
]
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"bugs": {
"url": "https://github.com/ParadeTo/vue-tree-list/issues"
},
"homepage": "https://github.com/ParadeTo/vue-tree-list#readme",
"devDependencies": {
"@commitlint/cli": "^7.2.1",
"@commitlint/config-conventional": "^7.1.2",
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-istanbul": "^3.0.0",
"babel-plugin-transform-runtime": "^6.0.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"babel-register": "^6.0.0",
"chai": "^3.5.0",
"cross-env": "^5.0.1",
"css-loader": "^0.26.2",
"file-loader": "^0.10.1",
"html-webpack-plugin": "^2.8.1",
"husky": "^1.2.0",
"isparta": "^4.0.0",
"isparta-loader": "^2.0.0",
"less": "^2.7.2",
"less-loader": "^2.2.3",
"sinon": "^2.3.5",
"sinon-chai": "^2.11.0",
"sourcemap": "^0.1.0",
"standard": "^12.0.1",
"url-loader": "^0.5.7",
"vue": "^2.6.6",
"vue-loader": "^11.1.3",
"vue-style-loader": "^2.0.3",
"vue-template-compiler": "^2.6.6",
"webpack": "^1.13.2",
"webpack-dev-server": "1.14.0",
"webpack-merge": "^0.14.1"
},
"dependencies": {
"cz-conventional-changelog": "^2.1.0"
"keywords": [
"vue",
"tree"
],
"license": "ISC",
"repository": {
"type": "git",
"url": "git+https://github.com/ParadeTo/vue-tree-list.git"
}
}

17
public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>vue-tree-list</title>
</head>
<body>
<noscript>
<strong>We're sorry but vue-tree-list doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -1,3 +1,5 @@
[![Actions Status](https://github.com/ParadeTo/vue-tree-list/workflows/Test/badge.svg)](https://github.com/ParadeTo/vue-tree-list/actions)
# vue-tree-list
A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.
@@ -8,7 +10,7 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
# use
``npm install vue-tree-list``
```javascript
```html
<template>
<div>
<button @click="addNode">Add Node</button>
@@ -21,10 +23,12 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
default-tree-node-name="new node"
default-leaf-node-name="new leaf"
v-bind:default-expanded="false">
<span class="icon" slot="addTreeNode">addTreeNode</span>
<span class="icon" slot="addLeafNode">addLeafNode</span>
<span class="icon" slot="editNode">editNode</span>
<span class="icon" slot="delNode">delNode</span>
<span class="icon" slot="addTreeNodeIcon">📂</span>
<span class="icon" slot="addLeafNodeIcon"></span>
<span class="icon" slot="editNodeIcon">📃</span>
<span class="icon" slot="delNodeIcon">✂️</span>
<span class="icon" slot="leafNodeIcon">🍃</span>
<span class="icon" slot="treeNodeIcon">🌲</span>
</vue-tree-list>
<button @click="getNewTree">Get new tree</button>
<pre>
@@ -32,6 +36,7 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
</pre>
</div>
</template>
<script>
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
export default {
@@ -124,6 +129,7 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
}
}
</script>
<style lang="less" rel="stylesheet/less">
.vtl {
.vtl-drag-disabled {
@@ -193,11 +199,26 @@ drop-before | {node, src, target} | Trigger after dropping a node before another
drop-after | {node, src, target} | Trigger after dropping a node after another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop after
# customize operation icons
The component has default icons for `addTreeNode`, `addLeafNode`, `editNode`, `delNode` button, but you can also customize them:
The component has default icons for `addTreeNodeIcon`, `addLeafNodeIcon`, `editNodeIcon`, `delNodeIcon`, `leafNodeIcon`, `treeNodeIcon` button, but you can also customize them and can access `model`, `root`, `expanded` as below:
```html
<span class="icon" slot="addTreeNode">addTreeNode</span>
<span class="icon" slot="addLeafNode">addLeafNode</span>
<span class="icon" slot="editNode">editNode</span>
<span class="icon" slot="delNode">delNode</span>
<template v-slot:addTreeNodeIcon="slotProps">
<span class="icon">📂</span>
</template>
<template v-slot:addLeafNodeIcon="slotProps">
<span class="icon"></span>
</template>
<template v-slot:editNodeIcon="slotProps">
<span class="icon">📃</span>
</template>
<template v-slot:delNodeIcon="slotProps">
<span class="icon">✂️</span>
</template>
<template v-slot:leafNodeIcon="slotProps">
<span class="icon">🍃</span>
</template>
<template v-slot:treeNodeIcon="slotProps">
<span class="icon">
{{ (slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded) ? '🌲' : '' }}</span>
</template>
```

View File

@@ -1,3 +1,4 @@
import {traverseTree} from './tools'
/**
* Tree data struct
* Created by ayou on 2017/7/20.
@@ -8,9 +9,10 @@
* dragDisabled: decide if it can be dragged
* disabled: desabled all operation
*/
const TreeNode = function (data) {
const { id, isLeaf } = data
this.id = (typeof id === 'undefined') ? new Date().valueOf() : id
export class TreeNode {
constructor(data) {
const {id, isLeaf} = data
this.id = typeof id === 'undefined' ? new Date().valueOf() : id
this.parent = null
this.children = null
this.isLeaf = !!isLeaf
@@ -21,13 +23,13 @@ const TreeNode = function (data) {
this[k] = data[k]
}
}
}
}
TreeNode.prototype.changeName = function (name) {
changeName(name) {
this.name = name
}
}
TreeNode.prototype.addChildren = function (children) {
addChildren(children) {
if (!this.children) {
this.children = []
}
@@ -45,26 +47,26 @@ TreeNode.prototype.addChildren = function (children) {
child.pid = this.id
this.children.push(child)
}
}
}
// remove self
TreeNode.prototype.remove = function () {
// remove self
remove() {
const parent = this.parent
const index = parent.findChildIndex(this)
parent.children.splice(index, 1)
}
}
// remove child
TreeNode.prototype._removeChild = function (child) {
// remove child
_removeChild(child) {
for (var i = 0, len = this.children.length; i < len; i++) {
if (this.children[i] === child) {
this.children.splice(i, 1)
break
}
}
}
}
TreeNode.prototype.isTargetChild = function (target) {
isTargetChild(target) {
let parent = target.parent
while (parent) {
if (parent === this) {
@@ -73,9 +75,9 @@ TreeNode.prototype.isTargetChild = function (target) {
parent = parent.parent
}
return false
}
}
TreeNode.prototype.moveInto = function (target) {
moveInto(target) {
if (this.name === 'root' || this === target) {
return
}
@@ -97,9 +99,9 @@ TreeNode.prototype.moveInto = function (target) {
target.children = []
}
target.children.unshift(this)
}
}
TreeNode.prototype.findChildIndex = function (child) {
findChildIndex(child) {
var index
for (let i = 0, len = this.children.length; i < len; i++) {
if (this.children[i] === child) {
@@ -108,14 +110,14 @@ TreeNode.prototype.findChildIndex = function (child) {
}
}
return index
}
}
TreeNode.prototype._beforeInsert = function (target) {
_canInsert(target) {
if (this.name === 'root' || this === target) {
return false
}
// cannot move ancestor to child
// cannot insert ancestor to child
if (this.isTargetChild(target)) {
return false
}
@@ -124,29 +126,35 @@ TreeNode.prototype._beforeInsert = function (target) {
this.parent = target.parent
this.pid = target.parent.id
return true
}
}
TreeNode.prototype.insertBefore = function (target) {
if (!this._beforeInsert(target)) return
insertBefore(target) {
if (!this._canInsert(target)) return
const pos = target.parent.findChildIndex(target)
target.parent.children.splice(pos, 0, this)
}
}
TreeNode.prototype.insertAfter = function (target) {
if (!this._beforeInsert(target)) return
insertAfter(target) {
if (!this._canInsert(target)) return
const pos = target.parent.findChildIndex(target)
target.parent.children.splice(pos + 1, 0, this)
}
toString() {
return JSON.stringify(traverseTree(this))
}
}
function Tree (data) {
this.root = new TreeNode({ name: 'root', isLeaf: false, id: 0 })
export class Tree {
constructor(data) {
this.root = new TreeNode({name: 'root', isLeaf: false, id: 0})
this.initNode(this.root, data)
return this.root
}
}
Tree.prototype.initNode = function (node, data) {
initNode(node, data) {
for (let i = 0, len = data.length; i < len; i++) {
var _data = data[i]
@@ -156,7 +164,5 @@ Tree.prototype.initNode = function (node, data) {
}
node.addChildren(child)
}
}
}
exports.Tree = Tree
exports.TreeNode = TreeNode

View File

@@ -1,98 +1,191 @@
<template>
<div class='vtl'>
<div v-if="model.name !== 'root'">
<div class="vtl-border vtl-up" :class="{'vtl-active': isDragEnterUp}"
<div class="vtl">
<div
v-if="model.name !== 'root'"
:id="model.id"
class="vtl-node"
:class="{'vtl-leaf-node': model.isLeaf, 'vtl-tree-node': !model.isLeaf}"
>
<div
class="vtl-border vtl-up"
:class="{'vtl-active': isDragEnterUp}"
@drop="dropBefore"
@dragenter="dragEnterUp"
@dragover='dragOverUp'
@dragleave="dragLeaveUp"></div>
<div :id='model.id' :class="treeNodeClass"
@dragover="dragOverUp"
@dragleave="dragLeaveUp"
/>
<div
:class="treeNodeClass"
:draggable="!model.dragDisabled"
@dragstart='dragStart'
@dragover='dragOver'
@dragenter='dragEnter'
@dragleave='dragLeave'
@drop='drop'
@dragend='dragEnd'
@mouseover='mouseOver'
@mouseout='mouseOut'
@click.stop='click'>
<span class="vtl-caret vtl-is-small" v-if="model.children && model.children.length > 0">
<i class="vtl-icon" :class="caretClass" @click.prevent.stop="toggle"></i>
@dragstart="dragStart"
@dragover="dragOver"
@dragenter="dragEnter"
@dragleave="dragLeave"
@drop="drop"
@dragend="dragEnd"
@mouseover="mouseOver"
@mouseout="mouseOut"
@click.stop="click"
>
<span
class="vtl-caret vtl-is-small"
v-if="model.children && model.children.length > 0"
>
<i
class="vtl-icon"
:class="caretClass"
@click.prevent.stop="toggle"
></i>
</span>
<span v-if="model.isLeaf">
<slot name="leafNodeIcon">
<slot
name="leafNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-menu-icon vtl-icon-file"></i>
</slot>
</span>
<span v-else>
<slot name="treeNodeIcon">
<slot
name="treeNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i>
</slot>
</span>
<div class="vtl-node-content" v-if="!editable">
{{model.name}}
{{ model.name }}
</div>
<input v-else class="vtl-input" type="text" ref="nodeInput" :value="model.name" @input="updateName" @blur="setUnEditable">
<input
v-else
class="vtl-input"
type="text"
ref="nodeInput"
:value="model.name"
@input="updateName"
@blur="setUnEditable"
/>
<div class="vtl-operation" v-show="isHover">
<span title="add tree node" @click.stop.prevent="addChild(false)" v-if="!model.isLeaf && !model.addTreeNodeDisabled">
<slot name="addTreeNode">
<span
title="add tree node"
@click.stop.prevent="addChild(false)"
v-if="!model.isLeaf && !model.addTreeNodeDisabled"
>
<slot
name="addTreeNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-folder-plus-e"></i>
</slot>
</span>
<span title="add leaf node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf && !model.addLeafNodeDisabled">
<slot name="addLeafNode">
<span
title="add leaf node"
@click.stop.prevent="addChild(true)"
v-if="!model.isLeaf && !model.addLeafNodeDisabled"
>
<slot
name="addLeafNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-plus"></i>
</slot>
</span>
<span title="edit" @click.stop.prevent="setEditable" v-if="!model.editNodeDisabled">
<slot name="editNode">
<span
title="edit"
@click.stop.prevent="setEditable"
v-if="!model.editNodeDisabled"
>
<slot
name="editNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-edit"></i>
</slot>
</span>
<span title="delete" @click.stop.prevent="delNode" v-if="!model.delNodeDisabled">
<slot name="delNode">
<span
title="delete"
@click.stop.prevent="delNode"
v-if="!model.delNodeDisabled"
>
<slot
name="delNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-trash"></i>
</slot>
</span>
</div>
</div>
<div v-if="model.children && model.children.length > 0 && expanded"
<div
v-if="model.children && model.children.length > 0 && expanded"
class="vtl-border vtl-bottom"
:class="{'vtl-active': isDragEnterBottom}"
@drop="dropAfter"
@dragenter="dragEnterBottom"
@dragover='dragOverBottom'
@dragleave="dragLeaveBottom"></div>
@dragover="dragOverBottom"
@dragleave="dragLeaveBottom"
></div>
</div>
<div :class="{'vtl-tree-margin': model.name !== 'root'}" v-show="model.name === 'root' || expanded" v-if="isFolder">
<item v-for="model in model.children"
<div
:class="{'vtl-tree-margin': model.name !== 'root'}"
v-show="model.name === 'root' || expanded"
v-if="isFolder"
>
<item
v-for="model in model.children"
:default-tree-node-name="defaultTreeNodeName"
:default-leaf-node-name="defaultLeafNodeName"
v-bind:default-expanded="defaultExpanded"
:default-expanded="defaultExpanded"
:model="model"
:key='model.id'>
<slot name="addTreeNode" slot="addTreeNode" />
<slot name="addLeafNode" slot="addLeafNode" />
<slot name="editNode" slot="editNode" />
<slot name="delNode" slot="delNode" />
:key="model.id"
>
<template v-slot:addTreeNodeIcon="slotProps">
<slot name="addTreeNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:addLeafNodeIcon="slotProps">
<slot name="addLeafNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:editNodeIcon="slotProps">
<slot name="editNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:delNodeIcon="slotProps">
<slot name="delNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:leafNodeIcon="slotProps">
<slot name="leafNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:treeNodeIcon="slotProps">
<slot name="treeNodeIcon" v-bind="slotProps" />
</template>
</item>
</div>
</div>
</template>
<script>
import { Tree, TreeNode } from './Tree.js'
import { addHandler, removeHandler } from './tools.js'
import {TreeNode} from './Tree.js'
import {addHandler, removeHandler} from './tools.js'
let compInOperation = null
let compInOperation = null
export default {
data: function () {
export default {
data: function() {
return {
isHover: false,
editable: false,
@@ -120,62 +213,67 @@
}
},
computed: {
itemIconClass () {
return this.model.isLeaf ? 'vtl-icon-file' : 'vtl-icon-folder'
rootNode() {
var node = this.$parent
while (node._props.model.name !== 'root') {
node = node.$parent
}
return node
},
caretClass () {
caretClass() {
return this.expanded ? 'vtl-icon-caret-down' : 'vtl-icon-caret-right'
},
isFolder () {
return this.model.children &&
this.model.children.length
isFolder() {
return this.model.children && this.model.children.length
},
treeNodeClass () {
treeNodeClass() {
const {
model: {
dragDisabled,
disabled
},
model: {dragDisabled, disabled},
isDragEnterNode
} = this
return {
'vtl-tree-node': true,
'vtl-node-main': true,
'vtl-active': isDragEnterNode,
'vtl-drag-disabled': dragDisabled,
'vtl-disabled': disabled
}
}
},
mounted () {
beforeCreate() {
this.$options.components.item = require('./VueTreeList').default
},
mounted() {
const vm = this
addHandler(window, 'keyup', function (e) {
addHandler(window, 'keyup', function(e) {
// click enter
if (e.keyCode === 13 && vm.editable) {
vm.editable = false
}
})
},
beforeDestroy () {
beforeDestroy() {
removeHandler(window, 'keyup')
},
methods: {
updateName (e) {
var oldName = this.model.name;
updateName(e) {
var oldName = this.model.name
this.model.changeName(e.target.value)
var node = this.getRootNode();
node.$emit('change-name', {'id': this.model.id, 'oldName': oldName, 'newName': e.target.value})
this.rootNode.$emit('change-name', {
id: this.model.id,
oldName: oldName,
newName: e.target.value
})
},
delNode () {
var node = this.getRootNode()
node.$emit('delete-node', this.model)
delNode() {
this.rootNode.$emit('delete-node', this.model)
},
setEditable () {
setEditable() {
this.editable = true
this.$nextTick(() => {
const $input = this.$refs.nodeInput
@@ -184,7 +282,7 @@
})
},
setUnEditable () {
setUnEditable() {
this.editable = false
},
@@ -194,120 +292,117 @@
}
},
mouseOver(e) {
mouseOver() {
if (this.model.disabled) return
this.isHover = true
},
mouseOut(e) {
mouseOut() {
this.isHover = false
},
click() {
var node = this.getRootNode()
node.$emit('click', this.model);
this.rootNode.$emit('click', this.model)
},
addChild(isLeaf) {
const name = isLeaf ? this.defaultLeafNodeName : this.defaultTreeNodeName
this.expanded = true
var node = new TreeNode({ name, isLeaf })
var node = new TreeNode({name, isLeaf})
this.model.addChildren(node, true)
var root = this.getRootNode();
root.$emit('add-node', node)
this.rootNode.$emit('add-node', node)
},
dragStart(e) {
if (!(this.model.dragDisabled || this.model.disabled)) {
compInOperation = this
// for firefox
e.dataTransfer.setData("data","data");
e.dataTransfer.setData('data', 'data')
e.dataTransfer.effectAllowed = 'move'
return true
}
return false
},
dragEnd(e) {
dragEnd() {
compInOperation = null
},
dragOver(e) {
e.preventDefault()
return true
},
dragEnter(e) {
dragEnter() {
if (!compInOperation) return
if (this.model.isLeaf) return
this.isDragEnterNode = true
},
dragLeave(e) {
dragLeave() {
this.isDragEnterNode = false
},
drop(e) {
drop() {
if (!compInOperation) return
const oldParent = compInOperation.model.parent;
const oldParent = compInOperation.model.parent
compInOperation.model.moveInto(this.model)
this.isDragEnterNode = false
var node = this.getRootNode();
node.$emit('drop', {target: this.model, node: compInOperation.model, src: oldParent})
this.rootNode.$emit('drop', {
target: this.model,
node: compInOperation.model,
src: oldParent
})
},
dragEnterUp () {
dragEnterUp() {
if (!compInOperation) return
this.isDragEnterUp = true
},
dragOverUp (e) {
dragOverUp(e) {
e.preventDefault()
return true
},
dragLeaveUp () {
dragLeaveUp() {
if (!compInOperation) return
this.isDragEnterUp = false
},
dropBefore () {
dropBefore() {
if (!compInOperation) return
const oldParent = compInOperation.model.parent;
const oldParent = compInOperation.model.parent
compInOperation.model.insertBefore(this.model)
this.isDragEnterUp = false
var node = this.getRootNode();
node.$emit('drop-before', {target: this.model, node: compInOperation.model, src: oldParent})
this.rootNode.$emit('drop-before', {
target: this.model,
node: compInOperation.model,
src: oldParent
})
},
dragEnterBottom () {
dragEnterBottom() {
if (!compInOperation) return
this.isDragEnterBottom = true
},
dragOverBottom (e) {
dragOverBottom(e) {
e.preventDefault()
return true
},
dragLeaveBottom () {
dragLeaveBottom() {
if (!compInOperation) return
this.isDragEnterBottom = false
},
dropAfter () {
dropAfter() {
if (!compInOperation) return
const oldParent = compInOperation.model.parent;
const oldParent = compInOperation.model.parent
compInOperation.model.insertAfter(this.model)
this.isDragEnterBottom = false
var node = this.getRootNode();
node.$emit('drop-after', {target: this.model, node: compInOperation.model, src: oldParent})
},
getRootNode() {
var node = this.$parent
while (node._props.model.name !== 'root') {
node = node.$parent
}
return node;
}
},
beforeCreate () {
this.$options.components.item = require('./VueTreeList.vue')
this.rootNode.$emit('drop-after', {
target: this.model,
node: compInOperation.model,
src: oldParent
})
}
}
}
</script>
<style lang="less" rel="stylesheet/less">
@font-face {
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?ui1hbx');
src: url('fonts/icomoon.eot?ui1hbx#iefix') format('embedded-opentype'),
@@ -316,9 +411,9 @@
url('fonts/icomoon.svg?ui1hbx#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
}
.vtl-icon {
.vtl-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
@@ -340,35 +435,34 @@
&:hover {
color: blue;
}
}
}
.vtl-icon-file:before {
content: "\e906";
}
.vtl-icon-folder:before {
content: "\e907";
}
.vtl-icon-caret-down:before {
content: "\e901";
}
.vtl-icon-caret-right:before {
content: "\e900";
}
.vtl-icon-edit:before {
content: "\e902";
}
.vtl-icon-folder-plus-e:before {
content: "\e903";
}
.vtl-icon-plus:before {
content: "\e904";
}
.vtl-icon-trash:before {
content: "\e905";
}
.vtl-icon-file:before {
content: '\e906';
}
.vtl-icon-folder:before {
content: '\e907';
}
.vtl-icon-caret-down:before {
content: '\e901';
}
.vtl-icon-caret-right:before {
content: '\e900';
}
.vtl-icon-edit:before {
content: '\e902';
}
.vtl-icon-folder-plus-e:before {
content: '\e903';
}
.vtl-icon-plus:before {
content: '\e904';
}
.vtl-icon-trash:before {
content: '\e905';
}
.vtl-border {
.vtl-border {
height: 5px;
&.vtl-up {
margin-top: -5px;
@@ -381,9 +475,9 @@
border-bottom: 3px dashed blue;
/*background-color: blue;*/
}
}
}
.vtl-tree-node {
.vtl-node-main {
display: flex;
align-items: center;
padding: 5px 0 5px 1rem;
@@ -405,13 +499,12 @@
margin-left: 2rem;
letter-spacing: 1px;
}
}
}
.vtl-item {
.vtl-item {
cursor: pointer;
}
.vtl-tree-margin {
}
.vtl-tree-margin {
margin-left: 2em;
}
}
</style>

View File

@@ -1,7 +1,8 @@
/**
* Created by ayou on 17/7/21.
*/
exports.VueTreeList = require('./VueTreeList.vue')
exports.TreeNode = require('./Tree.js').TreeNode
exports.Tree = require('./Tree.js').Tree
// exports.Record = require('./Tree.js').Record
import VueTreeList from "./VueTreeList";
import { Tree, TreeNode } from "./Tree";
export { Tree, TreeNode, VueTreeList };

View File

@@ -4,7 +4,7 @@
var handlerCache
exports.addHandler = function (element, type, handler) {
export const addHandler = function (element, type, handler) {
handlerCache = handler
if (element.addEventListener) {
element.addEventListener(type, handler, false)
@@ -15,7 +15,7 @@ exports.addHandler = function (element, type, handler) {
}
}
exports.removeHandler = function (element, type) {
export const removeHandler = function (element, type) {
if (element.removeEventListener) {
element.removeEventListener(type, handlerCache, false)
} else if (element.detachEvent) {
@@ -25,7 +25,21 @@ exports.removeHandler = function (element, type) {
}
}
// exports.fireFocusEvent = function (ele) {
// var event = new FocusEvent()
// ele.dispatch(event)
// }
// depth first search
export const traverseTree = (root) => {
var newRoot = {}
for (var k in root) {
if (k !== 'children' && k !== 'parent') {
newRoot[k] = root[k]
}
}
if (root.children && root.children.length > 0) {
newRoot.children = []
for (var i = 0, len = root.children.length; i < len; i++) {
newRoot.children.push(traverseTree(root.children[i]))
}
}
return newRoot
}

View File

@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render render correctly 1`] = `
<div class="vtl">
<!---->
<div class="">
<div class="vtl">
<div id="1" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="false" class="vtl-node-main vtl-drag-disabled"><span class="vtl-caret vtl-is-small"><i class="vtl-icon vtl-icon-caret-right"></i></span> <span><i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i></span>
<div class="vtl-node-content">
Node 1
</div>
<div class="vtl-operation" style="display: none;">
<!---->
<!---->
<!---->
<!---->
</div>
</div>
<!---->
</div>
<div class="vtl-tree-margin" style="display: none;">
<div class="vtl">
<div id="2" class="vtl-node vtl-leaf-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main">
<!----> <span><i class="vtl-icon vtl-menu-icon vtl-icon-file"></i></span>
<div class="vtl-node-content">
Node 1-2
</div>
<div class="vtl-operation" style="display: none;">
<!---->
<!----> <span title="edit"><i class="vtl-icon vtl-icon-edit"></i></span> <span title="delete"><i class="vtl-icon vtl-icon-trash"></i></span></div>
</div>
<!---->
</div>
<!---->
</div>
</div>
</div>
<div class="vtl">
<div id="3" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main vtl-disabled">
<!----> <span><i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i></span>
<div class="vtl-node-content">
Node 2
</div>
<div class="vtl-operation" style="display: none;"><span title="add tree node"><i class="vtl-icon vtl-icon-folder-plus-e"></i></span> <span title="add leaf node"><i class="vtl-icon vtl-icon-plus"></i></span> <span title="edit"><i class="vtl-icon vtl-icon-edit"></i></span> <span title="delete"><i class="vtl-icon vtl-icon-trash"></i></span></div>
</div>
<!---->
</div>
<!---->
</div>
<div class="vtl">
<div id="4" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main">
<!----> <span><i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i></span>
<div class="vtl-node-content">
Node 3
</div>
<div class="vtl-operation" style="display: none;"><span title="add tree node"><i class="vtl-icon vtl-icon-folder-plus-e"></i></span> <span title="add leaf node"><i class="vtl-icon vtl-icon-plus"></i></span> <span title="edit"><i class="vtl-icon vtl-icon-edit"></i></span> <span title="delete"><i class="vtl-icon vtl-icon-trash"></i></span></div>
</div>
<!---->
</div>
<!---->
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Slot render slot correctly 1`] = `
<div class="vtl">
<!---->
<div class="">
<div class="vtl">
<div id="t1" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main"><span class="vtl-caret vtl-is-small"><i class="vtl-icon vtl-icon-caret-down"></i></span> <span><span class="tree-node-icon icon">❀</span></span>
<div class="vtl-node-content">
Node 1
</div>
<div class="vtl-operation" style="display: none;"><span title="add tree node"><span class="add-tree-node-icon">📂</span></span> <span title="add leaf node"><span class="icon"></span></span> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span></span></span></div>
</div>
<div class="vtl-border vtl-bottom"></div>
</div>
<div class="vtl-tree-margin">
<div class="vtl">
<div id="t11" class="vtl-node vtl-leaf-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main">
<!----> <span><span class="icon">🍃</span></span>
<div class="vtl-node-content">
Node 1-1
</div>
<div class="vtl-operation" style="display: none;">
<!---->
<!----> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span class="del-node-icon">✂️</span></span></div>
</div>
<!---->
</div>
<!---->
</div>
</div>
</div>
<div class="vtl">
<div id="t2" class="vtl-node vtl-tree-node">
<div class="vtl-border vtl-up"></div>
<div draggable="true" class="vtl-node-main">
<!----> <span><span class="tree-node-icon icon">❀</span></span>
<div class="vtl-node-content">
Node 2
</div>
<div class="vtl-operation" style="display: none;"><span title="add tree node"><span class="add-tree-node-icon">📂</span></span> <span title="add leaf node"><span class="icon"></span></span> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span class="del-node-icon">✂️</span></span></div>
</div>
<!---->
</div>
<!---->
</div>
</div>
</div>
`;

87
tests/unit/drag.spec.js Normal file
View File

@@ -0,0 +1,87 @@
import Vue from 'vue'
import {mount} from '@vue/test-utils'
import {Tree, VueTreeList} from '@/index'
describe('Drag', () => {
let wrapper
beforeEach(() => {
const tree = new Tree([
{
name: 'Node 1',
id: 't1',
pid: 0,
children: [
{
name: 'Node 1-1',
id: 't11',
isLeaf: true,
pid: 't1'
},
{
name: 'Node 1-2',
id: 't12',
pid: 't1'
}
]
},
{
name: 'Node 2',
id: 't2',
pid: 0
},
{
name: 'Node 3',
id: 't3',
pid: 0
}
])
wrapper = mount(VueTreeList, {propsData: {model: new Tree([])}})
wrapper.setProps({model: tree})
})
it('drag before', done => {
const $tree2 = wrapper.find('#t2 .vtl-node-main')
const $tree1Up = wrapper.find('#t1 .vtl-up')
$tree2.trigger('dragstart', { dataTransfer: { setData: () => {} }})
$tree1Up.trigger('drop')
Vue.nextTick(() => {
expect(wrapper.find('.vtl-node').attributes('id')).toBe('t2')
done()
})
})
it('drag after', done => {
const $tree3 = wrapper.find('#t3 .vtl-node-main')
const $tree1Bottom = wrapper.find('#t1 .vtl-bottom')
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} }})
$tree1Bottom.trigger('drop')
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-tree-node').at(2).attributes('id')).toBe('t3')
done()
})
})
it('drag into', done => {
const $tree3 = wrapper.find('#t3 .vtl-node-main')
const $tree1 = wrapper.find('#t1 .vtl-node-main')
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} }})
$tree1.trigger('drop')
Vue.nextTick(() => {
expect(wrapper.find('#t1 + .vtl-tree-margin .vtl-node').attributes('id')).toBe('t3')
done()
})
})
it('cannot drag ancestor into child', done => {
const snapshot = wrapper.html()
const $tree1 = wrapper.find('#t1 .vtl-node-main')
const $tree1Child = wrapper.find('#t12 .vtl-node-main')
$tree1.trigger('dragstart', { dataTransfer: { setData: () => {} }})
$tree1Child.trigger('drop')
Vue.nextTick(() => {
expect(wrapper.html()).toBe(snapshot)
done()
})
})
})

View File

@@ -0,0 +1,86 @@
import Vue from 'vue'
import {mount} from '@vue/test-utils'
import {Tree, VueTreeList} from '@/index'
describe('Operation', () => {
let wrapper
beforeEach(() => {
const tree = new Tree([
{
name: 'Node 1',
id: 't1',
pid: 0,
children: [
{
name: 'Node 1-1',
id: 't11',
isLeaf: true,
pid: 't1'
}
]
},
{
name: 'Node 2',
id: 't2',
pid: 0
}
])
wrapper = mount(VueTreeList, {propsData: {model: new Tree([])}})
wrapper.setProps({model: tree})
})
it('delete leaf node', done => {
const $node11Trash = wrapper.find('#t11 [title="delete"]')
$node11Trash.trigger('click')
wrapper.emitted('delete-node')[0][0].remove()
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-node').length).toBe(2)
done()
})
})
it('delete tree node', done => {
const $node11Trash = wrapper.find('#t1 [title="delete"]')
$node11Trash.trigger('click')
wrapper.emitted('delete-node')[0][0].remove()
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-node').length).toBe(1)
done()
})
})
it('add leaf node', done => {
const $node1AddLeafNode = wrapper.find('#t1 [title="add leaf node"]')
$node1AddLeafNode.trigger('click')
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-leaf-node').length).toBe(2)
done()
})
})
it('add tree node', done => {
const $node1AddTreeNode = wrapper.find('#t1 [title="add tree node"]')
$node1AddTreeNode.trigger('click')
Vue.nextTick(() => {
expect(wrapper.findAll('.vtl-tree-node').length).toBe(3)
done()
})
})
it('change node name', done => {
const $node1Edit = wrapper.find('#t1 [title="edit"]')
$node1Edit.trigger('click')
Vue.nextTick(() => {
const $input = wrapper.find('#t1 .vtl-input')
$input.element.value = 'New Node 1'
$input.trigger('input')
var event = new KeyboardEvent('keyup', {keyCode: 13})
window.dispatchEvent(event)
Vue.nextTick(() => {
expect(wrapper.find('#t1').text()).toBe('New Node 1')
done()
})
})
})
})

49
tests/unit/render.spec.js Normal file
View File

@@ -0,0 +1,49 @@
import {mount} from '@vue/test-utils'
import {Tree, VueTreeList} from '@/index'
describe('Render', () => {
it('render correctly', () => {
const tree = new Tree([
{
name: 'Node 1',
id: 1,
pid: 0,
dragDisabled: true,
addTreeNodeDisabled: true,
addLeafNodeDisabled: true,
editNodeDisabled: true,
delNodeDisabled: true,
children: [
{
name: 'Node 1-2',
id: 2,
isLeaf: true,
pid: 1
}
]
},
{
name: 'Node 2',
id: 3,
pid: 0,
disabled: true
},
{
name: 'Node 3',
id: 4,
pid: 0
}
])
const wrapper = mount(VueTreeList, {
propsData: {
model: tree,
defaultTreeNodeName: 'new node',
defaultLeafNodeName: 'new leaf',
defaultExpanded: false
}
})
expect(wrapper).toMatchSnapshot()
})
})

79
tests/unit/slot.spec.js Normal file
View File

@@ -0,0 +1,79 @@
import Vue from 'vue'
import {mount} from '@vue/test-utils'
import {Tree, VueTreeList} from '@/index'
describe('Slot', () => {
let wrapper
beforeEach(() => {
const tree = new Tree([
{
name: 'Node 1',
id: 't1',
pid: 0,
children: [
{
name: 'Node 1-1',
id: 't11',
isLeaf: true,
pid: 't1'
}
]
},
{
name: 'Node 2',
id: 't2',
pid: 0
}
])
wrapper = mount(VueTreeList, {
propsData: {model: new Tree([])},
scopedSlots: {
addTreeNodeIcon() {
return <span class="add-tree-node-icon">📂</span>
},
addLeafNodeIcon() {
return <span class="icon"></span>
},
editNodeIcon() {
return <span class="icon">📃</span>
},
delNodeIcon(slotProps) {
return (slotProps.model.isLeaf || !slotProps.model.children) ? <span class="del-node-icon"></span> : <span />
},
leafNodeIcon() {
return <span class="icon">🍃</span>
},
treeNodeIcon(slotProps) {
return <span class="tree-node-icon icon">{ slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded ? '🌲' : '❀' }</span>
}
}
})
wrapper.setProps({model: tree})
})
it('render slot correctly', () => {
expect(wrapper).toMatchSnapshot()
})
it('toggle tree node show different icon', done => {
const $caretDown = wrapper.find('.vtl-icon-caret-down')
expect(wrapper.find('#t1 .tree-node-icon').text()).toBe('❀')
$caretDown.trigger('click')
Vue.nextTick(() => {
expect(wrapper.exists('.vtl-icon-caret-right')).toBe(true)
expect(wrapper.find('#t1 .tree-node-icon').text()).toBe('🌲')
done()
})
})
it('dont show ✂️ after add child ', done => {
const $addTreeNodeIcon = wrapper.find('#t2 .add-tree-node-icon')
expect(wrapper.find('#t2 .del-node-icon').exists()).toBe(true)
$addTreeNodeIcon.trigger('click')
Vue.nextTick(() => {
expect(wrapper.find('#t2 .del-node-icon').exists()).toBe(false)
done()
})
})
})

5
vue.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
css: {
extract: false
}
};