Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nutui/nutui",
"version": "4.3.15",
"version": "4.3.16",
"description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)",
"main": "dist/nutui.umd.js",
"module": "dist/nutui.es.js",
Expand Down
2 changes: 1 addition & 1 deletion publish/nutui-taro/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nutui/nutui-taro",
"version": "4.3.15",
"version": "4.3.16",
"description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)",
"main": "dist/nutui.umd.js",
"module": "dist/nutui.es.js",
Expand Down
2 changes: 1 addition & 1 deletion publish/nutui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nutui/nutui",
"version": "4.3.15",
"version": "4.3.16",
"description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)",
"main": "dist/nutui.umd.js",
"module": "dist/nutui.es.js",
Expand Down
90 changes: 86 additions & 4 deletions src/packages/__VUE/toast/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils'
import { vi } from 'vitest'
import { nextTick } from 'vue'
import { Toast } from '@nutui/nutui'

Expand Down Expand Up @@ -40,6 +41,7 @@ describe('component toast', () => {
const toastCover: any = wrapper.find('.nut-toast-cover')
expect(toastCover.element.style.backgroundColor).toEqual('black')
})

test('should close Toast when using closeOnClickOverlay prop and clicked', async () => {
const wrapper = mount(Toast, {
props: {
Expand All @@ -54,14 +56,94 @@ describe('component toast', () => {
await nextTick()
expect(toast.element.style.display).toEqual('none')
})
test('should render customClass when using customClass prop ', async () => {
test('should not close Toast when closeOnClickOverlay is false and clicked', async () => {
const wrapper = mount(Toast, {
props: {
customClass: 'custom'
cover: true,
closeOnClickOverlay: false,
duration: 0
}
})
await nextTick()
const toast: any = wrapper.find('.custom')
expect(toast.exists()).toBe(true)
const toast: any = wrapper.find('.nut-toast')
await toast.trigger('click')
await nextTick()
expect(toast.element.style.display).not.toEqual('none')
})

test('should auto-hide after duration', async () => {
vi.useFakeTimers()
const wrapper = mount(Toast, {
props: { msg: 'msg', duration: 100 }
})
await nextTick()
const toast: any = wrapper.find('.nut-toast')
expect(toast.element.style.display).not.toEqual('none')

vi.advanceTimersByTime(100)
await nextTick()
expect(toast.element.style.display).toEqual('none')
vi.useRealTimers()
})

test('should not auto-hide when duration is 0', async () => {
vi.useFakeTimers()
const wrapper = mount(Toast, {
props: { msg: 'msg', duration: 0 }
})
await nextTick()
const toast: any = wrapper.find('.nut-toast')
vi.advanceTimersByTime(5000)
await nextTick()
expect(toast.element.style.display).not.toEqual('none')
vi.useRealTimers()
})
test('should set state.closing to true after clicking overlay', async () => {
const wrapper = mount(Toast, {
props: { cover: true, closeOnClickOverlay: true, duration: 0 }
})
await nextTick()
const toast = wrapper.find('.nut-toast')
await toast.trigger('click')
await nextTick()
expect((wrapper.vm as any).state.closing).toBe(true)
expect(toast.element.style.display).toEqual('none')
})

test('should not re-trigger hide when clicking overlay during closing animation', async () => {
const wrapper = mount(Toast, {
props: { cover: true, closeOnClickOverlay: true, duration: 0 }
})
await nextTick()
const toast = wrapper.find('.nut-toast')

// 第一次点击:state.mounted = false,state.closing = true
await toast.trigger('click')
await nextTick()
expect((wrapper.vm as any).state.closing).toBe(true)
expect(toast.element.style.display).toEqual('none')

// 第二次点击:closing = true,clickCover 提前 return,state 保持不变
await toast.trigger('click')
await nextTick()
expect((wrapper.vm as any).state.closing).toBe(true)
expect((wrapper.vm as any).state.mounted).toBe(false)
})

test('should reset closing state when show() is called again', async () => {
const wrapper = mount(Toast, {
props: { cover: true, closeOnClickOverlay: true, duration: 0 }
})
await nextTick()
const toast = wrapper.find('.nut-toast')

await toast.trigger('click')
await nextTick()
expect((wrapper.vm as any).state.closing).toBe(true)

// 更新 duration 触发 watch → show() → closing 重置
await wrapper.setProps({ duration: 100 })
await nextTick()
expect((wrapper.vm as any).state.closing).toBe(false)
})
})
8 changes: 7 additions & 1 deletion src/packages/__VUE/toast/index.taro.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</Transition>
</template>
<script lang="ts">
import { Component, computed, PropType, watch } from 'vue'
import { Component, computed, PropType, reactive, watch } from 'vue'
import { createComponent, renderIcon } from '@/packages/utils/create'
const { create } = createComponent('toast')
import { Failure, Loading, Success, Tips } from '@nutui/icons-vue-taro'
Expand Down Expand Up @@ -112,12 +112,18 @@ export default create({
timer = null
}
}
const state = reactive({
closed: false
})
const hide = () => {
if (state.closed) return
state.closed = true
emit('update:visible', false)
emit('closed')
}
const show = () => {
clearTimer()
state.closed = false
if (props.duration) {
timer = setTimeout(() => {
hide()
Expand Down
13 changes: 7 additions & 6 deletions src/packages/__VUE/toast/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ export default create({
}
},
emits: ['close'],
setup(props, { emit }) {
setup(props) {
let timer: null | number | undefined
const state = reactive({
mounted: false
mounted: false,
closing: false
})
onMounted(() => {
state.mounted = true
Expand All @@ -109,20 +110,20 @@ export default create({
}
const hide = () => {
state.mounted = false
state.closing = true
}
const show = () => {
clearTimer()
state.closing = false
if (props.duration) {
timer = window.setTimeout(() => {
hide()
}, props.duration)
}
}
const clickCover = () => {
if (props.closeOnClickOverlay) {
hide()
emit('close')
}
if (!props.closeOnClickOverlay || state.closing) return // 点击遮罩时如果正在关闭中,则不触发关闭事件,避免重复调用close事件
hide()
}

if (props.duration) {
Expand Down