框架搭建 & 持续集成(一)
网站开发的流程
-
立项 - 确定要做,确定人员,确定预算等
-
需求 - 需求收集和分析
- 收集比分析更难,有的时候用户也不知道自己的需求
- 亨利·福特曾说过,「如果我最初是问消费者他们想要什么,他们应该是会告诉我,要一匹更快的马!」
- 可以用「用例图」来分析需求
-
可行性分析
-
系统设计(功能设计、框架设计)
- UML 图、时序图等
-
原型设计(草图、线框图)
- 草图用纸和笔画
- 线框图可以用 Balsamiq
-
交互设计
- 可以用 Axure RP、墨刀、Sketch.app
-
视觉设计
- 可以用 Photoshop、Fireworks、Sketch.app
-
程序开发
-
测试
-
功能预演
-
内测
-
灰度发布
-
正式发布
项目初始化
1. 本地新建目录,初始化为github仓库
2. 声明一下License
告诉别人项目是如何开源的
3. 用到哪些第三方的东西?- 『npm管理第三方包』
这里我们用npm,使用npm init
初始化项目生成package.json,使用npm对第三方的包进行管理
这样我们就选择了npm作为第三方库的来源
4. 用什么实现底层代码? -Vue
安装一下Vue npm i vue@2.5.17
使用webstorm控制git
webstorm的快捷键只需要记住两个:
- 按两次shift,调出搜索框,可以搜索任意内容
- 另外一个就是进入设置里面的keymap,搜索常用操作的快捷键
记得新建.gitignore
,像node_modules、.git、.vscode
都不需要提交到仓库
设置vcs(版本控制)的快捷键,填写提交备注,然后 commit and push
即可
这样按快捷键就可以调出窗口,使用git管理我们的代码了
5. 根目录下新建index,html,引入vue,创建vue实例
执行结果:
备注:记得勾选,否则容易出问题
创建一个button组件
1.初始化g-button
组件
根目录下新建button.js
,在index.html中引入这个js文件
在button.js
创建button组件
在index.html中使用这个组件g-button
2.开启 border-box
*{margin: 0;padding: 0;box-sizing: border-box;}
3.给字体大小、按钮高度设置变量 - 『css变量的使用』
按钮的高度和字体大小就可以由用于全局变量自行设置了
:root {
--button-height: 32px;
--font-size: 14px;
... ....
}
.g-button {
font-size: var(--font-size);
height: var(--button-height);
... ...
}
然后给g-button
中的模板添加这个样式
vue的特点就是可以使用单文件
目前代码存在的问题是:
组件是写在js文件中,html写在template中,样式写在html中的sytle标签中,相关的代码,被分别写字了三个不同的地方
vue可以用一个文件表示这三个语言
我们如果不借助构建工具就很难做到
我们需要选择什么作为我们的构建工具呢?
4.使用parcel构建工具 - 『parcel工具的使用,vue中注册组件,vue中使用scss&&scss的用法』
当涉及到新的知识,我们要到它的中文官网
如果一个包要给用户使用的就不用加 -D
,如安装vue的时候就不用写
如果一个包要给开发者使用就加-D
(developer开发者的意思)
安装它npm i -D parcel-bundler
创建src目录
在src下新建app.js 内容为:
new Vue({
el: '#app'
})
文件中的依赖我们可以使用import
现在app.js就是我们整个应用程序的入口,在这里面去依赖其他的东西
重构一下button.js
为vue的单文件button.vue
vue的单文件的格式为:
<template>
// html
</template>
<script>
// js
</script>
<style scoped>
// css
</style>
vue的单文件组件做了一个『语法糖』,我们不需要写Vue.component('g-button',{...})
// button.vue
<template>
</template>
<script>
Vue.component('g-button',{
template: `
<button class='g-button'>按钮</button>
`
})
</script>
<style scoped>
</style>
而是只要写一个对象就可以了,然后把这个对象export
出去就可以了
// button.vue
<template>
</template>
<script>
export default{
template: `
<button class='g-button'>按钮</button>
`
}
</script>
<style scoped>
</style>
也就是说script
只用做一件事情export default + 一个对象
然后,就是要把html放到<template><template/>
标签中
而css,我们更加倾向于使用scss
// button.vue
<template>
<button class='g-button'>按钮</button>
</template>
<script>
export default {}
</script>
<style lang="scss">
.g-button {
font-size: var(--font-size);
height: var(--button-height);
padding: 0 1em;
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
background: var(--button-bg);
&:hover {
border-color: var(--border-color-hover);
}
&:active {
background-color: var(--border-active-bg);
}
&:focus {
outline: none;
}
}
</style>
备注: 使用&
符号表示当前选择器
当前html还不认识我们使用的<g-button><g-button>/
组件,因此我们要在入口文件app.js
中引入vue和组件<g-button><g-button>/
,同时全局的注册这个<g-button><g-button>/
组件
// app.js
import Vue from "vue";
import Button from "./button.vue";
Vue.component('g-button',Button)
new Vue({
el: '#app'
})
然后尝试运行这个项目
vue在文档中说明了如何配置parcle
它的意思是使用vue的完整版,而不是使用vue的 run time 版
执行npm run dev
或者 npx parcel index.html --no-cache(在当前目录中找parcel并执行)
,parcel会自动的帮我们分析和安装项目中所有需要的依赖
这样,我们就成功的将最原始的js版本变成了parcle版本,也学会了vue的单文件组件的使用,它的作用是可以把html、css、js都放到一个文件中,这样可以让我们清晰的知道这个组件长什么样子、有哪些功能
parcel和webpack一样属于构建工具,parcel的好处就是不需要配置
看下项目文件的组织结构
html文件:
// html
<!DOCTYPE html>
<html lang="zh-Hanz">
<head>
<meta charset="UTF-8">
<title>G-Design-of-Vue</title>
<style>
//省略
</style>
</head>
<body>
<div id="app">
<g-button></g-button>
</div>
<script src="./src/app.js"></script>
</body>
</html>
app.js文件:
// app.js
import Vue from "vue";
import Button from "./button.vue";
Vue.component('g-button',Button)
new Vue({
el: '#app'
})
button.vue:
<template>
<button class='g-button'>按钮</button>
</template>
<script>
export default {}
</script>
<style lang="scss">
.g-button {
font-size: var(--font-size);
height: var(--button-height);
padding: 0 1em;
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
background: var(--button-bg);
&:hover {
border-color: var(--border-color-hover);
}
&:active {
background-color: var(--border-active-bg);
}
&:focus {
outline: none;
}
}
</style>
备注:
如果想知道:root
的兼容性请查看 Can I use,这个网站应该被每一个程序员熟知
如果:root
有兼容性问题,可以使用html{...}
,它其实就是一个选择器
如果此时你想去github查看提交的历史,推荐一个好用的工具:npm i -g git-open
运行git open
就会自动的跳转到github仓库
如果想查看全部具体的提交历史
知识点总结
- parcle的使用
- scss
- Vue单文件组件
- Webstorm快捷键
- CSS变量和:root选择器
- Vue.component 全局注册组件
不要妄图掌握所有知识点
前端的知识点比较宽泛
1.工具类
如: webpack、parcle、scss、less、bable等工具,更新速度是特别快的,知道怎么用就行了
2.语言特性(一定要深入了解)
Promise、async、异步、class、继承等
3.抽象
代码组织、流程、设计模式(阶梯式融入,项目中慢慢体会)
(备注:如果代码没问题,考虑下是不是工具出了问题,有可能是使用了缓存,要去除缓存,如--node-cache
)
添加icon
按钮文字用户自定义 - 『vue中的slot插槽』
当前按钮存在的问题是,按钮中的文字是写死的,应该让用户传进去
这时就用到了vue的特性插槽
定义插槽
// g-button
<template>
<button class='g-button'>
<slot></slot>
</button>
</template>
使用插槽
<g-button>vue插槽</g-button>
为按钮添加icon - 『iconfont中的symbol引入法』
接下来,我们要为按钮上添加icon
我们需要去iconfont上查找自己喜欢的icon,添加至自己新建的项目中
修改icon class的前缀
修改icon名为英文名
修改icon的位置和大小
批量去色,颜色我们自己通过css控制
选择symbol,生成代码
得到的JS,就是最终我们想要的JS,那么我们怎么使用这个JS呢?
点开使用帮助,把symbol那章看完,就知道怎么用了
第一步:拷贝项目下面生成的symbol代码:
<script src="//at.alicdn.com/t/c/font_4146498_50t4e1zkx2g.js"></script>
此时就把svg添加到页面中了
第二步:加入通用css代码(引入一次就行):
<style>
.icon {
width: 1.5em;
height: 1.5em;
}
</style>
第三步:挑选相应图标并获取类名,应用于页面:
<div id="app">
<g-button><svg class="icon"><use xlink:href="#i-setting"></use></svg>vue插槽</g-button>
</div>
执行结果
用户只要简单 - 『将用户不关心的代码放到定义组件的地方』
看下现在的代码的问题:
如果是站在使用组件人的角度看,下面的代码太不友好了,对于使用者来说,他只需要setting这个图标就行了
// index.html
<div id="app">
<g-button>
<svg class="icon">
<use xlink:href="#i-setting"></use>
</svg>
vue插槽
</g-button>
</div>
因此,我们要将组件中的代码,移到组件中去
// index.html
<div id="app">
<g-button>
vue插槽
</g-button>
</div>
// button.vue
<template>
<button class='g-button'>
<svg class="icon">
<use xlink:href="#i-setting"></use>
</svg>
<slot></slot>
</button>
</template>
代码移动了,但是效果是一样的
接受传参,指定icon -『 vue中的动态属性&&props』
现在的问题是要让用户选择使用什么icon
因此,要把"#i-setting"
中的内容当做参数,让用户传进来
Vue如何接受外部的传参?用props
// index.html
<div id="app">
<g-button icon="setting">
vue插槽
</g-button>
</div>
// button.vue
<template>
<button class='g-button'>
<svg class="icon">
<use :xlink:href=`#i-${icon}`></use>
</svg>
<slot></slot>
</button>
</template>
<script>
export default {
props:['icon']
}
</script>
备注:
<use :xlink:href=`#i-${icon}`>中加了冒号
它是vue指令v-bind的缩写
,它的意思是要绑定属性且属性xlink
(值是动态的),相当于等于号里面的就是js代码。${icon}
叫模板字符串的插值,插入了一个icon,icon的值是从props
中获取到的,props中的值是从 <g-button icon="setting"></g-button>
中传过来的,最终${icon}
的值就是setting
一个bug:用户不传参数icon -『 vue中使用v-if判断』
这样其实存在一个bug,如果用户不传参数icon,页面上还是会有svg,且href='#i-undefined'
也就是说,如果用户如果不传参数icon,就得将这个svg隐藏起来
<svg class="icon" v-if="icon">
<use :xlink:href=`#i-${icon}`></use>
</svg>
这样2个按钮就可以正常显示了
icon在右边呢?- 『vue中添加class的方法』
如果用户想让icon在右边怎么办?
下面进行一下用户的需求分析
首先会排除两边都有icon的情况,因为不常见,很少场景会用到。因此,我们只要考虑icon在左边,还是在右边
因此我们就要让button组件,接收外部传进来的参数icon-position
如下图所示,把变量iconPositin
当做key来绑定class,如:当iconPositin
的值等于left
的时候,添加的class就是class='left'
让样式不叫left
或 right
,而是叫icon-left
这样更容易理解
<button class='g-button' :class="{[`icon-${iconPosition}`]: true}">
</button>
下面就可以让样式决定icon在左边还是在右边了
// template
<button class='g-button' :class="{[`icon-${iconPosition}`]: true}">
<svg class="icon" v-if="icon">
<use :xlink:href=`#i-${icon}`></use>
</svg>
<span class="content">
<slot></slot>
</span>
</button>
// scss
.g-button {
display: inline-flex;justify-content: center;align-items: center;vertical-align: middle;
> .icon{order: 1;margin-right: .3em}
> .content{order: 2;}
&.icon-right {
> .icon{order: 2;}
> .content{order: 1;}
}
备注:
-
如果发现内联元素上下不对齐,那么就给这个内联元素加
vartical-align: middle;
如:display:inline-flex;
的元素上,所有加了inline
的样式都有这个毛病 -
如果想让写css更快,就在webstorm中设置打开模糊搜索
-
由于
<slot></slot>
中不能加class,所以要在它外面加上一个div -
上面scss的意思是默认情况下,icon在前,content在后
默认值 - 『props对象形式的写法』
下面继续优化,如果用户没有传参数iconPosition
,那么就给个默认值left
props除了之前说的数组的写法,还可以写成对象的形式
export default {
props: {
icon: {},
iconPosition:{
type: String,
default: 'left'
}
}
}
这样即使不传参数iconPosition
也会有默认值了
如果iconPosition的值为up? - 『props中加入validator:属性的检查器』
我们当前只提供icon在按钮的左边(默认)和右边(iconPosition=right),如果用户将参数iconPosition
传了up
该怎么办?
props: {
icon: {},
iconPosition:{
type: String,
default: 'left',
validator(value) {
if(value !== 'left' && value !== 'right'){
return false
}else{
return true
}
}
}
}
备注:
只有当API运用到实际的场景的需求中时,才能知道怎么用,什么时候用
代码优化- 『Webstorm提供的代码优化功能』
return
的布尔值是根据value !== 'left' && value !== 'right'
结果的布尔值来确定的
webstom中有一个很牛的功能,就是简化代码
validator(value) {
return !(value !== 'left' && value !== 'right');
}
将 svg 代码整理到 icon.vue
场景:
如果用户在页面的其他地方也想引入icon呢?那么它就会这么写:
<svg class='icon'>
<use :xlink:href=`#i-download`></use>
</svg>
我们发现这部分代码,不就和Button.vue
中引入图标的方法一样了吗?
重复就是bug,如果几处代码一样,就应该将他们抽离出来,放到一个地方
上面两个地方都使用了svg
代码几乎都一样,我们不妨将这部分代码组件化,放到单独的组件中
// 定义icon.vue 组件
<script>
export default ({
props:['name']
})
</script>
<template>
<!-- 为了防止与组件外面叫icon的名字冲突,所以叫g-icon,表示组件中的样式-->
<svg class="g-icon">
<use :xlink:href=`#i-${name}`></use>
</svg>
</template>
<style scoped lang="scss">
.g-icon {
width: 1.5em;
height: 1.5em;
}
</style>
// app.js中注册组件
Vue.component('g-icon',Icon)
// 使用组件
<g-icon class="icon" v-if="icon" :name="icon"></g-icon>
备注:
// 变量icon
<g-icon :name="icon"></g-icon>
// 字符串 icon
<g-icon name="icon"></g-icon>
添加菊花
iconfont中将icon添加至项目,改名字,编辑icon居中及大小,记得更新代码
将得到的新的js替换之前在html中引入的js
添加菊花动画 - 『css3动画』
@keyframes spin {
0%{transform: rotate(0deg);}
100%{transform: rotate(360deg)}
}
.loading{
animation: spin 1.5s linear infinite;
}
添加loading
属性传值loading
接下来要做的是,如果按钮有loading状态就展示loading状态,如果没有loading就不展示loading状态
// button.vue
props: {
icon: {},
loading:{
type: Boolean,
default: false
},
...
}
<template>
<button class='g-button' :class="{[`icon-${iconPosition}`]: true}">
<g-icon name="loading" v-if="loading" class="loading"></g-icon>
<g-icon v-if="icon && !loading" :name="icon" class="icon"></g-icon>
...
</template>
// index.html
<g-button :loading=true>
按钮
</g-button>
备注:
属性前加冒号和不加冒号的区别
<g-button icon="setting" loading="true">
按钮
</g-button>
如果不加冒号,那么true
就是字符串true
<g-button icon="setting" :loading="true">
按钮
</g-button>
如果加冒号,那么true
就是JS中的true,其实上面中的true可以不加双引号
<g-button icon="setting" :loading=true>
按钮
</g-button>
loading怎么都在左边了?
icon在右边,添加loading状态也应该在右边
如何解决这个问题?
就是让普通的icon和loading状态的icon,使用同一个class
// button.vue
<template>
<button class='g-button' :class="{[`icon-${iconPosition}`]: true}">
<g-icon name="loading" v-if="loading" class="loading icon"></g-icon>
<g-icon v-if="icon && !loading" :name="icon" class="icon"></g-icon>
<span class="content">
<slot></slot>
</span>
</button>
</template>
添加click事件
为什么点击button,loading1状态的值没有发生变化呢?
// app.js
new Vue({
el: '#app',
loading1: false
})
// button.vue
<g-button :loading="loading1" @click="!loading1">
按钮
</g-button>
因为g-button
是一个组件,组件中那么多元素,怎么知道click事件是加在哪个元素上呢?
因此,我们得告诉外面,组件中哪个元素触发这个click事件
<template>
<button class='g-button' :class="{[`icon-${iconPosition}`]: true}" @click="this.$emit('click')">
<g-icon name="loading" v-if="loading" class="loading icon"></g-icon>
<g-icon v-if="icon && !loading" :name="icon" class="icon"></g-icon>
<span class="content">
<slot></slot>
</span>
</button>
</template>
备注: 在模板中不用写this
,因此this.$emit('click')
也可以改为$emit('click')
上面代码的意思就是当g-button
组件中的button按钮被点击的时候,就触发外面的click事件
相当于
<template>
<button class='g-button' :class="{[`icon-${iconPosition}`]: true}" @click="x">
... ...
</button>
</template>
<script>
export default {
...
methods:{
x(){
this.$emit('click')
}
}
}
</script>
多个按钮合并为一行
如果站在使用者的角度来想,如果可以写成下面这样的代码是不是可以接受?
<g-button-group>
<g-button icon="left">上一页</g-button>
<g-button icon="right" icon-position="right">下一页</g-button>
</g-button-group>
接下来我们要做的就是实现这个<g-button-group></g-button-group>
组件
// g-button-group
<template>
<div class="g-button-group">
<slot></slot>
</div>
</template>
<script>
export default {
name: "g-button-group"
}
</script>
<style lang="scss" scoped>
.g-button-group{
display: inline-flex;
vertical-align: middle;
> .g-button{
border-radius: 0;
&:first-child{
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
}
&:last-child{
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
}
}
</style>
备注
slot
不能作为跟节点,因为插入到slot中的内容有可能是不止一个,因此要在<slot></slot>
外包一个div标签
解决边框合并
给<g-button></g-button>
添加margin-left: -1px;
但是当鼠标浮在button上之后,由于后面的button会盖在前面button的上面
因此当鼠标悬浮到button上的时候,让这个button的z-index:1;
这样既解决了border的合并,又解决了鼠标悬浮时样式的bug
> .g-button{
border-radius: 0;
margin-left: -1px;
&:hover{
position: relative;
z-index: 1;
}
}
阻止用户瞎搞 - 『mounted钩子函数』
如果用户在使用<g-button-group></g-button-group>
的时候,给里面的外面包一层div呢?
样式就会出现问题
那么如何阻止用户瞎搞呢?
在g-button-group
组件的mounted钩子函数中,检查一下它的儿子
mounted() {
for(let node of this.$el.children){
let name = node.nodeName.toLowerCase()
if(name !== 'button'){
console.warn(`g-button-group的子元素只能是 g-button,但是你确写的是${name}`)
}
}
}
备注
$childre
只能获取到当前实例的子组件- this.$el 获取Vue 实例使用的根 DOM
如果希望颜色显示在webstorm就做下面这样的设置
如果忘了之前代码修改了什么,我们可以查看本地历史
单元测试与mock
单元测试:通过代码测试代码
chai.js
Chai 是一个用于节点和浏览器的 BDD / TDD 断言库
BDD: Behavior Driven Development,行为驱动开发
TDD: Test Driven Development,测试驱动开发
Assert: 断言(主观认为),浏览器中有自带的断言,如果断言是对的,就什么事情都不会发生。如果断言是错的,就会抛出一个错误
我们需要借助chaijs来做单元测试,它能让我们使用断言的时候更加方便
chaijs提供了三种方式的断言:should、expect(推荐用这个) 和 assert
通过断言来做单元测试
安装chai.js
npm i -D chai
使用chai.js做单元测试
通过JS把按钮写到页面上
// index.html
<div id="test"></div>
// 单元测试 app.js
{
const Constructor = Vue.extend(Button)
const button = new Constructor({
propsData:{
icon:'setting'
}
})
button.$mount('#test')
}
备注
- 将Button组件变成构造函数
- 通过构造函数创建实例
- 将实例挂载到DOM元素上
Vue.extend()
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。作用是将对象变成函数$mount
如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用vm.$mount()
手动地挂载一个未挂载的实例- 如果想往使用
new
创建的实例中添加属性,就要使用propsData
单元测试icon
// 单元测试
{
const Constructor = Vue.extend(Button)
const button = new Constructor({
propsData:{
icon:'setting'
}
})
button.$mount('#test')
let useElement = button.$el.querySelector('use')
let href = useElement.getAttribute('xlink:href')
expect(href).to.eq('#i-setting')
}
如果控制台没有报错,说明测试用例通过了
单元测试就是传一个输入,然后从输出里看输入的值是否和输出的值匹配。如例子中输入的icon等于setting
,那么输出的use的href就是#i-setting
备注:
如果mount的时候不挂载到一个元素上也是可以的,button.$mount()
,那么就是挂载到内存中
那么我们怎么只要测试哪些东西呢?
我们只要看下button组件的输入参数即可
针对button组件来说,至少要写4个测试用例,分别针对输入参数:icon loading iconPosition
和 一个click事件
单元测试loading
// 单元测试loading
{
const Constructor = Vue.extend(Button);
const button = new Constructor({
propsData: {
icon: 'setting',
loading: true
}
});
// mount到内存中
button.$mount();
let useElement = button.$el.querySelector('use');
let href = useElement.getAttribute('xlink:href');
expect(href).to.eq('#i-loading');
}
单元测试svg的样式order
如果button上的icon为setting那么这个button的order是1
// 单元测试svg的样式order
{
const Constructor = Vue.extend(Button);
const button = new Constructor({
propsData: {
icon: 'setting'
}
});
// mount到内存中
button.$mount();
let svg = button.$el.querySelector('svg');
let order = window.getComputedStyle(svg).order;
expect(order).to.eq(1);
}
为什么断言是错的?
因为没有将button元素挂载到页面上,如果元素不在页面上就说明页面不渲染这个元素,那么css就不会加在这个button上
备注:
window.getComputedStyle()
方法用于获取指定元素的 CSS 样式
因此,我们需要创建一个DOM元素,将button挂载到这个DOM元素上
// 单元测试svg的样式order
{
const div = document.createElement('div')
document.body.appendChild(div)
const Constructor = Vue.extend(Button);
const button = new Constructor({
propsData: {
icon: 'setting'
}
});
// mount到内存中
button.$mount(div);
let svg = button.$el.querySelector('svg');
let order = window.getComputedStyle(svg).order;
expect(order).to.eq('1');
}
这样button就会出现在页面上,样式也就加到button上了
控制台中没有报错,说明测试用例是正确的
单元测试iconPosition
如果button上给一个iconPosition那么它的order应该变成2
// 单元测试iconPosition
{
const div = document.createElement('div')
document.body.appendChild(div)
const Constructor = Vue.extend(Button);
const button = new Constructor({
propsData: {
icon: 'setting',
iconPosition: 'right'
}
});
// mount到内存中
button.$mount(div);
let svg = button.$el.querySelector('svg');
let order = window.getComputedStyle(svg).order;
expect(order).to.eq('2');
}
断言完成后记得“打扫战场”
添加测试用例后发现一个问题: 页面会变得很乱
因此我们在添加测试用例,测试完成后,应该删除button元素,并且删除button对象
// 单元测试iconPosition
{
const div = document.createElement('div');
document.body.appendChild(div);
const Constructor = Vue.extend(Button);
const button = new Constructor({
propsData: {
icon: 'setting',
iconPosition: 'right'
}
});
// mount到内存中
button.$mount(div);
let svg = button.$el.querySelector('svg');
let order = window.getComputedStyle(svg).order;
expect(order).to.eq('2');
// “打扫战场”
button.$el.remove();
button.$destroy();
}
备注
- 如果断言错误就不会执行“打扫战场”的2句
$destroy
完全销毁vue实例remove()
把对象从它所属的 DOM 树中删除$el
:指向当前对象对应的的DOM元素
单元测试按钮可以监听click
// 单元测试 监听button组件的click事件
{
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: 'setting',
iconPosition: 'right'
}
});
vm.$mount();
vm.$on('click', function () {
expect(1).to.eq(1)
});
let button = vm.$el;
button.click();
}
button被点击,那么监听在组件上的click事件就会被触发,就会执行断言
但是这种测试用例的写法是错的
我们想要实现的效果是click
中的回调function
被执行
备注
g-button
是组件,它里面有button按钮$on
:监听当前实例上的自定义事件- webstorm中如果想对多个重复变量重命名,可以使用快捷键shift + F6,将button重命名为vm
我们需要使用函数的mock
(mock的意思是希望某些东西是受控制的,而不是使用真正的函数),需要借助一个库spies,(spy:间谍的复数形式)
spies是断言库chai的附加插件。它提供了最基本的间谍能力和测试功能。
意思就是派一个间谍去监听那个回调函数function
安装: npm install -D chai-spies
import chai from 'chai'
import spies from 'chai-spies'
chai.use(spies)
// 单元测试 监听button组件的click事件
{
const Constructor = Vue.extend(Button);
const vm = new Constructor({
propsData: {
icon: 'setting',
iconPosition: 'right'
}
});
vm.$mount();
let spy = chai.spy(function (){})
vm.$on('click', spy);
let button = vm.$el;
button.click();
expect(spy).to.have.been.called()
}
备注
- 不需要使用匿名函数,而是使用间谍函数,这个匿名函数被“间谍”监听了
- 当button被点击后,期望间谍被调用
- 这样就能确保点击Button组件后,button按钮的click事件被触发
但是这样测试是不是有点麻烦,需要使用parcel运行项目,打开浏览器和控制台,能不能一行命令搞定,使用自动化测试?
转载自:https://juejin.cn/post/7254005872149921850