address PR issue

This commit is contained in:
JeremyWuuuuu
2020-08-06 17:53:58 +08:00
committed by zazzaz
parent 0f04895881
commit df4fbb31b4
6 changed files with 94 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils'
import { h } from 'vue'
import { mount, VueWrapper } from '@vue/test-utils'
import { h, ComponentPublicInstance, nextTick } from 'vue'
import * as domExports from '../../utils/dom'
import { eventKeys } from '../../utils/aria'
import Notification from '../src/index.vue'
@@ -102,48 +102,17 @@ describe('Notification.vue', () => {
})
describe('Notification.type', () => {
test('should be able to render success notification', () => {
const type = 'success'
const wrapper = _mount({
props: {
type,
},
})
test('should be able to render typed notification', () => {
let wrapper: VueWrapper<ComponentPublicInstance>
expect(wrapper.find(`.el-icon-${type}`).exists()).toBe(true)
})
test('should be able to render warning notification', () => {
const type = 'warning'
const wrapper = _mount({
props: {
type,
},
})
expect(wrapper.find(`.el-icon-${type}`).exists()).toBe(true)
})
test('should be able to render info notification', () => {
const type = 'info'
const wrapper = _mount({
props: {
type,
},
})
expect(wrapper.find(`.el-icon-${type}`).exists()).toBe(true)
})
test('should be able to render error notification', () => {
const type = 'error'
const wrapper = _mount({
props: {
type,
},
})
expect(wrapper.find(`.el-icon-${type}`).exists()).toBe(true)
for (const type of ['success', 'warning', 'info', 'error']) {
wrapper = _mount({
props: {
type,
},
})
expect(wrapper.find('.el-notification__icon').classes()).toContain(`el-icon-${type}`)
}
})
test('should not be able to render invalid type icon', () => {
@@ -154,7 +123,7 @@ describe('Notification.vue', () => {
},
})
expect(wrapper.find(`.el-icon-${type}`).exists()).toBe(false)
expect(wrapper.find('.el-notification__icon').classes()).not.toContain(`el-icon-${type}`)
})
})
@@ -170,7 +139,8 @@ describe('Notification.vue', () => {
const closeBtn = wrapper.find('.el-notification__closeBtn')
expect(closeBtn.exists()).toBe(true)
await closeBtn.trigger('click')
wrapper.vm.destroyElement()
expect(onClose).toHaveBeenCalled()
})

View File

@@ -1,6 +1,5 @@
import { isVNode } from 'vue'
import Notification, { close, closeAll } from '../src/notify'
import type { NotificationVM } from '../src/notification.constants'
import type { INotificationHandle } from '../src/notification'
jest.useFakeTimers()
@@ -11,11 +10,12 @@ describe('Notification on command', () => {
closeAll()
})
test('it should get component instance when calling notification constructor', async () => {
const vm = Notification()
expect(isVNode(vm)).toBe(true)
test('it should get component handle', async () => {
const handle = Notification()
expect(document.querySelector(selector)).toBeDefined()
jest.runAllTicks()
handle.close()
expect(document.querySelector(selector)).toBeNull()
})
@@ -28,7 +28,7 @@ describe('Notification on command', () => {
})
test('it should close all notifications', () => {
const notifications: NotificationVM[] = []
const notifications: INotificationHandle[] = []
const onClose = jest.fn()
for (let i = 0; i < 4; i++) {
notifications.push(Notification({
@@ -43,4 +43,10 @@ describe('Notification on command', () => {
expect(document.querySelectorAll(selector).length).toBe(0)
})
test('it should be able to render all types notification', () => {
for (const type of ['success', 'warning', 'error', 'info'] as const) {
Notification[type]()
expect(document.querySelector(`.el-icon-${type}`)).toBeDefined()
}
})
})

View File

@@ -1,7 +1,5 @@
import { App } from 'vue'
import Notification from './src/index.vue'
import Notify from './src/notify'
export default (app: App): void => {
app.component(Notification.name, Notification)
app.config.globalProperties.$notify = Notify
}

View File

@@ -1,7 +1,7 @@
<template>
<transition name="el-notification-fade">
<div
v-if="visible"
v-show="visible"
:id="id"
:class="['el-notification', customClass, horizontalClass]"
:style="positionStyle"
@@ -10,8 +10,15 @@
@mouseleave="startTimer()"
@click="click"
>
<i v-if="type || iconClass" class="el-notification__icon" :class="[typeClass, iconClass]"></i>
<div class="el-notification__group" :class="{ 'is-with-icon': typeClass || iconClass }">
<i
v-if="type || iconClass"
class="el-notification__icon"
:class="[typeClass, iconClass]"
></i>
<div
class="el-notification__group"
:class="{ 'is-with-icon': typeClass || iconClass }"
>
<h2 class="el-notification__title" v-text="title"></h2>
<div v-show="message" class="el-notification__content">
<slot>
@@ -21,13 +28,17 @@
<p v-else v-html="message"></p>
</slot>
</div>
<div v-if="showClose" class="el-notification__closeBtn el-icon-close" @click.stop="close"></div>
<div
v-if="showClose"
class="el-notification__closeBtn el-icon-close"
@click.stop="close"
></div>
</div>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent, computed, ref, PropType } from 'vue'
import { defineComponent, computed, ref, PropType, nextTick } from 'vue'
// notificationVM is an alias of vue.VNode
import type { NotificationVM } from './notification.constants'
import { eventKeys } from '../../utils/aria'
@@ -92,7 +103,7 @@ export default defineComponent({
}
})
const visible = ref(true)
const visible = ref(false)
const closed = ref(false)
const timer = ref(null)
@@ -122,6 +133,7 @@ export default defineComponent({
}
}, this.duration)
}
this.visible = true
on(document, 'keydown', this.keydown)
},
beforeUnmount() {
@@ -129,8 +141,9 @@ export default defineComponent({
},
methods: {
destroyElement() {
this.visible = false
off(this.$el, 'transitionend', this.destroyElement)
this.$el.parentNode.removeChild(this.$el)
this.onClose()
},
// start counting down to destroy notification instance
startTimer() {
@@ -154,7 +167,6 @@ export default defineComponent({
close() {
this.closed = true
this.timer = null
this.onClose()
},
keydown({ keyCode }: KeyboardEvent) {
if (keyCode === eventKeys.delete || keyCode === eventKeys.backspace) {
@@ -171,3 +183,16 @@ export default defineComponent({
},
})
</script>
<style>
/* Due to the code */
/* TODO: remove these code */
.el-notification-fade-enter-active.right {
right: 0;
transform: translateX(100%);
}
.el-notification-fade-enter-active.left {
left: 0;
transform: translateX(-100%);
}
</style>

View File

@@ -1,6 +1,11 @@
import type { VNode } from 'vue'
export type INotification = (options?: INotificationOptions) => NotificationVM
export interface INotificationHandle {
close: () => void
}
export type INotification = (options?: INotificationOptions) => INotificationHandle
export type INotificationOptions = {
customClass?: string
@@ -10,8 +15,8 @@ export type INotificationOptions = {
id?: string
message?: string | VNode
zIndex?: number
onClose?: () => unknown
onClick?: () => unknown
onClose?: () => void
onClick?: () => void
offset?: number // defaults 0
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' // default top-right
showClose?: boolean

View File

@@ -1,6 +1,6 @@
import { createVNode, render } from 'vue'
import NotificationConstructor from './index.vue'
import type { INotificationOptions, INotification, NotificationQueue, NotificationVM } from './notification.constants'
import type { INotificationOptions, INotification, NotificationQueue, NotificationVM } from './notification'
import isServer from '../../utils/isServer'
import PopupManager from '../../utils/popup-manager'
import { isVNode } from '../../utils/util'
@@ -43,19 +43,27 @@ const Notification: INotification = function(options = {}) {
container.className = `container_${id}`
container.style.zIndex = String()
vm = createVNode(NotificationConstructor, options, isVNode(options.message) ? {
default: () => options.message,
}: null)
vm = createVNode(
NotificationConstructor,
options,
isVNode(options.message)
? {
default: () => options.message,
}
: null,
)
render(vm, container)
notifications.push({ vm, $el: container })
document.body.appendChild(container.firstElementChild)
document.body.appendChild(container)
return vm
return {
close: options.onClose,
}
};
(['success', 'warning', 'info', 'error'] as const).forEach(type => {
Object.assign(Notification, {
type: options => {
[type]: (options: NotificationVM | INotificationOptions | string = {}) => {
if (typeof options === 'string' || isVNode(options)) {
options = {
message: options,
@@ -82,6 +90,7 @@ export function close(
const { vm, $el } = notifications[idx]
if (!vm) return
userOnClose?.(vm)
const removedHeight = vm.el.offsetHeight
render(null, $el)
@@ -92,14 +101,16 @@ export function close(
for (let i = idx; i < len; i++) {
if (notifications[i].vm.component.props.position === position) {
const verticalPos = vm.props.position.split('-')[0]
notifications[i].vm.el.style[verticalPos] =
parseInt(
notifications[i].vm.el.style[verticalPos],
10,
) -
removedHeight -
16 +
'px'
const pos = parseInt(
notifications[i].vm.el.style[verticalPos],
10,
) -
removedHeight -
16
notifications[i].vm.component.props.offset = pos
render(notifications[i].vm, notifications[i].$el)
// .vm.el.style[verticalPos] = pos
}
}
}