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

View File

@ -29,7 +29,17 @@
<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">
<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>
</template>
</slot>
@ -91,6 +101,16 @@ export default {
type: String,
default: '',
},
placeholder: {
type: String,
},
inputType: {
type: String,
default: 'text',
},
validator: {
type: Function,
},
type: {
type: String,
default: 'default',
@ -128,6 +148,8 @@ export default {
return {
hidden: false,
value: this.input,
valid: true,
validatorMessage: '',
}
},
computed: {
@ -154,6 +176,15 @@ export default {
},
methods: {
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.$emit('confirm', { value: this.value })
$(this.$el).modal('hide')

View File

@ -1,5 +1,6 @@
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<{
name: string
@ -9,15 +10,13 @@ export default Vue.extend<{
async addClosetItem() {
let value: string
try {
({ value } = await this.$prompt(
this.$t('skinlib.applyNotice'),
{
title: this.$t('skinlib.setItemName'),
inputValue: this.name,
showCancelButton: true,
inputValidator: val => !!val || this.$t('skinlib.emptyItemName'),
},
) as MessageBoxInputData)
({ value } = await showModal({
mode: 'prompt',
title: this.$t('skinlib.setItemName'),
text: this.$t('skinlib.applyNotice'),
input: this.name,
validator: truthy(this.$t('skinlib.emptyItemName')),
}))
} catch {
return
}

View File

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

View File

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

View File

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

View File

@ -7,7 +7,10 @@ export interface ModalOptions {
title?: string
text?: string
dangerousHTML?: string
input?: boolean
input?: string
placeholder?: string
inputType?: string
validator?(value: any): string | boolean | void
type?: string
showHeader?: boolean
center?: boolean
@ -29,7 +32,7 @@ export function showModal(options: ModalOptions = {}): Promise<ModalResult> {
const instance = new Vue({
render: h => h(Modal, {
props: options,
props: Object.assign({ center: true }, options),
on: {
confirm: resolve,
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-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 tableOptions from '../../components/mixins/tableOptions'
import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default {
name: 'Market',
@ -149,11 +150,11 @@ export default {
},
async updatePlugin(plugin) {
try {
await this.$confirm(
this.$t('admin.confirmUpdate', {
await showModal({
text: this.$t('admin.confirmUpdate', {
plugin: plugin.title, old: plugin.installed, new: plugin.version,
}),
)
})
} catch {
return
}

View File

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

View File

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

View File

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

View File

@ -45,6 +45,7 @@
<script>
import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
const POLLING_INTERVAL = 500
@ -73,10 +74,19 @@ export default {
)
this.updating = false
if (code) {
this.$alert(message, { type: 'error' })
showModal({
mode: 'alert',
text: message,
type: 'danger',
okButtonType: 'outline-light',
})
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
},
async polling() {

View File

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

View File

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

View File

@ -103,7 +103,14 @@
<span v-if="type === 'cape'">{{ $t('general.cape') }}</span>
<span v-else>{{ type }}</span>
<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>
</td>
</tr>
@ -162,20 +169,59 @@
:skin="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>
</template>
<script>
import Modal from '../../components/Modal.vue'
import setAsAvatar from '../../components/mixins/setAsAvatar'
import addClosetItem from '../../components/mixins/addClosetItem'
import removeClosetItem from '../../components/mixins/removeClosetItem'
import emitMounted from '../../components/mixins/emitMounted'
import ApplyToPlayerDialog from '../../components/ApplyToPlayerDialog.vue'
import { showModal } from '../../scripts/notify'
import { truthy } from '../../scripts/validators'
export default {
name: 'Show',
components: {
ApplyToPlayerDialog,
Modal,
Previewer: () => import('../../components/Previewer.vue'),
},
mixins: [
@ -201,6 +247,7 @@ export default {
size: 0,
uploadAt: '',
public: true,
editingType: 'steve',
liked: blessing.extra.inCloset,
canBeDownloaded: blessing.extra.download,
currentUid: blessing.extra.currentUid,
@ -256,9 +303,11 @@ export default {
async changeTextureName() {
let value
try {
({ value } = await this.$prompt(this.$t('skinlib.setNewTextureName'), {
inputValue: this.name,
inputValidator: name => !!name || this.$t('skinlib.emptyNewTextureName'),
({ value } = await showModal({
mode: 'prompt',
text: this.$t('skinlib.setNewTextureName'),
input: this.name,
validator: truthy(this.$t('skinlib.emptyNewTextureName')),
}))
} catch {
return
@ -276,31 +325,12 @@ export default {
}
},
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(
'/skinlib/model',
{ tid: this.tid, model: value },
{ tid: this.tid, model: this.editingType },
)
if (code === 0) {
this.type = value
this.type = this.editingType
this.$message.success(message)
} else {
this.$message.warning(message)
@ -308,12 +338,11 @@ export default {
},
async togglePrivacy() {
try {
await this.$confirm(
this.public
await showModal({
text: this.public
? this.$t('skinlib.setPrivateNotice')
: this.$t('skinlib.setPublicNotice'),
{ type: 'warning' },
)
})
} catch {
return
}
@ -331,10 +360,10 @@ export default {
},
async deleteTexture() {
try {
await this.$confirm(
this.$t('skinlib.deleteNotice'),
{ type: 'warning' },
)
await showModal({
text: this.$t('skinlib.deleteNotice'),
okButtonType: 'danger',
})
} catch {
return
}
@ -361,9 +390,11 @@ export default {
})()
let reason
try {
({ value: reason } = await this.$prompt(prompt, {
({ value: reason } = await showModal({
mode: 'prompt',
title: this.$t('skinlib.report.title'),
inputPlaceholder: this.$t('skinlib.report.reason'),
text: prompt,
placeholder: this.$t('skinlib.report.reason'),
}))
} catch {
return

View File

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

View File

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

View File

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

View File

@ -196,6 +196,7 @@
import EmailVerification from '../../components/EmailVerification.vue'
import Modal from '../../components/Modal.vue'
import emitMounted from '../../components/mixins/emitMounted'
import { showModal } from '../../scripts/notify'
export default {
name: 'Profile',
@ -221,7 +222,7 @@ export default {
nl2br: str => str.replace(/\n/g, '<br>'),
async resetAvatar() {
try {
await this.$confirm(this.$t('user.resetAvatarConfirm'))
await showModal({ text: this.$t('user.resetAvatarConfirm') })
} catch {
return
}
@ -249,41 +250,29 @@ export default {
'/user/profile?action=password',
{ current_password: oldPassword, new_password: newPassword },
)
await showModal({ mode: 'alert', text: message })
if (code === 0) {
await this.$alert(message)
return (window.location = `${blessing.base_url}/auth/login`)
}
return this.$alert(message, { type: 'warning' })
},
async changeNickName() {
const { nickname } = this
try {
await this.$confirm(this.$t('user.changeNickName', { new_nickname: nickname }))
} catch {
return
}
const { code, message } = await this.$http.post(
'/user/profile?action=nickname',
{ new_nickname: nickname },
)
if (code === 0) {
Array.from(document.querySelectorAll('.nickname'))
Array
.from(document.querySelectorAll('[data-mark="nickname"]'))
.forEach(el => (el.textContent = nickname))
return this.$message.success(message)
}
return this.$alert(message, { type: 'warning' })
showModal({ mode: 'alert', text: message })
},
async changeEmail() {
const { email } = this
try {
await this.$confirm(this.$t('user.changeEmail', { new_email: email }))
} catch {
return
}
const { code, message } = await this.$http.post(
'/user/profile?action=email',
{ new_email: email, password: this.currentPassword },
@ -292,7 +281,7 @@ export default {
await this.$message.success(message)
return (window.location = `${blessing.base_url}/auth/login`)
}
return this.$alert(message, { type: 'warning' })
showModal({ mode: 'alert', text: message })
},
async deleteAccount() {
const { deleteConfirm: password } = this
@ -301,11 +290,9 @@ export default {
'/user/profile?action=delete',
{ password },
)
await showModal({ mode: 'alert', text: message })
if (code === 0) {
await this.$alert(message, { type: 'success' })
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 { mount } from '@vue/test-utils'
import { MessageBoxData } from 'element-ui/types/message-box'
import { flushPromises } from '../utils'
import { showModal } from '@/scripts/notify'
import ClosetItem from '@/components/ClosetItem.vue'
jest.mock('@/scripts/notify')
function factory(opt = {}) {
return {
tid: 1,
@ -43,14 +45,9 @@ test('rename texture', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 0 })
.mockResolvedValueOnce({ code: 1 })
Vue.prototype.$prompt.mockImplementationOnce(() => Promise.reject(new Error()))
.mockImplementation((_, options) => {
if (options.inputValidator) {
options.inputValidator('name')
options.inputValidator('')
}
return Promise.resolve({ value: 'new-name' } as MessageBoxData)
})
showModal
.mockRejectedValueOnce(null)
.mockResolvedValue({ value: 'new-name' })
const wrapper = mount(ClosetItem, { propsData: factory() })
const button = wrapper.findAll('.dropdown-item').at(0)
@ -58,6 +55,7 @@ test('rename texture', async () => {
await flushPromises()
expect(Vue.prototype.$http.post).not.toBeCalled()
// Warning message
button.trigger('click')
await flushPromises()
@ -74,9 +72,9 @@ test('remove texture', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 0 })
.mockResolvedValueOnce({ code: 1 })
Vue.prototype.$confirm
.mockRejectedValueOnce({})
.mockResolvedValue('confirm')
showModal
.mockRejectedValueOnce(null)
.mockResolvedValue({ value: '' })
const wrapper = mount(ClosetItem, { propsData: factory() })
const button = wrapper.findAll('.dropdown-item').at(1)
@ -98,9 +96,9 @@ test('set as avatar', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ code: 0 })
.mockResolvedValueOnce({ code: 1 })
Vue.prototype.$confirm
.mockRejectedValueOnce({})
.mockResolvedValue('confirm')
showModal
.mockRejectedValueOnce(null)
.mockResolvedValue({ value: '' })
const wrapper = mount(ClosetItem, { propsData: factory() })
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' })
})
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', () => {
const wrapper = mount(Modal, {
propsData: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,11 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Update from '@/views/admin/Update.vue'
jest.mock('@/scripts/notify')
afterEach(() => {
window.blessing.extra = { canUpdate: true }
})
@ -24,12 +27,16 @@ test('perform update', async () => {
button.trigger('click')
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')
jest.runOnlyPendingTimers()
await flushPromises()
expect($).toBeCalled()
expect(Vue.prototype.$http.get).toBeCalledWith(
'/admin/update/download',
{ action: 'progress' },

View File

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

View File

@ -1,8 +1,11 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils'
import { showModal } from '@/scripts/notify'
import Login from '@/views/auth/Login.vue'
jest.mock('@/scripts/notify')
const Captcha = Vue.extend({
methods: {
execute() {
@ -47,7 +50,10 @@ test('login', async () => {
form.trigger('submit')
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()
wrapper.setData({
@ -61,7 +67,10 @@ test('login', async () => {
})
form.trigger('submit')
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()
form.trigger('submit')

View File

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

View File

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

View File

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

View File

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