diff --git a/package.json b/package.json index dd963b5b..0b675155 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "eslint": "^5.14.0", "eslint-config-gplane": "^5.1.3", "eslint-formatter-beauty": "^3.0.0", + "eslint-plugin-project": "^0.2.2", "eslint-plugin-vue": "^5.2.2", "file-loader": "^3.0.1", "jest": "^24.5.0", @@ -80,6 +81,14 @@ "not ie 11", "Chrome > 52" ], + "prettier": { + "printWidth": 80, + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "arrowParens": "avoid", + "tabWidth": 2 + }, "eslintConfig": { "root": true, "extends": [ @@ -100,6 +109,12 @@ "globals": { "blessing": "readonly" }, + "plugins": [ + "project" + ], + "settings": { + "projectRulesDir": "resources/assets/eslint-rules" + }, "rules": { "camelcase": [ 2, @@ -113,7 +128,8 @@ 120 ], "prefer-object-spread": 0, - "import/no-unresolved": 0 + "import/no-unresolved": 0, + "project/linebreak-between-tests": 2 } }, "jest": { diff --git a/resources/assets/eslint-rules/linebreak-between-tests.js b/resources/assets/eslint-rules/linebreak-between-tests.js new file mode 100644 index 00000000..5945bd46 --- /dev/null +++ b/resources/assets/eslint-rules/linebreak-between-tests.js @@ -0,0 +1,27 @@ +module.exports = { + create(context) { + return { + CallExpression(node) { + const args = node.arguments + if ( + node.callee.name === 'test' && + args[0].type === 'Literal' && + args[1].type === 'ArrowFunctionExpression' + ) { + const next = context.getTokenAfter(node) + if ( + next && + next.value === 'test' && + node.loc.end.line + 1 === next.loc.start.line + ) { + context.report({ + node: context.getLastToken(node), + message: 'Linebreak should be inserted between test blocks.', + fix: fixer => fixer.insertTextAfter(node, '\n'), + }) + } + } + }, + } + }, +} diff --git a/resources/assets/tests/components/admin/Customization.test.js b/resources/assets/tests/components/admin/Customization.test.js index eec6e081..97872428 100644 --- a/resources/assets/tests/components/admin/Customization.test.js +++ b/resources/assets/tests/components/admin/Customization.test.js @@ -12,6 +12,7 @@ test('preview color', () => { expect(document.body.classList.contains('skin-blue')).toBeFalse() expect(document.body.classList.contains('skin-yellow')).toBeTrue() }) + test('submit color', () => { Vue.prototype.$http.post.mockResolvedValue({ errno: 0, msg: '' }) const wrapper = mount(Customization) diff --git a/resources/assets/tests/components/admin/Market.test.js b/resources/assets/tests/components/admin/Market.test.js index 93d257f1..95107855 100644 --- a/resources/assets/tests/components/admin/Market.test.js +++ b/resources/assets/tests/components/admin/Market.test.js @@ -23,6 +23,7 @@ test('render dependencies', async () => { expect(wrapper.find('span.label.bg-green').text()).toBe('a: ^1.0.0') expect(wrapper.find('span.label.bg-red').text()).toBe('c: ^2.0.0') }) + test('render operation buttons', async () => { Vue.prototype.$http.get.mockResolvedValue([ { @@ -47,6 +48,7 @@ test('render operation buttons', async () => { expect(tbody.find('tr:nth-child(3)').text()).toContain('admin.enablePlugin') expect(tbody.find('tr:nth-child(4)').text()).toContain('admin.installPlugin') }) + test('install plugin', async () => { Vue.prototype.$http.get.mockResolvedValue([ { @@ -70,6 +72,7 @@ test('install plugin', async () => { await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('admin.enablePlugin') }) + test('update plugin', async () => { Vue.prototype.$http.get.mockResolvedValue([ { diff --git a/resources/assets/tests/components/admin/Players.test.js b/resources/assets/tests/components/admin/Players.test.js index 01e61c05..1819402d 100644 --- a/resources/assets/tests/components/admin/Players.test.js +++ b/resources/assets/tests/components/admin/Players.test.js @@ -85,6 +85,7 @@ test('change texture', async () => { expect(wrapper.html()).toContain('/preview/64/5.png') expect(window.$).toBeCalledWith('.modal') }) + test('change player name', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -117,6 +118,7 @@ test('change player name', async () => { await flushPromises() expect(wrapper.text()).toContain('new') }) + test('change owner', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -146,6 +148,7 @@ test('change owner', async () => { await flushPromises() expect(wrapper.text()).toContain('3') }) + test('delete player', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ diff --git a/resources/assets/tests/components/admin/Plugins.test.js b/resources/assets/tests/components/admin/Plugins.test.js index 38b3b27f..93d2a024 100644 --- a/resources/assets/tests/components/admin/Plugins.test.js +++ b/resources/assets/tests/components/admin/Plugins.test.js @@ -24,6 +24,7 @@ test('render dependencies', async () => { expect(wrapper.find('span.label.bg-green').text()).toBe('a: ^1.0.0') expect(wrapper.find('span.label.bg-red').text()).toBe('c: ^2.0.0') }) + test('render operation buttons', async () => { Vue.prototype.$http.get.mockResolvedValue([ { @@ -46,6 +47,7 @@ test('render operation buttons', async () => { expect(tbody.find('tr:nth-child(3)').text()).toContain('admin.enablePlugin') expect(tbody.find('tr:nth-child(3)').text()).toContain('admin.deletePlugin') }) + test('enable plugin', async () => { Vue.prototype.$http.get.mockResolvedValue([ { @@ -95,6 +97,7 @@ test('enable plugin', async () => { await flushPromises() expect(wrapper.text()).toContain('admin.disablePlugin') }) + test('disable plugin', async () => { jest.spyOn(toastr, 'success') Vue.prototype.$http.get.mockResolvedValue([ @@ -120,6 +123,7 @@ test('disable plugin', async () => { expect(toastr.success).toBeCalledWith('0') expect(wrapper.text()).toContain('admin.enablePlugin') }) + test('delete plugin', async () => { jest.spyOn(toastr, 'success') Vue.prototype.$http.get.mockResolvedValue([ diff --git a/resources/assets/tests/components/admin/Update.test.js b/resources/assets/tests/components/admin/Update.test.js index c01b790b..bfba67d9 100644 --- a/resources/assets/tests/components/admin/Update.test.js +++ b/resources/assets/tests/components/admin/Update.test.js @@ -14,6 +14,7 @@ test('button should be disabled if update is unavailable', () => { const wrapper = mount(Update) expect(wrapper.find('.btn').attributes('disabled')).toBe('disabled') }) + test('perform update', async () => { window.$ = jest.fn(() => ({ modal() {}, diff --git a/resources/assets/tests/components/admin/Users.test.js b/resources/assets/tests/components/admin/Users.test.js index 39325d8a..c2c079e9 100644 --- a/resources/assets/tests/components/admin/Users.test.js +++ b/resources/assets/tests/components/admin/Users.test.js @@ -75,6 +75,7 @@ test('humanize permission', async () => { expect(text).toContain('admin.admin') expect(text).toContain('admin.superAdmin') }) + test('generate players page link', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -85,6 +86,7 @@ test('generate players page link', async () => { await wrapper.vm.$nextTick() expect(wrapper.find('[data-toggle="tooltip"]').attributes('href')).toBe('/admin/players?uid=1') }) + test('admin option should not be displayed for super admins', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -97,6 +99,7 @@ test('admin option should not be displayed for super admins', async () => { expect(text).not.toContain('admin.setAdmin') expect(text).not.toContain('admin.unsetAdmin') }) + test('banning option should not be displayed for super admins', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -109,6 +112,7 @@ test('banning option should not be displayed for super admins', async () => { expect(text).not.toContain('admin.ban') expect(text).not.toContain('admin.unban') }) + test('admin option should be displayed for admin as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -123,6 +127,7 @@ test('admin option should be displayed for admin as super admin', async () => { expect(text).toContain('admin.unsetAdmin') expect(text).not.toContain('admin.setAdmin') }) + test('banning option should not be displayed for admin as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -137,6 +142,7 @@ test('banning option should not be displayed for admin as super admin', async () expect(text).not.toContain('admin.ban') expect(text).not.toContain('admin.unban') }) + test('admin option should be displayed for normal users as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -151,6 +157,7 @@ test('admin option should be displayed for normal users as super admin', async ( expect(text).toContain('admin.setAdmin') expect(text).not.toContain('admin.unsetAdmin') }) + test('banning option should be displayed for normal users as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -165,6 +172,7 @@ test('banning option should be displayed for normal users as super admin', async expect(text).toContain('admin.ban') expect(text).not.toContain('admin.unban') }) + test('admin option should not be displayed for banned users as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -179,6 +187,7 @@ test('admin option should not be displayed for banned users as super admin', asy expect(text).not.toContain('admin.setAdmin') expect(text).not.toContain('admin.unsetAdmin') }) + test('banning option should be displayed for banned users as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -192,6 +201,7 @@ test('banning option should be displayed for banned users as super admin', async const text = wrapper.find('.vgt-table').text() expect(text).toContain('admin.unban') }) + test('admin option should not be displayed for other admins as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -206,6 +216,7 @@ test('admin option should not be displayed for other admins as admin', async () expect(text).not.toContain('admin.setAdmin') expect(text).not.toContain('admin.unsetAdmin') }) + test('banning option should not be displayed for other admins as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -220,6 +231,7 @@ test('banning option should not be displayed for other admins as admin', async ( expect(text).not.toContain('admin.ban') expect(text).not.toContain('admin.unban') }) + test('admin option should not be displayed for normal users as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -234,6 +246,7 @@ test('admin option should not be displayed for normal users as admin', async () expect(text).not.toContain('admin.setAdmin') expect(text).not.toContain('admin.unsetAdmin') }) + test('banning option should be displayed for normal users as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -248,6 +261,7 @@ test('banning option should be displayed for normal users as admin', async () => expect(text).toContain('admin.ban') expect(text).not.toContain('admin.unban') }) + test('admin option should not be displayed for banned users as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -262,6 +276,7 @@ test('admin option should not be displayed for banned users as admin', async () expect(text).not.toContain('admin.setAdmin') expect(text).not.toContain('admin.unsetAdmin') }) + test('banning option should be displayed for banned users as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -275,6 +290,7 @@ test('banning option should be displayed for banned users as admin', async () => const text = wrapper.find('.vgt-table').text() expect(text).toContain('admin.unban') }) + test('deletion button should not be displayed for super admins', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -285,6 +301,7 @@ test('deletion button should not be displayed for super admins', async () => { await wrapper.vm.$nextTick() expect(wrapper.find('.btn-danger').attributes('disabled')).toBe('disabled') }) + test('deletion button should be displayed for admins as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -297,6 +314,7 @@ test('deletion button should be displayed for admins as super admin', async () = await wrapper.vm.$nextTick() expect(wrapper.find('.btn-danger').attributes('disabled')).toBeNil() }) + test('deletion button should be displayed for normal users as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -309,6 +327,7 @@ test('deletion button should be displayed for normal users as super admin', asyn await wrapper.vm.$nextTick() expect(wrapper.find('.btn-danger').attributes('disabled')).toBeNil() }) + test('deletion button should be displayed for banned users as super admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -321,6 +340,7 @@ test('deletion button should be displayed for banned users as super admin', asyn await wrapper.vm.$nextTick() expect(wrapper.find('.btn-danger').attributes('disabled')).toBeNil() }) + test('deletion button should not be displayed for other admins as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -333,6 +353,7 @@ test('deletion button should not be displayed for other admins as admin', async await wrapper.vm.$nextTick() expect(wrapper.find('.btn-danger').attributes('disabled')).toBe('disabled') }) + test('deletion button should be displayed for normal users as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -345,6 +366,7 @@ test('deletion button should be displayed for normal users as admin', async () = await wrapper.vm.$nextTick() expect(wrapper.find('.btn-danger').attributes('disabled')).toBeNil() }) + test('deletion button should be displayed for banned users as admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -357,6 +379,7 @@ test('deletion button should be displayed for banned users as admin', async () = await wrapper.vm.$nextTick() expect(wrapper.find('.btn-danger').attributes('disabled')).toBeNil() }) + test('change email', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -391,6 +414,7 @@ test('change email', async () => { await flushPromises() expect(wrapper.text()).toContain('d@e.f') }) + test('toggle verification', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -415,6 +439,7 @@ test('toggle verification', async () => { await flushPromises() expect(wrapper.text()).toContain('admin.verified') }) + test('change nickname', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -449,6 +474,7 @@ test('change nickname', async () => { await flushPromises() expect(wrapper.text()).toContain('new') }) + test('change password', async () => { jest.spyOn(toastr, 'success') jest.spyOn(toastr, 'warning') @@ -484,6 +510,7 @@ test('change password', async () => { await flushPromises() expect(toastr.warning).toBeCalledWith('1') }) + test('change score', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -515,6 +542,7 @@ test('change score', async () => { await flushPromises() expect(wrapper.text()).toContain('45') }) + test('toggle admin', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -547,6 +575,7 @@ test('toggle admin', async () => { await flushPromises() expect(wrapper.text()).toContain('admin.normal') }) + test('toggle ban', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ @@ -579,6 +608,7 @@ test('toggle ban', async () => { await flushPromises() expect(wrapper.text()).toContain('admin.ban') }) + test('delete user', async () => { Vue.prototype.$http.get.mockResolvedValue({ data: [ diff --git a/resources/assets/tests/components/auth/Forgot.test.js b/resources/assets/tests/components/auth/Forgot.test.js index 996454d6..235e6626 100644 --- a/resources/assets/tests/components/auth/Forgot.test.js +++ b/resources/assets/tests/components/auth/Forgot.test.js @@ -8,6 +8,7 @@ test('click to refresh captcha', () => { wrapper.find('img').trigger('click') expect(Date.now).toBeCalledTimes(2) }) + test('submit forgot form', async () => { jest.spyOn(Date, 'now') Vue.prototype.$http.post diff --git a/resources/assets/tests/components/auth/Login.test.js b/resources/assets/tests/components/auth/Login.test.js index 865abfcb..2aa04a8e 100644 --- a/resources/assets/tests/components/auth/Login.test.js +++ b/resources/assets/tests/components/auth/Login.test.js @@ -10,6 +10,7 @@ test('show captcha if too many login fails', () => { const wrapper = mount(Login) expect(wrapper.find('img').attributes('src')).toMatch(/\/auth\/captcha\?v=\d+/) }) + test('click to refresh captcha', () => { window.blessing.extra = { tooManyFails: true } jest.spyOn(Date, 'now') @@ -17,6 +18,7 @@ test('click to refresh captcha', () => { wrapper.find('img').trigger('click') expect(Date.now).toBeCalledTimes(2) }) + test('login', async () => { window.blessing.extra = { tooManyFails: false } Vue.prototype.$http.post diff --git a/resources/assets/tests/components/auth/Register.test.js b/resources/assets/tests/components/auth/Register.test.js index 1eefee1e..5af297e0 100644 --- a/resources/assets/tests/components/auth/Register.test.js +++ b/resources/assets/tests/components/auth/Register.test.js @@ -13,6 +13,7 @@ test('click to refresh captcha', () => { wrapper.find('img').trigger('click') expect(Date.now).toBeCalledTimes(2) }) + test('require player name', () => { window.blessing.extra = { player: true } @@ -22,6 +23,7 @@ test('require player name', () => { window.blessing.extra = { player: false } }) + test('register', async () => { jest.spyOn(Date, 'now') Vue.prototype.$http.post @@ -97,6 +99,7 @@ test('register', async () => { jest.runAllTimers() expect(swal).toBeCalledWith({ type: 'success', text: 'ok' }) }) + test('register with player name', async () => { window.blessing.extra = { player: true } Vue.prototype.$http.post.mockResolvedValue({ errno: 0, msg: 'ok' }) diff --git a/resources/assets/tests/components/common/Previewer.test.js b/resources/assets/tests/components/common/Previewer.test.js index 3bec1849..d50f062c 100644 --- a/resources/assets/tests/components/common/Previewer.test.js +++ b/resources/assets/tests/components/common/Previewer.test.js @@ -12,21 +12,25 @@ test('initialize skinview3d', () => { expect(wrapper.vm.viewer.camera.position.z).toBe(70) expect(stub).toBeCalledWith(expect.any(HTMLElement)) }) + test('dispose viewer before destroy', () => { const wrapper = mount(Previewer) wrapper.destroy() expect(wrapper.vm.viewer.disposed).toBeTrue() }) + test('skin URL should be updated', () => { const wrapper = mount(Previewer) wrapper.setProps({ skin: 'abc' }) expect(wrapper.vm.viewer.skinUrl).toBe('abc') }) + test('cape URL should be updated', () => { const wrapper = mount(Previewer) wrapper.setProps({ cape: 'abc' }) expect(wrapper.vm.viewer.capeUrl).toBe('abc') }) + test('`footer` slot', () => { const wrapper = mount(Previewer, { slots: { @@ -35,10 +39,12 @@ test('`footer` slot', () => { }) expect(wrapper.find('#footer').exists()).toBeTrue() }) + test('disable closet mode', () => { const wrapper = mount(Previewer) expect(wrapper.find('.badge').text()).toBe('') }) + test('enable closet mode', () => { const wrapper = mount(Previewer, { propsData: { @@ -56,6 +62,7 @@ test('enable closet mode', () => { wrapper.setProps({ skin: 'abc', cape: 'abc' }) expect(wrapper.find('.badge').text()).toBe('general.skin & general.cape') }) + test('toggle pause', () => { const wrapper = mount(Previewer) const pauseButton = wrapper.find('.fa-pause') @@ -64,17 +71,20 @@ test('toggle pause', () => { expect(wrapper.find('.fa-play').exists()).toBeTrue() expect(wrapper.find('.fa-pause').exists()).toBeFalse() }) + test('toggle run', () => { const wrapper = mount(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) 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 @@ -83,6 +93,7 @@ test('reset', () => { wrapper.find('.fa-stop').trigger('click') expect(mockedSkinview3d.SkinViewer.prototype.dispose).toBeCalled() }) + test('custom title', () => { const wrapper = mount(Previewer, { propsData: { title: 'custom-title' } }) expect(wrapper.text()).toContain('custom-title') diff --git a/resources/assets/tests/components/skinlib/List.test.js b/resources/assets/tests/components/skinlib/List.test.js index b6eb4780..ff3c0d61 100644 --- a/resources/assets/tests/components/skinlib/List.test.js +++ b/resources/assets/tests/components/skinlib/List.test.js @@ -22,6 +22,7 @@ test('empty skin library', () => { const wrapper = mount(List) expect(wrapper.text()).toContain('general.noResult') }) + test('toggle texture type', () => { Vue.prototype.$http.get.mockResolvedValue({ items: [], total_pages: 0, current_uid: 0, @@ -114,6 +115,7 @@ test('sort items', () => { ) expect(wrapper.text()).toContain('skinlib.sort.time') }) + test('search by keyword', () => { Vue.prototype.$http.get.mockResolvedValue({ items: [], total_pages: 0, current_uid: 0, @@ -162,6 +164,7 @@ test('reset all filters', () => { wrapper.find('.btn-warning').trigger('click') expect(Vue.prototype.$http.get).toBeCalledTimes(1) }) + test('is anonymous', () => { Vue.prototype.$http.get.mockResolvedValue({ items: [], total_pages: 0, current_uid: 0, @@ -169,6 +172,7 @@ test('is anonymous', () => { const wrapper = mount(List) expect(wrapper.vm.anonymous).toBeTrue() }) + test('on page changed', () => { Vue.prototype.$http.get.mockResolvedValue({ items: [], total_pages: 0, current_uid: 0, diff --git a/resources/assets/tests/components/skinlib/Show.test.js b/resources/assets/tests/components/skinlib/Show.test.js index 087dd5a7..ae10c909 100644 --- a/resources/assets/tests/components/skinlib/Show.test.js +++ b/resources/assets/tests/components/skinlib/Show.test.js @@ -32,6 +32,7 @@ test('button for adding to closet should be disabled if not auth', () => { }) expect(wrapper.find('.btn-primary').attributes('disabled')).toBe('disabled') }) + test('button for adding to closet should be disabled if auth', () => { Vue.prototype.$http.get.mockResolvedValue({}) Object.assign(window.blessing.extra, { inCloset: true, currentUid: 1 }) @@ -43,6 +44,7 @@ test('button for adding to closet should be disabled if auth', () => { }) expect(wrapper.find('.btn-primary').text()).toBe('skinlib.removeFromCloset') }) + test('likes count indicator', async () => { Vue.prototype.$http.get.mockResolvedValue({ likes: 2 }) const wrapper = mount(Show, { @@ -55,6 +57,7 @@ test('likes count indicator', async () => { expect(wrapper.find('.likes').attributes('style')).toContain('color: rgb(224, 53, 59)') expect(wrapper.find('.likes').text()).toContain('2') }) + test('render basic information', async () => { Vue.prototype.$http.get.mockResolvedValue({ name: 'my-texture', @@ -77,6 +80,7 @@ test('render basic information', async () => { expect(text).toContain('2018') expect(text).toContain('author') }) + test('render action text of editing texture name', async () => { Object.assign(window.blessing.extra, { admin: true }) Vue.prototype.$http.get.mockResolvedValue({ uploader: 1, name: 'name' }) @@ -98,6 +102,7 @@ test('render action text of editing texture name', async () => { await wrapper.vm.$nextTick() expect(wrapper.contains('small')).toBeFalse() }) + test('render nickname of uploader', () => { Object.assign(window.blessing.extra, { nickname: null }) Vue.prototype.$http.get.mockResolvedValue({}) @@ -108,6 +113,7 @@ test('render nickname of uploader', () => { }) expect(wrapper.text()).toContain('general.unexistent-user') }) + test('operation panel should not be rendered if not auth', () => { Object.assign(window.blessing.extra, { currentUid: 0 }) Vue.prototype.$http.get.mockResolvedValue({}) @@ -118,6 +124,7 @@ test('operation panel should not be rendered if not auth', () => { }) expect(wrapper.contains('.box-warning')).toBeFalse() }) + test('link to downloading texture', async () => { Object.assign(window.blessing.extra, { download: false }) Vue.prototype.$http.get.mockResolvedValue({ hash: '123' }) @@ -130,6 +137,7 @@ test('link to downloading texture', async () => { expect(wrapper.contains('a[title="123"]')).toBeFalse() expect(wrapper.contains('span[title="123"]')).toBeTrue() }) + test('set as avatar', () => { Object.assign(window.blessing.extra, { currentUid: 1, inCloset: true }) Vue.prototype.$http.get.mockResolvedValueOnce({ type: 'steve' }) @@ -151,6 +159,7 @@ test('set as avatar', () => { }) expect(noSetAsAvatar.find('button.btn-default').isEmpty()).toBeTrue() }) + test('add to closet', async () => { Object.assign(window.blessing.extra, { currentUid: 1, inCloset: false }) Vue.prototype.$http.get.mockResolvedValue({ name: 'wow', likes: 2 }) @@ -190,6 +199,7 @@ test('add to closet', async () => { expect(wrapper.vm.likes).toBe(3) expect(wrapper.vm.liked).toBeTrue() }) + test('remove from closet', async () => { Object.assign(window.blessing.extra, { currentUid: 1, inCloset: true }) Vue.prototype.$http.get.mockResolvedValue({ likes: 2 }) @@ -223,6 +233,7 @@ test('remove from closet', async () => { expect(wrapper.vm.likes).toBe(1) expect(wrapper.vm.liked).toBeFalse() }) + test('change texture name', async () => { Object.assign(window.blessing.extra, { admin: true }) Vue.prototype.$http.get.mockResolvedValue({ name: 'old-name' }) @@ -259,6 +270,7 @@ test('change texture name', async () => { await flushPromises() expect(wrapper.vm.name).toBe('new-name') }) + test('change texture model', async () => { Vue.prototype.$http.get.mockResolvedValue({ type: 'steve' }) Vue.prototype.$http.post @@ -291,6 +303,7 @@ test('change texture model', async () => { await flushPromises() expect(wrapper.vm.type).toBe('alex') }) + test('toggle privacy', async () => { Vue.prototype.$http.get.mockResolvedValue({ public: true }) Vue.prototype.$http.post @@ -326,6 +339,7 @@ test('toggle privacy', async () => { await flushPromises() expect(wrapper.vm.public).toBeTrue() }) + test('delete texture', async () => { Vue.prototype.$http.get.mockResolvedValue({}) Vue.prototype.$http.post diff --git a/resources/assets/tests/components/skinlib/SkinLibItem.test.js b/resources/assets/tests/components/skinlib/SkinLibItem.test.js index 98741205..4b2eaf1e 100644 --- a/resources/assets/tests/components/skinlib/SkinLibItem.test.js +++ b/resources/assets/tests/components/skinlib/SkinLibItem.test.js @@ -15,6 +15,7 @@ test('urls', () => { expect(wrapper.find('a').attributes('href')).toBe('/skinlib/show/1') expect(wrapper.find('img').attributes('src')).toBe('/preview/1.png') }) + test('render basic information', () => { const wrapper = mount(SkinLibItem, { propsData: { @@ -26,6 +27,7 @@ test('render basic information', () => { expect(wrapper.text()).toContain('test') expect(wrapper.text()).toContain('skinlib.filter.steve') }) + test('anonymous user', () => { const wrapper = mount(SkinLibItem, { propsData: { anonymous: true }, @@ -35,6 +37,7 @@ test('anonymous user', () => { button.trigger('click') expect(Vue.prototype.$http.post).not.toBeCalled() }) + test('private texture', () => { const wrapper = mount(SkinLibItem, { propsData: { isPublic: false }, @@ -44,6 +47,7 @@ test('private texture', () => { wrapper.setProps({ isPublic: true }) expect(wrapper.text()).not.toContain('skinlib.private') }) + test('liked state', () => { const wrapper = mount(SkinLibItem, { propsData: { liked: true, anonymous: false }, @@ -57,6 +61,7 @@ test('liked state', () => { expect(button.attributes('title')).toBe('skinlib.addToCloset') expect(button.classes('liked')).toBeFalse() }) + test('remove from closet', async () => { Vue.prototype.$http.post .mockResolvedValueOnce({ errno: 1, msg: '1' }) @@ -86,6 +91,7 @@ test('remove from closet', async () => { await flushPromises() expect(wrapper.emitted('like-toggled')[0]).toEqual([false]) }) + test('add to closet', async () => { Vue.prototype.$http.post .mockResolvedValueOnce({ errno: 1, msg: '1' }) diff --git a/resources/assets/tests/components/skinlib/Upload.test.js b/resources/assets/tests/components/skinlib/Upload.test.js index f4c71b77..afc59549 100644 --- a/resources/assets/tests/components/skinlib/Upload.test.js +++ b/resources/assets/tests/components/skinlib/Upload.test.js @@ -30,6 +30,7 @@ test('display drap and drop notice', () => { wrapper.setData({ files: [{}] }) expect(wrapper.contains('img')).toBeTrue() }) + test('button for removing texture', () => { const wrapper = mount(Upload, { stubs: ['file-upload'], @@ -46,6 +47,7 @@ test('button for removing texture', () => { button.trigger('click') expect(wrapper.vm.texture).toBe('') }) + test('notice should be display if texture is private', () => { const wrapper = mount(Upload, { stubs: ['file-upload'], @@ -54,6 +56,7 @@ test('notice should be display if texture is private', () => { wrapper.find('[type=checkbox]').setChecked() expect(wrapper.find('.callout').text()).toBe('privacyNotice') }) + test('display score cost', () => { const origin = Vue.prototype.$t Vue.prototype.$t = (key, args) => `${key}${JSON.stringify(args)}` @@ -67,6 +70,7 @@ test('display score cost', () => { Vue.prototype.$t = origin }) + test('process input file', () => { window.URL.createObjectURL = jest.fn().mockReturnValue('file-url') jest.spyOn(window, 'Image') @@ -107,6 +111,7 @@ test('process input file', () => { window.Image.mockRestore() }) + test('upload file', async () => { window.Request = jest.fn() Vue.prototype.$http.post diff --git a/resources/assets/tests/components/user/Closet.test.js b/resources/assets/tests/components/user/Closet.test.js index 21f0e458..0c4c4297 100644 --- a/resources/assets/tests/components/user/Closet.test.js +++ b/resources/assets/tests/components/user/Closet.test.js @@ -74,6 +74,7 @@ test('different categories', () => { .classes('active')).toBeTrue() expect(wrapper.find('#cape-category').classes('active')).toBeTrue() }) + test('search textures', () => { Vue.prototype.$http.get.mockResolvedValue({}) @@ -100,6 +101,7 @@ test('empty closet', () => { wrapper.setData({ category: 'cape' }) expect(wrapper.find('#cape-category').text()).toContain('user.emptyClosetMsg') }) + test('no matched search result', () => { Vue.prototype.$http.get.mockResolvedValue({}) const wrapper = mount(Closet) @@ -108,6 +110,7 @@ test('no matched search result', () => { wrapper.setData({ category: 'cape' }) expect(wrapper.find('#cape-category').text()).toContain('general.noResult') }) + test('render items', async () => { Vue.prototype.$http.get.mockResolvedValue({ items: [ @@ -121,6 +124,7 @@ test('render items', async () => { await wrapper.vm.$nextTick() expect(wrapper.findAll(ClosetItem)).toHaveLength(2) }) + test('reload closet when page changed', () => { Vue.prototype.$http.get.mockResolvedValue({}) const wrapper = mount(Closet) @@ -128,6 +132,7 @@ test('reload closet when page changed', () => { jest.runAllTicks() expect(Vue.prototype.$http.get).toBeCalledTimes(2) }) + test('remove skin item', () => { Vue.prototype.$http.get.mockResolvedValue({}) const wrapper = mount(Closet) @@ -135,6 +140,7 @@ test('remove skin item', () => { wrapper.vm.removeSkinItem(0) expect(wrapper.find('#skin-category').text()).toContain('user.emptyClosetMsg') }) + test('remove cape item', () => { Vue.prototype.$http.get.mockResolvedValue({}) const wrapper = mount(Closet) @@ -142,12 +148,14 @@ test('remove cape item', () => { wrapper.vm.removeCapeItem(0) expect(wrapper.find('#cape-category').text()).toContain('user.emptyClosetMsg') }) + test('compute avatar URL', () => { Vue.prototype.$http.get.mockResolvedValue({}) const wrapper = mount(Closet) const { avatarUrl } = wrapper.vm expect(avatarUrl({ tid_skin: 1 })).toBe('/avatar/35/1') }) + test('select texture', async () => { Vue.prototype.$http.get .mockResolvedValueOnce({}) @@ -169,6 +177,7 @@ test('select texture', async () => { expect(Vue.prototype.$http.get).toBeCalledWith('/skinlib/info/2') expect(wrapper.vm.capeUrl).toBe('/textures/b') }) + test('apply texture', async () => { window.$ = jest.fn(() => ({ iCheck: () => ({ @@ -203,6 +212,7 @@ test('apply texture', async () => { expect(wrapper.find('.modal-body').text()).toContain('name') jest.runAllTimers() }) + test('submit applying texture', async () => { window.$ = jest.fn(() => ({ modal() {} })) jest.spyOn(toastr, 'info') @@ -246,6 +256,7 @@ test('submit applying texture', async () => { await wrapper.vm.$nextTick() expect(swal).toBeCalledWith({ type: 'success', text: 'ok' }) }) + test('reset selected texture', () => { Vue.prototype.$http.get.mockResolvedValue({}) const wrapper = mount(Closet) @@ -265,6 +276,7 @@ test('reset selected texture', () => { capeUrl: '', })) }) + test('select specified texture initially', async () => { window.history.pushState({}, 'title', `${location.href}?tid=1`) window.$ = jest.fn(() => ({ diff --git a/resources/assets/tests/components/user/ClosetItem.test.js b/resources/assets/tests/components/user/ClosetItem.test.js index 0cfc57de..007190b0 100644 --- a/resources/assets/tests/components/user/ClosetItem.test.js +++ b/resources/assets/tests/components/user/ClosetItem.test.js @@ -20,10 +20,12 @@ test('computed values', () => { expect(wrapper.find('img').attributes('src')).toBe('/preview/1.png') expect(wrapper.find('a.more').attributes('href')).toBe('/skinlib/show/1') }) + test('selected item', () => { const wrapper = mount(ClosetItem, { propsData: factory({ selected: true }) }) expect(wrapper.find('.item').classes('item-selected')).toBeTrue() }) + test('click item body', () => { const wrapper = mount(ClosetItem, { propsData: factory() }) @@ -33,6 +35,7 @@ test('click item body', () => { wrapper.find('.item-body').trigger('click') expect(wrapper.emitted().select).toBeTruthy() }) + test('rename texture', async () => { Vue.prototype.$http.post .mockResolvedValueOnce({ errno: 0 }) diff --git a/resources/assets/tests/components/user/Dashboard.test.js b/resources/assets/tests/components/user/Dashboard.test.js index c8aaa37f..3f6ec812 100644 --- a/resources/assets/tests/components/user/Dashboard.test.js +++ b/resources/assets/tests/components/user/Dashboard.test.js @@ -31,6 +31,7 @@ test('fetch score info', () => { mount(Dashboard) expect(Vue.prototype.$http.get).toBeCalledWith('/user/score-info') }) + test('players usage', async () => { Vue.prototype.$http.get.mockResolvedValue(scoreInfo()) const wrapper = mount(Dashboard) @@ -38,6 +39,7 @@ test('players usage', async () => { expect(wrapper.text()).toContain('3 / 15') expect(wrapper.find('.progress-bar-aqua').attributes('style')).toBe('width: 20%;') }) + test('storage usage', async () => { Vue.prototype.$http.get .mockResolvedValueOnce(scoreInfo()) @@ -61,12 +63,14 @@ test('storage usage', async () => { expect(wrapper.text()).toContain('2 / 4 MB') expect(wrapper.find('.progress-bar-yellow').attributes('style')).toBe('width: 50%;') }) + test('display score', async () => { Vue.prototype.$http.get.mockResolvedValue(scoreInfo()) const wrapper = mount(Dashboard) await wrapper.vm.$nextTick() expect(wrapper.find('#score').text()).toContain('835') }) + test('button `sign` state', async () => { Vue.prototype.$http.get .mockResolvedValueOnce(scoreInfo({ signAfterZero: true })) @@ -93,6 +97,7 @@ test('button `sign` state', async () => { await wrapper.vm.$nextTick() expect(wrapper.find('button').attributes('disabled')).toBe('disabled') }) + test('remaining time', async () => { const origin = Vue.prototype.$t Vue.prototype.$t = (key, args) => key + JSON.stringify(args) @@ -117,6 +122,7 @@ test('remaining time', async () => { Vue.prototype.$t = origin }) + test('sign', async () => { jest.spyOn(toastr, 'warning') swal.mockResolvedValue() diff --git a/resources/assets/tests/components/user/EmailVerification.test.js b/resources/assets/tests/components/user/EmailVerification.test.js index 164c1d94..a66e8a53 100644 --- a/resources/assets/tests/components/user/EmailVerification.test.js +++ b/resources/assets/tests/components/user/EmailVerification.test.js @@ -10,6 +10,7 @@ test('message box should not be render if verified', () => { const wrapper = mount(EmailVerification) expect(wrapper.isEmpty()).toBeTrue() }) + test('resend email', async () => { window.blessing.extra = { unverified: true } Vue.prototype.$http.post diff --git a/resources/assets/tests/components/user/Players.test.js b/resources/assets/tests/components/user/Players.test.js index 3a9f6e40..2b838468 100644 --- a/resources/assets/tests/components/user/Players.test.js +++ b/resources/assets/tests/components/user/Players.test.js @@ -18,11 +18,13 @@ test('display player name constraints', () => { expect(text).toContain('rule') expect(text).toContain('length') }) + test('fetch players data before mount', () => { Vue.prototype.$http.get.mockResolvedValue([]) mount(Players) expect(Vue.prototype.$http.get).toBeCalledWith('/user/player/list') }) + test('click to preview player', async () => { Vue.prototype.$http.get .mockResolvedValueOnce([ @@ -56,6 +58,7 @@ test('click to preview player', async () => { await flushPromises() expect(Vue.prototype.$http.get).toBeCalledWith('/skinlib/info/2') }) + test('change player name', async () => { Vue.prototype.$http.get .mockResolvedValueOnce([ @@ -89,6 +92,7 @@ test('change player name', async () => { await flushPromises() expect(wrapper.text()).toContain('new-name') }) + test('load iCheck', async () => { Vue.prototype.$http.get .mockResolvedValueOnce([ @@ -109,6 +113,7 @@ test('load iCheck', async () => { wrapper.find('.btn-warning').trigger('click') expect(window.$).toBeCalled() }) + test('delete player', async () => { Vue.prototype.$http.get .mockResolvedValueOnce([ @@ -133,12 +138,14 @@ test('delete player', async () => { await flushPromises() expect(wrapper.text()).not.toContain('to-be-deleted') }) + test('toggle preview mode', () => { Vue.prototype.$http.get.mockResolvedValueOnce([]) const wrapper = mount(Players) wrapper.find('[data-test="to2d"]').trigger('click') expect(wrapper.text()).toContain('user.player.texture-empty') }) + test('add player', async () => { window.$ = jest.fn(() => ({ modal() {} })) Vue.prototype.$http.get.mockResolvedValueOnce([]) @@ -163,6 +170,7 @@ test('add player', async () => { await flushPromises() expect(Vue.prototype.$http.get).toBeCalledTimes(2) }) + test('clear texture', async () => { window.$ = jest.fn(() => ({ modal() {} })) Vue.prototype.$http.get.mockResolvedValueOnce([ diff --git a/resources/assets/tests/components/user/Profile.test.js b/resources/assets/tests/components/user/Profile.test.js index a1ca3386..3bc865e4 100644 --- a/resources/assets/tests/components/user/Profile.test.js +++ b/resources/assets/tests/components/user/Profile.test.js @@ -17,6 +17,7 @@ test('computed values', () => { window.blessing.extra = { admin: false } expect(mount(Profile).vm.isAdmin).toBeFalse() }) + test('convert linebreak', () => { const wrapper = mount(Profile) expect(wrapper.vm.nl2br('a\nb\nc')).toBe('a
b
c') @@ -87,6 +88,7 @@ test('change password', async () => { await wrapper.vm.$nextTick() expect(swal).toBeCalledWith({ type: 'success', text: 'o' }) }) + test('change nickname', async () => { Vue.prototype.$http.post .mockResolvedValueOnce({ errno: 1, msg: 'w' }) @@ -129,6 +131,7 @@ test('change nickname', async () => { await flushPromises() expect(swal).toBeCalledWith({ type: 'success', text: 'o' }) }) + test('change email', async () => { Vue.prototype.$http.post .mockResolvedValueOnce({ errno: 1, msg: 'w' }) @@ -171,6 +174,7 @@ test('change email', async () => { await flushPromises() expect(swal).toBeCalledWith({ type: 'success', text: 'o' }) }) + test('delete account', async () => { window.blessing.extra = { admin: true } swal.mockResolvedValue() diff --git a/resources/assets/tests/js/check-updates.test.js b/resources/assets/tests/js/check-updates.test.js index 16c85664..f1ef3474 100644 --- a/resources/assets/tests/js/check-updates.test.js +++ b/resources/assets/tests/js/check-updates.test.js @@ -30,6 +30,7 @@ test('check for BS updates', async () => { await checkForUpdates() expect(document.querySelector('a').innerHTML).toContain('4.0.0') }) + test('check for plugins updates', async () => { window.fetch = jest.fn() .mockResolvedValueOnce({ ok: false }) diff --git a/resources/assets/tests/js/event.test.js b/resources/assets/tests/js/event.test.js index 457c9b29..ea1dc67a 100644 --- a/resources/assets/tests/js/event.test.js +++ b/resources/assets/tests/js/event.test.js @@ -3,6 +3,7 @@ import * as emitter from '@/js/event' test('mount variable to global', () => { expect(window.bsEmitter).toBeFrozen() }) + test('add listener and emit event', () => { const mockA = jest.fn() const mockB = jest.fn() diff --git a/resources/assets/tests/js/i18n.test.js b/resources/assets/tests/js/i18n.test.js index 54807a57..ab19b528 100644 --- a/resources/assets/tests/js/i18n.test.js +++ b/resources/assets/tests/js/i18n.test.js @@ -4,6 +4,7 @@ import Vue from 'vue' test('mount to global', () => { expect(window.trans).toBe(trans) }) + test('translate text', () => { window.blessing.i18n = { a: { b: { c: 'text', d: 'Hi, :name!' } } } expect(trans('a.b.c')).toBe('text') @@ -11,6 +12,7 @@ test('translate text', () => { expect(trans('a.b.d', { name: 'me' })).toBe('Hi, me!') expect(trans('d.e')).toBe('d.e') }) + test('Vue directive', () => { const byString = Vue.extend({ render(h) { diff --git a/resources/assets/tests/js/net.test.js b/resources/assets/tests/js/net.test.js index f5afd66d..5d2190f1 100644 --- a/resources/assets/tests/js/net.test.js +++ b/resources/assets/tests/js/net.test.js @@ -33,6 +33,7 @@ test('the GET method', async () => { await net.get('/abc') expect(window.fetch.mock.calls[1][0].url).toBe('/abc') }) + test('the POST method', async () => { window.fetch = jest.fn() .mockResolvedValue({ @@ -74,6 +75,7 @@ test('the POST method', async () => { await net.post('/abc') expect(window.fetch.mock.calls[2][0].body).toBe('{}') }) + test('low level fetch', async () => { const json = jest.fn().mockResolvedValue({}) window.fetch = jest.fn() diff --git a/resources/assets/tests/js/notify.test.js b/resources/assets/tests/js/notify.test.js index aee36c40..1b6b5314 100644 --- a/resources/assets/tests/js/notify.test.js +++ b/resources/assets/tests/js/notify.test.js @@ -18,6 +18,7 @@ test('show message', () => { expect(element.hasClass('callout-info')).toBeTrue() expect(element.html()).toBe('hi') }) + test('show AJAX error', () => { $.fn.modal = function () { document.body.innerHTML = this.html() @@ -25,6 +26,7 @@ test('show AJAX error', () => { notify.showAjaxError(new Error('an-error')) expect(document.body.innerHTML).toContain('an-error') }) + test('show modal', () => { notify.showModal('message') expect($('.modal-title').html()).toBe('Message') @@ -34,6 +36,7 @@ test('show modal', () => { destroyOnClose: false, }) }) + test('show sweetalert', () => { jest.spyOn(Swal, 'fire') notify.swal({}) diff --git a/resources/assets/tests/js/utils.test.js b/resources/assets/tests/js/utils.test.js index bc2c6b9a..1ec18194 100644 --- a/resources/assets/tests/js/utils.test.js +++ b/resources/assets/tests/js/utils.test.js @@ -10,12 +10,14 @@ test('debounce', () => { jest.runAllTimers() expect(stub).toBeCalledTimes(1) }) + test('queryString', () => { history.pushState({}, 'page', `${location.href}?key=value`) expect(utils.queryString('key')).toBe('value') expect(utils.queryString('a')).toBeUndefined() expect(utils.queryString('a', 'b')).toBe('b') }) + test('queryStringify', () => { expect(utils.queryStringify({ a: 'b', c: 'd' })).toBe('a=b&c=d') }) diff --git a/yarn.lock b/yarn.lock index 95bb3885..f0a4f821 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3222,6 +3222,13 @@ eslint-plugin-node@^8.0.1: resolve "^1.8.1" semver "^5.5.0" +eslint-plugin-project@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/eslint-plugin-project/-/eslint-plugin-project-0.2.2.tgz#aace7d7d252dc2403a3167cecf59228b80d983f3" + integrity sha512-76UJdTXg3+y0ix/xRjG2DjWumxV6oUYe7q/rEPDddGgxUpv08ybn4DNzm3h5MOqIZ9qY+m6CG1x6o/fAvQZusQ== + dependencies: + js-yaml "^3.12.1" + eslint-plugin-vue@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.2.2.tgz#86601823b7721b70bc92d54f1728cfc03b36283c" @@ -5132,6 +5139,14 @@ js-yaml@^3.12.0, js-yaml@^3.5.2, js-yaml@^3.9.0: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.12.1: + version "3.12.2" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz#ef1d067c5a9d9cb65bd72f285b5d8105c77f14fc" + integrity sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@~3.10.0: version "3.10.0" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"