Merge pull request #340 from huixisheng/2.0

Support Breadcrumb and   add test  unit and optimize webpack config
This commit is contained in:
Aresn 2017-03-05 21:50:57 +08:00 committed by GitHub
commit 99246a8954
56 changed files with 362 additions and 143 deletions

4
.gitignore vendored
View file

@ -15,5 +15,7 @@ npm-debug.log
*.swp *.swp
*.swo *.swo
*.log *.log
test/dist/ examples/dist/
dist/ dist/
yarn-error.log
test/unit/coverage

View file

@ -53,7 +53,7 @@
- [x] Tabs - [x] Tabs
- [x] Dropdown - [x] Dropdown
- [ ] Page - [ ] Page
- [ ] Breadcrumb - [x] Breadcrumb
- [x] Steps - [x] Steps
- [ ] LoadingBar - [ ] LoadingBar
- [x] Circle - [x] Circle
@ -76,7 +76,7 @@
## Install ## 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) Use [iview-project](https://github.com/iview/iview-project)(Recommended) Or [vue-cli](https://github.com/vuejs/vue-cli)

View file

@ -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'),
}
}
};

View file

@ -6,109 +6,37 @@ var path = require('path');
var webpack = require('webpack'); var webpack = require('webpack');
// var ExtractTextPlugin = require('extract-text-webpack-plugin'); // var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-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: { entry: {
main: './test/main', main: './examples/main',
vendors: ['vue', 'vue-router'] vendors: ['vue', 'vue-router']
}, },
// 输出 // 输出
output: { output: {
path: path.join(__dirname, '../test/dist'), path: path.join(__dirname, '../examples/dist'),
publicPath: '', publicPath: '',
filename: '[name].js', filename: '[name].js',
chunkFilename: '[name].chunk.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: { resolve: {
// require时省略的扩展名require('module') 不需要module.js
extensions: ['.js', '.vue'],
alias: { alias: {
iview: '../../src/index', iview: '../../src/index',
vue: 'vue/dist/vue.js' vue: 'vue/dist/vue.js'
} }
}, },
plugins: [ 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 webpack.optimize.CommonsChunkPlugin({ name: 'vendors', filename: 'vendor.bundle.js' }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: true, inject: true,
filename: path.join(__dirname, '../test/dist/index.html'), filename: path.join(__dirname, '../examples/dist/index.html'),
template: path.join(__dirname, '../test/index.html') // 模版文件 template: path.join(__dirname, '../examples/index.html')
}) }),
// new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js'), // 提取第三方库 new FriendlyErrorsPlugin()
] ]
}; });

View file

@ -1,7 +1,9 @@
var path = require('path'); var path = require('path');
var webpack = require('webpack'); var webpack = require('webpack');
var merge = require('webpack-merge')
var webpackBaseConfig = require('./webpack.base.config.js');
module.exports = { module.exports = merge(webpackBaseConfig, {
entry: { entry: {
main: './src/index.js' main: './src/index.js'
}, },
@ -21,26 +23,6 @@ module.exports = {
amd: 'vue' 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: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
@ -48,4 +30,4 @@ module.exports = {
} }
}) })
] ]
} });

View file

@ -1,7 +1,11 @@
var path = require('path'); var path = require('path');
var webpack = require('webpack'); var webpack = require('webpack');
var merge = require('webpack-merge')
var webpackBaseConfig = require('./webpack.base.config.js');
module.exports = {
module.exports = merge(webpackBaseConfig, {
entry: { entry: {
main: './src/index.js' main: './src/index.js'
}, },
@ -21,26 +25,6 @@ module.exports = {
amd: 'vue' 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: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
@ -53,4 +37,4 @@ module.exports = {
} }
}) })
] ]
} });

View file

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

View file

@ -39,6 +39,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
<li><router-link to="/poptip">Poptip</router-link></li> <li><router-link to="/poptip">Poptip</router-link></li>
<li><router-link to="/slider">Slider</router-link></li> <li><router-link to="/slider">Slider</router-link></li>
<li><router-link to="/dropdown">Dropdown</router-link></li> <li><router-link to="/dropdown">Dropdown</router-link></li>
<li><router-link to="/breadcrumb">Breadcrumb</router-link></li>
</ul> </ul>
</nav> </nav>
<router-view></router-view> <router-view></router-view>

View file

@ -120,6 +120,10 @@ const router = new VueRouter({
{ {
path: '/dropdown', path: '/dropdown',
component: require('./routers/dropdown.vue') component: require('./routers/dropdown.vue')
},
{
path: '/breadcrumb',
component: require('./routers/breadcrumb.vue')
} }
] ]
}); });

View file

@ -0,0 +1,35 @@
<style>
.demo-breadcrumb-separator{
color: #ff5500;
padding: 0 5px;
}
</style>
<template>
<div>
<Breadcrumb separator="<b class='demo-breadcrumb-separator'>=></b>">
<Breadcrumb-item href="/">Home4</Breadcrumb-item>
<Breadcrumb-item href="/components/breadcrumb">Components</Breadcrumb-item>
<Breadcrumb-item>Breadcrumb</Breadcrumb-item>
</Breadcrumb>
<Breadcrumb separator="">
<Breadcrumb-item href="/">
<template>Home</template>
<template slot="separator">
<b style="color: #ff5500;">-></b>
</template>
</Breadcrumb-item>
<Breadcrumb-item href="/components/breadcrumb">
<template>Breadcrumb</template>
<template slot="separator">
<b style="color: #ff5500;">-></b>
</template>
</Breadcrumb-item>
<Breadcrumb-item>Breadcrumb</Breadcrumb-item>
</Breadcrumb>
</div>
</template>
<script>
export default {
}
</script>

View file

@ -25,7 +25,8 @@
"dist:prod": "webpack --config build/webpack.dist.prod.config.js", "dist:prod": "webpack --config build/webpack.dist.prod.config.js",
"dist": "npm run dist:style && npm run dist:dev && npm run dist:prod", "dist": "npm run dist:style && npm run dist:dev && npm run dist:prod",
"lint": "eslint --fix --ext .js,.vue src", "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" "prepublish": "npm run dist"
}, },
"repository": { "repository": {
@ -54,11 +55,15 @@
"babel-plugin-transform-runtime": "^6.12.0", "babel-plugin-transform-runtime": "^6.12.0",
"babel-preset-es2015": "^6.9.0", "babel-preset-es2015": "^6.9.0",
"babel-runtime": "^6.11.6", "babel-runtime": "^6.11.6",
"chai": "^3.5.0",
"cross-env": "^3.1.4",
"css-loader": "^0.23.1", "css-loader": "^0.23.1",
"eslint": "^3.12.2", "eslint": "^3.12.2",
"eslint-plugin-html": "^1.7.0", "eslint-plugin-html": "^1.7.0",
"extract-text-webpack-plugin": "^2.0.0", "extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.8.5", "file-loader": "^0.8.5",
"friendly-errors-webpack-plugin": "^1.6.1",
"function-bind": "^1.1.0",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.1", "gulp-autoprefixer": "^3.1.1",
"gulp-clean-css": "^2.0.13", "gulp-clean-css": "^2.0.13",
@ -66,8 +71,21 @@
"gulp-rename": "^1.2.2", "gulp-rename": "^1.2.2",
"html-loader": "^0.3.0", "html-loader": "^0.3.0",
"html-webpack-plugin": "^2.28.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": "^2.7.1",
"less-loader": "^2.2.3", "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", "style-loader": "^0.13.1",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"vue": "^2.2.1", "vue": "^2.2.1",
@ -78,6 +96,11 @@
"vue-style-loader": "^1.0.0", "vue-style-loader": "^1.0.0",
"vue-template-compiler": "^2.2.1", "vue-template-compiler": "^2.2.1",
"webpack": "^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"
} }
} }

View file

@ -6,8 +6,10 @@
<span v-else :class="linkClasses"> <span v-else :class="linkClasses">
<slot></slot> <slot></slot>
</span> </span>
<span :class="separatorClasses"> <span :class="separatorClasses" v-html="separator" v-if="!showSeparator">
<slot name="separator">{{{ separator }}}</slot> </span>
<span :class="separatorClasses" v-else>
<slot name="separator"></slot>
</span> </span>
</span> </span>
</template> </template>
@ -18,12 +20,17 @@
props: { props: {
href: { href: {
type: String type: String
},
separator: {
type: String,
default: '/'
} }
}, },
data () {
return {
separator: '',
showSeparator: false
};
},
mounted () {
this.showSeparator = this.$slots.separator !== undefined;
},
computed: { computed: {
linkClasses () { linkClasses () {
return `${prefixCls}-link`; return `${prefixCls}-link`;

View file

@ -18,7 +18,7 @@
return `${prefixCls}`; return `${prefixCls}`;
} }
}, },
compiled () { mounted () {
this.updateChildren(); this.updateChildren();
}, },
methods: { methods: {

View file

@ -5,7 +5,7 @@ import Affix from './components/affix';
import Alert from './components/alert'; import Alert from './components/alert';
// import BackTop from './components/back-top'; // import BackTop from './components/back-top';
import Badge from './components/badge'; import Badge from './components/badge';
// import Breadcrumb from './components/breadcrumb'; import Breadcrumb from './components/breadcrumb';
import Button from './components/button'; import Button from './components/button';
import Card from './components/card'; import Card from './components/card';
import Carousel from './components/carousel'; import Carousel from './components/carousel';
@ -51,8 +51,8 @@ const iview = {
Alert, Alert,
// BackTop, // BackTop,
Badge, Badge,
// Breadcrumb, Breadcrumb,
// BreadcrumbItem: Breadcrumb.Item, BreadcrumbItem: Breadcrumb.Item,
// iButton: Button, // iButton: Button,
Button, Button,
ButtonGroup: Button.Group, ButtonGroup: Button.Group,

18
test/unit/index.js Normal file
View file

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

33
test/unit/karma.conf.js Normal file
View file

@ -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' },
]
},
});
};

View file

@ -0,0 +1,24 @@
import { createVue, destroyVM } from '../util';
describe('Breadcrumb.vue', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});
it('create', done => {
vm = createVue(`
<Breadcrumb separator="<b class='demo-breadcrumb-separator'>=></b>">
<Breadcrumb-item href="/">Home4</Breadcrumb-item>
<Breadcrumb-item href="/components/breadcrumb">Components</Breadcrumb-item>
<Breadcrumb-item>Breadcrumb</Breadcrumb-item>
</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('<b class="demo-breadcrumb-separator">=&gt;</b>');
done();
});
});
});

85
test/unit/util.js Normal file
View file

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