Rewrite showModal
This commit is contained in:
parent
84afc32d84
commit
e5b8a130a0
156
resources/assets/src/components/Modal.vue
Normal file
156
resources/assets/src/components/Modal.vue
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="modal fade"
|
||||||
|
tabindex="-1"
|
||||||
|
role="dialog"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="modal-dialog" :class="{ 'modal-dialog-centered': center }" role="document">
|
||||||
|
<div class="modal-content" :class="[`bg-${type}`]">
|
||||||
|
<div v-if="showHeader" class="modal-header">
|
||||||
|
<h5 class="modal-title">{{ title }}</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="close"
|
||||||
|
data-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
@click="dismiss"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<slot>
|
||||||
|
<template v-if="text">
|
||||||
|
<p v-for="(line, i) in lines" :key="i">{{ line }}</p>
|
||||||
|
</template>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<div v-else-if="dangerousHTML" v-html="dangerousHTML" />
|
||||||
|
<template v-if="mode === 'prompt'">
|
||||||
|
<div class="form-group">
|
||||||
|
<input v-model="value" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" :class="footerClasses">
|
||||||
|
<slot name="footer">
|
||||||
|
<button
|
||||||
|
v-if="mode !== 'alert'"
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="[`btn-${cancelButtonType}`]"
|
||||||
|
data-dismiss="modal"
|
||||||
|
@click="dismiss"
|
||||||
|
>
|
||||||
|
{{ cancelButtonText }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="[`btn-${okButtonType}`]"
|
||||||
|
@click="confirm"
|
||||||
|
>
|
||||||
|
{{ okButtonText }}
|
||||||
|
</button>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import $ from 'jquery'
|
||||||
|
import { trans } from '../scripts/i18n'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Modal',
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'confirm',
|
||||||
|
validator: value => ['alert', 'confirm', 'prompt'].includes(value),
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: trans('general.tip'),
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
dangerousHTML: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'default',
|
||||||
|
},
|
||||||
|
showHeader: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
center: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
okButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: trans('general.confirm'),
|
||||||
|
},
|
||||||
|
okButtonType: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary',
|
||||||
|
},
|
||||||
|
cancelButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: trans('general.cancel'),
|
||||||
|
},
|
||||||
|
cancelButtonType: {
|
||||||
|
type: String,
|
||||||
|
default: 'secondary',
|
||||||
|
},
|
||||||
|
flexFooter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: this.input,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
lines() {
|
||||||
|
return this.text.split(/\r?\n/)
|
||||||
|
},
|
||||||
|
footerClasses() {
|
||||||
|
return {
|
||||||
|
'd-flex': this.flexFooter,
|
||||||
|
'justify-content-between': this.flexFooter,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
$(this.$el).on('hide.bs.modal', /* istanbul ignore next */ () => {
|
||||||
|
this.dismiss()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirm() {
|
||||||
|
this.$emit('confirm', { value: this.value })
|
||||||
|
$(this.$el).modal('hide')
|
||||||
|
this.$destroy()
|
||||||
|
},
|
||||||
|
dismiss() {
|
||||||
|
this.$emit('dismiss')
|
||||||
|
this.$destroy()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import { Message, MessageBox } from 'element-ui'
|
|
||||||
import { post } from './net'
|
import { post } from './net'
|
||||||
import { trans } from './i18n'
|
import { trans } from './i18n'
|
||||||
|
import { showModal } from './notify'
|
||||||
|
|
||||||
export async function logout() {
|
export async function logout() {
|
||||||
try {
|
try {
|
||||||
await MessageBox.confirm(trans('general.confirmLogout'), {
|
await showModal({
|
||||||
type: 'warning',
|
text: trans('general.confirmLogout'),
|
||||||
|
center: true,
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { message } = await post('/auth/logout')
|
await post('/auth/logout')
|
||||||
setTimeout(() => (window.location.href = blessing.base_url), 1000)
|
window.location.href = blessing.base_url
|
||||||
Message.success(message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const button = document.querySelector('#logout-button')
|
const button = document.querySelector('#logout-button')
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { emit } from './event'
|
import { emit } from './event'
|
||||||
import { queryStringify } from './utils'
|
import { queryStringify } from './utils'
|
||||||
import { showAjaxError, showModal } from './notify'
|
import { showModal } from './notify'
|
||||||
|
import { trans } from './i18n'
|
||||||
|
|
||||||
class HTTPError extends Error {
|
class HTTPError extends Error {
|
||||||
response: Response
|
response: Response
|
||||||
|
|
@ -52,7 +53,11 @@ export async function walkFetch(request: Request): Promise<any> {
|
||||||
message: Object.keys(errors).map(field => errors[field][0])[0],
|
message: Object.keys(errors).map(field => errors[field][0])[0],
|
||||||
}
|
}
|
||||||
} else if (response.status === 403) {
|
} else if (response.status === 403) {
|
||||||
showModal(message, undefined, 'warning')
|
showModal({
|
||||||
|
mode: 'alert',
|
||||||
|
text: message,
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +71,12 @@ export async function walkFetch(request: Request): Promise<any> {
|
||||||
throw new HTTPError(message || body, cloned)
|
throw new HTTPError(message || body, cloned)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emit('fetchError', error)
|
emit('fetchError', error)
|
||||||
showAjaxError(error)
|
showModal({
|
||||||
|
mode: 'alert',
|
||||||
|
title: trans('general.fatalError'),
|
||||||
|
dangerousHTML: error.message,
|
||||||
|
type: 'danger',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ export default async function handler(event: Event) {
|
||||||
content: string
|
content: string
|
||||||
time: string
|
time: string
|
||||||
} = await get(`/user/notifications/${id!}`)
|
} = await get(`/user/notifications/${id!}`)
|
||||||
showModal(`${content}<br><small>${time}</small>`, title)
|
showModal({
|
||||||
|
mode: 'alert',
|
||||||
|
title,
|
||||||
|
dangerousHTML: `${content}<br><small>${time}</small>`,
|
||||||
|
})
|
||||||
item.remove()
|
item.remove()
|
||||||
const counter = document
|
const counter = document
|
||||||
.querySelector('.notifications-counter') as HTMLSpanElement
|
.querySelector('.notifications-counter') as HTMLSpanElement
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,44 @@
|
||||||
/* eslint-disable max-params */
|
|
||||||
import $ from 'jquery'
|
import $ from 'jquery'
|
||||||
import { ModalOption as BootstrapModalOption } from 'bootstrap'
|
import Vue from 'vue'
|
||||||
import { trans } from './i18n'
|
import Modal from '../components/Modal.vue'
|
||||||
|
|
||||||
export function showAjaxError(error: Error): void {
|
export interface ModalOptions {
|
||||||
showModal(
|
mode?: 'alert' | 'confirm' | 'prompt'
|
||||||
error.message.replace(/\n/g, '<br>'),
|
title?: string
|
||||||
trans('general.fatalError'),
|
text?: string
|
||||||
'danger',
|
dangerousHTML?: string
|
||||||
)
|
input?: boolean
|
||||||
|
type?: string
|
||||||
|
showHeader?: boolean
|
||||||
|
center?: boolean
|
||||||
|
okButtonText?: string
|
||||||
|
okButtonType?: string
|
||||||
|
cancelButtonText?: string
|
||||||
|
cancelButtonType?: string
|
||||||
|
flexFooter?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModalOptions = {
|
export interface ModalResult {
|
||||||
btnText?: string
|
value: string
|
||||||
callback?: CallableFunction
|
}
|
||||||
destroyOnClose?: boolean
|
|
||||||
} & BootstrapModalOption
|
|
||||||
|
|
||||||
export function showModal(
|
export function showModal(options: ModalOptions = {}): Promise<ModalResult> {
|
||||||
message: string, title = 'Message',
|
return new Promise((resolve, reject) => {
|
||||||
type = 'default',
|
const container = document.createElement('div')
|
||||||
options: ModalOptions = {},
|
document.body.appendChild(container)
|
||||||
): void {
|
|
||||||
const btnType = type === 'default' ? 'btn-primary' : 'btn-outline'
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
||||||
const btnText = options.btnText || 'OK'
|
|
||||||
const onClick = options.callback === undefined
|
|
||||||
? 'data-dismiss="modal"'
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
||||||
: `onclick="${options.callback}"`
|
|
||||||
const destroyOnClose = options.destroyOnClose !== false
|
|
||||||
|
|
||||||
const dom = `
|
const instance = new Vue({
|
||||||
<div class="modal fade in">
|
render: h => h(Modal, {
|
||||||
<div class="modal-dialog">
|
props: options,
|
||||||
<div class="modal-content bg-${type}">
|
on: {
|
||||||
<div class="modal-header">
|
confirm: resolve,
|
||||||
<h4 class="modal-title">${title}</h4>
|
dismiss: reject,
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
},
|
||||||
<span aria-hidden="true">×</span>
|
}),
|
||||||
</button>
|
}).$mount(container)
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>${message}</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" ${onClick} class="btn btn-outline-light ${btnType}">
|
|
||||||
${btnText}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
|
|
||||||
$(dom)
|
$(instance.$el).modal('show')
|
||||||
.on('hidden.bs.modal', /* istanbul ignore next */ function modal() {
|
})
|
||||||
if (destroyOnClose) {
|
|
||||||
$(this).remove()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.modal(options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(blessing, { notify: { showModal } })
|
Object.assign(blessing, { notify: { showModal } })
|
||||||
|
|
|
||||||
149
resources/assets/tests/components/Modal.test.ts
Normal file
149
resources/assets/tests/components/Modal.test.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
import 'bootstrap'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Modal from '@/components/Modal.vue'
|
||||||
|
|
||||||
|
test('title', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
title: 'kumiko',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.modal-title').text()).toBe('kumiko')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('close button at header', () => {
|
||||||
|
const wrapper = mount(Modal)
|
||||||
|
wrapper.find('.modal-header > button').trigger('click')
|
||||||
|
expect(wrapper.emitted().dismiss).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('render lines', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
text: 'kumiko\nreina',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const paragraphs = wrapper.findAll('p')
|
||||||
|
expect(paragraphs).toHaveLength(2)
|
||||||
|
expect(paragraphs.at(0).text()).toBe('kumiko')
|
||||||
|
expect(paragraphs.at(1).text()).toBe('reina')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamic html', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
dangerousHTML: '<div class="eupho">kumiko</div>',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.eupho').text()).toBe('kumiko')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cancel by button at footer', () => {
|
||||||
|
const wrapper = mount(Modal)
|
||||||
|
wrapper.find('.btn-secondary').trigger('click')
|
||||||
|
expect(wrapper.emitted().dismiss).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('alert mode', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
mode: 'alert',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.btn-secondary').exists()).toBeFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prompt mode', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
mode: 'prompt',
|
||||||
|
input: 'default-value',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.btn-secondary').exists()).toBeTrue()
|
||||||
|
|
||||||
|
wrapper.find('input').setValue('hazuki')
|
||||||
|
wrapper.find('.btn-primary').trigger('click')
|
||||||
|
expect(wrapper.emitted().confirm[0][0]).toStrictEqual({ value: 'hazuki' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('modal type', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
type: 'danger',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.modal-content').classes('bg-danger')).toBeTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hide header', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
showHeader: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.modal-header').exists()).toBeFalse()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('centered modal', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
center: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(
|
||||||
|
wrapper.find('.modal-dialog').classes('modal-dialog-centered'),
|
||||||
|
).toBeTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('customize ok button', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
okButtonText: 'OK',
|
||||||
|
okButtonType: 'danger',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const button = wrapper.find('.btn:nth-child(2)')
|
||||||
|
expect(button.text().trim()).toBe('OK')
|
||||||
|
expect(button.classes('btn-danger')).toBeTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('customize cancel button', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
cancelButtonText: 'CANCEL',
|
||||||
|
cancelButtonType: 'danger',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const button = wrapper.find('.btn:nth-child(1)')
|
||||||
|
expect(button.text().trim()).toBe('CANCEL')
|
||||||
|
expect(button.classes('btn-danger')).toBeTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('flex footer', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
propsData: {
|
||||||
|
flexFooter: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.modal-footer').classes())
|
||||||
|
.toContainValues(['d-flex', 'justify-content-between'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('default slot', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
slots: {
|
||||||
|
default: '<div class="trumpet">reina</div>',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.modal-body > .trumpet').text()).toBe('reina')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('footer slot', () => {
|
||||||
|
const wrapper = mount(Modal, {
|
||||||
|
slots: {
|
||||||
|
footer: '<div class="contrabass">sapphire</div>',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.modal-footer > .contrabass').text()).toBe('sapphire')
|
||||||
|
})
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { logout } from '@/scripts/logout'
|
import { logout } from '@/scripts/logout'
|
||||||
import { post } from '@/scripts/net'
|
import { post } from '@/scripts/net'
|
||||||
import { MessageBox } from 'element-ui'
|
import { showModal } from '@/scripts/notify'
|
||||||
|
|
||||||
jest.mock('element-ui')
|
|
||||||
jest.mock('@/scripts/net')
|
jest.mock('@/scripts/net')
|
||||||
|
jest.mock('@/scripts/notify')
|
||||||
|
|
||||||
test('log out', async () => {
|
test('log out', async () => {
|
||||||
jest.spyOn(MessageBox, 'confirm')
|
showModal
|
||||||
.mockRejectedValueOnce('cancel')
|
.mockRejectedValueOnce({})
|
||||||
.mockResolvedValue('confirm')
|
.mockResolvedValueOnce({ value: '' })
|
||||||
post.mockResolvedValue({ message: '' })
|
post.mockResolvedValue({ message: '' })
|
||||||
|
|
||||||
await logout()
|
await logout()
|
||||||
|
|
@ -16,5 +16,4 @@ test('log out', async () => {
|
||||||
|
|
||||||
await logout()
|
await logout()
|
||||||
expect(post).toBeCalledWith('/auth/logout')
|
expect(post).toBeCalledWith('/auth/logout')
|
||||||
jest.runAllTimers()
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import * as net from '@/scripts/net'
|
import * as net from '@/scripts/net'
|
||||||
import { on } from '@/scripts/event'
|
import { on } from '@/scripts/event'
|
||||||
import { showAjaxError, showModal } from '@/scripts/notify'
|
import { showModal } from '@/scripts/notify'
|
||||||
|
|
||||||
jest.mock('@/scripts/notify')
|
jest.mock('@/scripts/notify')
|
||||||
|
|
||||||
|
|
@ -148,17 +148,31 @@ test('low level fetch', async () => {
|
||||||
on('fetchError', stub)
|
on('fetchError', stub)
|
||||||
|
|
||||||
await net.walkFetch(request as Request)
|
await net.walkFetch(request as Request)
|
||||||
expect(showAjaxError.mock.calls[0][0]).toBeInstanceOf(Error)
|
expect(showModal).toBeCalledWith({
|
||||||
expect(showAjaxError.mock.calls[0][0]).toHaveProperty('message', 'network')
|
mode: 'alert',
|
||||||
|
title: 'general.fatalError',
|
||||||
|
dangerousHTML: 'network',
|
||||||
|
type: 'danger',
|
||||||
|
})
|
||||||
expect(stub).toBeCalledWith(expect.any(Error))
|
expect(stub).toBeCalledWith(expect.any(Error))
|
||||||
|
|
||||||
await net.walkFetch(request as Request)
|
await net.walkFetch(request as Request)
|
||||||
expect(showAjaxError.mock.calls[1][0]).toBeInstanceOf(Error)
|
expect(showModal).toBeCalledWith({
|
||||||
|
mode: 'alert',
|
||||||
|
title: 'general.fatalError',
|
||||||
|
dangerousHTML: '404',
|
||||||
|
type: 'danger',
|
||||||
|
})
|
||||||
expect(stub.mock.calls[1][0]).toHaveProperty('message', '404')
|
expect(stub.mock.calls[1][0]).toHaveProperty('message', '404')
|
||||||
expect(stub.mock.calls[1][0]).toHaveProperty('response')
|
expect(stub.mock.calls[1][0]).toHaveProperty('response')
|
||||||
|
|
||||||
await net.walkFetch(request as Request)
|
await net.walkFetch(request as Request)
|
||||||
expect(showAjaxError.mock.calls[2][0]).toBeInstanceOf(Error)
|
expect(showModal).toBeCalledWith({
|
||||||
|
mode: 'alert',
|
||||||
|
title: 'general.fatalError',
|
||||||
|
dangerousHTML: 'error',
|
||||||
|
type: 'danger',
|
||||||
|
})
|
||||||
expect(stub.mock.calls[2][0]).toHaveProperty('message', 'error')
|
expect(stub.mock.calls[2][0]).toHaveProperty('message', 'error')
|
||||||
expect(stub.mock.calls[2][0]).toHaveProperty('response')
|
expect(stub.mock.calls[2][0]).toHaveProperty('response')
|
||||||
|
|
||||||
|
|
@ -212,12 +226,19 @@ test('process backend errors', async () => {
|
||||||
expect(result.message).toBe('required')
|
expect(result.message).toBe('required')
|
||||||
|
|
||||||
await net.walkFetch({ headers: new Headers() } as Request)
|
await net.walkFetch({ headers: new Headers() } as Request)
|
||||||
expect(showModal).toBeCalledWith('forbidden', undefined, 'warning')
|
expect(showModal).toBeCalledWith({
|
||||||
|
mode: 'alert',
|
||||||
|
text: 'forbidden',
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
|
||||||
await net.walkFetch({ headers: new Headers() } as Request)
|
await net.walkFetch({ headers: new Headers() } as Request)
|
||||||
expect(showAjaxError.mock.calls[0][0].message).toBe(
|
expect(showModal).toBeCalledWith({
|
||||||
'fake exception\n<details>[1] k.php#L2\n[2] v.php#L3</details>',
|
mode: 'alert',
|
||||||
)
|
title: 'general.fatalError',
|
||||||
|
dangerousHTML: 'fake exception\n<details>[1] k.php#L2\n[2] v.php#L3</details>',
|
||||||
|
type: 'danger',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('inject to Vue instance', () => {
|
test('inject to Vue instance', () => {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,11 @@ test('read notification', async () => {
|
||||||
document.querySelector('a')!.click()
|
document.querySelector('a')!.click()
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(get).toBeCalledWith('/user/notifications/1')
|
expect(get).toBeCalledWith('/user/notifications/1')
|
||||||
expect(showModal).toBeCalledWith('content<br><small>time</small>', 'title')
|
expect(showModal).toBeCalledWith({
|
||||||
|
mode: 'alert',
|
||||||
|
title: 'title',
|
||||||
|
dangerousHTML: 'content<br><small>time</small>',
|
||||||
|
})
|
||||||
expect(document.querySelectorAll('a')).toHaveLength(1)
|
expect(document.querySelectorAll('a')).toHaveLength(1)
|
||||||
expect(document.querySelector('span')!.textContent).toBe('1')
|
expect(document.querySelector('span')!.textContent).toBe('1')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,13 @@
|
||||||
import $ from 'jquery'
|
import 'bootstrap'
|
||||||
import * as notify from '@/scripts/notify'
|
import * as notify from '@/scripts/notify'
|
||||||
|
|
||||||
test('show AJAX error', () => {
|
test('show modal', async () => {
|
||||||
// @ts-ignore
|
process.nextTick(() => {
|
||||||
$.fn.modal = function () {
|
expect(
|
||||||
document.body.innerHTML = this.html()
|
document.querySelector('.modal-title')!.textContent,
|
||||||
}
|
).toBe('general.tip')
|
||||||
notify.showAjaxError(new Error('an-error'))
|
document.querySelector<HTMLButtonElement>('.btn-primary')!.click()
|
||||||
expect(document.body.innerHTML).toContain('an-error')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('show modal', () => {
|
|
||||||
notify.showModal('message')
|
|
||||||
expect($('.modal-title').html()).toBe('Message')
|
|
||||||
|
|
||||||
notify.showModal('message', '', 'default', {
|
|
||||||
callback: () => undefined,
|
|
||||||
destroyOnClose: false,
|
|
||||||
})
|
})
|
||||||
|
const { value } = await notify.showModal()
|
||||||
|
expect(value).toBe('')
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,3 @@
|
||||||
/* eslint-disable @typescript-eslint/indent */
|
import { ModalOptions, ModalResult } from '../../src/scripts/notify'
|
||||||
import * as notify from '../../src/scripts/notify'
|
|
||||||
|
|
||||||
export const showAjaxError = {} as jest.Mock<
|
export const showModal = {} as jest.Mock<Promise<ModalResult>, [ModalOptions | void]>
|
||||||
ReturnType<typeof notify.showAjaxError>,
|
|
||||||
Parameters<typeof notify.showAjaxError>
|
|
||||||
>
|
|
||||||
|
|
||||||
export const showModal = {} as jest.Mock<
|
|
||||||
ReturnType<typeof notify.showModal>,
|
|
||||||
Parameters<typeof notify.showModal>
|
|
||||||
>
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user