likes
comments
collection
share

框架搭建 & 持续集成(一)

作者站长头像
站长
· 阅读数 69

网站开发的流程

  1. 立项 - 确定要做,确定人员,确定预算等

  2. 需求 - 需求收集和分析

    • 收集比分析更难,有的时候用户也不知道自己的需求
    • 亨利·福特曾说过,「如果我最初是问消费者他们想要什么,他们应该是会告诉我,要一匹更快的马!」
    • 可以用「用例图」来分析需求
  3. 可行性分析

  4. 系统设计(功能设计、框架设计)

    • UML 图、时序图等
  5. 原型设计(草图、线框图)

    • 草图用纸和笔画
    • 线框图可以用 Balsamiq
  6. 交互设计

    • 可以用 Axure RP、墨刀、Sketch.app
  7. 视觉设计

    • 可以用 Photoshop、Fireworks、Sketch.app
  8. 程序开发

  9. 测试

  10. 功能预演

  11. 内测

  12. 灰度发布

  13. 正式发布

项目初始化

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的快捷键只需要记住两个:

  1. 按两次shift,调出搜索框,可以搜索任意内容
  2. 另外一个就是进入设置里面的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'

框架搭建 & 持续集成(一)

让样式不叫leftright,而是叫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做单元测试

Vue文档中介绍了单元测试

通过JS把按钮写到页面上


// index.html
<div id="test"></div>

// 单元测试  app.js
{
    const Constructor = Vue.extend(Button)
    const button = new Constructor({
        propsData:{
            icon:'setting'
        }
    })
    button.$mount('#test')
}

框架搭建 & 持续集成(一)

备注

  1. 将Button组件变成构造函数
  2. 通过构造函数创建实例
  3. 将实例挂载到DOM元素上
  4. Vue.extend()使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。作用是将对象变成函数
  5. $mount如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例
  6. 如果想往使用 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
评论
请登录