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
*.swo
*.log
test/dist/
examples/dist/
dist/
yarn-error.log
test/unit/coverage

View file

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

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 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()
]
};
});

View file

@ -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 = {
}
})
]
}
});

View file

@ -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 = {
}
})
]
}
});

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="/slider">Slider</router-link></li>
<li><router-link to="/dropdown">Dropdown</router-link></li>
<li><router-link to="/breadcrumb">Breadcrumb</router-link></li>
</ul>
</nav>
<router-view></router-view>

View file

@ -120,6 +120,10 @@ const router = new VueRouter({
{
path: '/dropdown',
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": "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"
}
}

View file

@ -6,8 +6,10 @@
<span v-else :class="linkClasses">
<slot></slot>
</span>
<span :class="separatorClasses">
<slot name="separator">{{{ separator }}}</slot>
<span :class="separatorClasses" v-html="separator" v-if="!showSeparator">
</span>
<span :class="separatorClasses" v-else>
<slot name="separator"></slot>
</span>
</span>
</template>
@ -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`;

View file

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

View file

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

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