diff --git a/.gitignore b/.gitignore index 19514b87..c5cfd2b4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,7 @@ npm-debug.log *.swp *.swo *.log -test/dist/ +examples/dist/ dist/ +yarn-error.log +test/unit/coverage \ No newline at end of file diff --git a/README.md b/README.md index 6f88487f..dd61e872 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ - [x] Tabs - [x] Dropdown - [ ] Page -- [ ] Breadcrumb +- [x] Breadcrumb - [x] Steps - [ ] LoadingBar - [x] Circle @@ -76,7 +76,7 @@ ## Install -### Install vue-webpack project in the first place +### Install vue-webpack project in the first place Use [iview-project](https://github.com/iview/iview-project)(Recommended) Or [vue-cli](https://github.com/vuejs/vue-cli) diff --git a/build/webpack.base.config.js b/build/webpack.base.config.js new file mode 100644 index 00000000..2a258deb --- /dev/null +++ b/build/webpack.base.config.js @@ -0,0 +1,68 @@ +/** + * 公共配置 + */ +var webpack = require('webpack'); +var path = require('path'); +function resolve (dir) { + return path.join(__dirname, '..', dir) +} + +module.exports = { + // 加载器 + module: { + // https://doc.webpack-china.org/guides/migrating/#module-loaders-module-rules + rules: [ + { + // https://vue-loader.vuejs.org/en/configurations/extract-css.html + test: /\.vue$/, + loader: 'vue-loader', + options: { + loaders: { + css: 'vue-style-loader!css-loader', + less: 'vue-style-loader!css-loader!less-loader' + }, + postLoaders: { + html: 'babel-loader' + } + } + }, + { + test: /\.js$/, + loader: 'babel-loader', exclude: /node_modules/ + }, + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader', + 'autoprefixer-loader' + ] + }, + { + test: /\.less$/, + use: [ + 'style-loader', + 'css-loader', + 'less-loader' + ] + }, + { + test: /\.scss$/, + use: [ + 'style-loader', + 'css-loader', + 'sass-loader?sourceMap' + ] + }, + { test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, loader: 'url-loader?limit=8192'}, + { test: /\.(html|tpl)$/, loader: 'html-loader' } + ] + }, + resolve: { + extensions: ['.js', '.vue'], + alias: { + 'vue': 'vue/dist/vue.esm.js', + '@': resolve('src'), + } + } +}; diff --git a/build/webpack.dev.config.js b/build/webpack.dev.config.js index 6c442115..699d29eb 100644 --- a/build/webpack.dev.config.js +++ b/build/webpack.dev.config.js @@ -6,109 +6,37 @@ var path = require('path'); var webpack = require('webpack'); // var ExtractTextPlugin = require('extract-text-webpack-plugin'); var HtmlWebpackPlugin = require('html-webpack-plugin'); +var merge = require('webpack-merge') +var webpackBaseConfig = require('./webpack.base.config.js'); +var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') -module.exports = { + +module.exports = merge(webpackBaseConfig, { // 入口 entry: { - main: './test/main', + main: './examples/main', vendors: ['vue', 'vue-router'] }, // 输出 output: { - path: path.join(__dirname, '../test/dist'), + path: path.join(__dirname, '../examples/dist'), publicPath: '', filename: '[name].js', chunkFilename: '[name].chunk.js' }, - // 加载器 - module: { - // https://doc.webpack-china.org/guides/migrating/#module-loaders-module-rules - rules: [ - { - // https://vue-loader.vuejs.org/en/configurations/extract-css.html - test: /\.vue$/, - loader: 'vue-loader', - options: { - loaders: { - css: 'vue-style-loader!css-loader', - less: 'vue-style-loader!css-loader!less-loader' - }, - postLoaders: { - html: 'babel-loader' - } - } - }, - // { test: /\.vue$/, loader: 'vue' }, - // Module build failed: Error: The node API for `babel` has been moved to `babel-core`. - // https://github.com/babel/babel-loader/blob/master/README.md#the-node-api-for-babel-has-been-moved-to-babel-core - { - test: /\.js$/, - loader: 'babel-loader', exclude: /node_modules/ - }, - { - test: /\.css$/, - use: [ - 'style-loader', - 'css-loader', - 'autoprefixer-loader' - ] - }, - { - test: /\.less$/, - use: [ - 'style-loader', - 'css-loader', - 'less-loader' - ] - // loader: 'style!css!less' - }, - { - test: /\.scss$/, - use: [ - 'style-loader', - 'css-loader', - 'sass-loader?sourceMap' - ] - // loader: 'style!css!sass?sourceMap' - }, - { test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, loader: 'url-loader?limit=8192'}, - { test: /\.(html|tpl)$/, loader: 'html-loader' } - ] - }, - // vue: { - // loaders: { - // css: ExtractTextPlugin.extract( - // "style-loader", - // "css-loader?sourceMap", - // { - // publicPath: "/test/dist/" - // } - // ), - // less: ExtractTextPlugin.extract( - // 'vue-style-loader', - // 'css-loader!less-loader' - // ), - // js: 'babel' - // } - // }, resolve: { - // require时省略的扩展名,如:require('module') 不需要module.js - extensions: ['.js', '.vue'], alias: { iview: '../../src/index', vue: 'vue/dist/vue.js' } }, plugins: [ - // new ExtractTextPlugin({ filename: '[name].css', disable: false, allChunks: true }), - // new ExtractTextPlugin("[name].css",{ allChunks : true,resolve : ['modules'] }), // 提取CSS - // https://doc.webpack-china.org/plugins/commons-chunk-plugin/ new webpack.optimize.CommonsChunkPlugin({ name: 'vendors', filename: 'vendor.bundle.js' }), new HtmlWebpackPlugin({ inject: true, - filename: path.join(__dirname, '../test/dist/index.html'), - template: path.join(__dirname, '../test/index.html') // 模版文件 - }) - // new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js'), // 提取第三方库 + filename: path.join(__dirname, '../examples/dist/index.html'), + template: path.join(__dirname, '../examples/index.html') + }), + new FriendlyErrorsPlugin() ] -}; +}); diff --git a/build/webpack.dist.dev.config.js b/build/webpack.dist.dev.config.js index 3aac363f..eab3547c 100644 --- a/build/webpack.dist.dev.config.js +++ b/build/webpack.dist.dev.config.js @@ -1,7 +1,9 @@ var path = require('path'); var webpack = require('webpack'); +var merge = require('webpack-merge') +var webpackBaseConfig = require('./webpack.base.config.js'); -module.exports = { +module.exports = merge(webpackBaseConfig, { entry: { main: './src/index.js' }, @@ -21,26 +23,6 @@ module.exports = { amd: 'vue' } }, - resolve: { - extensions: ['.js', '.vue'] - }, - module: { - rules: [ - { - test: /\.vue$/, - loader: 'vue-loader', - options: { - postLoaders: { - html: 'babel-loader' - } - } - }, - { - test: /\.js$/, - loader: 'babel-loader', exclude: /node_modules/ - } - ] - }, plugins: [ new webpack.DefinePlugin({ 'process.env': { @@ -48,4 +30,4 @@ module.exports = { } }) ] -} +}); diff --git a/build/webpack.dist.prod.config.js b/build/webpack.dist.prod.config.js index 81703bb1..34a6a896 100644 --- a/build/webpack.dist.prod.config.js +++ b/build/webpack.dist.prod.config.js @@ -1,7 +1,11 @@ var path = require('path'); var webpack = require('webpack'); +var merge = require('webpack-merge') +var webpackBaseConfig = require('./webpack.base.config.js'); -module.exports = { + + +module.exports = merge(webpackBaseConfig, { entry: { main: './src/index.js' }, @@ -21,26 +25,6 @@ module.exports = { amd: 'vue' } }, - resolve: { - extensions: ['.js', '.vue'] - }, - module: { - rules: [ - { - test: /\.vue$/, - loader: 'vue-loader', - options: { - postLoaders: { - html: 'babel-loader' - } - } - }, - { - test: /\.js$/, - loader: 'babel-loader', exclude: /node_modules/ - } - ] - }, plugins: [ new webpack.DefinePlugin({ 'process.env': { @@ -53,4 +37,4 @@ module.exports = { } }) ] -} +}); diff --git a/build/webpack.test.config.js b/build/webpack.test.config.js new file mode 100644 index 00000000..6c3212b7 --- /dev/null +++ b/build/webpack.test.config.js @@ -0,0 +1,25 @@ +/** + * 用于单元测试 + */ + +var webpack = require('webpack') +var merge = require('webpack-merge') +var webpackBaseConfig = require('./webpack.base.config.js'); + + +var webpackConfig = merge(webpackBaseConfig, { + // use inline sourcemap for karma-sourcemap-loader + devtool: '#inline-source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: '"testing"' + } + }) + ] +}) + +// no need for app entry during tests +delete webpackConfig.entry + +module.exports = webpackConfig diff --git a/test/app.vue b/examples/app.vue similarity index 97% rename from test/app.vue rename to examples/app.vue index 4278a1fc..283fa189 100644 --- a/test/app.vue +++ b/examples/app.vue @@ -39,6 +39,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
  • Poptip
  • Slider
  • Dropdown
  • +
  • Breadcrumb
  • diff --git a/test/index.html b/examples/index.html similarity index 100% rename from test/index.html rename to examples/index.html diff --git a/test/main.js b/examples/main.js similarity index 96% rename from test/main.js rename to examples/main.js index 3e7625bd..edcc5324 100644 --- a/test/main.js +++ b/examples/main.js @@ -120,6 +120,10 @@ const router = new VueRouter({ { path: '/dropdown', component: require('./routers/dropdown.vue') + }, + { + path: '/breadcrumb', + component: require('./routers/breadcrumb.vue') } ] }); diff --git a/test/routers/affix.vue b/examples/routers/affix.vue similarity index 100% rename from test/routers/affix.vue rename to examples/routers/affix.vue diff --git a/test/routers/alert.vue b/examples/routers/alert.vue similarity index 100% rename from test/routers/alert.vue rename to examples/routers/alert.vue diff --git a/test/routers/badge.vue b/examples/routers/badge.vue similarity index 100% rename from test/routers/badge.vue rename to examples/routers/badge.vue diff --git a/examples/routers/breadcrumb.vue b/examples/routers/breadcrumb.vue new file mode 100644 index 00000000..057bce55 --- /dev/null +++ b/examples/routers/breadcrumb.vue @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/test/routers/button.vue b/examples/routers/button.vue similarity index 100% rename from test/routers/button.vue rename to examples/routers/button.vue diff --git a/test/routers/card.vue b/examples/routers/card.vue similarity index 100% rename from test/routers/card.vue rename to examples/routers/card.vue diff --git a/test/routers/carousel.vue b/examples/routers/carousel.vue similarity index 100% rename from test/routers/carousel.vue rename to examples/routers/carousel.vue diff --git a/test/routers/cascader.vue b/examples/routers/cascader.vue similarity index 100% rename from test/routers/cascader.vue rename to examples/routers/cascader.vue diff --git a/test/routers/checkbox.vue b/examples/routers/checkbox.vue similarity index 100% rename from test/routers/checkbox.vue rename to examples/routers/checkbox.vue diff --git a/test/routers/circle.vue b/examples/routers/circle.vue similarity index 100% rename from test/routers/circle.vue rename to examples/routers/circle.vue diff --git a/test/routers/collapse.vue b/examples/routers/collapse.vue similarity index 100% rename from test/routers/collapse.vue rename to examples/routers/collapse.vue diff --git a/test/routers/date.vue b/examples/routers/date.vue similarity index 100% rename from test/routers/date.vue rename to examples/routers/date.vue diff --git a/test/routers/dropdown.vue b/examples/routers/dropdown.vue similarity index 100% rename from test/routers/dropdown.vue rename to examples/routers/dropdown.vue diff --git a/test/routers/form.vue b/examples/routers/form.vue similarity index 100% rename from test/routers/form.vue rename to examples/routers/form.vue diff --git a/test/routers/grid.vue b/examples/routers/grid.vue similarity index 100% rename from test/routers/grid.vue rename to examples/routers/grid.vue diff --git a/test/routers/input-number.vue b/examples/routers/input-number.vue similarity index 100% rename from test/routers/input-number.vue rename to examples/routers/input-number.vue diff --git a/test/routers/input.vue b/examples/routers/input.vue similarity index 100% rename from test/routers/input.vue rename to examples/routers/input.vue diff --git a/test/routers/menu.vue b/examples/routers/menu.vue similarity index 100% rename from test/routers/menu.vue rename to examples/routers/menu.vue diff --git a/test/routers/message.vue b/examples/routers/message.vue similarity index 100% rename from test/routers/message.vue rename to examples/routers/message.vue diff --git a/test/routers/more.vue b/examples/routers/more.vue similarity index 100% rename from test/routers/more.vue rename to examples/routers/more.vue diff --git a/test/routers/notice.vue b/examples/routers/notice.vue similarity index 100% rename from test/routers/notice.vue rename to examples/routers/notice.vue diff --git a/test/routers/page.vue b/examples/routers/page.vue similarity index 100% rename from test/routers/page.vue rename to examples/routers/page.vue diff --git a/test/routers/poptip.vue b/examples/routers/poptip.vue similarity index 100% rename from test/routers/poptip.vue rename to examples/routers/poptip.vue diff --git a/test/routers/progress.vue b/examples/routers/progress.vue similarity index 100% rename from test/routers/progress.vue rename to examples/routers/progress.vue diff --git a/test/routers/radio.vue b/examples/routers/radio.vue similarity index 100% rename from test/routers/radio.vue rename to examples/routers/radio.vue diff --git a/test/routers/rate.vue b/examples/routers/rate.vue similarity index 100% rename from test/routers/rate.vue rename to examples/routers/rate.vue diff --git a/test/routers/select.vue b/examples/routers/select.vue similarity index 100% rename from test/routers/select.vue rename to examples/routers/select.vue diff --git a/test/routers/slider.vue b/examples/routers/slider.vue similarity index 100% rename from test/routers/slider.vue rename to examples/routers/slider.vue diff --git a/test/routers/steps.vue b/examples/routers/steps.vue similarity index 100% rename from test/routers/steps.vue rename to examples/routers/steps.vue diff --git a/test/routers/switch.vue b/examples/routers/switch.vue similarity index 100% rename from test/routers/switch.vue rename to examples/routers/switch.vue diff --git a/test/routers/table.vue b/examples/routers/table.vue similarity index 100% rename from test/routers/table.vue rename to examples/routers/table.vue diff --git a/test/routers/tabs.vue b/examples/routers/tabs.vue similarity index 100% rename from test/routers/tabs.vue rename to examples/routers/tabs.vue diff --git a/test/routers/tag.vue b/examples/routers/tag.vue similarity index 100% rename from test/routers/tag.vue rename to examples/routers/tag.vue diff --git a/test/routers/timeline.vue b/examples/routers/timeline.vue similarity index 100% rename from test/routers/timeline.vue rename to examples/routers/timeline.vue diff --git a/test/routers/tooltip.vue b/examples/routers/tooltip.vue similarity index 100% rename from test/routers/tooltip.vue rename to examples/routers/tooltip.vue diff --git a/test/routers/transfer.vue b/examples/routers/transfer.vue similarity index 100% rename from test/routers/transfer.vue rename to examples/routers/transfer.vue diff --git a/test/routers/tree.vue b/examples/routers/tree.vue similarity index 100% rename from test/routers/tree.vue rename to examples/routers/tree.vue diff --git a/test/routers/upload.vue b/examples/routers/upload.vue similarity index 100% rename from test/routers/upload.vue rename to examples/routers/upload.vue diff --git a/package.json b/package.json index 23aa8688..569b65d1 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "dist:prod": "webpack --config build/webpack.dist.prod.config.js", "dist": "npm run dist:style && npm run dist:dev && npm run dist:prod", "lint": "eslint --fix --ext .js,.vue src", - "test": "npm run dist && npm run lint", + "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", + "test": "npm run lint && npm run unit", "prepublish": "npm run dist" }, "repository": { @@ -54,11 +55,15 @@ "babel-plugin-transform-runtime": "^6.12.0", "babel-preset-es2015": "^6.9.0", "babel-runtime": "^6.11.6", + "chai": "^3.5.0", + "cross-env": "^3.1.4", "css-loader": "^0.23.1", "eslint": "^3.12.2", "eslint-plugin-html": "^1.7.0", "extract-text-webpack-plugin": "^2.0.0", "file-loader": "^0.8.5", + "friendly-errors-webpack-plugin": "^1.6.1", + "function-bind": "^1.1.0", "gulp": "^3.9.1", "gulp-autoprefixer": "^3.1.1", "gulp-clean-css": "^2.0.13", @@ -66,8 +71,21 @@ "gulp-rename": "^1.2.2", "html-loader": "^0.3.0", "html-webpack-plugin": "^2.28.0", + "karma": "^1.4.1", + "karma-coverage": "^1.1.1", + "karma-mocha": "^1.3.0", + "karma-phantomjs-launcher": "^1.0.2", + "karma-sinon-chai": "^1.2.4", + "karma-sourcemap-loader": "^0.3.7", + "karma-spec-reporter": "0.0.26", + "karma-webpack": "^2.0.2", "less": "^2.7.1", "less-loader": "^2.2.3", + "lolex": "^1.5.2", + "mocha": "^3.2.0", + "phantomjs-prebuilt": "^2.1.13", + "sinon": "^1.17.7", + "sinon-chai": "^2.8.0", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "vue": "^2.2.1", @@ -78,6 +96,11 @@ "vue-style-loader": "^1.0.0", "vue-template-compiler": "^2.2.1", "webpack": "^2.2.1", - "webpack-dev-server": "^2.4.1" + "webpack-dev-server": "^2.4.1", + "webpack-merge": "^3.0.0" + }, + "engines": { + "node": ">= 4.0.0", + "npm": ">= 3.0.0" } } diff --git a/src/components/breadcrumb/breadcrumb-item.vue b/src/components/breadcrumb/breadcrumb-item.vue index 427d2655..0a7af614 100644 --- a/src/components/breadcrumb/breadcrumb-item.vue +++ b/src/components/breadcrumb/breadcrumb-item.vue @@ -6,8 +6,10 @@ - - {{{ separator }}} + + + + @@ -18,12 +20,17 @@ props: { href: { type: String - }, - separator: { - type: String, - default: '/' } }, + data () { + return { + separator: '', + showSeparator: false + }; + }, + mounted () { + this.showSeparator = this.$slots.separator !== undefined; + }, computed: { linkClasses () { return `${prefixCls}-link`; diff --git a/src/components/breadcrumb/breadcrumb.vue b/src/components/breadcrumb/breadcrumb.vue index 81b89a6c..8f4a7d94 100644 --- a/src/components/breadcrumb/breadcrumb.vue +++ b/src/components/breadcrumb/breadcrumb.vue @@ -18,7 +18,7 @@ return `${prefixCls}`; } }, - compiled () { + mounted () { this.updateChildren(); }, methods: { diff --git a/src/index.js b/src/index.js index dbc99497..5aa84e8d 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import Affix from './components/affix'; import Alert from './components/alert'; // import BackTop from './components/back-top'; import Badge from './components/badge'; -// import Breadcrumb from './components/breadcrumb'; +import Breadcrumb from './components/breadcrumb'; import Button from './components/button'; import Card from './components/card'; import Carousel from './components/carousel'; @@ -51,8 +51,8 @@ const iview = { Alert, // BackTop, Badge, - // Breadcrumb, - // BreadcrumbItem: Breadcrumb.Item, + Breadcrumb, + BreadcrumbItem: Breadcrumb.Item, // iButton: Button, Button, ButtonGroup: Button.Group, diff --git a/test/unit/index.js b/test/unit/index.js new file mode 100644 index 00000000..b282cc31 --- /dev/null +++ b/test/unit/index.js @@ -0,0 +1,18 @@ +import Vue from 'vue'; +Vue.config.productionTip = false; + +// Polyfill fn.bind() for PhantomJS +/* eslint-disable no-extend-native */ +Function.prototype.bind = require('function-bind'); + +// require all test files (files that ends with .spec.js) +const testsContext = require.context('./specs', true, /\.spec$/); +testsContext.keys().forEach(testsContext); + +// require all src files except main.js for coverage. +// you can also change this to match only the subset of files that +// you want coverage for. +// const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/); +// @todo +const srcContext = require.context('../../src/components/breadcrumb', true, /^\.\/(?!styles.*?(\.less)?$)/); +srcContext.keys().forEach(srcContext); diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js new file mode 100644 index 00000000..2fe05ea0 --- /dev/null +++ b/test/unit/karma.conf.js @@ -0,0 +1,33 @@ +// This is a karma config file. For more details see +// http://karma-runner.github.io/0.13/config/configuration-file.html +// we are also using it with karma-webpack +// https://github.com/webpack/karma-webpack + +var webpackConfig = require('../../build/webpack.test.config.js'); + +module.exports = function (config) { + config.set({ + // to run in additional browsers: + // 1. install corresponding karma launcher + // http://karma-runner.github.io/0.13/config/browsers.html + // 2. add it to the `browsers` array below. + browsers: ['PhantomJS'], + frameworks: ['mocha', 'sinon-chai'], + reporters: ['spec', 'coverage'], + files: ['./index.js'], + preprocessors: { + './index.js': ['webpack', 'sourcemap'] + }, + webpack: webpackConfig, + webpackMiddleware: { + noInfo: true, + }, + coverageReporter: { + dir: './coverage', + reporters: [ + { type: 'lcov', subdir: '.' }, + { type: 'text-summary' }, + ] + }, + }); +}; diff --git a/test/unit/specs/breadcrumb.spec.js b/test/unit/specs/breadcrumb.spec.js new file mode 100644 index 00000000..3166c415 --- /dev/null +++ b/test/unit/specs/breadcrumb.spec.js @@ -0,0 +1,24 @@ +import { createVue, destroyVM } from '../util'; + +describe('Breadcrumb.vue', () => { + let vm; + afterEach(() => { + destroyVM(vm); + }); + it('create', done => { + vm = createVue(` + + Home4 + Components + Breadcrumb + + `); + expect(vm.$el.querySelectorAll('.ivu-breadcrumb-item-link').length).to.equal(3); + + vm.$nextTick(_ => { + // console.log(vm.$el.querySelector('.ivu-breadcrumb-item-separator').innerHTML); + expect(vm.$el.querySelector('.ivu-breadcrumb-item-separator').innerHTML).to.equal('=>'); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/util.js b/test/unit/util.js new file mode 100644 index 00000000..47cd3313 --- /dev/null +++ b/test/unit/util.js @@ -0,0 +1,85 @@ +import Vue from 'vue'; +import iView from '../../src/index'; + +Vue.use(iView); + +let id = 0; + +const createElm = function() { + const elm = document.createElement('div'); + + elm.id = 'app' + ++id; + document.body.appendChild(elm); + + return elm; +}; + +/** + * 回收 vm + * @param {Object} vm + */ +exports.destroyVM = function(vm) { + vm.$el && + vm.$el.parentNode && + vm.$el.parentNode.removeChild(vm.$el); +}; + +/** + * 创建一个 Vue 的实例对象 + * @param {Object|String} Compo 组件配置,可直接传 template + * @param {Boolean=false} mounted 是否添加到 DOM 上 + * @return {Object} vm + */ +exports.createVue = function(Compo, mounted = false) { + const elm = createElm(); + + if (Object.prototype.toString.call(Compo) === '[object String]') { + Compo = { template: Compo }; + } + return new Vue(Compo).$mount(mounted === false ? null : elm); +}; + +/** + * 创建一个测试组件实例 + * @link http://vuejs.org/guide/unit-testing.html#Writing-Testable-Components + * @param {Object} Compo - 组件对象 + * @param {Object} propsData - props 数据 + * @param {Boolean=false} mounted - 是否添加到 DOM 上 + * @return {Object} vm + */ +exports.createTest = function(Compo, propsData = {}, mounted = false) { + if (propsData === true || propsData === false) { + mounted = propsData; + propsData = {}; + } + const elm = createElm(); + const Ctor = Vue.extend(Compo); + return new Ctor({ propsData }).$mount(mounted === false ? null : elm); +}; + +/** + * 触发一个事件 + * mouseenter, mouseleave, mouseover, keyup, change, click 等 + * @param {Element} elm + * @param {String} name + * @param {*} opts + */ +exports.triggerEvent = function(elm, name, ...opts) { + let eventName; + + if (/^mouse|click/.test(name)) { + eventName = 'MouseEvents'; + } else if (/^key/.test(name)) { + eventName = 'KeyboardEvent'; + } else { + eventName = 'HTMLEvents'; + } + const evt = document.createEvent(eventName); + + evt.initEvent(name, ...opts); + elm.dispatchEvent + ? elm.dispatchEvent(evt) + : elm.fireEvent('on' + name, evt); + + return elm; +};