refactor(components): [rate] switch to script-setup syntax (#6565)

Co-authored-by: 三咲智子 <sxzz@sxzz.moe>
This commit is contained in:
btea 2022-03-13 20:20:21 +08:00 committed by GitHub
parent 61cfd369dc
commit 18bd8c4952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 303 additions and 373 deletions

View File

@ -20,6 +20,7 @@
"packages/components/icon/",
"packages/components/link/",
"packages/components/page-header/",
"packages/components/rate/",
"packages/components/row/",
"packages/components/slot/",
"packages/components/tag/",

View File

@ -1,160 +0,0 @@
import { mount } from '@vue/test-utils'
import Rate from '../src/rate.vue'
describe('Rate.vue', () => {
test('create', () => {
const wrapper = mount(Rate, {
props: {
max: 10,
},
})
const stars = wrapper.findAll('.el-rate__item')
expect(stars.length).toEqual(10)
})
test('size', async () => {
const wrapper = mount(Rate, {
props: {
size: 'large',
},
})
expect(wrapper.find('.el-rate--large').exists()).toBe(true)
await wrapper.setProps({ size: '' })
expect(wrapper.find('.el-rate--default').exists()).toBe(true)
})
test('allow half', async () => {
const wrapper = mount({
template: `
<div>
<el-rate v-model="value" allow-half ref='rate' />
</div>
`,
props: {},
data() {
return {
value: 0,
}
},
components: {
'el-rate': Rate,
},
})
const vm = wrapper.vm
const secondStar = wrapper.findAll('.el-rate__item')[1]
.element as HTMLElement
vm.$refs.rate.setCurrentValue(1, { target: secondStar, offsetX: 0 })
// expect(vm.$refs.rate.currentValue).toEqual(0.5)
secondStar.click()
vm.$refs.rate.resetCurrentValue()
expect(vm.value).toEqual(0.5)
})
test('with texts', () => {
const wrapper = mount(Rate, {
props: {
showText: true,
modelValue: 4,
texts: ['1', '2', '3', '4', '5'],
},
})
const text = wrapper.find('.el-rate__text').element
expect(text.textContent).toEqual('4')
})
test('value change', async () => {
const wrapper = mount(Rate, {
props: {
modelValue: 0,
},
})
const vm = wrapper.vm
await wrapper.setProps({ modelValue: 3 })
expect(vm.modelValue).toEqual(3)
})
test('click', () => {
const wrapper = mount({
template: `
<div>
<el-rate v-model="value1" />
</div>
`,
props: {},
data() {
return {
value1: 0,
}
},
components: {
'el-rate': Rate,
},
})
const vm = wrapper.vm
const thirdStar = wrapper.findAll('.el-rate__item')[2]
.element as HTMLElement
thirdStar.click()
expect(vm.value1).toEqual(3)
})
test('colors', () => {
const wrapper = mount({
template: `
<div>
<el-rate v-model="value" :colors="['#99A9BF', '#F7BA2A', '#FF9900']"></el-rate>
</div>
`,
props: {},
data() {
return {
value: 4,
}
},
components: {
'el-rate': Rate,
},
})
// const vm = wrapper.vm
const thirdStar = (
wrapper.findAll('.el-rate__item')[2].element as HTMLElement
).querySelector('.el-rate__icon') as any
expect(thirdStar.style.color).toEqual('rgb(255, 153, 0)')
})
test('change event', () => {
const wrapper = mount({
template: `
<div>
<el-rate v-model="value" @change="handleChange"></el-rate>
</div>
`,
data() {
return {
value: 4,
changeCount: 0,
}
},
methods: {
handleChange() {
this.changeCount++
},
},
components: {
'el-rate': Rate,
},
})
const vm = wrapper.vm
const fourthStar = wrapper.findAll('.el-rate__item')[3]
.element as HTMLElement
fourthStar.click()
expect(vm.value).toEqual(4)
expect(vm.changeCount).toEqual(0)
const fifthStar = wrapper.findAll('.el-rate__item')[4]
.element as HTMLElement
fifthStar.click()
expect(vm.value).toEqual(5)
expect(vm.changeCount).toEqual(1)
})
})

View File

@ -0,0 +1,114 @@
import { ref } from 'vue'
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Rate from '../src/rate.vue'
import type { RateInstance } from '../src/rate'
describe('Rate.vue', () => {
it('create', () => {
const wrapper = mount(Rate, {
props: {
max: 10,
},
})
const stars = wrapper.findAll('.el-rate__item')
expect(stars.length).toMatchInlineSnapshot('10')
})
it('size', async () => {
const wrapper = mount(Rate, {
props: {
size: 'large',
},
})
expect(wrapper.find('.el-rate--large').exists()).toBe(true)
await wrapper.setProps({ size: '' })
expect(wrapper.find('.el-rate--default').exists()).toBe(true)
})
it('allow half', async () => {
const value = ref(0)
const wrapper = mount(() => <Rate v-model={value.value} allowHalf />)
const vm = wrapper.getComponent(Rate).vm as RateInstance
const secondStar = wrapper.findAll('.el-rate__item')[1]
.element as HTMLElement
vm.setCurrentValue(1, {
target: secondStar,
offsetX: 0,
} as any as MouseEvent)
secondStar.click()
vm.resetCurrentValue()
expect(value.value).toEqual(0.5)
})
it('with texts', () => {
const wrapper = mount(Rate, {
props: {
showText: true,
modelValue: 4,
texts: ['1', '2', '3', '4', '5'],
},
})
const text = wrapper.find('.el-rate__text').element
expect(text.textContent).toMatchInlineSnapshot('"4"')
})
it('value change', async () => {
const wrapper = mount(Rate, {
props: {
modelValue: 0,
},
})
const vm = wrapper.vm
await wrapper.setProps({ modelValue: 3 })
expect(vm.modelValue).toMatchInlineSnapshot('3')
})
it('click', () => {
const value1 = ref(0)
const wrapper = mount(() => <Rate v-model={value1.value} />)
const thirdStar = wrapper.findAll('.el-rate__item')[2]
.element as HTMLElement
thirdStar.click()
expect(value1.value).toEqual(3)
})
it('colors', () => {
const value = ref(4)
const wrapper = mount(() => (
<Rate v-model={value.value} colors={['#99A9BF', '#F7BA2A', '#FF9900']} />
))
const thirdStar = (
wrapper.findAll('.el-rate__item')[2].element as HTMLElement
).querySelector<HTMLElement>('.el-rate__icon')!
expect(thirdStar.style.color).toMatchInlineSnapshot('"rgb(255, 153, 0)"')
})
it('change event', () => {
const value = ref(4)
const changeCount = ref(0)
const handleChange = () => {
changeCount.value++
}
const wrapper = mount(() => (
<Rate v-model={value.value} onChange={handleChange} />
))
const fourthStar = wrapper.findAll('.el-rate__item')[3]
.element as HTMLElement
fourthStar.click()
expect(value.value).toEqual(4)
expect(changeCount.value).toEqual(0)
const fifthStar = wrapper.findAll('.el-rate__item')[4]
.element as HTMLElement
fifthStar.click()
expect(value.value).toEqual(5)
expect(changeCount.value).toEqual(1)
})
})

View File

@ -1,6 +1,6 @@
<template>
<div
:class="rateKls"
:class="rateClasses"
role="slider"
:aria-valuenow="currentValue"
:aria-valuetext="text"
@ -44,19 +44,15 @@
</span>
</div>
</template>
<script lang="ts">
import { defineComponent, inject, computed, ref, watch } from 'vue'
import { isObject, isArray } from '@vue/shared'
import { formContextKey } from '@element-plus/tokens'
import { hasClass } from '@element-plus/utils'
<script lang="ts" setup>
import { inject, computed, ref, watch } from 'vue'
import { EVENT_CODE, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { isObject, isArray, hasClass } from '@element-plus/utils'
import { formContextKey } from '@element-plus/tokens'
import { ElIcon } from '@element-plus/components/icon'
import { StarFilled, Star } from '@element-plus/icons-vue'
import { useNamespace, useSize } from '@element-plus/hooks'
import { rateProps, rateEmits } from './rate'
import type { FormContext } from '@element-plus/tokens'
function getValueFromMap<T>(
value: number,
map: Record<string, T | { excluded?: boolean; value: T }>
@ -77,231 +73,210 @@ function getValueFromMap<T>(
return (isExcludedObject(matchedValue) && matchedValue.value) || matchedValue
}
export default defineComponent({
defineOptions({
name: 'ElRate',
components: {
ElIcon,
StarFilled,
Star,
},
props: rateProps,
emits: rateEmits,
})
setup(props, { emit }) {
const elForm = inject(formContextKey, {} as FormContext)
const props = defineProps(rateProps)
const emit = defineEmits(rateEmits)
const rateSize = useSize()
const ns = useNamespace('rate')
const formContext = inject(formContextKey, undefined)
const rateSize = useSize()
const ns = useNamespace('rate')
const currentValue = ref(props.modelValue)
const hoverIndex = ref(-1)
const pointerAtLeftHalf = ref(true)
const currentValue = ref(props.modelValue)
const hoverIndex = ref(-1)
const pointerAtLeftHalf = ref(true)
const rateKls = computed(() => [ns.b(), ns.m(rateSize.value)])
const rateClasses = computed(() => [ns.b(), ns.m(rateSize.value)])
const rateDisabled = computed(() => props.disabled || formContext?.disabled)
const rateDisabled = computed(() => props.disabled || elForm.disabled)
const text = computed(() => {
let result = ''
if (props.showScore) {
result = props.scoreTemplate.replace(
/\{\s*value\s*\}/,
rateDisabled.value ? `${props.modelValue}` : `${currentValue.value}`
)
} else if (props.showText) {
result = props.texts[Math.ceil(currentValue.value) - 1]
}
return result
})
const valueDecimal = computed(
() => props.modelValue * 100 - Math.floor(props.modelValue) * 100
const text = computed(() => {
let result = ''
if (props.showScore) {
result = props.scoreTemplate.replace(
/\{\s*value\s*\}/,
rateDisabled.value ? `${props.modelValue}` : `${currentValue.value}`
)
const colorMap = computed(() =>
isArray(props.colors)
? {
[props.lowThreshold]: props.colors[0],
[props.highThreshold]: { value: props.colors[1], excluded: true },
[props.max]: props.colors[2],
}
: props.colors
)
const activeColor = computed(() =>
getValueFromMap(currentValue.value, colorMap.value)
)
const decimalStyle = computed(() => {
let width = ''
if (rateDisabled.value) {
width = `${valueDecimal.value}%`
} else if (props.allowHalf) {
width = '50%'
} else if (props.showText) {
result = props.texts[Math.ceil(currentValue.value) - 1]
}
return result
})
const valueDecimal = computed(
() => props.modelValue * 100 - Math.floor(props.modelValue) * 100
)
const colorMap = computed(() =>
isArray(props.colors)
? {
[props.lowThreshold]: props.colors[0],
[props.highThreshold]: { value: props.colors[1], excluded: true },
[props.max]: props.colors[2],
}
return {
color: activeColor.value,
width,
: props.colors
)
const activeColor = computed(() =>
getValueFromMap(currentValue.value, colorMap.value)
)
const decimalStyle = computed(() => {
let width = ''
if (rateDisabled.value) {
width = `${valueDecimal.value}%`
} else if (props.allowHalf) {
width = '50%'
}
return {
color: activeColor.value,
width,
}
})
const componentMap = computed(() =>
isArray(props.icons)
? {
[props.lowThreshold]: props.icons[0],
[props.highThreshold]: {
value: props.icons[1],
excluded: true,
},
[props.max]: props.icons[2],
}
})
const componentMap = computed(() =>
isArray(props.icons)
? {
[props.lowThreshold]: props.icons[0],
[props.highThreshold]: {
value: props.icons[1],
excluded: true,
},
[props.max]: props.icons[2],
}
: props.icons
)
const decimalIconComponent = computed(() =>
getValueFromMap(props.modelValue, componentMap.value)
)
const voidComponent = computed(() =>
rateDisabled.value ? props.disabledVoidIcon : props.voidIcon
)
const activeComponent = computed(() =>
getValueFromMap(currentValue.value, componentMap.value)
)
const iconComponents = computed(() => {
const result = Array.from({ length: props.max })
const threshold = currentValue.value
result.fill(activeComponent.value, 0, threshold)
result.fill(voidComponent.value, threshold, props.max)
return result
})
: props.icons
)
const decimalIconComponent = computed(() =>
getValueFromMap(props.modelValue, componentMap.value)
)
const voidComponent = computed(() =>
rateDisabled.value ? props.disabledVoidIcon : props.voidIcon
)
const activeComponent = computed(() =>
getValueFromMap(currentValue.value, componentMap.value)
)
const iconComponents = computed(() => {
const result = Array.from({ length: props.max })
const threshold = currentValue.value
result.fill(activeComponent.value, 0, threshold)
result.fill(voidComponent.value, threshold, props.max)
return result
})
function showDecimalIcon(item: number) {
const showWhenDisabled =
rateDisabled.value &&
valueDecimal.value > 0 &&
item - 1 < props.modelValue &&
item > props.modelValue
const showWhenAllowHalf =
props.allowHalf &&
pointerAtLeftHalf.value &&
item - 0.5 <= currentValue.value &&
item > currentValue.value
return showWhenDisabled || showWhenAllowHalf
function showDecimalIcon(item: number) {
const showWhenDisabled =
rateDisabled.value &&
valueDecimal.value > 0 &&
item - 1 < props.modelValue &&
item > props.modelValue
const showWhenAllowHalf =
props.allowHalf &&
pointerAtLeftHalf.value &&
item - 0.5 <= currentValue.value &&
item > currentValue.value
return showWhenDisabled || showWhenAllowHalf
}
function getIconStyle(item: number) {
const voidColor = rateDisabled.value
? props.disabledVoidColor
: props.voidColor
return {
color: item <= currentValue.value ? activeColor.value : voidColor,
}
}
function selectValue(value: number) {
if (rateDisabled.value) {
return
}
if (props.allowHalf && pointerAtLeftHalf.value) {
emit(UPDATE_MODEL_EVENT, currentValue.value)
if (props.modelValue !== currentValue.value) {
emit('change', currentValue.value)
}
function getIconStyle(item: number) {
const voidColor = rateDisabled.value
? props.disabledVoidColor
: props.voidColor
return {
color: item <= currentValue.value ? activeColor.value : voidColor,
}
} else {
emit(UPDATE_MODEL_EVENT, value)
if (props.modelValue !== value) {
emit('change', value)
}
}
}
function selectValue(value: number) {
if (rateDisabled.value) {
return
}
if (props.allowHalf && pointerAtLeftHalf.value) {
emit(UPDATE_MODEL_EVENT, currentValue.value)
if (props.modelValue !== currentValue.value) {
emit('change', currentValue.value)
}
} else {
emit(UPDATE_MODEL_EVENT, value)
if (props.modelValue !== value) {
emit('change', value)
}
}
function handleKey(e: KeyboardEvent) {
if (rateDisabled.value) {
return
}
let _currentValue = currentValue.value
const code = e.code
if (code === EVENT_CODE.up || code === EVENT_CODE.right) {
if (props.allowHalf) {
_currentValue += 0.5
} else {
_currentValue += 1
}
function handleKey(e: KeyboardEvent) {
if (rateDisabled.value) {
return
}
let _currentValue = currentValue.value
const code = e.code
if (code === EVENT_CODE.up || code === EVENT_CODE.right) {
if (props.allowHalf) {
_currentValue += 0.5
} else {
_currentValue += 1
}
e.stopPropagation()
e.preventDefault()
} else if (code === EVENT_CODE.left || code === EVENT_CODE.down) {
if (props.allowHalf) {
_currentValue -= 0.5
} else {
_currentValue -= 1
}
e.stopPropagation()
e.preventDefault()
}
_currentValue = _currentValue < 0 ? 0 : _currentValue
_currentValue = _currentValue > props.max ? props.max : _currentValue
emit(UPDATE_MODEL_EVENT, _currentValue)
emit('change', _currentValue)
return _currentValue
e.stopPropagation()
e.preventDefault()
} else if (code === EVENT_CODE.left || code === EVENT_CODE.down) {
if (props.allowHalf) {
_currentValue -= 0.5
} else {
_currentValue -= 1
}
e.stopPropagation()
e.preventDefault()
}
_currentValue = _currentValue < 0 ? 0 : _currentValue
_currentValue = _currentValue > props.max ? props.max : _currentValue
emit(UPDATE_MODEL_EVENT, _currentValue)
emit('change', _currentValue)
return _currentValue
}
function setCurrentValue(value: number, event: MouseEvent) {
if (rateDisabled.value) {
return
}
if (props.allowHalf) {
// TODO: use cache via computed https://github.com/element-plus/element-plus/pull/5456#discussion_r786472092
let target = event.target as HTMLElement
if (hasClass(target, ns.e('item'))) {
target = target.querySelector(`.${ns.e('icon')}`)!
}
if (target.clientWidth === 0 || hasClass(target, ns.e('decimal'))) {
target = target.parentNode as HTMLElement
}
pointerAtLeftHalf.value = event.offsetX * 2 <= target.clientWidth
currentValue.value = pointerAtLeftHalf.value ? value - 0.5 : value
} else {
currentValue.value = value
}
hoverIndex.value = value
function setCurrentValue(value: number, event: MouseEvent) {
if (rateDisabled.value) {
return
}
if (props.allowHalf) {
// TODO: use cache via computed https://github.com/element-plus/element-plus/pull/5456#discussion_r786472092
let target = event.target as HTMLElement
if (hasClass(target, ns.e('item'))) {
target = target.querySelector(`.${ns.e('icon')}`)!
}
function resetCurrentValue() {
if (rateDisabled.value) {
return
}
if (props.allowHalf) {
pointerAtLeftHalf.value =
props.modelValue !== Math.floor(props.modelValue)
}
currentValue.value = props.modelValue
hoverIndex.value = -1
if (target.clientWidth === 0 || hasClass(target, ns.e('decimal'))) {
target = target.parentNode as HTMLElement
}
pointerAtLeftHalf.value = event.offsetX * 2 <= target.clientWidth
currentValue.value = pointerAtLeftHalf.value ? value - 0.5 : value
} else {
currentValue.value = value
}
hoverIndex.value = value
}
watch(
() => props.modelValue,
(val) => {
currentValue.value = val
pointerAtLeftHalf.value =
props.modelValue !== Math.floor(props.modelValue)
}
)
function resetCurrentValue() {
if (rateDisabled.value) {
return
}
if (props.allowHalf) {
pointerAtLeftHalf.value = props.modelValue !== Math.floor(props.modelValue)
}
currentValue.value = props.modelValue
hoverIndex.value = -1
}
if (!props.modelValue) {
emit(UPDATE_MODEL_EVENT, 0)
}
watch(
() => props.modelValue,
(val) => {
currentValue.value = val
pointerAtLeftHalf.value = props.modelValue !== Math.floor(props.modelValue)
}
)
return {
ns,
hoverIndex,
currentValue,
rateDisabled,
text,
decimalStyle,
decimalIconComponent,
iconComponents,
rateKls,
if (!props.modelValue) {
emit(UPDATE_MODEL_EVENT, 0)
}
showDecimalIcon,
getIconStyle,
selectValue,
handleKey,
setCurrentValue,
resetCurrentValue,
}
},
defineExpose({
/** @description set current value */
setCurrentValue,
/** @description reset current value */
resetCurrentValue,
})
</script>