Migrate to TypeScript

This commit is contained in:
Pig Fang 2019-03-18 09:55:24 +08:00
parent cfe419d41c
commit d1d4c54818
31 changed files with 207 additions and 132 deletions

View File

@ -176,8 +176,8 @@
],
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/resources/assets/src/$1",
"\\.css$": "<rootDir>/resources/assets/tests/__mocks__/style.js",
"\\.(png|jpg)$": "<rootDir>/resources/assets/tests/__mocks__/file.js"
"\\.css$": "<rootDir>/resources/assets/tests/__mocks__/style.ts",
"\\.(png|jpg)$": "<rootDir>/resources/assets/tests/__mocks__/file.ts"
},
"setupFilesAfterEnv": [
"<rootDir>/resources/assets/tests/setup.js"

View File

@ -1,87 +1,87 @@
export default [
{
path: 'user',
component: () => import('./user/Dashboard'),
component: () => import('./user/Dashboard.vue'),
el: '#usage-box',
},
{
path: 'user/closet',
component: () => import('./user/Closet'),
component: () => import('./user/Closet.vue'),
el: '.content',
},
{
path: 'user/player',
component: () => import('./user/Players'),
component: () => import('./user/Players.vue'),
el: '.content',
},
{
path: 'user/profile',
component: () => import('./user/Profile'),
component: () => import('./user/Profile.vue'),
el: '.content',
},
{
path: 'admin/users',
component: () => import('./admin/Users'),
component: () => import('./admin/Users.vue'),
el: '.content',
},
{
path: 'admin/players',
component: () => import('./admin/Players'),
component: () => import('./admin/Players.vue'),
el: '.content',
},
{
path: 'admin/customize',
component: () => import('./admin/Customization'),
component: () => import('./admin/Customization.vue'),
el: '#change-color',
},
{
path: 'admin/plugins/manage',
component: () => import('./admin/Plugins'),
component: () => import('./admin/Plugins.vue'),
el: '.content',
},
{
path: 'admin/plugins/market',
component: () => import('./admin/Market'),
component: () => import('./admin/Market.vue'),
el: '.content',
},
{
path: 'admin/update',
component: () => import('./admin/Update'),
component: () => import('./admin/Update.vue'),
el: '#update-button',
},
{
path: 'auth/login',
component: () => import('./auth/Login'),
component: () => import('./auth/Login.vue'),
el: 'form',
},
{
path: 'auth/register',
component: () => import('./auth/Register'),
component: () => import('./auth/Register.vue'),
el: 'form',
},
{
path: 'auth/forgot',
component: () => import('./auth/Forgot'),
component: () => import('./auth/Forgot.vue'),
el: 'form',
},
{
path: 'auth/reset/(\\d+)',
component: () => import('./auth/Reset'),
component: () => import('./auth/Reset.vue'),
el: 'form',
},
{
path: 'skinlib',
component: () => import('./skinlib/List'),
component: () => import('./skinlib/List.vue'),
el: '.content-wrapper',
},
{
path: 'skinlib/show/(\\d+)',
component: () => import('./skinlib/Show'),
component: () => import('./skinlib/Show.vue'),
el: '.content > .row:nth-child(1)',
},
{
path: 'skinlib/upload',
component: () => import('./skinlib/Upload'),
component: () => import('./skinlib/Upload.vue'),
el: '.content',
},
]

View File

@ -1,10 +1,26 @@
/* eslint-disable max-params */
/* eslint-disable max-classes-per-file */
export class SkinViewer {
disposed: boolean
skinUrl: string
capeUrl: string
animationPaused: boolean
camera: { position: { z: number } }
constructor() {
this.skinUrl = ''
this.capeUrl = ''
this.disposed = false
this.animationPaused = false
this.camera = {
position: {},
position: {
z: 0,
},
}
}
@ -14,7 +30,7 @@ export class SkinViewer {
}
export class CompositeAnimation {
add(animation) {
add(animation: any) {
return animation
}
}

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Customization from '@/components/admin/Customization'
import Customization from '@/components/admin/Customization.vue'
window.blessing.extra = { currentSkin: 'skin-blue' }

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Market from '@/components/admin/Market'
import Market from '@/components/admin/Market.vue'
import { flushPromises } from '../../utils'
import { swal } from '@/js/notify'

View File

@ -1,7 +1,7 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils'
import Players from '@/components/admin/Players'
import Players from '@/components/admin/Players.vue'
import { swal } from '@/js/notify'
jest.mock('@/js/notify')
@ -18,10 +18,16 @@ test('fetch data after initializing', () => {
})
test('update tables', () => {
interface Methods {
onPageChange(options: { currentPage: number }): void
onPerPageChange(options: { currentPerPage: number }): void
onSortChange(options: { sortType: 'asc' | 'desc', columnIndex: number }): void
}
Vue.prototype.$http.get.mockResolvedValue({
data: Array.from({ length: 20 }).map((item, pid) => ({ pid })),
data: Array.from({ length: 20 }).map((_, pid) => ({ pid })),
})
const wrapper = mount(Players)
const wrapper = mount<Vue & Methods>(Players)
wrapper.find('.vgt-input').setValue('abc')
expect(Vue.prototype.$http.get).toBeCalledWith(
@ -83,7 +89,7 @@ test('change texture', async () => {
button.trigger('click')
await flushPromises()
expect(wrapper.html()).toContain('/preview/64/5.png')
expect(window.$).toBeCalledWith('.modal')
expect($).toBeCalledWith('.modal')
})
test('change player name', async () => {
@ -95,11 +101,13 @@ test('change player name', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({ errno: 0, msg: '0' })
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(options => {
options.inputValidator()
options.inputValidator('new')
return { value: 'new' }
if (options.inputValidator) {
options.inputValidator('')
options.inputValidator('new')
}
return Promise.resolve({ value: 'new' })
})
const wrapper = mount(Players)
await wrapper.vm.$nextTick()

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Plugins from '@/components/admin/Plugins'
import Plugins from '@/components/admin/Plugins.vue'
import toastr from 'toastr'
import { flushPromises } from '../../utils'
import { swal } from '@/js/notify'

View File

@ -29,7 +29,7 @@ test('perform update', async () => {
button.trigger('click')
await flushPromises()
expect(window.$).not.toBeCalled()
expect($).not.toBeCalled()
expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/update/download',
{ action: 'prepare-download' }
@ -37,7 +37,7 @@ test('perform update', async () => {
button.trigger('click')
jest.runOnlyPendingTimers()
await flushPromises()
expect(window.$).toBeCalled()
expect($).toBeCalled()
expect(Vue.prototype.$http.get).toBeCalledWith(
'/admin/update/download',
{ action: 'get-progress' }

View File

@ -1,14 +1,14 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils'
import Users from '@/components/admin/Users'
import Users from '@/components/admin/Users.vue'
import toastr from 'toastr'
import { swal } from '@/js/notify'
import '@/js/i18n'
import { flushPromises } from '../../utils'
jest.mock('@/js/notify')
jest.mock('@/js/i18n', () => ({
trans: key => key,
trans: (key: string) => key,
}))
test('fetch data after initializing', () => {
@ -23,10 +23,16 @@ test('fetch data after initializing', () => {
})
test('update tables', () => {
interface Methods {
onPageChange(options: { currentPage: number }): void
onPerPageChange(options: { currentPerPage: number }): void
onSortChange(options: { sortType: 'asc' | 'desc', columnIndex: number }): void
}
Vue.prototype.$http.get.mockResolvedValue({
data: Array.from({ length: 20 }).map((item, uid) => ({ uid })),
data: Array.from({ length: 20 }).map((_, uid) => ({ uid })),
})
const wrapper = mount(Users)
const wrapper = mount<Vue & Methods>(Users)
wrapper.find('.vgt-input').setValue('abc')
expect(Vue.prototype.$http.get).toBeCalledWith(
@ -389,11 +395,13 @@ test('change email', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({ errno: 0, msg: '0' })
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(options => {
options.inputValidator()
options.inputValidator('value')
return { value: 'd@e.f' }
if (options.inputValidator) {
options.inputValidator('')
options.inputValidator('value')
}
return Promise.resolve({ value: 'd@e.f' })
})
const wrapper = mount(Users)
await wrapper.vm.$nextTick()
@ -449,11 +457,13 @@ test('change nickname', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({ errno: 0, msg: '0' })
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(options => {
options.inputValidator()
options.inputValidator('value')
return { value: 'new' }
if (options.inputValidator) {
options.inputValidator('')
options.inputValidator('value')
}
return Promise.resolve({ value: 'new' })
})
const wrapper = mount(Users)
await wrapper.vm.$nextTick()

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Forgot from '@/components/auth/Forgot'
import Forgot from '@/components/auth/Forgot.vue'
test('click to refresh captcha', () => {
jest.spyOn(Date, 'now')

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Login from '@/components/auth/Login'
import Login from '@/components/auth/Login.vue'
import { swal } from '@/js/notify'
jest.mock('@/js/notify')

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Register from '@/components/auth/Register'
import Register from '@/components/auth/Register.vue'
import { swal } from '@/js/notify'
jest.mock('@/js/notify')

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Reset from '@/components/auth/Reset'
import Reset from '@/components/auth/Reset.vue'
import { swal } from '@/js/notify'
jest.mock('@/js/notify')

View File

@ -1,32 +1,43 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Previewer from '@/components/common/Previewer'
import Previewer from '@/components/common/Previewer.vue'
import * as emitter from '@/js/event'
import * as mockedSkinview3d from '../../__mocks__/skinview3d'
type Viewer = Vue & { viewer: mockedSkinview3d.SkinViewer }
interface Handles {
handles: {
run: { paused: boolean }
walk: { paused: boolean }
rotate: { paused: boolean }
}
}
test('initialize skinview3d', () => {
const stub = jest.fn()
emitter.on('skinViewerMounted', stub)
const wrapper = mount(Previewer)
const wrapper = mount<Viewer>(Previewer)
expect(wrapper.vm.viewer).toBeInstanceOf(mockedSkinview3d.SkinViewer)
expect(wrapper.vm.viewer.camera.position.z).toBe(70)
expect(stub).toBeCalledWith(expect.any(HTMLElement))
})
test('dispose viewer before destroy', () => {
const wrapper = mount(Previewer)
const wrapper = mount<Viewer>(Previewer)
wrapper.destroy()
expect(wrapper.vm.viewer.disposed).toBeTrue()
})
test('skin URL should be updated', () => {
const wrapper = mount(Previewer)
const wrapper = mount<Viewer>(Previewer)
wrapper.setProps({ skin: 'abc' })
expect(wrapper.vm.viewer.skinUrl).toBe('abc')
})
test('cape URL should be updated', () => {
const wrapper = mount(Previewer)
const wrapper = mount<Viewer>(Previewer)
wrapper.setProps({ cape: 'abc' })
expect(wrapper.vm.viewer.capeUrl).toBe('abc')
})
@ -73,22 +84,24 @@ test('toggle pause', () => {
})
test('toggle run', () => {
const wrapper = mount(Previewer)
const wrapper = mount<Vue & Handles>(Previewer)
wrapper.find('.fa-forward').trigger('click')
expect(wrapper.vm.handles.run.paused).toBeFalse()
expect(wrapper.vm.handles.walk.paused).toBeTrue()
})
test('toggle rotate', () => {
const wrapper = mount(Previewer)
const wrapper = mount<Vue & Handles>(Previewer)
wrapper.find('.fa-redo-alt').trigger('click')
expect(wrapper.vm.handles.rotate.paused).toBeTrue()
})
test('reset', () => {
mockedSkinview3d.SkinViewer.prototype.dispose = jest.fn(function () {
this.disposed = true
}.bind(mockedSkinview3d.SkinViewer))
mockedSkinview3d.SkinViewer.prototype.dispose = jest.fn(
function (this: mockedSkinview3d.SkinViewer) {
this.disposed = true
}.bind(new mockedSkinview3d.SkinViewer())
)
const wrapper = mount(Previewer)
wrapper.find('.fa-stop').trigger('click')
expect(mockedSkinview3d.SkinViewer.prototype.dispose).toBeCalled()

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import List from '@/components/skinlib/List'
import List from '@/components/skinlib/List.vue'
test('fetch data before mounting', () => {
Vue.prototype.$http.get.mockResolvedValue({
@ -169,7 +169,7 @@ test('is anonymous', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0,
})
const wrapper = mount(List)
const wrapper = mount<Vue & { anonymous: boolean }>(List)
expect(wrapper.vm.anonymous).toBeTrue()
})
@ -177,7 +177,7 @@ test('on page changed', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0,
})
const wrapper = mount(List)
const wrapper = mount<Vue & { pageChanged(page: number): void }>(List)
jest.runAllTimers()
wrapper.vm.pageChanged(2)
expect(Vue.prototype.$http.get).toBeCalledWith(
@ -196,7 +196,10 @@ test('on like toggled', async () => {
total_pages: 1,
current_uid: 0,
})
const wrapper = mount(List)
const wrapper = mount<Vue & {
onLikeToggled(tid: number, like: boolean): void,
items: Array<{ liked: boolean, likes: number }>
}>(List)
await wrapper.vm.$nextTick()
wrapper.vm.onLikeToggled(0, true)
expect(wrapper.vm.items[0].liked).toBeTrue()

View File

@ -1,12 +1,20 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Show from '@/components/skinlib/Show'
import Show from '@/components/skinlib/Show.vue'
import toastr from 'toastr'
import { flushPromises } from '../../utils'
import { swal } from '@/js/notify'
jest.mock('@/js/notify')
type Component = Vue & {
liked: boolean
likes: number
public: boolean
name: string
type: 'steve' | 'alex' | 'cape'
}
window.blessing.extra = {
download: true,
currentUid: 0,
@ -15,12 +23,11 @@ window.blessing.extra = {
inCloset: false,
}
/** @type {import('Vue').ComponentOptions} */
const previewer = {
const previewer = Vue.extend({
render(h) {
return h('div', this.$slots.footer)
},
}
})
test('button for adding to closet should be disabled if not auth', () => {
Vue.prototype.$http.get.mockResolvedValue({})
@ -165,7 +172,7 @@ test('add to closet', async () => {
Vue.prototype.$http.get.mockResolvedValue({ name: 'wow', likes: 2 })
Vue.prototype.$http.post.mockResolvedValue({ errno: 0, msg: '' })
swal.mockResolvedValue({})
const wrapper = mount(Show, {
const wrapper = mount<Component>(Show, {
mocks: {
$route: ['/skinlib/show/1', '1'],
},
@ -182,7 +189,7 @@ test('remove from closet', async () => {
Vue.prototype.$http.get.mockResolvedValue({ likes: 2 })
Vue.prototype.$http.post.mockResolvedValue({ errno: 0 })
swal.mockResolvedValue({})
const wrapper = mount(Show, {
const wrapper = mount<Component>(Show, {
mocks: {
$route: ['/skinlib/show/1', '1'],
},
@ -201,13 +208,15 @@ test('change texture name', async () => {
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValue({ errno: 0, msg: '0' })
jest.spyOn(toastr, 'warning')
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(({ inputValidator }) => {
inputValidator()
inputValidator('new-name')
return { value: 'new-name' }
if (inputValidator) {
inputValidator('')
inputValidator('new-name')
}
return Promise.resolve({ value: 'new-name' })
})
const wrapper = mount(Show, {
const wrapper = mount<Component>(Show, {
mocks: {
$route: ['/skinlib/show/1', '1'],
},
@ -239,7 +248,7 @@ test('change texture model', async () => {
jest.spyOn(toastr, 'warning')
swal.mockResolvedValueOnce({ dismiss: 1 })
.mockResolvedValue({ value: 'alex' })
const wrapper = mount(Show, {
const wrapper = mount<Component>(Show, {
mocks: {
$route: ['/skinlib/show/1', '1'],
},
@ -272,7 +281,7 @@ test('toggle privacy', async () => {
jest.spyOn(toastr, 'warning')
swal.mockResolvedValueOnce({ dismiss: 1 })
.mockResolvedValue({})
const wrapper = mount(Show, {
const wrapper = mount<Component>(Show, {
mocks: {
$route: ['/skinlib/show/1', '1'],
},

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import SkinLibItem from '@/components/skinlib/SkinLibItem'
import SkinLibItem from '@/components/skinlib/SkinLibItem.vue'
import toastr from 'toastr'
import { flushPromises } from '../../utils'
import { swal } from '@/js/notify'
@ -79,13 +79,13 @@ test('add to closet', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValue({ errno: 0 })
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(({ inputValidator }) => {
if (inputValidator) {
inputValidator()
inputValidator('')
inputValidator('name')
}
return { value: 'name' }
return Promise.resolve({ value: 'name' })
})
jest.spyOn(toastr, 'warning')
const wrapper = mount(SkinLibItem, {

View File

@ -1,8 +1,7 @@
/* eslint-disable accessor-pairs */
/* eslint-disable no-invalid-this */
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Upload from '@/components/skinlib/Upload'
import Upload from '@/components/skinlib/Upload.vue'
import { flushPromises } from '../../utils'
import toastr from 'toastr'
import { swal } from '@/js/notify'
@ -32,14 +31,12 @@ test('display drap and drop notice', () => {
})
test('button for removing texture', () => {
const wrapper = mount(Upload, {
const wrapper = mount<Vue & { texture: string }>(Upload, {
stubs: ['file-upload'],
})
wrapper.vm.$refs = {
upload: {
clear: jest.fn(),
},
}
Object.defineProperty(wrapper.vm.$refs.upload, 'clear', {
get: () => jest.fn(),
})
const button = wrapper.find('.btn-default')
expect(button.isVisible()).toBeFalse()
wrapper.setData({ files: [{}] })
@ -73,15 +70,15 @@ test('display score cost', () => {
test('process input file', () => {
window.URL.createObjectURL = jest.fn().mockReturnValue('file-url')
jest.spyOn(window, 'Image')
.mockImplementationOnce(function () {
;(window as Window & { Image: jest.Mock }).Image = jest.fn()
.mockImplementationOnce(function (this: HTMLImageElement) {
this.src = ''
this.onload = null
Object.defineProperty(this, 'onload', {
set: fn => fn(),
})
})
.mockImplementationOnce(function () {
.mockImplementationOnce(function (this: HTMLImageElement) {
this.src = ''
this.width = 500
this.onload = null
@ -90,7 +87,12 @@ test('process input file', () => {
})
})
const blob = new Blob()
const wrapper = mount(Upload, {
type Component = Vue & {
name: string
texture: string
inputFile(attrs?: { file: Blob, name: string }): void
}
const wrapper = mount<Component>(Upload, {
stubs: ['file-upload'],
})
@ -109,18 +111,18 @@ test('process input file', () => {
expect(wrapper.vm.texture).toBe('file-url')
window.Image.mockRestore()
;(window as Window & { Image: jest.Mock }).Image.mockRestore()
})
test('upload file', async () => {
window.Request = jest.fn()
(window as Window & { Request: jest.Mock }).Request = jest.fn()
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({
errno: 0, msg: '0', tid: 1,
})
jest.spyOn(toastr, 'info')
swal.mockReturnValue()
swal.mockReturnValue(Promise.resolve({}))
const wrapper = mount(Upload, {
stubs: ['file-upload'],

View File

@ -1,8 +1,8 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Closet from '@/components/user/Closet'
import ClosetItem from '@/components/user/ClosetItem'
import Previewer from '@/components/common/Previewer'
import Closet from '@/components/user/Closet.vue'
import ClosetItem from '@/components/user/ClosetItem.vue'
import Previewer from '@/components/common/Previewer.vue'
import toastr from 'toastr'
import { swal } from '@/js/notify'
@ -80,7 +80,7 @@ test('search textures', () => {
const wrapper = mount(Closet)
const input = wrapper.find('input')
input.element.value = 'q'
;(input.element as HTMLInputElement).value = 'q'
input.trigger('input')
jest.runAllTimers()
jest.runAllTicks()
@ -127,7 +127,7 @@ test('render items', async () => {
test('reload closet when page changed', () => {
Vue.prototype.$http.get.mockResolvedValue({})
const wrapper = mount(Closet)
const wrapper = mount<Vue & { pageChanged(): void }>(Closet)
wrapper.vm.pageChanged()
jest.runAllTicks()
expect(Vue.prototype.$http.get).toBeCalledTimes(2)
@ -135,7 +135,7 @@ test('reload closet when page changed', () => {
test('remove skin item', () => {
Vue.prototype.$http.get.mockResolvedValue({})
const wrapper = mount(Closet)
const wrapper = mount<Vue & { removeSkinItem(tid: number): void }>(Closet)
wrapper.setData({ skinItems: [{ tid: 1 }] })
wrapper.vm.removeSkinItem(0)
expect(wrapper.find('#skin-category').text()).toContain('user.emptyClosetMsg')
@ -143,7 +143,7 @@ test('remove skin item', () => {
test('remove cape item', () => {
Vue.prototype.$http.get.mockResolvedValue({})
const wrapper = mount(Closet)
const wrapper = mount<Vue & { removeCapeItem(tid: number): void }>(Closet)
wrapper.setData({ capeItems: [{ tid: 1 }], category: 'cape' })
wrapper.vm.removeCapeItem(0)
expect(wrapper.find('#cape-category').text()).toContain('user.emptyClosetMsg')
@ -151,7 +151,8 @@ test('remove cape item', () => {
test('compute avatar URL', () => {
Vue.prototype.$http.get.mockResolvedValue({})
const wrapper = mount(Closet)
// eslint-disable-next-line camelcase
const wrapper = mount<Vue & { avatarUrl(player: { tid_skin: number }): string }>(Closet)
const { avatarUrl } = wrapper.vm
expect(avatarUrl({ tid_skin: 1 })).toBe('/avatar/35/1')
})
@ -162,7 +163,7 @@ test('select texture', async () => {
.mockResolvedValueOnce({ type: 'steve', hash: 'a' })
.mockResolvedValueOnce({ type: 'cape', hash: 'b' })
const wrapper = mount(Closet)
const wrapper = mount<Vue & { skinUrl: string, capeUrl: string }>(Closet)
wrapper.setData({ skinItems: [{ tid: 1 }] })
wrapper.find(ClosetItem).vm.$emit('select')
await wrapper.vm.$nextTick()
@ -181,7 +182,7 @@ test('select texture', async () => {
test('apply texture', async () => {
window.$ = jest.fn(() => ({
iCheck: () => ({
on(evt, cb) {
on(_: Event, cb: CallableFunction) {
cb()
},
}),
@ -282,7 +283,7 @@ test('select specified texture initially', async () => {
window.$ = jest.fn(() => ({
modal() {},
iCheck: () => ({
on(evt, cb) {
on(_: Event, cb: CallableFunction) {
cb()
},
}),

View File

@ -1,7 +1,7 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils'
import ClosetItem from '@/components/user/ClosetItem'
import ClosetItem from '@/components/user/ClosetItem.vue'
import { swal } from '@/js/notify'
jest.mock('@/js/notify')
@ -40,11 +40,13 @@ test('rename texture', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 0 })
.mockResolvedValueOnce({ errno: 1 })
swal.mockImplementationOnce(() => ({ dismiss: 'cancel' }))
swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(options => {
options.inputValidator('name')
options.inputValidator()
return { value: 'new-name' }
if (options.inputValidator) {
options.inputValidator('name')
options.inputValidator('')
}
return Promise.resolve({ value: 'new-name' })
})
const wrapper = mount(ClosetItem, { propsData: factory() })
const button = wrapper.findAll('.dropdown-menu > li').at(0)
@ -71,7 +73,7 @@ test('remove texture', async () => {
.mockResolvedValueOnce({ errno: 0 })
.mockResolvedValueOnce({ errno: 1 })
swal
.mockResolvedValueOnce({ dismiss: 'cancel' })
.mockResolvedValueOnce({ dismiss: 1 })
.mockResolvedValue({})
const wrapper = mount(ClosetItem, { propsData: factory() })
@ -96,7 +98,7 @@ test('set as avatar', async () => {
.mockResolvedValueOnce({ errno: 0 })
.mockResolvedValueOnce({ errno: 1 })
swal
.mockResolvedValueOnce({ dismiss: 'cancel' })
.mockResolvedValueOnce({ dismiss: 1 })
.mockResolvedValue({})
const wrapper = mount(ClosetItem, { propsData: factory() })
@ -115,7 +117,7 @@ test('set as avatar', async () => {
await flushPromises()
await wrapper.vm.$nextTick()
expect(Vue.prototype.$http.post).toBeCalledWith('/user/profile/avatar', { tid: 1 })
expect(document.querySelector('img').src).toMatch(/\d+$/)
expect(document.querySelector('img')!.src).toMatch(/\d+$/)
})
test('no avatar option if texture is cape', () => {

View File

@ -1,7 +1,7 @@
/* eslint-disable no-mixed-operators */
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Dashboard from '@/components/user/Dashboard'
import Dashboard from '@/components/user/Dashboard.vue'
import toastr from 'toastr'
import { swal } from '@/js/notify'
@ -125,7 +125,7 @@ test('remaining time', async () => {
test('sign', async () => {
jest.spyOn(toastr, 'warning')
swal.mockResolvedValue()
swal.mockResolvedValue({})
Vue.prototype.$http.get.mockResolvedValue(scoreInfo({
user: { lastSignAt: Date.now() - 30 * 3600 * 1000 },
}))

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import EmailVerification from '@/components/user/EmailVerification'
import EmailVerification from '@/components/user/EmailVerification.vue'
import { swal } from '@/js/notify'
jest.mock('@/js/notify')

View File

@ -1,7 +1,7 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils'
import Players from '@/components/user/Players'
import Players from '@/components/user/Players.vue'
import { swal } from '@/js/notify'
jest.mock('toastr')
@ -67,13 +67,13 @@ test('change player name', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1 })
.mockResolvedValue({ errno: 0 })
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(({ inputValidator }) => {
if (inputValidator) {
inputValidator()
inputValidator('')
inputValidator('new-name')
return { value: 'new-name' }
}
return Promise.resolve({ value: 'new-name' })
})
const wrapper = mount(Players)
await wrapper.vm.$nextTick()
@ -100,7 +100,7 @@ test('load iCheck', async () => {
])
window.$ = jest.fn(() => ({
iCheck: () => ({
on(evt, cb) {
on(_: Event, cb: CallableFunction) {
cb()
},
}),

View File

@ -1,7 +1,7 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import { flushPromises } from '../../utils'
import Profile from '@/components/user/Profile'
import Profile from '@/components/user/Profile.vue'
import toastr from 'toastr'
import { swal } from '@/js/notify'
@ -11,15 +11,15 @@ window.blessing.extra = { unverified: false }
test('computed values', () => {
window.blessing.extra = { admin: true }
const wrapper = mount(Profile)
const wrapper = mount<Vue & { siteName: string, isAdmin: boolean }>(Profile)
expect(wrapper.vm.siteName).toBe('Blessing Skin')
expect(wrapper.vm.isAdmin).toBeTrue()
window.blessing.extra = { admin: false }
expect(mount(Profile).vm.isAdmin).toBeFalse()
expect(mount<Vue & { isAdmin: boolean }>(Profile).vm.isAdmin).toBeFalse()
})
test('convert linebreak', () => {
const wrapper = mount(Profile)
const wrapper = mount<Vue & { nl2br(input: string): string }>(Profile)
expect(wrapper.vm.nl2br('a\nb\nc')).toBe('a<br>b<br>c')
})
@ -44,7 +44,7 @@ test('reset avatar', async () => {
)
await flushPromises()
expect(toastr.success).toBeCalledWith('ok')
expect(document.querySelector('img').src).toMatch(/\d+$/)
expect(document.querySelector('img')!.src).toMatch(/\d+$/)
})
test('change password', async () => {
@ -52,7 +52,7 @@ test('change password', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: 'w' })
.mockResolvedValueOnce({ errno: 0, msg: 'o' })
swal.mockResolvedValue()
swal.mockResolvedValue({})
const wrapper = mount(Profile)
const button = wrapper.find('[data-test=changePassword]')
@ -125,7 +125,7 @@ test('change nickname', async () => {
button.trigger('click')
await flushPromises()
expect(swal).toBeCalledWith({ type: 'success', text: 'o' })
expect(document.querySelector('.nickname').textContent).toBe('nickname')
expect(document.querySelector('.nickname')!.textContent).toBe('nickname')
})
test('change email', async () => {
@ -173,7 +173,7 @@ test('change email', async () => {
test('delete account', async () => {
window.blessing.extra = { admin: true }
swal.mockResolvedValue()
swal.mockResolvedValue({})
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: 'w' })
.mockResolvedValue({ errno: 0, msg: 'o' })

View File

@ -9,7 +9,10 @@ interface Window {
blessing: {
i18n: object
extra: object
}
fetch: jest.Mock
$: jest.Mock
}

5
resources/assets/tests/vue.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

View File

@ -12,6 +12,9 @@
"@/js/*": [
"./resources/assets/tests/ts-shims/*",
"./resources/assets/src/js/*"
],
"@/components/*": [
"./resources/assets/src/components/*"
]
}
},