Replace Element msgbox with Bootstrap modal

This commit is contained in:
Pig Fang 2019-11-30 12:01:17 +08:00
parent b1ae902091
commit 2da0a3576b
43 changed files with 605 additions and 455 deletions

View File

@ -43,6 +43,8 @@
<script> <script>
import setAsAvatar from './mixins/setAsAvatar' import setAsAvatar from './mixins/setAsAvatar'
import removeClosetItem from './mixins/removeClosetItem' import removeClosetItem from './mixins/removeClosetItem'
import { showModal } from '../scripts/notify'
import { truthy } from '../scripts/validators'
export default { export default {
name: 'ClosetItem', name: 'ClosetItem',
@ -82,12 +84,12 @@ export default {
async rename() { async rename() {
let newTextureName let newTextureName
try { try {
({ value: newTextureName } = await this.$prompt( ({ value: newTextureName } = await showModal(
this.$t('user.renameClosetItem'),
{ {
inputValue: this.textureName, mode: 'prompt',
showCancelButton: true, text: this.$t('user.renameClosetItem'),
inputValidator: value => !!value || this.$t('skinlib.emptyNewTextureName'), input: this.textureName,
validator: truthy(this.$t('skinlib.emptyNewTextureName')),
}, },
)) ))
} catch { } catch {

View File

@ -29,7 +29,17 @@
<div v-else-if="dangerousHTML" v-html="dangerousHTML" /> <div v-else-if="dangerousHTML" v-html="dangerousHTML" />
<template v-if="mode === 'prompt'"> <template v-if="mode === 'prompt'">
<div class="form-group"> <div class="form-group">
<input v-model="value" type="text" class="form-control"> <input
v-model="value"
:type="inputType"
class="form-control"
:placeholder="placeholder"
@input="valid = true"
>
</div>
<div v-if="!valid" class="alert alert-danger">
<i class="icon far fa-times-circle" />
{{ validatorMessage }}
</div> </div>
</template> </template>
</slot> </slot>
@ -91,6 +101,16 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
placeholder: {
type: String,
},
inputType: {
type: String,
default: 'text',
},
validator: {
type: Function,
},
type: { type: {
type: String, type: String,
default: 'default', default: 'default',
@ -128,6 +148,8 @@ export default {
return { return {
hidden: false, hidden: false,
value: this.input, value: this.input,
valid: true,
validatorMessage: '',
} }
}, },
computed: { computed: {
@ -154,6 +176,15 @@ export default {
}, },
methods: { methods: {
confirm() { confirm() {
if (typeof this.validator === 'function') {
const result = this.validator(this.value)
if (typeof result === 'string') {
this.validatorMessage = result
this.valid = false
return
}
}
this.hidden = true this.hidden = true
this.$emit('confirm', { value: this.value }) this.$emit('confirm', { value: this.value })
$(this.$el).modal('hide') $(this.$el).modal('hide')

View File

@ -1,5 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import { MessageBoxInputData } from 'element-ui/types/message-box' import { showModal } from '../../scripts/notify'
import { truthy } from '../../scripts/validators'
export default Vue.extend<{ export default Vue.extend<{
name: string name: string
@ -9,15 +10,13 @@ export default Vue.extend<{
async addClosetItem() { async addClosetItem() {
let value: string let value: string
try { try {
({ value } = await this.$prompt( ({ value } = await showModal({
this.$t('skinlib.applyNotice'), mode: 'prompt',
{ title: this.$t('skinlib.setItemName'),
title: this.$t('skinlib.setItemName'), text: this.$t('skinlib.applyNotice'),
inputValue: this.name, input: this.name,
showCancelButton: true, validator: truthy(this.$t('skinlib.emptyItemName')),
inputValidator: val => !!val || this.$t('skinlib.emptyItemName'), }))
},
) as MessageBoxInputData)
} catch { } catch {
return return
} }

View File

@ -1,4 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import { showModal } from '../../scripts/notify'
export default Vue.extend({ export default Vue.extend({
data: () => ({ plugins: [] }), data: () => ({ plugins: [] }),
@ -7,15 +8,15 @@ export default Vue.extend({
name, dependencies: { all }, originalIndex, name, dependencies: { all }, originalIndex,
}: { }: {
name: string name: string
dependencies: { all: { [name: string]: string } } dependencies: { all: Record<string, string> }
originalIndex: number originalIndex: number
}) { }) {
if (Object.keys(all).length === 0) { if (Object.keys(all).length === 0) {
try { try {
await this.$confirm( await showModal({
this.$t('admin.noDependenciesNotice'), text: this.$t('admin.noDependenciesNotice'),
{ type: 'warning' }, okButtonType: 'warning',
) })
} catch { } catch {
return return
} }
@ -31,12 +32,21 @@ export default Vue.extend({
this.$message.success(message) this.$message.success(message)
this.$set(this.plugins[originalIndex], 'enabled', true) this.$set(this.plugins[originalIndex], 'enabled', true)
} else { } else {
const h = this.$createElement const div = document.createElement('div')
const vnode = h('div', {}, [ const p = document.createElement('p')
h('p', message), p.textContent = message
h('ul', {}, reason.map(item => h('li', item))), div.appendChild(p)
]) const ul = document.createElement('ul')
this.$alert('', { message: vnode, type: 'warning' }) reason.forEach(item => {
const li = document.createElement('li')
li.textContent = item
ul.appendChild(li)
})
div.appendChild(ul)
showModal({
mode: 'alert',
dangerousHTML: div.outerHTML,
})
} }
}, },
}, },

View File

@ -1,4 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import { showModal } from '../../scripts/notify'
export default Vue.extend<{ export default Vue.extend<{
name: string name: string
@ -7,10 +8,10 @@ export default Vue.extend<{
methods: { methods: {
async removeClosetItem() { async removeClosetItem() {
try { try {
await this.$confirm( await showModal({
this.$t('user.removeFromClosetNotice'), text: this.$t('user.removeFromClosetNotice'),
{ type: 'warning' }, okButtonType: 'danger',
) })
} catch { } catch {
return return
} }

View File

@ -1,4 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import { showModal } from '../../scripts/notify'
export default Vue.extend<{ export default Vue.extend<{
tid: number tid: number
@ -6,10 +7,10 @@ export default Vue.extend<{
methods: { methods: {
async setAsAvatar() { async setAsAvatar() {
try { try {
await this.$confirm( await showModal({
this.$t('user.setAvatarNotice'), title: this.$t('user.setAvatar'),
this.$t('user.setAvatar'), text: this.$t('user.setAvatarNotice'),
) })
} catch { } catch {
return return
} }

View File

@ -1,26 +1,10 @@
import Vue from 'vue' import Vue from 'vue'
import { import { Message } from 'element-ui'
Button,
Input,
Message,
MessageBox,
} from 'element-ui'
Vue.use(Button)
Vue.use(Input)
Object.assign(Vue.prototype, { Object.assign(Vue.prototype, {
$message: Message, $message: Message,
$msgbox: MessageBox,
$alert: MessageBox.alert,
$confirm: MessageBox.confirm,
$prompt: MessageBox.prompt,
}) })
blessing.ui = { blessing.ui = {
message: Message, message: Message,
msgbox: MessageBox,
alert: MessageBox.alert,
confirm: MessageBox.confirm,
prompt: MessageBox.prompt,
} }

View File

@ -7,7 +7,10 @@ export interface ModalOptions {
title?: string title?: string
text?: string text?: string
dangerousHTML?: string dangerousHTML?: string
input?: boolean input?: string
placeholder?: string
inputType?: string
validator?(value: any): string | boolean | void
type?: string type?: string
showHeader?: boolean showHeader?: boolean
center?: boolean center?: boolean
@ -29,7 +32,7 @@ export function showModal(options: ModalOptions = {}): Promise<ModalResult> {
const instance = new Vue({ const instance = new Vue({
render: h => h(Modal, { render: h => h(Modal, {
props: options, props: Object.assign({ center: true }, options),
on: { on: {
confirm: resolve, confirm: resolve,
dismiss: reject, dismiss: reject,

View File

@ -0,0 +1,7 @@
export function truthy(message: string) {
return (value?: unknown): string | void => {
if (!value) {
return message
}
}
}

View File

@ -1,4 +1 @@
@import '~element-theme-chalk/src/button.scss';
@import '~element-theme-chalk/src/input.scss';
@import '~element-theme-chalk/src/message.scss'; @import '~element-theme-chalk/src/message.scss';
@import '~element-theme-chalk/src/message-box.scss';

View File

@ -76,6 +76,7 @@ import 'vue-good-table/dist/vue-good-table.min.css'
import enablePlugin from '../../components/mixins/enablePlugin' import enablePlugin from '../../components/mixins/enablePlugin'
import tableOptions from '../../components/mixins/tableOptions' import tableOptions from '../../components/mixins/tableOptions'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default { export default {
name: 'Market', name: 'Market',
@ -149,11 +150,11 @@ export default {
}, },
async updatePlugin(plugin) { async updatePlugin(plugin) {
try { try {
await this.$confirm( await showModal({
this.$t('admin.confirmUpdate', { text: this.$t('admin.confirmUpdate', {
plugin: plugin.title, old: plugin.installed, new: plugin.version, plugin: plugin.title, old: plugin.installed, new: plugin.version,
}), }),
) })
} catch { } catch {
return return
} }

View File

@ -65,6 +65,7 @@
<modal <modal
id="modal-change-texture" id="modal-change-texture"
:title="$t('admin.changeTexture')" :title="$t('admin.changeTexture')"
center
@confirm="changeTexture" @confirm="changeTexture"
> >
<div class="form-group"> <div class="form-group">
@ -94,6 +95,8 @@ import Modal from '../../components/Modal.vue'
import tableOptions from '../../components/mixins/tableOptions' import tableOptions from '../../components/mixins/tableOptions'
import serverTable from '../../components/mixins/serverTable' import serverTable from '../../components/mixins/serverTable'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
import { truthy } from '../../scripts/validators'
export default { export default {
name: 'PlayersManagement', name: 'PlayersManagement',
@ -172,9 +175,11 @@ export default {
async changeName(player) { async changeName(player) {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('admin.changePlayerNameNotice'), { ({ value } = await showModal({
inputValue: player.name, mode: 'prompt',
inputValidator: name => !!name || this.$t('admin.emptyPlayerName'), text: this.$t('admin.changePlayerNameNotice'),
input: player.name,
validator: truthy(this.$t('admin.emptyPlayerName')),
})) }))
} catch { } catch {
return return
@ -194,12 +199,15 @@ export default {
async changeOwner(player) { async changeOwner(player) {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('admin.changePlayerOwner'), { ({ value } = await showModal({
inputValue: player.uid, mode: 'prompt',
text: this.$t('admin.changePlayerOwner'),
input: player.uid,
})) }))
} catch { } catch {
return return
} }
value = Number.parseInt(value)
const { code, message } = await this.$http.post( const { code, message } = await this.$http.post(
'/admin/players?action=owner', '/admin/players?action=owner',
@ -214,8 +222,9 @@ export default {
}, },
async deletePlayer({ pid, originalIndex }) { async deletePlayer({ pid, originalIndex }) {
try { try {
await this.$confirm(this.$t('admin.deletePlayerNotice'), { await showModal({
type: 'warning', text: this.$t('admin.deletePlayerNotice'),
okButtonType: 'danger',
}) })
} catch { } catch {
return return

View File

@ -80,6 +80,7 @@ import 'vue-good-table/dist/vue-good-table.min.css'
import enablePlugin from '../../components/mixins/enablePlugin' import enablePlugin from '../../components/mixins/enablePlugin'
import tableOptions from '../../components/mixins/tableOptions' import tableOptions from '../../components/mixins/tableOptions'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default { export default {
name: 'Plugins', name: 'Plugins',
@ -141,9 +142,15 @@ export default {
this.$message.warning(message) this.$message.warning(message)
} }
}, },
async deletePlugin({ name, originalIndex }) { async deletePlugin({
name, title, originalIndex,
}) {
try { try {
await this.$confirm(this.$t('admin.confirmDeletion'), { type: 'warning' }) await showModal({
title,
text: this.$t('admin.confirmDeletion'),
okButtonType: 'danger',
})
} catch { } catch {
return return
} }

View File

@ -31,6 +31,7 @@ import { VueGoodTable } from 'vue-good-table'
import 'vue-good-table/dist/vue-good-table.min.css' import 'vue-good-table/dist/vue-good-table.min.css'
import tableOptions from '../../components/mixins/tableOptions' import tableOptions from '../../components/mixins/tableOptions'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default { export default {
name: 'Translations', name: 'Translations',
@ -67,8 +68,10 @@ export default {
async modify(line) { async modify(line) {
let text = null let text = null
try { try {
({ value: text } = await this.$prompt(this.$t('admin.i18n.updating'), { ({ value: text } = await showModal({
inputValue: line.text, mode: 'prompt',
text: this.$t('admin.i18n.updating'),
input: line.text,
})) }))
} catch { } catch {
return return
@ -87,8 +90,9 @@ export default {
}, },
async remove({ id, originalIndex }) { async remove({ id, originalIndex }) {
try { try {
await this.$confirm(this.$t('admin.i18n.confirmDelete'), { await showModal({
type: 'warning', text: this.$t('admin.i18n.confirmDelete'),
okButtonType: 'danger',
}) })
} catch { } catch {
return return

View File

@ -45,6 +45,7 @@
<script> <script>
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
const POLLING_INTERVAL = 500 const POLLING_INTERVAL = 500
@ -73,10 +74,19 @@ export default {
) )
this.updating = false this.updating = false
if (code) { if (code) {
this.$alert(message, { type: 'error' }) showModal({
mode: 'alert',
text: message,
type: 'danger',
okButtonType: 'outline-light',
})
return return
} }
await this.$alert(this.$t('admin.updateCompleted'), { type: 'success' }) await showModal({
mode: 'alert',
text: this.$t('admin.updateCompleted'),
okButtonType: 'success',
})
window.location = blessing.base_url window.location = blessing.base_url
}, },
async polling() { async polling() {

View File

@ -45,8 +45,10 @@
<a <a
v-if="props.row.permission < 1 || (props.row.operations === 2 && props.row.permission < 2)" v-if="props.row.permission < 1 || (props.row.operations === 2 && props.row.permission < 2)"
:title="$t('admin.changePermission')" :title="$t('admin.changePermission')"
data-toggle="modal"
data-target="#modal-permission"
data-test="permission" data-test="permission"
@click="changePermission(props.row)" @click="editingUser = props.row, permission = props.row.permission"
> >
<i class="fas fa-edit btn-edit" /> <i class="fas fa-edit btn-edit" />
</a> </a>
@ -81,6 +83,40 @@
<span v-else v-text="props.formattedRow[props.column.field]" /> <span v-else v-text="props.formattedRow[props.column.field]" />
</template> </template>
</vue-good-table> </vue-good-table>
<modal
id="modal-permission"
:title="$t(this.$t('admin.newPermission'))"
center
@confirm="changePermission"
>
<label class="mr-3">
<input
v-model="permission"
type="radio"
name="permission"
:value="-1"
>
{{ $t('admin.banned') }}
</label>
<label class="mr-3">
<input
v-model="permission"
type="radio"
name="permission"
:value="0"
>
{{ $t('admin.normal') }}
</label>
<label v-if="editingUser.operations === 2">
<input
v-model="permission"
type="radio"
name="permission"
:value="1"
>
{{ $t('admin.admin') }}
</label>
</modal>
</div> </div>
</template> </template>
@ -88,13 +124,17 @@
import { VueGoodTable } from 'vue-good-table' import { VueGoodTable } from 'vue-good-table'
import 'vue-good-table/dist/vue-good-table.min.css' import 'vue-good-table/dist/vue-good-table.min.css'
import { trans } from '../../scripts/i18n' import { trans } from '../../scripts/i18n'
import Modal from '../../components/Modal.vue'
import tableOptions from '../../components/mixins/tableOptions' import tableOptions from '../../components/mixins/tableOptions'
import serverTable from '../../components/mixins/serverTable' import serverTable from '../../components/mixins/serverTable'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
import { truthy } from '../../scripts/validators'
export default { export default {
name: 'UsersManagement', name: 'UsersManagement',
components: { components: {
Modal,
VueGoodTable, VueGoodTable,
}, },
filters: { filters: {
@ -149,6 +189,8 @@ export default {
field: 'operations', label: this.$t('admin.operationsTitle'), sortable: false, globalSearchDisabled: true, field: 'operations', label: this.$t('admin.operationsTitle'), sortable: false, globalSearchDisabled: true,
}, },
], ],
editingUser: {},
permission: 0,
} }
}, },
beforeMount() { beforeMount() {
@ -166,9 +208,11 @@ export default {
async changeEmail(user) { async changeEmail(user) {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('admin.newUserEmail'), { ({ value } = await showModal({
inputValue: user.email, mode: 'prompt',
inputValidator: val => !!val || this.$t('auth.emptyEmail'), text: this.$t('admin.newUserEmail'),
input: user.email,
validator: truthy(this.$t('auth.emptyEmail')),
})) }))
} catch { } catch {
return return
@ -200,9 +244,11 @@ export default {
async changeNickName(user) { async changeNickName(user) {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('admin.newUserNickname'), { ({ value } = await showModal({
inputValue: user.nickname, mode: 'prompt',
inputValidator: val => !!val || this.$t('auth.emptyNickname'), text: this.$t('admin.newUserNickname'),
input: user.nickname,
validator: truthy(this.$t('auth.emptyNickname')),
})) }))
} catch { } catch {
return return
@ -222,7 +268,9 @@ export default {
async changePassword(user) { async changePassword(user) {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('admin.newUserPassword'), { ({ value } = await showModal({
mode: 'prompt',
text: this.$t('admin.newUserPassword'),
inputType: 'password', inputType: 'password',
})) }))
} catch { } catch {
@ -238,9 +286,11 @@ export default {
async changeScore(user) { async changeScore(user) {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('admin.newScore'), { ({ value } = await showModal({
mode: 'prompt',
text: this.$t('admin.newScore'),
input: user.score,
inputType: 'number', inputType: 'number',
inputValue: user.score,
})) }))
} catch { } catch {
return return
@ -258,41 +308,14 @@ export default {
this.$message.warning(message) this.$message.warning(message)
} }
}, },
async changePermission(user) { async changePermission() {
const operator = user.operations const permission = Number.parseInt(this.permission)
const options = [
this.$t('admin.banned'),
this.$t('admin.normal'),
]
if (operator === 2) {
options.push(this.$t('admin.admin'))
}
const h = this.$createElement
const vnode = h('div', null, [
h('span', null, this.$t('admin.newPermission')),
h(
'select',
{ attrs: { selectedIndex: 0 } },
options.map(option => h('option', null, option)),
),
])
try {
await this.$msgbox({
message: vnode,
showCancelButton: true,
})
} catch {
return
}
const value = vnode.children[1].elm.selectedIndex - 1
const { code, message } = await this.$http.post('/admin/users?action=permission', { const { code, message } = await this.$http.post('/admin/users?action=permission', {
uid: user.uid, uid: this.editingUser.uid,
permission: value, permission,
}) })
if (code === 0) { if (code === 0) {
user.permission = +value this.editingUser.permission = permission
this.$message.success(message) this.$message.success(message)
} else { } else {
this.$message.warning(message) this.$message.warning(message)
@ -300,8 +323,9 @@ export default {
}, },
async deleteUser({ uid, originalIndex }) { async deleteUser({ uid, originalIndex }) {
try { try {
await this.$confirm(this.$t('admin.deleteUserNotice'), { await showModal({
type: 'warning', text: this.$t('admin.deleteUserNotice'),
okButtonType: 'danger',
}) })
} catch { } catch {
return return

View File

@ -61,6 +61,7 @@
<script> <script>
import Captcha from '../../components/Captcha.vue' import Captcha from '../../components/Captcha.vue'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default { export default {
name: 'Login', name: 'Login',
@ -115,10 +116,16 @@ export default {
if (loginFails > 3 && !this.tooManyFails) { if (loginFails > 3 && !this.tooManyFails) {
if (this.recaptcha) { if (this.recaptcha) {
if (!this.invisible) { if (!this.invisible) {
this.$alert(this.$t('auth.tooManyFails.recaptcha'), { type: 'error' }) showModal({
mode: 'alert',
text: this.$t('auth.tooManyFails.recaptcha'),
})
} }
} else { } else {
this.$alert(this.$t('auth.tooManyFails.captcha'), { type: 'error' }) showModal({
mode: 'alert',
text: this.$t('auth.tooManyFails.captcha'),
})
} }
this.tooManyFails = true this.tooManyFails = true
} }

View File

@ -103,7 +103,14 @@
<span v-if="type === 'cape'">{{ $t('general.cape') }}</span> <span v-if="type === 'cape'">{{ $t('general.cape') }}</span>
<span v-else>{{ type }}</span> <span v-else>{{ type }}</span>
<small v-if="hasEditPermission"> <small v-if="hasEditPermission">
<a v-t="'skinlib.show.edit'" href="#" @click="changeModel" /> <a
href="#"
data-toggle="modal"
data-target="#modal-type"
@click="editingType = type"
>
{{ $t('skinlib.show.edit') }}
</a>
</small> </small>
</td> </td>
</tr> </tr>
@ -162,20 +169,59 @@
:skin="type !== 'cape' ? tid : 0" :skin="type !== 'cape' ? tid : 0"
:cape="type === 'cape' ? tid : 0" :cape="type === 'cape' ? tid : 0"
/> />
<modal
id="modal-type"
:title="$t(this.$t('skinlib.setNewTextureModel'))"
center
@confirm="changeModel"
>
<label class="mr-3">
<input
v-model="editingType"
type="radio"
name="type"
value="steve"
>
Steve
</label>
<label class="mr-3">
<input
v-model="editingType"
type="radio"
name="type"
value="alex"
>
Alex
</label>
<label>
<input
v-model="editingType"
type="radio"
name="type"
value="cape"
>
{{ $t('general.cape') }}
</label>
</modal>
</div> </div>
</template> </template>
<script> <script>
import Modal from '../../components/Modal.vue'
import setAsAvatar from '../../components/mixins/setAsAvatar' import setAsAvatar from '../../components/mixins/setAsAvatar'
import addClosetItem from '../../components/mixins/addClosetItem' import addClosetItem from '../../components/mixins/addClosetItem'
import removeClosetItem from '../../components/mixins/removeClosetItem' import removeClosetItem from '../../components/mixins/removeClosetItem'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import ApplyToPlayerDialog from '../../components/ApplyToPlayerDialog.vue' import ApplyToPlayerDialog from '../../components/ApplyToPlayerDialog.vue'
import { showModal } from '../../scripts/notify'
import { truthy } from '../../scripts/validators'
export default { export default {
name: 'Show', name: 'Show',
components: { components: {
ApplyToPlayerDialog, ApplyToPlayerDialog,
Modal,
Previewer: () => import('../../components/Previewer.vue'), Previewer: () => import('../../components/Previewer.vue'),
}, },
mixins: [ mixins: [
@ -201,6 +247,7 @@ export default {
size: 0, size: 0,
uploadAt: '', uploadAt: '',
public: true, public: true,
editingType: 'steve',
liked: blessing.extra.inCloset, liked: blessing.extra.inCloset,
canBeDownloaded: blessing.extra.download, canBeDownloaded: blessing.extra.download,
currentUid: blessing.extra.currentUid, currentUid: blessing.extra.currentUid,
@ -256,9 +303,11 @@ export default {
async changeTextureName() { async changeTextureName() {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('skinlib.setNewTextureName'), { ({ value } = await showModal({
inputValue: this.name, mode: 'prompt',
inputValidator: name => !!name || this.$t('skinlib.emptyNewTextureName'), text: this.$t('skinlib.setNewTextureName'),
input: this.name,
validator: truthy(this.$t('skinlib.emptyNewTextureName')),
})) }))
} catch { } catch {
return return
@ -276,31 +325,12 @@ export default {
} }
}, },
async changeModel() { async changeModel() {
const h = this.$createElement
const vnode = h('div', null, [
h('span', null, this.$t('skinlib.setNewTextureModel')),
h('select', { attrs: { selectedIndex: 0 } }, [
h('option', { attrs: { value: 'steve' } }, 'Steve'),
h('option', { attrs: { value: 'alex' } }, 'Alex'),
h('option', { attrs: { value: 'cape' } }, this.$t('general.cape')),
]),
])
try {
await this.$msgbox({
message: vnode,
showCancelButton: true,
})
} catch {
return
}
const value = ['steve', 'alex', 'cape'][vnode.children[1].elm.selectedIndex]
const { code, message } = await this.$http.post( const { code, message } = await this.$http.post(
'/skinlib/model', '/skinlib/model',
{ tid: this.tid, model: value }, { tid: this.tid, model: this.editingType },
) )
if (code === 0) { if (code === 0) {
this.type = value this.type = this.editingType
this.$message.success(message) this.$message.success(message)
} else { } else {
this.$message.warning(message) this.$message.warning(message)
@ -308,12 +338,11 @@ export default {
}, },
async togglePrivacy() { async togglePrivacy() {
try { try {
await this.$confirm( await showModal({
this.public text: this.public
? this.$t('skinlib.setPrivateNotice') ? this.$t('skinlib.setPrivateNotice')
: this.$t('skinlib.setPublicNotice'), : this.$t('skinlib.setPublicNotice'),
{ type: 'warning' }, })
)
} catch { } catch {
return return
} }
@ -331,10 +360,10 @@ export default {
}, },
async deleteTexture() { async deleteTexture() {
try { try {
await this.$confirm( await showModal({
this.$t('skinlib.deleteNotice'), text: this.$t('skinlib.deleteNotice'),
{ type: 'warning' }, okButtonType: 'danger',
) })
} catch { } catch {
return return
} }
@ -361,9 +390,11 @@ export default {
})() })()
let reason let reason
try { try {
({ value: reason } = await this.$prompt(prompt, { ({ value: reason } = await showModal({
mode: 'prompt',
title: this.$t('skinlib.report.title'), title: this.$t('skinlib.report.title'),
inputPlaceholder: this.$t('skinlib.report.reason'), text: prompt,
placeholder: this.$t('skinlib.report.reason'),
})) }))
} catch { } catch {
return return

View File

@ -33,6 +33,7 @@
<script> <script>
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default { export default {
name: 'BindPlayer', name: 'BindPlayer',
@ -54,7 +55,7 @@ export default {
async fetchPlayers() { async fetchPlayers() {
const players = (await this.$http.get('/user/player/list')).data const players = (await this.$http.get('/user/player/list')).data
this.players = players.map(player => player.name) this.players = players.map(player => player.name)
;[this.selected] = this.players this.selected = this.players[0]
}, },
async submit() { async submit() {
this.pending = true this.pending = true
@ -64,7 +65,7 @@ export default {
) )
this.pending = false this.pending = false
if (code === 0) { if (code === 0) {
await this.$alert(message) await showModal({ mode: 'alert', text: message })
window.location.href = `${blessing.base_url}/user` window.location.href = `${blessing.base_url}/user`
} else { } else {
this.message = message this.message = message

View File

@ -77,6 +77,7 @@ import Modal from '../../components/Modal.vue'
import tableOptions from '../../components/mixins/tableOptions' import tableOptions from '../../components/mixins/tableOptions'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { walkFetch, init } from '../../scripts/net' import { walkFetch, init } from '../../scripts/net'
import { showModal } from '../../scripts/notify'
export default { export default {
name: 'OAuthApps', name: 'OAuthApps',
@ -140,11 +141,11 @@ export default {
async modifyName(client) { async modifyName(client) {
let name let name
try { try {
const { value } = await this.$prompt('', { ({ value: name } = await showModal({
mode: 'prompt',
title: this.$t('user.oauth.name'), title: this.$t('user.oauth.name'),
inputValue: client.name, input: client.name,
}) }))
name = value
} catch { } catch {
return return
} }
@ -153,11 +154,11 @@ export default {
async modifyCallback(client) { async modifyCallback(client) {
let redirect let redirect
try { try {
const { value } = await this.$prompt('', { ({ value: redirect } = await showModal({
mode: 'prompt',
title: this.$t('user.oauth.redirect'), title: this.$t('user.oauth.redirect'),
inputValue: client.redirect, input: client.redirect,
}) }))
redirect = value
} catch { } catch {
return return
} }
@ -181,7 +182,10 @@ export default {
}, },
async remove(client) { async remove(client) {
try { try {
await this.$confirm(this.$t('user.oauth.confirmRemove'), { type: 'warning' }) await showModal({
text: this.$t('user.oauth.confirmRemove'),
okButtonType: 'danger',
})
} catch { } catch {
return return
} }

View File

@ -134,6 +134,8 @@
import Modal from '../../components/Modal.vue' import Modal from '../../components/Modal.vue'
import AddPlayerDialog from '../../components/AddPlayerDialog.vue' import AddPlayerDialog from '../../components/AddPlayerDialog.vue'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
import { truthy } from '../../scripts/validators'
export default { export default {
name: 'Players', name: 'Players',
@ -206,9 +208,11 @@ export default {
async changeName(player) { async changeName(player) {
let value let value
try { try {
({ value } = await this.$prompt(this.$t('user.changePlayerName'), { ({ value } = await showModal({
inputValue: player.name, mode: 'prompt',
inputValidator: val => !!val || this.$t('user.emptyPlayerName'), text: this.$t('user.changePlayerName'),
input: player.name,
validator: truthy(this.$t('user.emptyPlayerName')),
})) }))
} catch { } catch {
return return
@ -246,9 +250,10 @@ export default {
}, },
async deletePlayer(player, index) { async deletePlayer(player, index) {
try { try {
await this.$confirm(this.$t('user.deletePlayerNotice'), { await showModal({
title: this.$t('user.deletePlayer'), title: this.$t('user.deletePlayer'),
type: 'warning', text: this.$t('user.deletePlayerNotice'),
okButtonType: 'danger',
}) })
} catch { } catch {
return return

View File

@ -196,6 +196,7 @@
import EmailVerification from '../../components/EmailVerification.vue' import EmailVerification from '../../components/EmailVerification.vue'
import Modal from '../../components/Modal.vue' import Modal from '../../components/Modal.vue'
import emitMounted from '../../components/mixins/emitMounted' import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default { export default {
name: 'Profile', name: 'Profile',
@ -221,7 +222,7 @@ export default {
nl2br: str => str.replace(/\n/g, '<br>'), nl2br: str => str.replace(/\n/g, '<br>'),
async resetAvatar() { async resetAvatar() {
try { try {
await this.$confirm(this.$t('user.resetAvatarConfirm')) await showModal({ text: this.$t('user.resetAvatarConfirm') })
} catch { } catch {
return return
} }
@ -249,41 +250,29 @@ export default {
'/user/profile?action=password', '/user/profile?action=password',
{ current_password: oldPassword, new_password: newPassword }, { current_password: oldPassword, new_password: newPassword },
) )
await showModal({ mode: 'alert', text: message })
if (code === 0) { if (code === 0) {
await this.$alert(message)
return (window.location = `${blessing.base_url}/auth/login`) return (window.location = `${blessing.base_url}/auth/login`)
} }
return this.$alert(message, { type: 'warning' })
}, },
async changeNickName() { async changeNickName() {
const { nickname } = this const { nickname } = this
try {
await this.$confirm(this.$t('user.changeNickName', { new_nickname: nickname }))
} catch {
return
}
const { code, message } = await this.$http.post( const { code, message } = await this.$http.post(
'/user/profile?action=nickname', '/user/profile?action=nickname',
{ new_nickname: nickname }, { new_nickname: nickname },
) )
if (code === 0) { if (code === 0) {
Array.from(document.querySelectorAll('.nickname')) Array
.from(document.querySelectorAll('[data-mark="nickname"]'))
.forEach(el => (el.textContent = nickname)) .forEach(el => (el.textContent = nickname))
return this.$message.success(message) return this.$message.success(message)
} }
return this.$alert(message, { type: 'warning' }) showModal({ mode: 'alert', text: message })
}, },
async changeEmail() { async changeEmail() {
const { email } = this const { email } = this
try {
await this.$confirm(this.$t('user.changeEmail', { new_email: email }))
} catch {
return
}
const { code, message } = await this.$http.post( const { code, message } = await this.$http.post(
'/user/profile?action=email', '/user/profile?action=email',
{ new_email: email, password: this.currentPassword }, { new_email: email, password: this.currentPassword },
@ -292,7 +281,7 @@ export default {
await this.$message.success(message) await this.$message.success(message)
return (window.location = `${blessing.base_url}/auth/login`) return (window.location = `${blessing.base_url}/auth/login`)
} }
return this.$alert(message, { type: 'warning' }) showModal({ mode: 'alert', text: message })
}, },
async deleteAccount() { async deleteAccount() {
const { deleteConfirm: password } = this const { deleteConfirm: password } = this
@ -301,11 +290,9 @@ export default {
'/user/profile?action=delete', '/user/profile?action=delete',
{ password }, { password },
) )
await showModal({ mode: 'alert', text: message })
if (code === 0) { if (code === 0) {
await this.$alert(message, { type: 'success' })
window.location = `${blessing.base_url}/auth/login` window.location = `${blessing.base_url}/auth/login`
} else {
return this.$alert(message, { type: 'warning' })
} }
}, },
}, },

View File

@ -1,9 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../utils' import { flushPromises } from '../utils'
import { showModal } from '@/scripts/notify'
import ClosetItem from '@/components/ClosetItem.vue' import ClosetItem from '@/components/ClosetItem.vue'
jest.mock('@/scripts/notify')
function factory(opt = {}) { function factory(opt = {}) {
return { return {
tid: 1, tid: 1,
@ -43,14 +45,9 @@ test('rename texture', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 0 }) .mockResolvedValueOnce({ code: 0 })
.mockResolvedValueOnce({ code: 1 }) .mockResolvedValueOnce({ code: 1 })
Vue.prototype.$prompt.mockImplementationOnce(() => Promise.reject(new Error())) showModal
.mockImplementation((_, options) => { .mockRejectedValueOnce(null)
if (options.inputValidator) { .mockResolvedValue({ value: 'new-name' })
options.inputValidator('name')
options.inputValidator('')
}
return Promise.resolve({ value: 'new-name' } as MessageBoxData)
})
const wrapper = mount(ClosetItem, { propsData: factory() }) const wrapper = mount(ClosetItem, { propsData: factory() })
const button = wrapper.findAll('.dropdown-item').at(0) const button = wrapper.findAll('.dropdown-item').at(0)
@ -58,6 +55,7 @@ test('rename texture', async () => {
await flushPromises() await flushPromises()
expect(Vue.prototype.$http.post).not.toBeCalled() expect(Vue.prototype.$http.post).not.toBeCalled()
// Warning message
button.trigger('click') button.trigger('click')
await flushPromises() await flushPromises()
@ -74,9 +72,9 @@ test('remove texture', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 0 }) .mockResolvedValueOnce({ code: 0 })
.mockResolvedValueOnce({ code: 1 }) .mockResolvedValueOnce({ code: 1 })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce({}) .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(ClosetItem, { propsData: factory() }) const wrapper = mount(ClosetItem, { propsData: factory() })
const button = wrapper.findAll('.dropdown-item').at(1) const button = wrapper.findAll('.dropdown-item').at(1)
@ -98,9 +96,9 @@ test('set as avatar', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 0 }) .mockResolvedValueOnce({ code: 0 })
.mockResolvedValueOnce({ code: 1 }) .mockResolvedValueOnce({ code: 1 })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce({}) .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(ClosetItem, { propsData: factory() }) const wrapper = mount(ClosetItem, { propsData: factory() })
const button = wrapper.findAll('.dropdown-item').at(3) const button = wrapper.findAll('.dropdown-item').at(3)

View File

@ -77,6 +77,46 @@ test('prompt mode', () => {
expect(wrapper.emitted().confirm[0][0]).toStrictEqual({ value: 'hazuki' }) expect(wrapper.emitted().confirm[0][0]).toStrictEqual({ value: 'hazuki' })
}) })
test('input placeholder', () => {
const wrapper = mount(Modal, {
propsData: {
mode: 'prompt',
placeholder: 'hibike',
},
})
expect(wrapper.find('input').attributes('placeholder')).toBe('hibike')
})
test('validate input', () => {
const stub = jest.fn()
.mockReturnValueOnce(false)
.mockReturnValueOnce('invalid')
const wrapper = mount(Modal, {
propsData: {
mode: 'prompt',
validator: stub,
},
})
const button = wrapper.find('.btn-primary')
button.trigger('click')
expect(wrapper.find('.alert').exists()).toBeFalse()
button.trigger('click')
expect(wrapper.find('.alert').text()).toContain('invalid')
})
test('input type', () => {
const wrapper = mount(Modal, {
propsData: {
mode: 'prompt',
inputType: 'password',
},
})
expect(wrapper.find('[type=password]').exists()).toBeTrue()
})
test('modal type', () => { test('modal type', () => {
const wrapper = mount(Modal, { const wrapper = mount(Modal, {
propsData: { propsData: {

View File

@ -1,8 +1,10 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import SkinLibItem from '@/components/SkinLibItem.vue'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../utils' import { flushPromises } from '../utils'
import { showModal } from '@/scripts/notify'
import SkinLibItem from '@/components/SkinLibItem.vue'
jest.mock('@/scripts/notify')
test('urls', () => { test('urls', () => {
const wrapper = mount(SkinLibItem, { const wrapper = mount(SkinLibItem, {
@ -60,7 +62,7 @@ test('liked state', () => {
test('remove from closet', async () => { test('remove from closet', async () => {
Vue.prototype.$http.post.mockResolvedValue({ code: 0 }) Vue.prototype.$http.post.mockResolvedValue({ code: 0 })
Vue.prototype.$confirm.mockResolvedValue('confirm') showModal.mockResolvedValue({ value: '' })
const wrapper = mount(SkinLibItem, { const wrapper = mount(SkinLibItem, {
propsData: { propsData: {
tid: 1, liked: true, anonymous: false, tid: 1, liked: true, anonymous: false,
@ -75,15 +77,9 @@ test('add to closet', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0 }) .mockResolvedValue({ code: 0 })
Vue.prototype.$prompt showModal
.mockImplementationOnce(() => Promise.reject()) .mockRejectedValueOnce(null)
.mockImplementation((_, { inputValidator }) => { .mockResolvedValue({ value: 'name' })
if (inputValidator) {
inputValidator('')
inputValidator('name')
}
return Promise.resolve({ value: 'name' } as MessageBoxData)
})
const wrapper = mount(SkinLibItem, { const wrapper = mount(SkinLibItem, {
propsData: { propsData: {
tid: 1, liked: false, anonymous: false, tid: 1, liked: false, anonymous: false,

View File

@ -7,7 +7,7 @@ jest.mock('@/scripts/notify')
test('log out', async () => { test('log out', async () => {
showModal showModal
.mockRejectedValueOnce({}) .mockRejectedValueOnce(null)
.mockResolvedValueOnce({ value: '' }) .mockResolvedValueOnce({ value: '' })
post.mockResolvedValue({ message: '' }) post.mockResolvedValue({ message: '' })

View File

@ -0,0 +1,12 @@
import * as validators from '@/scripts/validators'
test('truthy', () => {
const validator = validators.truthy('invalid')
expect(validator()).toBe('invalid')
expect(validator(null)).toBe('invalid')
expect(validator(undefined)).toBe('invalid')
expect(validator(0)).toBe('invalid')
expect(validator('')).toBe('invalid')
expect(validator([])).toBeUndefined()
expect(validator({})).toBeUndefined()
})

View File

@ -74,8 +74,3 @@ Vue.prototype.$message = {
warning: jest.fn(), warning: jest.fn(),
error: jest.fn(), error: jest.fn(),
} }
// @ts-ignore
Vue.prototype.$msgbox = jest.fn()
Vue.prototype.$alert = jest.fn()
Vue.prototype.$confirm = jest.fn()
Vue.prototype.$prompt = jest.fn()

View File

@ -17,10 +17,6 @@ declare module 'vue/types/vue' {
warning: jest.Mock<ReturnType<typeof Message>, Parameters<typeof Message>> warning: jest.Mock<ReturnType<typeof Message>, Parameters<typeof Message>>
error: jest.Mock<ReturnType<typeof Message>, Parameters<typeof Message>> error: jest.Mock<ReturnType<typeof Message>, Parameters<typeof Message>>
} }
$msgbox: jest.Mock<ReturnType<typeof MessageBox>, [ElMessageBoxOptions]>
$alert: jest.Mock<ReturnType<typeof MessageBox>, [string, ElMessageBoxOptions]>
$confirm: jest.Mock<ReturnType<typeof MessageBox>, [string, ElMessageBoxOptions]>
$prompt: jest.Mock<ReturnType<typeof MessageBox>, [string, ElMessageBoxOptions]>
} }
} }
} }

View File

@ -1,7 +1,10 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Market from '@/views/admin/Market.vue'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Market from '@/views/admin/Market.vue'
jest.mock('@/scripts/notify')
test('render dependencies', async () => { test('render dependencies', async () => {
Vue.prototype.$http.get.mockResolvedValue([ Vue.prototype.$http.get.mockResolvedValue([
@ -82,9 +85,9 @@ test('update plugin', async () => {
]) ])
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(Market) const wrapper = mount(Market)
await flushPromises() await flushPromises()
const button = wrapper.find('button') const button = wrapper.find('button')

View File

@ -1,10 +1,12 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Modal from '@/components/Modal.vue' import Modal from '@/components/Modal.vue'
import Players from '@/views/admin/Players.vue' import Players from '@/views/admin/Players.vue'
jest.mock('@/scripts/notify')
test('fetch data after initializing', () => { test('fetch data after initializing', () => {
Vue.prototype.$http.get.mockResolvedValue({ data: [] }) Vue.prototype.$http.get.mockResolvedValue({ data: [] })
mount(Players) mount(Players)
@ -55,15 +57,9 @@ test('change player name', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValueOnce({ code: 0, message: '0' }) .mockResolvedValueOnce({ code: 0, message: '0' })
Vue.prototype.$prompt showModal
.mockImplementationOnce(() => Promise.reject()) .mockRejectedValueOnce(null)
.mockImplementation((_, options) => { .mockResolvedValue({ value: 'new' })
if (options.inputValidator) {
options.inputValidator('')
options.inputValidator('new')
}
return Promise.resolve({ value: 'new' } as MessageBoxData)
})
const wrapper = mount(Players) const wrapper = mount(Players)
await flushPromises() await flushPromises()
const button = wrapper.find('[data-test="name"]') const button = wrapper.find('[data-test="name"]')
@ -91,9 +87,9 @@ test('change owner', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValueOnce({ code: 0, message: '0' }) .mockResolvedValueOnce({ code: 0, message: '0' })
Vue.prototype.$prompt showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue({ value: '3' } as MessageBoxData) .mockResolvedValue({ value: '3' })
const wrapper = mount(Players) const wrapper = mount(Players)
await flushPromises() await flushPromises()
@ -106,7 +102,7 @@ test('change owner', async () => {
await flushPromises() await flushPromises()
expect(Vue.prototype.$http.post).toBeCalledWith( expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/players?action=owner', '/admin/players?action=owner',
{ pid: 1, uid: '3' }, { pid: 1, uid: 3 },
) )
button.trigger('click') button.trigger('click')
await flushPromises() await flushPromises()
@ -122,9 +118,9 @@ test('delete player', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValueOnce({ code: 0, message: '0' }) .mockResolvedValueOnce({ code: 0, message: '0' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(Players) const wrapper = mount(Players)
await flushPromises() await flushPromises()

View File

@ -1,7 +1,8 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Plugins from '@/views/admin/Plugins.vue'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Plugins from '@/views/admin/Plugins.vue'
jest.mock('@/scripts/notify') jest.mock('@/scripts/notify')
@ -60,22 +61,27 @@ test('enable plugin', async () => {
code: 1, message: '1', data: { reason: ['abc'] }, code: 1, message: '1', data: { reason: ['abc'] },
}) })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(Plugins) const wrapper = mount(Plugins)
await flushPromises() await flushPromises()
wrapper.findAll('.actions').at(0) wrapper
.findAll('.actions')
.at(0)
.find('a') .find('a')
.trigger('click') .trigger('click')
await flushPromises() await flushPromises()
expect(Vue.prototype.$confirm).toBeCalledWith('admin.noDependenciesNotice', { expect(showModal).toBeCalledWith({
type: 'warning', text: 'admin.noDependenciesNotice',
okButtonType: 'warning',
}) })
expect(Vue.prototype.$http.post).not.toBeCalled() expect(Vue.prototype.$http.post).not.toBeCalled()
wrapper.findAll('.actions').at(0) wrapper
.findAll('.actions')
.at(0)
.find('a') .find('a')
.trigger('click') .trigger('click')
await flushPromises() await flushPromises()
@ -83,12 +89,14 @@ test('enable plugin', async () => {
'/admin/plugins/manage', '/admin/plugins/manage',
{ action: 'enable', name: 'a' }, { action: 'enable', name: 'a' },
) )
expect(Vue.prototype.$alert).toBeCalledWith('', ({ expect(showModal).toBeCalledWith({
type: 'warning', mode: 'alert',
message: expect.anything(), dangerousHTML: expect.stringContaining('<li>abc</li>'),
})) })
wrapper.findAll('.actions').at(1) wrapper
.findAll('.actions')
.at(1)
.find('a') .find('a')
.trigger('click') .trigger('click')
await flushPromises() await flushPromises()
@ -123,15 +131,18 @@ test('disable plugin', async () => {
test('delete plugin', async () => { test('delete plugin', async () => {
Vue.prototype.$http.get.mockResolvedValue([ Vue.prototype.$http.get.mockResolvedValue([
{ {
name: 'a', dependencies: { all: {}, unsatisfied: {} }, enabled: false, name: 'a',
title: 'My Plugin',
dependencies: { all: {}, unsatisfied: {} },
enabled: false,
}, },
]) ])
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(Plugins) const wrapper = mount(Plugins)
await flushPromises() await flushPromises()
const button = wrapper.find('.actions').findAll('a') const button = wrapper.find('.actions').findAll('a')
@ -139,8 +150,10 @@ test('delete plugin', async () => {
button.trigger('click') button.trigger('click')
await flushPromises() await flushPromises()
expect(Vue.prototype.$confirm).toBeCalledWith('admin.confirmDeletion', { expect(showModal).toBeCalledWith({
type: 'warning', title: 'My Plugin',
text: 'admin.confirmDeletion',
okButtonType: 'danger',
}) })
expect(Vue.prototype.$http.post).not.toBeCalled() expect(Vue.prototype.$http.post).not.toBeCalled()

View File

@ -1,9 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Translations from '@/views/admin/Translations.vue' import Translations from '@/views/admin/Translations.vue'
jest.mock('@/scripts/notify')
test('fetch data', async () => { test('fetch data', async () => {
Vue.prototype.$http.get.mockResolvedValue([ Vue.prototype.$http.get.mockResolvedValue([
{ {
@ -26,10 +28,10 @@ test('modify line', async () => {
Vue.prototype.$http.put Vue.prototype.$http.put
.mockResolvedValueOnce({ code: 1, message: 'failed' }) .mockResolvedValueOnce({ code: 1, message: 'failed' })
.mockResolvedValueOnce({ code: 0, message: 'ok' }) .mockResolvedValueOnce({ code: 0, message: 'ok' })
Vue.prototype.$prompt showModal
.mockRejectedValueOnce(null) .mockRejectedValueOnce(null)
.mockResolvedValueOnce({ value: '' } as MessageBoxData) .mockResolvedValueOnce({ value: '' })
.mockResolvedValueOnce({ value: 'wanshengwei' } as MessageBoxData) .mockResolvedValueOnce({ value: 'wanshengwei' })
const wrapper = mount(Translations) const wrapper = mount(Translations)
await flushPromises() await flushPromises()
@ -65,9 +67,9 @@ test('delete line', async () => {
}, },
]) ])
Vue.prototype.$http.del.mockResolvedValueOnce({ message: 'ok' }) Vue.prototype.$http.del.mockResolvedValueOnce({ message: 'ok' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce(null) .mockRejectedValueOnce(null)
.mockResolvedValueOnce('confirm') .mockResolvedValueOnce({ value: '' })
const wrapper = mount(Translations) const wrapper = mount(Translations)
await flushPromises() await flushPromises()

View File

@ -1,8 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Update from '@/views/admin/Update.vue' import Update from '@/views/admin/Update.vue'
jest.mock('@/scripts/notify')
afterEach(() => { afterEach(() => {
window.blessing.extra = { canUpdate: true } window.blessing.extra = { canUpdate: true }
}) })
@ -24,12 +27,16 @@ test('perform update', async () => {
button.trigger('click') button.trigger('click')
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('fail', { type: 'error' }) expect(showModal).toBeCalledWith({
mode: 'alert',
text: 'fail',
type: 'danger',
okButtonType: 'outline-light',
})
button.trigger('click') button.trigger('click')
jest.runOnlyPendingTimers() jest.runOnlyPendingTimers()
await flushPromises() await flushPromises()
expect($).toBeCalled()
expect(Vue.prototype.$http.get).toBeCalledWith( expect(Vue.prototype.$http.get).toBeCalledWith(
'/admin/update/download', '/admin/update/download',
{ action: 'progress' }, { action: 'progress' },

View File

@ -1,13 +1,15 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Users from '@/views/admin/Users.vue'
import '@/scripts/i18n'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import '@/scripts/i18n'
import Modal from '@/components/Modal.vue'
import { showModal } from '@/scripts/notify'
import Users from '@/views/admin/Users.vue'
jest.mock('@/scripts/i18n', () => ({ jest.mock('@/scripts/i18n', () => ({
trans: (key: string) => key, trans: (key: string) => key,
})) }))
jest.mock('@/scripts/notify')
test('fetch data after initializing', () => { test('fetch data after initializing', () => {
Vue.prototype.$http.get.mockResolvedValue({ data: [] }) Vue.prototype.$http.get.mockResolvedValue({ data: [] })
@ -236,15 +238,9 @@ test('change email', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValueOnce({ code: 0, message: '0' }) .mockResolvedValueOnce({ code: 0, message: '0' })
Vue.prototype.$prompt showModal
.mockImplementationOnce(() => Promise.reject()) .mockRejectedValueOnce(null)
.mockImplementation((_, options) => { .mockResolvedValue({ value: 'd@e.f' })
if (options.inputValidator) {
options.inputValidator('')
options.inputValidator('value')
}
return Promise.resolve({ value: 'd@e.f' } as MessageBoxData)
})
const wrapper = mount(Users) const wrapper = mount(Users)
await flushPromises() await flushPromises()
const button = wrapper.find('[data-test="email"]') const button = wrapper.find('[data-test="email"]')
@ -299,15 +295,9 @@ test('change nickname', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValueOnce({ code: 0, message: '0' }) .mockResolvedValueOnce({ code: 0, message: '0' })
Vue.prototype.$prompt showModal
.mockImplementationOnce(() => Promise.reject()) .mockRejectedValueOnce(null)
.mockImplementation((_, options) => { .mockResolvedValue({ value: 'new' })
if (options.inputValidator) {
options.inputValidator('')
options.inputValidator('value')
}
return Promise.resolve({ value: 'new' } as MessageBoxData)
})
const wrapper = mount(Users) const wrapper = mount(Users)
await flushPromises() await flushPromises()
const button = wrapper.find('[data-test="nickname"]') const button = wrapper.find('[data-test="nickname"]')
@ -337,9 +327,9 @@ test('change password', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 0, message: '0' }) .mockResolvedValueOnce({ code: 0, message: '0' })
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
Vue.prototype.$prompt showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue({ value: 'password' }as MessageBoxData) .mockResolvedValue({ value: 'password' })
const wrapper = mount(Users) const wrapper = mount(Users)
await flushPromises() await flushPromises()
@ -372,9 +362,9 @@ test('change score', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValueOnce({ code: 0, message: '0' }) .mockResolvedValueOnce({ code: 0, message: '0' })
Vue.prototype.$prompt showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue({ value: '45' }as MessageBoxData) .mockResolvedValue({ value: '45' })
const wrapper = mount(Users) const wrapper = mount(Users)
await flushPromises() await flushPromises()
@ -397,70 +387,48 @@ test('change score', async () => {
}) })
test('change permission', async () => { test('change permission', async () => {
Vue.prototype.$http.get Vue.prototype.$http.get.mockResolvedValue({
.mockResolvedValueOnce({ data: [
data: [ {
{ uid: 1, permission: 0, operations: 2,
uid: 1, permission: 0, operations: 2, },
}, {
], uid: 2, permission: 0, operations: 1,
}) },
.mockResolvedValueOnce({ ],
data: [ })
{
uid: 1, permission: 0, operations: 1,
},
],
})
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$msgbox
.mockImplementationOnce(() => Promise.reject())
.mockImplementationOnce(options => {
if (options.message) {
const vnode = options.message as Vue.VNode
const elm = document.createElement('select')
elm.appendChild(document.createElement('option'))
elm.appendChild(document.createElement('option'))
elm.appendChild(document.createElement('option'))
elm.selectedIndex = 2
;(vnode.children as Vue.VNode[])[1].elm = elm
}
return Promise.resolve({} as MessageBoxData)
})
.mockImplementation(options => {
if (options.message) {
const vnode = options.message as Vue.VNode
const elm = document.createElement('select')
elm.appendChild(document.createElement('option'))
elm.appendChild(document.createElement('option'))
elm.selectedIndex = 0
;(vnode.children as Vue.VNode[])[1].elm = elm
}
return Promise.resolve({} as MessageBoxData)
})
let wrapper = mount(Users) const wrapper = mount(Users)
await flushPromises() await flushPromises()
let button = wrapper.find('[data-test=permission]')
button.trigger('click') wrapper
expect(Vue.prototype.$http.post).not.toBeCalled() .findAll('[data-test=permission]')
.at(0)
.trigger('click')
expect(wrapper.findAll('[type=radio]')).toHaveLength(3)
wrapper
.findAll('[data-test=permission]')
.at(1)
.trigger('click')
expect(wrapper.findAll('[type=radio]')).toHaveLength(2)
const button = wrapper.findAll('[data-test=permission]').at(1)
button.trigger('click') button.trigger('click')
wrapper.find('[type=radio]:nth-child(1)').setChecked()
wrapper.find(Modal).vm.$emit('confirm')
await flushPromises() await flushPromises()
expect(Vue.prototype.$http.post).toBeCalledWith( expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/users?action=permission', '/admin/users?action=permission',
{ uid: 1, permission: 1 }, { uid: 2, permission: -1 },
) )
expect(wrapper.text()).toContain('admin.normal')
wrapper = mount(Users)
await flushPromises()
button = wrapper.find('[data-test=permission]')
button.trigger('click') button.trigger('click')
wrapper.find('[type=radio]:nth-child(1)').setChecked()
wrapper.find(Modal).vm.$emit('confirm')
await flushPromises() await flushPromises()
expect(wrapper.text()).toContain('admin.banned') expect(wrapper.text()).toContain('admin.banned')
}) })
@ -474,9 +442,9 @@ test('delete user', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(Users) const wrapper = mount(Users)
await flushPromises() await flushPromises()

View File

@ -1,8 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Login from '@/views/auth/Login.vue' import Login from '@/views/auth/Login.vue'
jest.mock('@/scripts/notify')
const Captcha = Vue.extend({ const Captcha = Vue.extend({
methods: { methods: {
execute() { execute() {
@ -47,7 +50,10 @@ test('login', async () => {
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('auth.tooManyFails.captcha', { type: 'error' }) expect(showModal).toBeCalledWith({
mode: 'alert',
text: 'auth.tooManyFails.captcha',
})
expect(wrapper.find('img').exists()).toBeTrue() expect(wrapper.find('img').exists()).toBeTrue()
wrapper.setData({ wrapper.setData({
@ -61,7 +67,10 @@ test('login', async () => {
}) })
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('auth.tooManyFails.recaptcha', { type: 'error' }) expect(showModal).toBeCalledWith({
mode: 'alert',
text: 'auth.tooManyFails.recaptcha',
})
wrapper.find('[type="checkbox"]').setChecked() wrapper.find('[type="checkbox"]').setChecked()
form.trigger('submit') form.trigger('submit')

View File

@ -1,8 +1,10 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Show from '@/views/skinlib/Show.vue'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Show from '@/views/skinlib/Show.vue'
jest.mock('@/scripts/notify')
type Component = Vue & { type Component = Vue & {
liked: boolean liked: boolean
@ -191,7 +193,7 @@ test('set as avatar', async () => {
}) })
await flushPromises() await flushPromises()
wrapper.find('[data-test="setAsAvatar"]').trigger('click') wrapper.find('[data-test="setAsAvatar"]').trigger('click')
expect(Vue.prototype.$confirm).toBeCalled() expect(showModal).toBeCalled()
}) })
test('hide "set avatar" button when texture is cape', async () => { test('hide "set avatar" button when texture is cape', async () => {
@ -210,7 +212,7 @@ test('add to closet', async () => {
Object.assign(window.blessing.extra, { currentUid: 1, inCloset: false }) Object.assign(window.blessing.extra, { currentUid: 1, inCloset: false })
Vue.prototype.$http.get.mockResolvedValue({ data: { name: 'wow', likes: 2 } }) Vue.prototype.$http.get.mockResolvedValue({ data: { name: 'wow', likes: 2 } })
Vue.prototype.$http.post.mockResolvedValue({ code: 0, message: '' }) Vue.prototype.$http.post.mockResolvedValue({ code: 0, message: '' })
Vue.prototype.$prompt.mockResolvedValue({ value: 'a' } as MessageBoxData) showModal.mockResolvedValue({ value: 'a' })
const wrapper = mount<Component>(Show, { const wrapper = mount<Component>(Show, {
mocks: { mocks: {
$route: ['/skinlib/show/1', '1'], $route: ['/skinlib/show/1', '1'],
@ -245,15 +247,9 @@ test('change texture name', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$prompt showModal
.mockImplementationOnce(() => Promise.reject('cancel')) .mockRejectedValueOnce(null)
.mockImplementation((_, { inputValidator }) => { .mockResolvedValue({ value: 'new-name' })
if (inputValidator) {
inputValidator('')
inputValidator('new-name')
}
return Promise.resolve({ value: 'new-name' } as MessageBoxData)
})
const wrapper = mount<Component>(Show, { const wrapper = mount<Component>(Show, {
mocks: { mocks: {
$route: ['/skinlib/show/1', '1'], $route: ['/skinlib/show/1', '1'],
@ -283,34 +279,24 @@ test('change texture model', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$msgbox
.mockImplementationOnce(() => Promise.reject())
.mockImplementation(options => {
if (options.message) {
const vnode = options.message as Vue.VNode
const elm = document.createElement('select')
elm.appendChild(document.createElement('option'))
elm.appendChild(document.createElement('option'))
elm.selectedIndex = 1
;(vnode.children as Vue.VNode[])[1].elm = elm
}
return Promise.resolve({} as MessageBoxData)
})
const wrapper = mount<Component>(Show, { const wrapper = mount<Component>(Show, {
mocks: { mocks: {
$route: ['/skinlib/show/1', '1'], $route: ['/skinlib/show/1', '1'],
}, },
stubs: { previewer }, stubs: { previewer },
}) })
const modal = wrapper.find('#modal-type')
const button = wrapper const button = wrapper
.findAll('small') .findAll('small')
.at(1) .at(1)
.find('a') .find('a')
button.trigger('click') button.trigger('click')
expect(Vue.prototype.$http.post).not.toBeCalled() wrapper
.findAll('[type=radio]')
button.trigger('click') .at(1)
.setChecked()
modal.vm.$emit('confirm')
await flushPromises() await flushPromises()
expect(Vue.prototype.$http.post).toBeCalledWith( expect(Vue.prototype.$http.post).toBeCalledWith(
'/skinlib/model', '/skinlib/model',
@ -319,6 +305,11 @@ test('change texture model', async () => {
expect(Vue.prototype.$message.warning).toBeCalledWith('1') expect(Vue.prototype.$message.warning).toBeCalledWith('1')
button.trigger('click') button.trigger('click')
wrapper
.findAll('[type=radio]')
.at(1)
.setChecked()
modal.vm.$emit('confirm')
await flushPromises() await flushPromises()
expect(wrapper.vm.type).toBe('alex') expect(wrapper.vm.type).toBe('alex')
}) })
@ -328,9 +319,9 @@ test('toggle privacy', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount<Component>(Show, { const wrapper = mount<Component>(Show, {
mocks: { mocks: {
$route: ['/skinlib/show/1', '1'], $route: ['/skinlib/show/1', '1'],
@ -364,9 +355,9 @@ test('delete texture', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: '1' }) .mockResolvedValueOnce({ code: 1, message: '1' })
.mockResolvedValue({ code: 0, message: '0' }) .mockResolvedValue({ code: 0, message: '0' })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(Show, { const wrapper = mount(Show, {
mocks: { mocks: {
$route: ['/skinlib/show/1', '1'], $route: ['/skinlib/show/1', '1'],
@ -397,10 +388,10 @@ test('report texture', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: 'duplicated' }) .mockResolvedValueOnce({ code: 1, message: 'duplicated' })
.mockResolvedValue({ code: 0, message: 'success' }) .mockResolvedValue({ code: 0, message: 'success' })
Vue.prototype.$prompt showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue({ value: 'reason' } as MessageBoxData) .mockResolvedValue({ value: 'reason' })
const wrapper = mount(Show, { const wrapper = mount(Show, {
mocks: { mocks: {
$route: ['/skinlib/show/1', '1'], $route: ['/skinlib/show/1', '1'],
@ -410,24 +401,30 @@ test('report texture', async () => {
const button = wrapper.find('[data-test=report]') const button = wrapper.find('[data-test=report]')
button.trigger('click') button.trigger('click')
expect(Vue.prototype.$prompt).toBeCalledWith('', { expect(showModal).toBeCalledWith({
mode: 'prompt',
title: 'skinlib.report.title', title: 'skinlib.report.title',
inputPlaceholder: 'skinlib.report.reason', text: '',
placeholder: 'skinlib.report.reason',
}) })
expect(Vue.prototype.$http.post).not.toBeCalled() expect(Vue.prototype.$http.post).not.toBeCalled()
wrapper.setData({ reportScore: -5 }) wrapper.setData({ reportScore: -5 })
button.trigger('click') button.trigger('click')
expect(Vue.prototype.$prompt).toBeCalledWith('skinlib.report.negative', { expect(showModal).toBeCalledWith({
mode: 'prompt',
title: 'skinlib.report.title', title: 'skinlib.report.title',
inputPlaceholder: 'skinlib.report.reason', text: 'skinlib.report.negative',
placeholder: 'skinlib.report.reason',
}) })
wrapper.setData({ reportScore: 5 }) wrapper.setData({ reportScore: 5 })
button.trigger('click') button.trigger('click')
expect(Vue.prototype.$prompt).toBeCalledWith('skinlib.report.positive', { expect(showModal).toBeCalledWith({
mode: 'prompt',
title: 'skinlib.report.title', title: 'skinlib.report.title',
inputPlaceholder: 'skinlib.report.reason', text: 'skinlib.report.positive',
placeholder: 'skinlib.report.reason',
}) })
await flushPromises() await flushPromises()
expect(Vue.prototype.$http.post).toBeCalledWith( expect(Vue.prototype.$http.post).toBeCalledWith(

View File

@ -1,8 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Bind from '@/views/user/Bind.vue' import Bind from '@/views/user/Bind.vue'
jest.mock('@/scripts/notify')
test('list existed players', async () => { test('list existed players', async () => {
Vue.prototype.$http.get Vue.prototype.$http.get
.mockResolvedValue({ data: [{ name: 'a' }, { name: 'b' }] }) .mockResolvedValue({ data: [{ name: 'a' }, { name: 'b' }] })
@ -36,5 +39,5 @@ test('submit', async () => {
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('ok') expect(showModal).toBeCalledWith({ mode: 'alert', text: 'ok' })
}) })

View File

@ -1,11 +1,13 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { walkFetch } from '@/scripts/net' import { walkFetch } from '@/scripts/net'
import { showModal } from '@/scripts/notify'
import Modal from '@/components/Modal.vue' import Modal from '@/components/Modal.vue'
import OAuth from '@/views/user/OAuth.vue' import OAuth from '@/views/user/OAuth.vue'
jest.mock('@/scripts/notify')
jest.mock('@/scripts/net', () => ({ jest.mock('@/scripts/net', () => ({
walkFetch: jest.fn(), walkFetch: jest.fn(),
init: {}, init: {},
@ -55,9 +57,9 @@ test('modify name', async () => {
walkFetch walkFetch
.mockResolvedValueOnce({ message: 'fail' }) .mockResolvedValueOnce({ message: 'fail' })
.mockResolvedValueOnce({ id: 1, name: 'new-name' }) .mockResolvedValueOnce({ id: 1, name: 'new-name' })
Vue.prototype.$prompt showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue({ value: 'new-name' } as MessageBoxData) .mockResolvedValue({ value: 'new-name' })
const wrapper = mount(OAuth) const wrapper = mount(OAuth)
await flushPromises() await flushPromises()
const button = wrapper.find('[data-test=name]') const button = wrapper.find('[data-test=name]')
@ -89,9 +91,9 @@ test('modify redirect', async () => {
walkFetch walkFetch
.mockResolvedValueOnce({ message: 'fail' }) .mockResolvedValueOnce({ message: 'fail' })
.mockResolvedValueOnce({ id: 1, redirect: 'https://example.net/' }) .mockResolvedValueOnce({ id: 1, redirect: 'https://example.net/' })
Vue.prototype.$prompt showModal
.mockRejectedValueOnce('') .mockRejectedValueOnce(null)
.mockResolvedValue({ value: 'https://example.net/' } as MessageBoxData) .mockResolvedValue({ value: 'https://example.net/' })
const wrapper = mount(OAuth) const wrapper = mount(OAuth)
await flushPromises() await flushPromises()
const button = wrapper.find('[data-test=callback]') const button = wrapper.find('[data-test=callback]')
@ -120,9 +122,9 @@ test('remove app', async () => {
Vue.prototype.$http.get.mockResolvedValue([ Vue.prototype.$http.get.mockResolvedValue([
{ id: 1, name: 'name' }, { id: 1, name: 'name' },
]) ])
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('cancel') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(OAuth) const wrapper = mount(OAuth)
await flushPromises() await flushPromises()

View File

@ -1,9 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Players from '@/views/user/Players.vue' import Players from '@/views/user/Players.vue'
jest.mock('@/scripts/notify')
window.blessing.extra = { window.blessing.extra = {
rule: 'rule', rule: 'rule',
length: 'length', length: 'length',
@ -90,14 +92,9 @@ test('change player name', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1 }) .mockResolvedValueOnce({ code: 1 })
.mockResolvedValue({ code: 0 }) .mockResolvedValue({ code: 0 })
Vue.prototype.$prompt.mockImplementationOnce(() => Promise.reject('cancel')) showModal
.mockImplementation((_, { inputValidator }) => { .mockRejectedValueOnce(null)
if (inputValidator) { .mockResolvedValue({ value: 'new-name' })
inputValidator('')
inputValidator('new-name')
}
return Promise.resolve({ value: 'new-name' } as MessageBoxData)
})
const wrapper = mount(Players) const wrapper = mount(Players)
await flushPromises() await flushPromises()
const button = wrapper.find('.btn-default') const button = wrapper.find('.btn-default')
@ -126,9 +123,9 @@ test('delete player', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1 }) .mockResolvedValueOnce({ code: 1 })
.mockResolvedValue({ code: 0 }) .mockResolvedValue({ code: 0 })
Vue.prototype.$confirm showModal
.mockRejectedValueOnce({}) .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
const wrapper = mount(Players) const wrapper = mount(Players)
await flushPromises() await flushPromises()
const button = wrapper.findAll('.btn-danger') const button = wrapper.findAll('.btn-danger')

View File

@ -1,8 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils' import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Profile from '@/views/user/Profile.vue' import Profile from '@/views/user/Profile.vue'
jest.mock('@/scripts/notify')
window.blessing.extra = { unverified: false } window.blessing.extra = { unverified: false }
test('computed values', () => { test('computed values', () => {
@ -20,9 +23,9 @@ test('convert linebreak', () => {
}) })
test('reset avatar', async () => { test('reset avatar', async () => {
Vue.prototype.$confirm showModal
.mockRejectedValueOnce('close') .mockRejectedValueOnce(null)
.mockResolvedValue('confirm') .mockResolvedValue({ value: '' })
Vue.prototype.$http.post.mockResolvedValue({ message: 'ok' }) Vue.prototype.$http.post.mockResolvedValue({ message: 'ok' })
const wrapper = mount(Profile) const wrapper = mount(Profile)
const button = wrapper.find('[data-test=resetAvatar]') const button = wrapper.find('[data-test=resetAvatar]')
@ -63,29 +66,22 @@ test('change password', async () => {
'/user/profile?action=password', '/user/profile?action=password',
{ current_password: '1', new_password: '1' }, { current_password: '1', new_password: '1' },
) )
expect(Vue.prototype.$alert).toBeCalledWith('w', { type: 'warning' }) expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('o') expect(showModal).toBeCalledWith({ mode: 'alert', text: 'o' })
}) })
test('change nickname', async () => { test('change nickname', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: 'w' }) .mockResolvedValueOnce({ code: 1, message: 'w' })
.mockResolvedValue({ code: 0, message: 'o' }) .mockResolvedValue({ code: 0, message: 'o' })
Vue.prototype.$confirm
.mockRejectedValueOnce('close')
.mockResolvedValue('confirm')
const wrapper = mount(Profile) const wrapper = mount(Profile)
const form = wrapper.find('[data-test=changeNickName]') const form = wrapper.find('[data-test=changeNickName]')
document.body.innerHTML += '<span class="nickname"></span>' document.body.innerHTML += '<span data-mark="nickname"></span>'
wrapper.setData({ nickname: 'nickname' }) wrapper.setData({ nickname: 'nickname' })
form.trigger('submit')
expect(Vue.prototype.$http.post).not.toBeCalled()
expect(Vue.prototype.$confirm).toBeCalledWith('user.changeNickName')
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$http.post).toBeCalledWith( expect(Vue.prototype.$http.post).toBeCalledWith(
@ -93,29 +89,22 @@ test('change nickname', async () => {
{ new_nickname: 'nickname' }, { new_nickname: 'nickname' },
) )
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('w', { type: 'warning' }) expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$message.success).toBeCalledWith('o') expect(Vue.prototype.$message.success).toBeCalledWith('o')
expect(document.querySelector('.nickname')!.textContent).toBe('nickname') expect(document.querySelector('span')!.textContent).toBe('nickname')
}) })
test('change email', async () => { test('change email', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 1, message: 'w' }) .mockResolvedValueOnce({ code: 1, message: 'w' })
.mockResolvedValue({ code: 0, message: 'o' }) .mockResolvedValue({ code: 0, message: 'o' })
Vue.prototype.$confirm
.mockRejectedValueOnce('close')
.mockResolvedValue('confirm')
const wrapper = mount(Profile) const wrapper = mount(Profile)
const form = wrapper.find('[data-test=changeEmail]') const form = wrapper.find('[data-test=changeEmail]')
wrapper.setData({ email: 'a@b.c', currentPassword: 'abc' }) wrapper.setData({ email: 'a@b.c', currentPassword: 'abc' })
form.trigger('submit')
expect(Vue.prototype.$confirm).toBeCalledWith('user.changeEmail')
expect(Vue.prototype.$http.post).not.toBeCalled()
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$http.post).toBeCalledWith( expect(Vue.prototype.$http.post).toBeCalledWith(
@ -123,7 +112,7 @@ test('change email', async () => {
{ new_email: 'a@b.c', password: 'abc' }, { new_email: 'a@b.c', password: 'abc' },
) )
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('w', { type: 'warning' }) expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
@ -145,9 +134,9 @@ test('delete account', async () => {
{ password: 'abc' }, { password: 'abc' },
) )
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('w', { type: 'warning' }) expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
form.trigger('submit') form.trigger('submit')
await flushPromises() await flushPromises()
expect(Vue.prototype.$alert).toBeCalledWith('o', { type: 'success' }) expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
}) })

View File

@ -1,7 +1,9 @@
<li class="nav-item dropdown user-menu"> <li class="nav-item dropdown user-menu">
<a href="#" class="nav-link" data-toggle="dropdown"> <a href="#" class="nav-link" data-toggle="dropdown">
<img src="{{ avatar }}" class="user-image img-circle elevation-2" alt="User Image"> <img src="{{ avatar }}" class="user-image img-circle elevation-2" alt="User Image">
<span class="d-none d-md-inline d-sm-block">{{ user.nickname ?? user.email }}</span> <span class="d-none d-md-inline d-sm-block" data-mark="nickname">
{{ user.nickname ?? user.email }}
</span>
</a> </a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">

View File

@ -4,7 +4,7 @@
<img src="{{ avatar }}" class="img-circle elevation-2" alt="User Image"> <img src="{{ avatar }}" class="img-circle elevation-2" alt="User Image">
</div> </div>
<div class="info"> <div class="info">
<a class="d-block">{{ user.nickname ?? user.email }}</a> <a class="d-block" data-mark="nickname">{{ user.nickname ?? user.email }}</a>
</div> </div>
</div> </div>