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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import { mount } from '@vue/test-utils' 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' } window.blessing.extra = { currentSkin: 'skin-blue' }

View File

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

View File

@ -1,7 +1,7 @@
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 Players from '@/components/admin/Players' import Players from '@/components/admin/Players.vue'
import { swal } from '@/js/notify' import { swal } from '@/js/notify'
jest.mock('@/js/notify') jest.mock('@/js/notify')
@ -18,10 +18,16 @@ test('fetch data after initializing', () => {
}) })
test('update tables', () => { 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({ 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') wrapper.find('.vgt-input').setValue('abc')
expect(Vue.prototype.$http.get).toBeCalledWith( expect(Vue.prototype.$http.get).toBeCalledWith(
@ -83,7 +89,7 @@ test('change texture', async () => {
button.trigger('click') button.trigger('click')
await flushPromises() await flushPromises()
expect(wrapper.html()).toContain('/preview/64/5.png') expect(wrapper.html()).toContain('/preview/64/5.png')
expect(window.$).toBeCalledWith('.modal') expect($).toBeCalledWith('.modal')
}) })
test('change player name', async () => { test('change player name', async () => {
@ -95,11 +101,13 @@ test('change player name', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' }) .mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({ errno: 0, msg: '0' }) .mockResolvedValueOnce({ errno: 0, msg: '0' })
swal.mockImplementationOnce(() => ({ dismiss: 1 })) swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(options => { .mockImplementation(options => {
options.inputValidator() if (options.inputValidator) {
options.inputValidator('new') options.inputValidator('')
return { value: 'new' } options.inputValidator('new')
}
return Promise.resolve({ value: 'new' })
}) })
const wrapper = mount(Players) const wrapper = mount(Players)
await wrapper.vm.$nextTick() await wrapper.vm.$nextTick()

View File

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

View File

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

View File

@ -1,14 +1,14 @@
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 Users from '@/components/admin/Users.vue'
import Users from '@/components/admin/Users'
import toastr from 'toastr' import toastr from 'toastr'
import { swal } from '@/js/notify' import { swal } from '@/js/notify'
import '@/js/i18n' import '@/js/i18n'
import { flushPromises } from '../../utils'
jest.mock('@/js/notify') jest.mock('@/js/notify')
jest.mock('@/js/i18n', () => ({ jest.mock('@/js/i18n', () => ({
trans: key => key, trans: (key: string) => key,
})) }))
test('fetch data after initializing', () => { test('fetch data after initializing', () => {
@ -23,10 +23,16 @@ test('fetch data after initializing', () => {
}) })
test('update tables', () => { 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({ 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') wrapper.find('.vgt-input').setValue('abc')
expect(Vue.prototype.$http.get).toBeCalledWith( expect(Vue.prototype.$http.get).toBeCalledWith(
@ -389,11 +395,13 @@ test('change email', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' }) .mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({ errno: 0, msg: '0' }) .mockResolvedValueOnce({ errno: 0, msg: '0' })
swal.mockImplementationOnce(() => ({ dismiss: 1 })) swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(options => { .mockImplementation(options => {
options.inputValidator() if (options.inputValidator) {
options.inputValidator('value') options.inputValidator('')
return { value: 'd@e.f' } options.inputValidator('value')
}
return Promise.resolve({ value: 'd@e.f' })
}) })
const wrapper = mount(Users) const wrapper = mount(Users)
await wrapper.vm.$nextTick() await wrapper.vm.$nextTick()
@ -449,11 +457,13 @@ test('change nickname', async () => {
Vue.prototype.$http.post Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' }) .mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({ errno: 0, msg: '0' }) .mockResolvedValueOnce({ errno: 0, msg: '0' })
swal.mockImplementationOnce(() => ({ dismiss: 1 })) swal.mockImplementationOnce(() => Promise.resolve({ dismiss: 1 }))
.mockImplementation(options => { .mockImplementation(options => {
options.inputValidator() if (options.inputValidator) {
options.inputValidator('value') options.inputValidator('')
return { value: 'new' } options.inputValidator('value')
}
return Promise.resolve({ value: 'new' })
}) })
const wrapper = mount(Users) const wrapper = mount(Users)
await wrapper.vm.$nextTick() await wrapper.vm.$nextTick()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,10 @@ interface Window {
blessing: { blessing: {
i18n: object i18n: object
extra: object
} }
fetch: jest.Mock 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/*": [ "@/js/*": [
"./resources/assets/tests/ts-shims/*", "./resources/assets/tests/ts-shims/*",
"./resources/assets/src/js/*" "./resources/assets/src/js/*"
],
"@/components/*": [
"./resources/assets/src/components/*"
] ]
} }
}, },