背景
在参加完开课吧的web全栈课程途中,得知了开课吧团队(现花果山团队:hug-sun)fork了一份element-ui,帮助开发者学习vue3、组件化后,我就偷摸混进了他们的内部群里。
流程
具体流程可以参考@春去春又来的手摸手教程 PS:就是他摸的我。
需求
我这个小菜鸟,当然是找简单的组件,开始捏软柿子啦。这不就盯上了Link组件!
TDD
通过slot的方式,定义Link组件内容
测试
it('should show content', () => {
const content = 'Link'
const wrapper = mount(Link,{
slots: {
default: content
}
}
expect(wrapper.text()).toContain('Link')
})
代码实现
<template>
<a>
<span>
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink'
}
</script>
<style>
</style>
通过props.type控制Link组件的主题样式,可选值[primary/success/warning/danger/info]
测试
it('set the type, link displays the corresponding style', () => {
const type = 'primary'
const wrapper = mount(Link,{
props: {
type
}
}
expect(wrapper.classes()).toContain(`el-link--${type}`)
})
代码实现
<template>
<a :class="classes">
<span>
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink',
props: {
type: {
type: String,
default: 'default'
}
},
setup(props){
const classes = useClasses(props)
return {
classes
}
}
}
const useClasses = (props) => {
return [
props.type ? `el-link--${props.type}` : ''
]
}
</script>
<style>
</style>
通过props.disabled控制Link组件是否禁用
测试
it('set the disabled, link displays the corresponding style', () => {
const disabled = true
const wrapper = mount(Link, {
props: {
disabled
}
})
expect(wrapper.classes()).toContain('is-disabled')
})
代码实现
<template>
<a :class="classes">
<span>
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink',
props: {
type: {
type: String,
default: 'default'
},
disabled: {
type: Boolean,
default: false
}
},
setup(props){
const classes = useClasses(props)
return {
classes
}
}
}
const useClasses = (props) => {
return [
props.type ? `el-link--${props.type}` : '',
props.disabled && 'is-disabled'
]
}
</script>
<style>
</style>
通过props.underline控制Link组件是否显示下划线
测试
it('set the underline, link displays the corresponding style', () => {
const underline = true
const wrapper = mount(Link, {
props: {
underline
}
})
expect(wrapper.classes()).toContain('is-underline')
})
代码实现
<template>
<a :class="classes">
<span>
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink',
props: {
type: {
type: String,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
underline: {
type: Boolean,
default: true
}
},
setup(props){
const classes = useClasses(props)
return {
classes
}
}
}
const useClasses = (props) => {
return [
props.type ? `el-link--${props.type}` : '',
props.disabled && 'is-disabled',
props.underline && !props.disabled && 'is-underline'
]
}
</script>
<style>
</style>
通过props.href制定Link组件的图片
测试
it('set the href', () =>{
const href = 'https://element3-ui.com/'
const wrapper = mount(Link, {
props: {
href
}
}
expect(wrapper.attributes('href')).toBe(href)
})
代码实现
<template>
<a :class="classes" :href=" disabled ? null : href" v-bind="$attrs">
<span>
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink',
props: {
type: {
type: String,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
underline: {
type: Boolean,
default: true
},
href: String
},
setup(props){
const classes = useClasses(props)
return {
classes
}
}
}
const useClasses = (props) => {
return [
props.type ? `el-link--${props.type}` : '',
props.disabled && 'is-disabled',
props.underline && !props.disabled && 'is-underline'
]
}
</script>
<style>
</style>
通过props.icon指定Link组件的图片
测试
it('set the icon, link displays the corresponding style', () => {
const icon = 'el-icon-search'
const wrapper = mount(Link, {
props: {
icon
}
}
const i = wrapper.find('i')
expect(i.exists()).toBe(true)
expect(i.classes()).toContain(icon)
})
代码实现
<template>
<a :class="classes" :href=" disabled ? null : href" v-bind="$attrs">
<i :class="icon"></i>
<span>
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink',
props: {
type: {
type: String,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
underline: {
type: Boolean,
default: true
},
href: String,
icon: String
},
setup(props){
const classes = useClasses(props)
return {
classes
}
}
}
const useClasses = (props) => {
return [
props.type ? `el-link--${props.type}` : '',
props.disabled && 'is-disabled',
props.underline && !props.disabled && 'is-underline'
]
}
</script>
<style>
</style>
用户可以自定义click事件,但组件设置禁用或href属性后,用户的自定义事件静默失效
测试
it('should captured click events emitted via click', () => {
const wrapper = mount(Link)
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
it("when disabled prop is equal to true",() => {
const wrapper = mount(Link, {
props: {
disabled: true
}
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
it('when href prop is to be truthy', () => {
const wrapper = mount(Link, {
props: {
href: 'https://element3-ui.com/'
}
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
代码实现
<template>
<a :class="classes" :href=" disabled ? null : href" v-bind="$attrs" @click="handleClick">
<i :class="icon"></i>
<span>
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink',
props: {
type: {
type: String,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
underline: {
type: Boolean,
default: true
},
href: String,
icon: String
},
emits:['click'],
setup(props, { emit }){
const classes = useClasses(props)
const handleClick = (event) => {
if(props.disabled) return
if(props.href) return
emit('click', event)
}
return {
classes,
handleClick
}
}
}
const useClasses = (props) => {
return [
props.type ? `el-link--${props.type}` : '',
props.disabled && 'is-disabled',
props.underline && !props.disabled && 'is-underline'
]
}
</script>
<style>
</style>
TDD环节挺干的,你以为到这里就结束了吗,还有一些细节需要整理,比如用户没有指定icon,i标签需要加载吗?下面列出完整的代码跟测试
完整代码
<template>
<a
:class="classes"
:href="disabled ? null : href"
v-bind="$attrs"
@click="handleClick"
>
<i v-if="icon" :class="icon"></i>
<span v-if="$slots.default" class="el-link--inner">
<slot></slot>
</span>
</a>
</template>
<script>
export default {
name: 'ElLink',
props: {
type: {
type: String,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
underline: {
type: Boolean,
default: true
},
href: String,
icon: String
},
emits: ['click'],
setup(props, { emit }) {
const classes = useClasses(props)
const handleClick = (event) => {
if (props.disabled) return
if (props.href) return
emit('click', event)
}
return {
classes,
handleClick
}
}
}
const useClasses = (props) => {
return [
props.type ? `el-link--${props.type}` : '',
props.disabled && 'is-disabled',
props.underline && !props.disabled && 'is-underline'
]
}
</script>
<style></style>
完整测试代码
import { mount } from '@vue/test-utils'
import Link from '../Link.vue'
describe('Link', () => {
describe('props', () => {
it('initialize the Link component', () => {
const wrapper = mount(Link)
expect(wrapper.find('i').exists()).toBe(false)
expect(wrapper.find('span').exists()).toBe(false)
})
it('should show content', () => {
const content = 'Link'
const wrapper = mount(Link, {
slots: {
default: content
}
})
expect(wrapper.text()).toContain('Link')
})
it('set the type, link displays the corresponding style', () => {
const type = 'primary'
const wrapper = mount(Link, {
props: {
type
}
})
expect(wrapper.classes()).toContain(`el-link--${type}`)
})
it('set the disabled, link displays the corresponding style', () => {
const disabled = true
const wrapper = mount(Link, {
props: {
disabled
}
})
expect(wrapper.classes()).toContain('is-disabled')
})
it('set the underline, link displays the corresponding style', () => {
const underline = true
const wrapper = mount(Link, {
props: {
underline
}
})
console.log(wrapper.vm)
expect(wrapper.classes()).toContain('is-underline')
})
it('set the href', () => {
const href = 'https://element3-ui.com/'
const wrapper = mount(Link, {
props: {
href
}
})
expect(wrapper.attributes('href')).toBe(href)
})
it('set the icon, link displays the corresponding style', () => {
const icon = 'el-icon-search'
const wrapper = mount(Link, {
props: {
icon
}
})
const i = wrapper.find('i')
expect(i.exists()).toBe(true)
expect(i.classes()).toContain(icon)
})
it('should get target attr value', () => {
const wrapper = mount(Link, {
props: {
target: '_blank'
}
})
expect(wrapper.attributes('target')).toBe('_blank')
})
})
describe('click', () => {
it('should captured click events emitted via click', () => {
const wrapper = mount(Link)
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
describe("can't captured click event emitted", () => {
it('when disabled prop is equal to true', () => {
const wrapper = mount(Link, {
props: {
disabled: true
}
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
it('when href prop is to be truthy', () => {
const wrapper = mount(Link, {
props: {
href: 'https://element3-ui.com/'
}
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
})
})
})
覆盖率
总结
在这次重构组件中,体验到了测试的魅力,在有测试的保障下,我写代码的时候,逻辑更清晰了,如有神助,代码出错也能迅速定位,并解决。今天下单了《重构 改善既有代码的设计(第2版)》,让我们学起来,奥利给
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!