From d1faaf64f50534f7f55f3e4cabbbacfd9bde70dd Mon Sep 17 00:00:00 2001 From: gplane Date: Thu, 20 Jul 2017 15:36:59 +0800 Subject: [PATCH] Add unit test for JavaScript files --- .eslintrc.js | 5 + .gitignore | 1 + package.json | 7 + .../assets/src/js/__tests__/admin.test.js | 823 +++++++++++++++++ .../assets/src/js/__tests__/auth.test.js | 368 ++++++++ .../assets/src/js/__tests__/common.test.js | 179 ++++ .../assets/src/js/__tests__/skinlib.test.js | 490 ++++++++++ .../assets/src/js/__tests__/user.test.js | 868 ++++++++++++++++++ resources/assets/src/js/admin/customize.js | 10 +- resources/assets/src/js/admin/players.js | 11 + resources/assets/src/js/admin/plugins.js | 10 +- resources/assets/src/js/admin/update.js | 4 + resources/assets/src/js/admin/users.js | 12 + resources/assets/src/js/auth/captcha.js | 4 + resources/assets/src/js/auth/forgot.js | 50 + resources/assets/src/js/auth/reset.js | 49 - resources/assets/src/js/common/i18n.js | 7 + resources/assets/src/js/common/notify.js | 8 + .../assets/src/js/common/texture-preview.js | 4 + resources/assets/src/js/common/utils.js | 11 +- resources/assets/src/js/skinlib/index.js | 18 +- resources/assets/src/js/skinlib/operations.js | 25 +- resources/assets/src/js/user/closet.js | 10 + resources/assets/src/js/user/player.js | 17 +- resources/assets/src/js/user/profile.js | 13 +- resources/assets/src/js/user/sign.js | 8 +- yarn.lock | 823 ++++++++++++++++- 27 files changed, 3740 insertions(+), 95 deletions(-) create mode 100644 resources/assets/src/js/__tests__/admin.test.js create mode 100644 resources/assets/src/js/__tests__/auth.test.js create mode 100644 resources/assets/src/js/__tests__/common.test.js create mode 100644 resources/assets/src/js/__tests__/skinlib.test.js create mode 100644 resources/assets/src/js/__tests__/user.test.js create mode 100644 resources/assets/src/js/auth/forgot.js diff --git a/.eslintrc.js b/.eslintrc.js index cb4c085e..7dd774c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,9 +25,14 @@ module.exports = { "getQueryString": false, "TexturePreview": false }, + "parserOptions": { + "ecmaVersion": 2017 + }, "env":{ + "commonjs": true, "es6": true, "browser": true, + "jest": true, "jquery": true } }; diff --git a/.gitignore b/.gitignore index 95fbe922..28b4abc7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .env .sass-cache +coverage vendor/* plugins/* storage/textures/* diff --git a/package.json b/package.json index 045bb5e9..941af543 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "scripts": { "build": "gulp build", "release": "gulp release", + "test": "jest --silent", + "test:watch": "jest --silent --watch", + "test:cover": "jest --silent --coverage", "dev": "gulp watch" }, "devDependencies": { @@ -27,6 +30,7 @@ "gulp-sass": "^3.1.0", "gulp-uglify": "^3.0.0", "gulp-zip": "^4.0.0", + "jest": "^20.0.4", "run-sequence": "^2.0.0" }, "dependencies": { @@ -41,5 +45,8 @@ "jquery": "^3.2.1", "sweetalert2": "^6.6.5", "toastr": "^2.1.2" + }, + "optionalDependencies": { + "@types/jest": "^20.0.2" } } diff --git a/resources/assets/src/js/__tests__/admin.test.js b/resources/assets/src/js/__tests__/admin.test.js new file mode 100644 index 00000000..3b6acf14 --- /dev/null +++ b/resources/assets/src/js/__tests__/admin.test.js @@ -0,0 +1,823 @@ +const $ = require('jquery'); +window.$ = window.jQuery = $; + +describe('tests for "customize" module', () => { + const modulePath = '../admin/customize'; + + it('change skin preview after switching color', () => { + window.current_skin = 'skin-blue'; + document.body.className = window.current_skin; + document.body.innerHTML = ` +
+ +
`; + + require(modulePath); + $('#layout-skins-list [data-skin]').click(); + + expect($('body').hasClass('skin-blue')).toBe(false); + expect($('body').hasClass('skin-purple')).toBe(true); + expect(window.current_skin).toBe('skin-purple'); + }); + + it('submit information of skin', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.current_skin = ''; + + document.body.innerHTML = ''; + const submitColor = require(modulePath); + + await submitColor(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/customize?action=color', + dataType: 'json', + data: { color_scheme: '' } + }); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + + await submitColor(); + expect(toastr.warning).toBeCalledWith('warning'); + }); +}); + +describe('tests for "players" module', () => { + const modulePath = '../admin/players'; + + it('change player reference', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + + document.body.innerHTML = ` +
+
+
+ `; + $('select').on('change', require(modulePath).changePreference); + + await $('select').val('slim').trigger('change'); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/players?action=preference', + dataType: 'json', + data: { + pid: '1', + preference: 'slim' + } + }); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + + await $('select').trigger('change'); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('submit changed texture information', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const modal = jest.fn(); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.$.fn.modal = modal; + + document.body.innerHTML = ` + + + + + + `; + const ajaxChangeTexture = require(modulePath).ajaxChangeTexture; + + await ajaxChangeTexture(1); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/players?action=texture', + dataType: 'json', + data: { pid: 1, model: 'default', tid: '1' } + }); + expect(document.getElementById('shouldBeRemoved')).toBeNull(); + expect(document.getElementById('shouldNotBeRemoved')).not.toBeNull(); + expect(modal).toBeCalledWith('hide'); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + expect($('img').attr('src')).toBe('preview/64/1.png'); + + await ajaxChangeTexture(1); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('change player name', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn(options => { + options.inputValidator('newName'); + return Promise.resolve('newName'); + }); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + + document.body.innerHTML = ` + + + + + + + + + +
+ `; + const changePlayerName = require(modulePath).changePlayerName; + + await changePlayerName(1, 'oldName'); + expect(swal).toBeCalledWith(expect.objectContaining({ + text: 'admin.changePlayerNameNotice', + input: 'text', + inputValue: 'oldName' + })); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/players?action=name', + dataType: 'json', + data: { pid: 1, name: 'newName' } + }); + await changePlayerName(1, 'oldName'); + expect($('tr#1 > td:nth-child(3)').text()).toBe('newName'); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + }); + + it('change owner', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn().mockReturnValue(Promise.resolve(2)); + const debounce = jest.fn(fn => fn); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + window.debounce = debounce; + + document.body.innerHTML = ` + + + + + + + + + +
1
+
+ +
+
+ `; + const changeOwner = require(modulePath).changeOwner; + + await changeOwner(1, 'oldName'); + expect(debounce).toBeCalled(); + expect(swal).toBeCalledWith({ + html: 'admin.changePlayerOwner
 ', + input: 'number', + inputValue: '1', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/players?action=owner', + dataType: 'json', + data: { pid: 1, uid: 2 } + }); + await changeOwner(1, 'oldName'); + expect($('tr#1 > td:nth-child(2)').text()).toBe((2).toString()); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + }); + + it('delete player', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn().mockReturnValue(Promise.resolve('newName')); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + + document.body.innerHTML = ` + + + + + + +
+ `; + const deletePlayer = require(modulePath).deletePlayer; + + await deletePlayer(1); + expect(swal).toBeCalledWith({ + text: 'admin.deletePlayerNotice', + type: 'warning', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/players?action=delete', + dataType: 'json', + data: { pid: 1 } + }); + await deletePlayer(1); + expect(document.getElementById('1')).toBeNull(); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + }); +}); + +describe('tests for "plugins" module', () => { + const modulePath = '../admin/plugins'; + + it('enable a plugin', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const reloadTable = jest.fn(); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + $.pluginsTable = { + ajax: { + reload: reloadTable + } + }; + + const enablePlugin = require(modulePath).enablePlugin; + + await enablePlugin('plugin'); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/plugins?action=enable&name=plugin', + dataType: 'json' + }); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + expect(reloadTable).toBeCalledWith(null, false); + + await enablePlugin('plugin'); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('disable a plugin', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const reloadTable = jest.fn(); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + $.pluginsTable = { + ajax: { + reload: reloadTable + } + }; + + const disablePlugin = require(modulePath).disablePlugin; + + await disablePlugin('plugin'); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/plugins?action=disable&name=plugin', + dataType: 'json' + }); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + expect(reloadTable).toBeCalledWith(null, false); + + await disablePlugin('plugin'); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('delete a plugin', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const reloadTable = jest.fn(); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + $.pluginsTable = { + ajax: { + reload: reloadTable + } + }; + window.swal = swal; + + const deletePlugin = require(modulePath).deletePlugin; + + await deletePlugin('plugin'); + expect(swal).toBeCalledWith({ + text: 'admin.confirmDeletion', + type: 'warning', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/plugins?action=delete&name=plugin', + dataType: 'json' + }); + await deletePlugin('plugin'); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + expect(reloadTable).toBeCalledWith(null, false); + }); +}); + +describe('tests for "update" module', () => { + it('download updates', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ + file_size: 5000 + })) + .mockReturnValueOnce(Promise.resolve()); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const modal = jest.fn(); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + $.fn.modal = modal; + + document.body.innerHTML = ` +
+ + `; + + await require('../admin/update')(); + expect(fetch).toBeCalledWith(expect.objectContaining({ + url: 'admin/update/download?action=prepare-download', + type: 'GET', + dataType: 'json', + })); + expect($('#file-size').html()).toBe('5000'); + expect(modal).toBeCalledWith({ + backdrop: 'static', + keyboard: false + }); + expect(fetch).toBeCalledWith({ + url: 'admin/update/download?action=start-download', + type: 'POST', + dataType: 'json' + }); + }); +}); + +describe('tests for "users" module', () => { + const modulePath = '../admin/users'; + + it('change user email', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn(options => { + options.inputValidator('a@b.c'); + return Promise.resolve('a@b.c'); + }); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + + document.body.innerHTML = ` + + + + + + + +
d@e.f
+ `; + const changeUserEmail = require(modulePath).changeUserEmail; + + await changeUserEmail(1); + expect(swal).toBeCalledWith(expect.objectContaining({ + text: 'admin.newUserEmail', + showCancelButton: true, + input: 'text', + inputValue: 'd@e.f' + })); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=email', + dataType: 'json', + data: { uid: 1, email: 'a@b.c' } + }); + await changeUserEmail(1); + expect($('tr > td:nth-child(2)').text()).toBe('a@b.c'); + expect(toastr.success).toBeCalledWith('success'); + }); + + it('change user nick name', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn(options => { + options.inputValidator('foo'); + return Promise.resolve('foo'); + }); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + + document.body.innerHTML = ` + + + + + + + + +
hhh
+ `; + const changeUserNickName = require(modulePath).changeUserNickName; + + await changeUserNickName(1); + expect(swal).toBeCalledWith(expect.objectContaining({ + text: 'admin.newUserNickname', + showCancelButton: true, + input: 'text', + inputValue: 'hhh' + })); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=nickname', + dataType: 'json', + data: { uid: 1, nickname: 'foo' } + }); + await changeUserNickName(1); + expect($('tr > td:nth-child(3)').text()).toBe('foo'); + expect(toastr.success).toBeCalledWith('success'); + }); + + it('change user password', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn().mockReturnValue(Promise.resolve('secret')); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + + const changeUserPwd = require(modulePath).changeUserPwd; + + await changeUserPwd(1); + expect(swal).toBeCalledWith(expect.objectContaining({ + text: 'admin.newUserPassword', + showCancelButton: true, + input: 'password', + })); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=password', + dataType: 'json', + data: { uid: 1, password: 'secret' } + }); + await changeUserPwd(1); + expect(toastr.success).toBeCalledWith('success'); + }); + + it('change user score', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + + document.body.innerHTML = ` + + + + + + + + +
+ `; + const changeUserScore = require(modulePath).changeUserScore; + + await changeUserScore('user-1', 50); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=score', + dataType: 'json', + data: { uid: '1', score: 50 } + }); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + + await changeUserScore('user-1', 50); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('change ban status', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ + errno: 0, + msg: 'success', + permission: 0 + })) + .mockReturnValueOnce(Promise.resolve({ + errno: 0, + msg: 'success', + permission: -1 + })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + + document.body.innerHTML = ` + + + + + + + +
+ `; + await require(modulePath).changeBanStatus(1); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=ban', + dataType: 'json', + data: { uid: 1 } + }); + expect($('#ban-1').attr('data')).toBe('normal'); + expect($('#ban-1').text()).toBe('admin.ban'); + expect($('.status').text()).toBe('admin.normal'); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + + document.body.innerHTML = ` + + + + + + + +
+ `; + await require(modulePath).changeBanStatus(1); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=ban', + dataType: 'json', + data: { uid: 1 } + }); + expect($('#ban-1').attr('data')).toBe('banned'); + expect($('#ban-1').text()).toBe('admin.unban'); + expect($('.status').text()).toBe('admin.banned'); + + await require(modulePath).changeBanStatus(1); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('change admin status', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ + errno: 0, + msg: 'success', + permission: 0 + })) + .mockReturnValueOnce(Promise.resolve({ + errno: 0, + msg: 'success', + permission: 1 + })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + + document.body.innerHTML = ` + + + + + + + +
+ `; + await require(modulePath).changeAdminStatus(1); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=admin', + dataType: 'json', + data: { uid: 1 } + }); + expect($('#admin-1').attr('data')).toBe('normal'); + expect($('#admin-1').text()).toBe('admin.setAdmin'); + expect($('.status').text()).toBe('admin.normal'); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + + document.body.innerHTML = ` + + + + + + + +
+ `; + await require(modulePath).changeAdminStatus(1); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=admin', + dataType: 'json', + data: { uid: 1 } + }); + expect($('#admin-1').attr('data')).toBe('admin'); + expect($('#admin-1').text()).toBe('admin.unsetAdmin'); + expect($('.status').text()).toBe('admin.admin'); + + await require(modulePath).changeAdminStatus(1); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('delete a user', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + + document.body.innerHTML = ''; + const deleteUserAccount = require(modulePath).deleteUserAccount; + + await deleteUserAccount(1); + expect(swal).toBeCalledWith({ + text: 'admin.deleteUserNotice', + type: 'warning', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'admin/users?action=delete', + dataType: 'json', + data: { uid: 1 } + }); + await deleteUserAccount(1); + expect(document.getElementById('user-1')).toBeNull(); + }); + + it('"input" element should be focused out when press enter key', () => { + document.body.innerHTML = ` +
+
+ +
+
+ `; + + require(modulePath); + + $('.score').focus(); + const event = $.Event('keypress'); + event.which = 13; + $('.score').trigger(event); + + expect($('.score').is(':focus')).toBe(false); + }); +}); diff --git a/resources/assets/src/js/__tests__/auth.test.js b/resources/assets/src/js/__tests__/auth.test.js new file mode 100644 index 00000000..cdee354d --- /dev/null +++ b/resources/assets/src/js/__tests__/auth.test.js @@ -0,0 +1,368 @@ +const $ = require('jquery'); +window.$ = window.jQuery = $; + +describe('tests for "captcha" module', () => { + it('refresh captcha', async () => { + const url = jest.fn(path => path); + window.url = url; + + document.body.innerHTML = ` + + + `; + + require('../auth/captcha')(); + + expect($('.captcha').attr('src')).toEqual(expect.stringContaining('auth/captcha?')); + expect($('#captcha').val()).toBe(''); + }); +}); + +describe('tests for "login" module', () => { + const modulePath = '../auth/login'; + + it('login', async () => { + const fetch = jest.fn() + .mockImplementationOnce(option => { + option.beforeSend(); + return Promise.resolve({ errno: 0, msg: 'success' }); + }) + .mockImplementationOnce(() => Promise.resolve( + { errno: 1, msg: 'warning1', login_fails: 1 } + )) + .mockImplementationOnce(() => Promise.resolve( + { errno: 1, msg: 'warning2', login_fails: 4 } + )); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn(); + const refreshCaptcha = jest.fn(); + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.showMsg = jest.fn(); + window.refreshCaptcha = refreshCaptcha; + + document.body.innerHTML = ` + + +
+ + + + `; + + require(modulePath); + + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyIdentification'); + expect($('#identification').is(':focus')).toBe(true); + + $('#identification').val('username'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyPassword'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('password'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyCaptcha'); + expect($('#captcha').is(':focus')).toBe(true); + + $('#captcha').val('captcha'); + await $('button').click(); + expect(fetch).toBeCalledWith(expect.objectContaining({ + type: 'POST', + url: 'auth/login', + dataType: 'json', + data: { + identification: 'username', + password: 'password', + keep: true, + captcha: 'captcha' + } + })); + expect($('button').html()).toBe( + ' auth.loggingIn' + ); + expect($('button').prop('disabled')).toBe(true); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + + $('#captcha-form').css('display', 'none'); + await $('button').click(); + expect($('#captcha-form').css('display')).toBe('none'); + expect(refreshCaptcha).toBeCalled(); + expect(showMsg).toBeCalledWith('warning1', 'warning'); + expect($('button').html()).toBe('auth.login'); + expect($('button').prop('disabled')).toBe(false); + + await $('button').click(); + expect(swal).toBeCalledWith({ type: 'error', html: 'auth.tooManyFails' }); + expect($('#captcha-form').css('display')).not.toBe('none'); + expect(showMsg).toBeCalledWith('warning2', 'warning'); + }); +}); + +describe('tests for "register" module', () => { + const modulePath = '../auth/register'; + + it('register', async () => { + const fetch = jest.fn() + .mockImplementationOnce(option => { + option.beforeSend(); + return Promise.resolve({ errno: 0, msg: 'success' }); + }) + .mockImplementationOnce(() => Promise.resolve( + { errno: 1, msg: 'warning' } + )); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn(); + const showMsg = jest.fn(); + const refreshCaptcha = jest.fn(); + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.showMsg = showMsg; + window.refreshCaptcha = refreshCaptcha; + + document.body.innerHTML = ` + + + + +
+ + + `; + + require(modulePath); + + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyEmail'); + expect($('#email').is(':focus')).toBe(true); + + $('#email').val('email'); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidEmail'); + expect(showMsg).toBeCalledWith('auth.invalidEmail', 'warning'); + expect($('#email').is(':focus')).toBe(true); + + $('#email').val('a@b.c'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyPassword'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('secret'); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidPassword'); + expect(showMsg).toBeCalledWith('auth.invalidPassword', 'warning'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('too_long_password'); + $('#password').blur(); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidPassword'); + expect(showMsg).toBeCalledWith('auth.invalidPassword', 'warning'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('password'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyConfirmPwd'); + expect($('#confirm-pwd').is(':focus')).toBe(true); + + $('#confirm-pwd').val('not_same'); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidConfirmPwd'); + expect(showMsg).toBeCalledWith('auth.invalidConfirmPwd', 'warning'); + expect($('#confirm-pwd').is(':focus')).toBe(true); + + $('#confirm-pwd').val('password'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyNickname'); + expect($('#nickname').is(':focus')).toBe(true); + + $('#nickname').val('nickname'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyCaptcha'); + expect($('#captcha').is(':focus')).toBe(true); + + $('#captcha').val('captcha'); + await $('button').click(); + expect(fetch).toBeCalledWith(expect.objectContaining({ + type: 'POST', + url: 'auth/register', + dataType: 'json', + data: { + email: 'a@b.c', + nickname: 'nickname', + password: 'password', + captcha: 'captcha' + } + })); + expect($('button').html()).toBe( + ' auth.registering' + ); + expect($('button').prop('disabled')).toBe(true); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + + await $('button').click(); + expect(refreshCaptcha).toBeCalled(); + expect(showMsg).toBeCalledWith('warning', 'warning'); + expect($('button').html()).toBe('auth.register'); + }); +}); + +describe('tests for "forgot" module', () => { + const modulePath = '../auth/forgot'; + + it('forgot password', async () => { + const fetch = jest.fn() + .mockImplementationOnce(option => { + option.beforeSend(); + return Promise.resolve({ errno: 0, msg: 'success' }); + }) + .mockImplementationOnce(() => Promise.resolve( + { errno: 1, msg: 'warning' } + )); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn(); + const showMsg = jest.fn(); + const refreshCaptcha = jest.fn(); + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.showMsg = showMsg; + window.refreshCaptcha = refreshCaptcha; + + document.body.innerHTML = ` + +
+ + + `; + + require(modulePath); + + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyEmail'); + expect($('#email').is(':focus')).toBe(true); + + $('#email').val('email'); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidEmail'); + expect(showMsg).toBeCalledWith('auth.invalidEmail', 'warning'); + expect($('#email').is(':focus')).toBe(true); + + $('#email').val('a@b.c'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyCaptcha'); + expect($('#captcha').is(':focus')).toBe(true); + + $('#captcha').val('captcha'); + await $('button').click(); + expect(fetch).toBeCalledWith(expect.objectContaining({ + type: 'POST', + url: 'auth/forgot', + dataType: 'json', + data: { + email: 'a@b.c', + captcha: 'captcha' + } + })); + expect($('button').html()).toBe('auth.send'); + expect($('button').prop('disabled')).toBe(true); + expect(showMsg).toBeCalledWith('success', 'success'); + + await $('button').click(); + expect(refreshCaptcha).toBeCalled(); + expect(showMsg).toBeCalledWith('warning', 'warning'); + expect($('button').html()).toBe('auth.send'); + }); +}); + +describe('tests for "reset" module', () => { + const modulePath = '../auth/reset'; + + it('reset password', async () => { + const fetch = jest.fn() + .mockImplementationOnce(option => { + option.beforeSend(); + return Promise.resolve({ errno: 0, msg: 'success' }); + }) + .mockImplementationOnce(() => Promise.resolve( + { errno: 1, msg: 'warning' } + )); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const showMsg = jest.fn(); + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.showMsg = showMsg; + window.refreshCaptcha = jest.fn(); + + document.body.innerHTML = ` + + + + + `; + + require(modulePath); + + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyPassword'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('secret'); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidPassword'); + expect(showMsg).toBeCalledWith('auth.invalidPassword', 'warning'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('too_long_password'); + $('#password').blur(); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidPassword'); + expect(showMsg).toBeCalledWith('auth.invalidPassword', 'warning'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('password'); + $('button').click(); + expect(trans).toBeCalledWith('auth.emptyConfirmPwd'); + expect($('#confirm-pwd').is(':focus')).toBe(true); + + $('#confirm-pwd').val('not_same'); + $('button').click(); + expect(trans).toBeCalledWith('auth.invalidConfirmPwd'); + expect(showMsg).toBeCalledWith('auth.invalidConfirmPwd', 'warning'); + expect($('#confirm-pwd').is(':focus')).toBe(true); + + $('#confirm-pwd').val('password'); + await $('button').click(); + expect(fetch).toBeCalledWith(expect.objectContaining({ + type: 'POST', + url: 'auth/reset', + dataType: 'json', + data: { + uid: '1', + password: 'password' + } + })); + expect($('button').html()).toBe( + ' auth.resetting' + ); + expect($('button').prop('disabled')).toBe(true); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + + await $('button').click(); + expect(showMsg).toBeCalledWith('warning', 'warning'); + expect($('button').html()).toBe('auth.reset'); + }); +}); diff --git a/resources/assets/src/js/__tests__/common.test.js b/resources/assets/src/js/__tests__/common.test.js new file mode 100644 index 00000000..5e729fa1 --- /dev/null +++ b/resources/assets/src/js/__tests__/common.test.js @@ -0,0 +1,179 @@ +const $ = require('jquery'); +window.jQuery = window.$ = $; + +describe('tests for "i18n" module', () => { + const modulePath = '../common/i18n'; + + it('load locales', () => { + window.isEmpty = obj => !obj; // Just for test + const loadLocales = require(modulePath).loadLocales; + + $.locales = { + en: { text: 'text', nested: { sth: ':sth here!' } } + }; + + loadLocales(); + expect($.currentLocale).toEqual({ text: 'text', nested: { sth: ':sth here!' } }); + }); + + it('get translated text', () => { + const trans = require(modulePath).trans; + + expect(trans('text')).toBe('text'); + expect(trans('text.nothing')).toBe('text.nothing'); + expect(trans('nested.sth')).toBe(':sth here!'); + expect(trans('nested.sth', { sth: 'abc' })).toBe('abc here!'); + }); +}); + +describe('tests for "logout" module', () => { + const modulePath = '../common/logout'; + + it('logout', async () => { + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const trans = jest.fn(key => key); + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ msg: 'success' })); + window.swal = swal; + window.trans = trans; + window.fetch = fetch; + window.url = jest.fn(path => path); + + document.body.innerHTML = ''; + require(modulePath); + + await $('button').click(); + expect(swal).toBeCalledWith({ + text: 'general.confirmLogout', + type: 'warning', + showCancelButton: true, + confirmButtonText: 'general.confirm', + cancelButtonText: 'general.cancel' + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'auth/logout', + dataType: 'json' + }); + await $('button').click(); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + }); +}); + +describe('tests for "notify" module', () => { + const modulePath = '../common/notify'; + + it('show message', () => { + document.body.innerHTML = '
'; + + const showMsg = require(modulePath).showMsg; + + showMsg('msg1'); + expect($('div').hasClass('a-class')).toBe(false); + expect($('div').hasClass('callout')).toBe(true); + expect($('div').hasClass('callout-info')).toBe(true); + expect($('div').html()).toBe('msg1'); + + showMsg('msg2', 'warning'); + expect($('div').hasClass('callout-info')).toBe(false); + expect($('div').hasClass('callout')).toBe(true); + expect($('div').hasClass('callout-warning')).toBe(true); + expect($('div').html()).toBe('msg2'); + }); + + it('show ajax error', () => { + const warn = jest.fn(); + window.console.warn = warn; + window.trans = jest.fn(key => key); + + const showAjaxError = require(modulePath).showAjaxError; + + showAjaxError('error'); + expect(warn).toBeCalledWith('error'); + + showAjaxError({}); + expect(warn).toBeCalledWith('Empty Ajax response body.'); + }); + + it('show modal dialog', () => { + const modal = jest.fn(); + $.fn.modal = modal; + + const showModal = require(modulePath).showModal; + showModal(''); + expect(modal).toBeCalled(); + }); +}); + +describe('tests for "polyfill" module', () => { + const modulePath = '../common/polyfill'; + + String.prototype.includes = undefined; + String.prototype.endsWith = undefined; + require(modulePath); + + it('String#includes', () => { + expect('blessing-skin'.includes('skin')).toBe(true); + expect('blessing-skin'.includes('server')).toBe(false); + expect('blessing-skin'.includes('skin', 9)).toBe(true); + expect('blessing-skin'.includes('blessing', 9)).toBe(false); + }); + + it('String#endsWith', () => { + expect('blessing-skin'.endsWith('skin')).toBe(true); + expect('blessing-skin'.endsWith('server')).toBe(false); + expect('blessing-skin'.endsWith('blessing', 8)).toBe(true); + }); +}); + +describe('tests for "utils" module', () => { + const modulePath = '../common/utils'; + + window.blessing = { + version: '' + }; + + it('check a variable if it is empty', () => { + const isEmpty = require(modulePath).isEmpty; + + expect(isEmpty()).toBe(true); + expect(isEmpty(null)).toBe(true); + expect(isEmpty(undefined)).toBe(true); + expect(isEmpty(0)).toBe(false); + expect(isEmpty(false)).toBe(false); + expect(isEmpty('')).toBe(true); + expect(isEmpty({})).toBe(true); + expect(isEmpty({ sth: '' })).toBe(false); + }); + + it('fake fetch', () => { + $.ajax = jest.fn(); + const fetch = require(modulePath).fetch; + const xhr = fetch({ type: 'GET' }); + + expect($.ajax).toBeCalledWith({ type: 'GET' }); + expect(Object.getPrototypeOf(xhr)).toBe(Promise.prototype); + }); + + it('make a debounced function', done => { + const func = jest.fn(); + const debounced = require(modulePath).debounce(func, 100); + + debounced(); + debounced(); + + setTimeout(() => { + expect(func.mock.calls.length).toBe(1); + done(); + }, 100); + }); + + it('get a absolute url', () => { + window.blessing = { base_url: 'http://localhost' }; + const url = require(modulePath).url; + + expect(url()).toBe('http://localhost/'); + expect(url('test')).toBe('http://localhost/test'); + expect(url('/test')).toBe('http://localhost/test'); + }); +}); diff --git a/resources/assets/src/js/__tests__/skinlib.test.js b/resources/assets/src/js/__tests__/skinlib.test.js new file mode 100644 index 00000000..c1bcf701 --- /dev/null +++ b/resources/assets/src/js/__tests__/skinlib.test.js @@ -0,0 +1,490 @@ +const $ = require('jquery'); +window.$ = window.jQuery = $; + +window.getQueryString = jest.fn((key, defaultValue) => defaultValue); + +describe('tests for "index" module', () => { + const modulePath = '../skinlib/index'; + + it('render skin library', () => { + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const jqPaginator = jest.fn(); + window.trans = trans; + window.url = url; + $.fn.jqPaginator = jqPaginator; + + document.body.innerHTML = ` +
+
+
+ `; + const renderSkinlib = require(modulePath).renderSkinlib; + + renderSkinlib([]); + expect($('#skinlib-container').html()).toBe( + '

general.noResult

' + ); + expect($('#skinlib-paginator').css('display')).toBe('none'); + expect($('.overlay').css('display')).toBe('none'); + + renderSkinlib([{ + tid: 1, + name: 'name', + type: 'steve', + public: 0 + }]); + expect($('#skinlib-paginator').css('display')).not.toBe('none'); + expect($('.item').attr('tid')).toBe('1'); + expect($('.item-body > img').attr('src')).toBe('preview/1.png'); + expect($('.texture-name > span').attr('title')).toBe('name'); + expect($('.texture-name > span > small').text()).toBe('skinlib.filter.steve'); + expect($('a.more.like').attr('title')).toBe('skinlib.anonymous'); + expect($('a.more.like').hasClass('liked')).toBe(false); + expect($('a.more.like').hasClass('anonymous')).toBe(true); + expect($('small.more').hasClass('hide')).toBe(false); + expect($('small.private-label').text().trim()).toBe('skinlib.private'); + + renderSkinlib([{ + tid: 1, + name: 'name', + type: 'steve', + public: 0, + liked: true + }]); + expect($('a.more.like').attr('title')).toBe('skinlib.removeFromCloset'); + expect($('a.more.like').hasClass('liked')).toBe(true); + expect($('a.more.like').hasClass('anonymous')).toBe(false); + expect($('small.more').hasClass('hide')).toBe(false); + expect($('small.private-label').text().trim()).toBe('skinlib.private'); + + renderSkinlib([{ + tid: 1, + name: 'name', + type: 'steve', + public: 0, + liked: false + }]); + expect($('a.more.like').attr('title')).toBe('skinlib.addToCloset'); + expect($('a.more.like').hasClass('liked')).toBe(false); + expect($('a.more.like').hasClass('anonymous')).toBe(false); + expect($('small.more').hasClass('hide')).toBe(false); + expect($('small.private-label').text().trim()).toBe('skinlib.private'); + + renderSkinlib([{ + tid: 1, + name: 'name', + type: 'steve', + public: 1, + liked: false + }]); + expect($('small.more').hasClass('hide')).toBe(true); + }); + + it('update paginator', () => { + const trans = jest.fn(key => key); + const jqPaginator = jest.fn(); + window.trans = trans; + $.fn.jqPaginator = jqPaginator; + + document.body.innerHTML = ` +

+
+ + `; + const updatePaginator = require(modulePath).updatePaginator; + + updatePaginator(2, 2); + expect(trans).toBeCalledWith('general.pagination', { page: 2, total: 2 }); + expect(jqPaginator).toBeCalledWith(expect.objectContaining({ + currentPage: 2, + totalPages: 2 + })); + expect($('option').length).toBe(2); + expect($('option[value=1]').prop('selected')).toBe(false); + expect($('option[value=2]').prop('selected')).toBe(true); + + $('#skinlib-paginator').html('something'); + updatePaginator(2, 2); + expect(jqPaginator).toBeCalledWith('option', { + currentPage: 2, + totalPages: 2 + }); + }); + + it('update breadcrumb', () => { + const trans = jest.fn(key => key); + const jqPaginator = jest.fn(); + window.trans = trans; + $.fn.jqPaginator = jqPaginator; + + document.body.innerHTML = ` +
+
+
+
+ + `; + const updateBreadCrumb = require(modulePath).updateBreadCrumb; + + updateBreadCrumb(); + expect($('#filter-indicator').html().replace(/\s/g, '')).toBe( + 'general.skinskinlib.filter.skin' + ); + + $.skinlib.filter = 'cape'; + updateBreadCrumb(); + expect($('#filter-indicator').html()).toBe('general.cape'); + + expect($('#uploader-indicator').html()).toBe('skinlib.filter.allUsers'); + $.skinlib.uploader = 1; + updateBreadCrumb(); + expect(trans).toBeCalledWith('skinlib.filter.uploader', { uid: 1 }); + expect($('#uploader-indicator').html()).toBe('skinlib.filter.uploader'); + + expect($('#sort-indicator').html()).toBe('skinlib.sort.time'); + + $.skinlib.keyword = '%20q'; + updateBreadCrumb(); + expect(trans).lastCalledWith( + 'general.searchResult', + { keyword: ' q' } + ); + expect($('#search-indicator').html()).toBe('general.searchResult'); + expect($('#navbar-search-input').val()).toBe(' q'); + }); + + it('reload skin library', async () => { + const fetch = jest.fn().mockReturnValue(Promise.resolve({ + items: [] + })); + const url = jest.fn(path => path); + window.fetch = fetch; + window.url = url; + window.showAjaxError = jest.fn(); + document.body.innerHTML = ` +
+ `; + const reloadSkinlib = require(modulePath).reloadSkinlib; + window.history.pushState = jest.fn(); + + await reloadSkinlib(); + expect(fetch).toBeCalledWith(expect.objectContaining({ + type: 'GET', + url: 'skinlib/data', + dataType: 'json', + data: { + page: 2, + filter: 'cape', + sort: 'time', + uploader: 1, + keyword: '%20q' + } + })); + }); + + it('update query string', () => { + const url = jest.fn(path => path); + window.url = url; + document.body.innerHTML = ` + + `; + const updateUrlQueryString = require(modulePath).updateUrlQueryString; + window.history.pushState = jest.fn(); + + const query = 'page=2&filter=cape&sort=time&uploader=1&keyword=%2520q'; + + updateUrlQueryString(); + expect(window.history.pushState).toBeCalledWith( + null, + null, + 'skinlib?' + query); + expect($('li[data-code=zh_CN] > a').prop('href')).toBe(`?lang=zh_CN&${query}`); + expect($('li[data-code=en] > a').prop('href')).toBe(`?lang=en&${query}`); + }); +}); + +describe('tests for "operations" module', () => { + const modulePath = '../skinlib/operations'; + + it('add to closet', async () => { + const url = jest.fn(path => path); + window.url = url; + const trans = jest.fn(key => key); + window.trans = trans; + const swal = jest.fn(option => { + option.inputValidator('custom'); + return Promise.resolve('custom'); + }); + window.swal = swal; + $.getJSON = jest.fn((option, cb) => { + cb({ name: 'name' }); + }); + + const addToCloset = require(modulePath).addToCloset; + + await addToCloset(1); + expect($.getJSON.mock.calls[0][0]).toBe('skinlib/info/1'); + expect(swal).toBeCalledWith(expect.objectContaining({ + title: 'skinlib.setItemName', + inputValue: 'name', + input: 'text', + showCancelButton: true, + })); + }); + + it('add to closet (by ajax)', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + window.fetch = fetch; + const url = jest.fn(path => path); + window.url = url; + const trans = jest.fn(key => key); + window.trans = trans; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.swal = swal; + const modal = jest.fn(); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.toastr = toastr; + $.fn.modal = modal; + + document.body.innerHTML = ` + + + `; + const ajaxAddToCloset = require(modulePath).ajaxAddToCloset; + + await ajaxAddToCloset(1, 'name'); + expect(document.getElementById('shouldBeRemoved')).toBeNull(); + expect(document.getElementById('shouldNotBeRemoved')).not.toBeNull(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/closet/add', + dataType: 'json', + data: { tid: 1, name: 'name' } + }); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + expect(modal).toBeCalledWith('hide'); + + await ajaxAddToCloset(1, 'name'); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('remove from closet', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + window.fetch = fetch; + const url = jest.fn(path => path); + window.url = url; + const trans = jest.fn(key => key); + window.trans = trans; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.swal = swal; + const modal = jest.fn(); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.toastr = toastr; + + const removeFromCloset = require(modulePath).removeFromCloset; + + await removeFromCloset(1); + expect(swal).toBeCalledWith({ + text: 'user.removeFromClosetNotice', + type: 'warning', + showCancelButton: true, + cancelButtonColor: '#3085d6', + confirmButtonColor: '#d33' + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: '/user/closet/remove', + dataType: 'json', + data: { tid: 1 } + }); + await removeFromCloset(1); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + }); + + it('change texture name', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + window.fetch = fetch; + const url = jest.fn(path => path); + window.url = url; + const trans = jest.fn(key => key); + window.trans = trans; + const swal = jest.fn(option => { + option.inputValidator('new-name'); + return Promise.resolve('new-name'); + }); + window.swal = swal; + const modal = jest.fn(); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.toastr = toastr; + + document.body.innerHTML = '
'; + const changeTextureName = require(modulePath).changeTextureName; + + await changeTextureName(1, 'oldName'); + expect(swal).toBeCalledWith(expect.objectContaining({ + text: 'skinlib.setNewTextureName', + input: 'text', + inputValue: 'oldName', + showCancelButton: true, + })); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'skinlib/rename', + dataType: 'json', + data: { tid: 1, new_name: 'new-name' } + }); + await changeTextureName(1, 'oldName'); + expect($('div').text()).toBe('new-name'); + expect(toastr.success).toBeCalledWith('success'); + }); + + it('update texture status', () => { + window.trans = jest.fn(key => key); + document.body.innerHTML = ` +
5
+ + + `; + const updateTextureStatus = require(modulePath).updateTextureStatus; + + updateTextureStatus(1, 'add'); + expect($('a[tid=1]').attr('href')).toBe('javascript:removeFromCloset(1);'); + expect($('a[tid=1]').attr('title')).toBe('skinlib.removeFromCloset'); + expect($('a[tid=1]').hasClass('liked')).toBe(true); + expect($('#1').attr('href')).toBe('javascript:removeFromCloset(1);'); + expect($('#1').html()).toBe('skinlib.removeFromCloset'); + expect($('div').html()).toBe('6'); + + updateTextureStatus(1, 'remove'); + expect($('a[tid=1]').attr('href')).toBe('javascript:addToCloset(1);'); + expect($('a[tid=1]').attr('title')).toBe('skinlib.addToCloset'); + expect($('a[tid=1]').hasClass('liked')).toBe(false); + expect($('#1').attr('href')).toBe('javascript:addToCloset(1);'); + expect($('#1').html()).toBe('skinlib.addToCloset'); + expect($('div').html()).toBe('5'); + }); + + it('click changing privacy button', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + window.fetch = fetch; + const url = jest.fn(path => path); + window.url = url; + const trans = jest.fn(key => key); + window.trans = trans; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.swal = swal; + const modal = jest.fn(); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.toastr = toastr; + + document.body.innerHTML = ''; + require(modulePath); + + await $('a').click(); + expect(swal).toBeCalledWith({ + text: 'skinlib.setPublicNotice', + type: 'warning', + showCancelButton: true + }); + expect(document.getElementsByTagName('a').length).toBe(0); + }); + + it('change privacy', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success', public: '0' })) + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + window.fetch = fetch; + const url = jest.fn(path => path); + window.url = url; + const trans = jest.fn(key => key); + window.trans = trans; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.swal = swal; + const modal = jest.fn(); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.toastr = toastr; + + document.body.innerHTML = ` + skinlib.setAsPrivate + skinlib.setAsPublic + `; + const changePrivacy = require(modulePath).changePrivacy; + + await changePrivacy(1); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'skinlib/privacy', + dataType: 'json', + data: { tid: 1 } + }); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + expect($('#1').html()).toBe('skinlib.setAsPublic'); + + await changePrivacy(1); + expect($('#2').html()).toBe('skinlib.setAsPrivate'); + + await changePrivacy(1); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('delete texture', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + window.fetch = fetch; + const url = jest.fn(path => path); + window.url = url; + const trans = jest.fn(key => key); + window.trans = trans; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.swal = swal; + const modal = jest.fn(); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.toastr = toastr; + + const deleteTexture = require(modulePath).deleteTexture; + + await deleteTexture(1); + expect(swal).toBeCalledWith({ + text: 'skinlib.deleteNotice', + type: 'warning', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'skinlib/delete', + dataType: 'json', + data: { tid: 1 } + }); + await deleteTexture(1); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + }); +}); diff --git a/resources/assets/src/js/__tests__/user.test.js b/resources/assets/src/js/__tests__/user.test.js new file mode 100644 index 00000000..141f80e5 --- /dev/null +++ b/resources/assets/src/js/__tests__/user.test.js @@ -0,0 +1,868 @@ +const $ = require('jquery'); +window.$ = window.jQuery = $; + +describe('tests for "closet" module', () => { + const modulePath = '../user/closet'; + + $.fn.jqPaginator = jest.fn(); + + it('preview textures', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ type: 'skin', hash: 1 })) + .mockReturnValueOnce(Promise.resolve({ type: 'cape', hash: 2 })); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const MSP = { + changeSkin: jest.fn(), + changeCape: jest.fn() + }; + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.MSP = MSP; + + document.body.innerHTML = ` +
+ + + `; + require(modulePath); + + await $('#next > .item-body').click(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'skinlib/info/1', + dataType: 'json' + }); + expect($('#next').hasClass('item-selected')).toBe(true); + expect(MSP.changeSkin).toBeCalledWith('textures/1'); + expect($('#textures-indicator').text()).toBe('general.skin'); + + document.body.innerHTML = ` +
+ + + `; + window.selectedTextures = []; + + await $('#next > .item-body').click(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'skinlib/info/2', + dataType: 'json' + }); + expect($('#next').hasClass('item-selected')).toBe(true); + expect(MSP.changeCape).toBeCalledWith('textures/2'); + expect($('#textures-indicator').text()).toBe('general.skin & general.cape'); + }); + + it('render closet', () => { + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + window.trans = trans; + window.url = url; + + document.body.innerHTML = ` + +
+
+ `; + const renderCloset = require(modulePath).renderCloset; + + renderCloset([], 'skin'); + expect($('#closet-paginator').css('display')).toBe('none'); + expect(trans).toBeCalledWith('user.emptyClosetMsg', { url: 'skinlib?filter=skin' }); + expect($('#skin-category').html()).toBe( + '
user.emptyClosetMsg
' + ); + + $('input').val('q'); + renderCloset([], 'skin'); + expect($('#skin-category').html()).toBe( + '
general.noResult
' + ); + + renderCloset([{ tid: 1, name: 'name', type: 'steve' }], 'skin'); + expect($('#closet-paginator').css('display')).not.toBe('none'); + expect($('.item').attr('tid')).toBe('1'); + expect($('img').attr('src')).toBe('/preview/1.png'); + expect($('.texture-name').html().trim()).toBe( + 'name (steve)' + ); + expect($('a.more').attr('href')).toBe('/skinlib/show/1'); + expect($('a.more').attr('title')).toBe('user.viewInSkinlib'); + }); + + it('reload closet', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ + items: [], + category: 'skin', + total_pages: 1 + })); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn().mockReturnValue(Promise.resolve('name')); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const MSP = { + changeSkin: jest.fn(), + changeCape: jest.fn() + }; + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.toastr = toastr; + + document.body.innerHTML = ` +
+
+ +
+
+
+ `; + const reloadCloset = require(modulePath).reloadCloset; + + await reloadCloset('skin', 1, 'q'); + expect(fetch).toBeCalledWith({ + type: 'GET', + url: url('user/closet-data'), + dataType: 'json', + data: { + category: 'skin', + page: 1, + q: 'q' + } + }); + expect($('#closet-paginator').attr('last-skin-page')).toBe('1'); + }); + + it('rename item', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn().mockReturnValue(Promise.resolve('name')); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const MSP = { + changeSkin: jest.fn(), + changeCape: jest.fn() + }; + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.toastr = toastr; + + document.body.innerHTML = ` +
+
+ +
+
+ `; + const renameClosetItem = require(modulePath).renameClosetItem; + + await renameClosetItem(1, 'oldName'); + expect(swal).toBeCalledWith(expect.objectContaining({ + title: trans('user.renameClosetItem'), + input: 'text', + inputValue: 'oldName', + showCancelButton: true, + })); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'closet/rename', + dataType: 'json', + data: { tid: 1, new_name: 'name' } + }); + + await renameClosetItem(1, 'oldName'); + expect(toastr.success).toBeCalledWith('success'); + expect($('span').html('name')); + }); + + it('remove item from closet', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const MSP = { + changeSkin: jest.fn(), + changeCape: jest.fn() + }; + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.toastr = toastr; + + document.body.innerHTML = ` +
+
+
+ `; + const removeFromCloset = require(modulePath).removeFromCloset; + + await removeFromCloset(1); + expect(swal).toBeCalledWith({ + text: 'user.removeFromClosetNotice', + type: 'warning', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'closet/remove', + dataType: 'json', + data: { tid: 1 } + }); + + await removeFromCloset(1); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + expect(document.getElementById('shouldBeRemoved')).toBeNull(); + expect(trans).toBeCalledWith('user.emptyClosetMsg', { url: url('skinlib?filter=skin') }); + expect($('#skin-category').html()).toBe( + '
user.emptyClosetMsg
' + ); + }); + + it('set avatar', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const MSP = { + changeSkin: jest.fn(), + changeCape: jest.fn() + }; + window.fetch = fetch; + window.trans = trans; + window.url = url; + window.swal = swal; + window.toastr = toastr; + + document.body.innerHTML = ` + User Image + `; + const setAsAvatar = require(modulePath).setAsAvatar; + + await setAsAvatar(1); + expect(swal).toBeCalledWith({ + title: 'user.setAvatar', + text: 'user.setAvatarNotice', + type: 'question', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/profile/avatar', + dataType: 'json', + data: { tid: 1 } + }); + + await setAsAvatar(1); + expect(toastr.success).toBeCalledWith('success'); + expect($('img').attr('src').endsWith('src')).toBe(false); + }); +}); + +describe('tests for "player" module', () => { + const modulePath = '../user/player'; + + it('show player texture preview', async () => { + const url = jest.fn(path => path); + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ + tid_steve: 1, + tid_alex: 2, + tid_cape: 3, + preference: 'steve', + player_name: 'name' + })); + window.url = url; + window.fetch = fetch; + window.TexturePreview = require('../common/texture-preview'); + window.MSP = { + changeSkin: jest.fn(), + changeCape: jest.fn(), + setStatus: jest.fn(), + getStatus: jest.fn() + }; + + document.body.innerHTML = ` +
+
+ `; + require(modulePath); + + await $('#2').click(); + expect($('#1').hasClass('player-selected')).toBe(false); + expect($('#2').hasClass('player-selected')).toBe(true); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/player/show', + dataType: 'json', + data: { pid: '2' } + }); + }); + + it('change player reference', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + + document.body.innerHTML = ` + + `; + $('select').on('change', require(modulePath).changePreference); + + await $('select').val('slim').trigger('change'); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/player/preference', + dataType: 'json', + data: { + pid: '1', + preference: 'slim' + } + }); + expect(toastr.warning).not.toBeCalled(); + expect(toastr.success).toBeCalledWith('success'); + + await $('select').trigger('change'); + expect(toastr.warning).toBeCalledWith('warning'); + }); + + it('change player name', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const trans = jest.fn(key => key); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const swal = jest.fn() + .mockImplementationOnce(options => { + options.inputValidator('name'); + return Promise.resolve('name'); + }) + .mockImplementation(() => Promise.resolve()); + window.fetch = fetch; + window.url = url; + window.trans = trans; + window.toastr = toastr; + window.swal = swal; + + document.body.innerHTML = ` + + + + + + +
1old
+ `; + const changePlayerName = require(modulePath).changePlayerName; + + await changePlayerName(1); + expect(swal).toBeCalledWith(expect.objectContaining({ + title: 'user.changePlayerName', + text: 'placeholder', + inputValue: 'old', + input: 'text', + showCancelButton: true + })); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/player/rename', + dataType: 'json', + data: { pid: 1, new_player_name: 'name' } + }); + await changePlayerName(1); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + expect($('#player-name').html()).toBe('name'); + }); + + it('submit clearing texture request', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const trans = jest.fn(key => key); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const modal = jest.fn(); + window.fetch = fetch; + window.url = url; + window.trans = trans; + window.toastr = toastr; + $.fn.modal = modal; + + document.body.innerHTML = ` + + + + + + `; + const ajaxClearTexture = require(modulePath).ajaxClearTexture; + + ajaxClearTexture(1); + expect(document.getElementById('shouldBeRemoved')).toBeNull(); + expect(document.getElementById('shouldNotBeRemoved')).not.toBeNull(); + expect(toastr.warning).toBeCalledWith('user.noClearChoice'); + + $('#clear-steve').prop('checked', true); + await ajaxClearTexture(1); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/player/texture/clear', + dataType: 'json', + data: { pid: 1, steve: 1, alex: 0, cape: 0 } + }); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + expect(modal).toBeCalledWith('hide'); + + await ajaxClearTexture(1); + expect(swal).lastCalledWith({ type: 'error', html: 'warning' }); + }); + + it('delete player', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.swal = swal; + + document.body.innerHTML = ` + + `; + const deletePlayer = require(modulePath).deletePlayer; + + await deletePlayer(1); + expect(swal).toBeCalledWith({ + title: 'user.deletePlayer', + text: 'user.deletePlayerNotice', + type: 'warning', + showCancelButton: true, + cancelButtonColor: '#3085d6', + confirmButtonColor: '#d33' + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/player/delete', + dataType: 'json', + data: { pid: 1 } + }); + + await deletePlayer(1); + expect(swal).lastCalledWith({ type: 'success', html: 'success' }); + expect(document.getElementById('1')).toBeNull(); + }); + + it('add a new player', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const modal = jest.fn(); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.swal = swal; + $.fn.modal = modal; + + document.body.innerHTML = ` + + `; + const addNewPlayer = require(modulePath).addNewPlayer; + + await addNewPlayer(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/player/add', + dataType: 'json', + data: { player_name: 'name' } + }); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + + await addNewPlayer(); + expect(toastr.warning).toBeCalledWith('warning'); + expect(modal.mock.calls.length).toBe(1); + }); + + it('set texture', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn(), + info: jest.fn() + }; + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const modal = jest.fn(); + window.fetch = fetch; + window.url = url; + window.toastr = toastr; + window.swal = swal; + $.fn.modal = modal; + window.selectedTextures = {}; + + document.body.innerHTML = ` + + `; + const setTexture = require(modulePath).setTexture; + + setTexture(); + expect(toastr.info).toBeCalledWith('user.emptySelectedPlayer'); + + $('input').prop('checked', true); + setTexture(); + expect(toastr.info).toBeCalledWith('user.emptySelectedTexture'); + + window.selectedTextures = { skin: 1, cape: 2 }; + await setTexture(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/player/set', + dataType: 'json', + data: { 'pid': '1', 'tid[skin]': 1, 'tid[cape]': 2 } + }); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + expect(modal).toBeCalledWith('hide'); + + await setTexture(); + expect(toastr.warning).toBeCalledWith('warning'); + expect(modal.mock.calls.length).toBe(1); + }); +}); + +describe('tests for "profile" module', () => { + const modulePath = '../user/profile'; + + it('change nickname', async () => { + const fetch = jest.fn() + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + window.fetch = fetch; + window.swal = swal; + window.trans = trans; + window.url = url; + window.debounce = jest.fn(fn => fn); + + document.body.innerHTML = ` +
+ + `; + const changeNickName = require(modulePath).changeNickName; + + await changeNickName(); + expect(swal).toBeCalledWith({ type: 'error', html: 'user.emptyNewNickName' }); + + $('input').val('name'); + await changeNickName(); + expect(trans).toBeCalledWith('user.changeNickName', { new_nickname: 'name' }); + expect(swal).toBeCalledWith({ + text: 'user.changeNickName', + type: 'question', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/profile?action=nickname', + dataType: 'json', + data: { new_nickname: 'name' } + }); + + await changeNickName(); + expect($('.nickname').text()).toBe('name'); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + }); + + it('change password', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })) + .mockReturnValue(Promise.resolve({ errno: 0, msg: 'success' })); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const toastr = { + info: jest.fn(), + warning: jest.fn() + }; + window.fetch = fetch; + window.swal = swal; + window.trans = trans; + window.url = url; + window.toastr = toastr; + window.logout = jest.fn().mockReturnValue(Promise.resolve({ errno: 0 })); + + document.body.innerHTML = ` + + + + `; + const changePassword = require(modulePath).changePassword; + + changePassword(); + expect(toastr.info).toBeCalledWith('user.emptyPassword'); + expect($('#password').is(':focus')).toBe(true); + + $('#password').val('password'); + changePassword(); + expect(toastr.info).toBeCalledWith('user.emptyNewPassword'); + expect($('#new-passwd').is(':focus')).toBe(true); + + $('#new-passwd').val('new-password'); + changePassword(); + expect(toastr.info).toBeCalledWith('auth.emptyConfirmPwd'); + expect($('#confirm-pwd').is(':focus')).toBe(true); + + $('#confirm-pwd').val('not-same').blur(); + changePassword(); + expect(toastr.warning).toBeCalledWith('auth.invalidConfirmPwd'); + expect($('#confirm-pwd').is(':focus')).toBe(true); + + $('#confirm-pwd').val('new-password'); + await changePassword(); + expect(swal).toBeCalledWith({ text: 'success', type: 'success' }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/profile?action=password', + dataType: 'json', + data: { current_password: 'password', new_password: 'new-password' } + }); + + await changePassword(); + expect(swal).toBeCalledWith({ type: 'warning', text: 'warning' }); + + await changePassword(); + expect(logout).toBeCalled(); + }); + + it('change email', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const toastr = { + info: jest.fn(), + warning: jest.fn() + }; + window.fetch = fetch; + window.swal = swal; + window.trans = trans; + window.url = url; + window.toastr = toastr; + window.logout = jest.fn().mockReturnValue(Promise.resolve({ errno: 0 })); + + document.body.innerHTML = ` + + + `; + const changeEmail = require(modulePath).changeEmail; + + changeEmail(); + expect(swal).toBeCalledWith({ type: 'error', html: 'user.emptyNewEmail' }); + + $('#new-email').val('email'); + changeEmail(); + expect(swal).toBeCalledWith({ type: 'warning', html: 'auth.invalidEmail' }); + + $('#new-email').val('a@b.c'); + await changeEmail(); + expect(trans).toBeCalledWith('user.changeEmail', { new_email: 'a@b.c' }); + expect(swal).toBeCalledWith({ + text: 'user.changeEmail', + type: 'question', + showCancelButton: true + }); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/profile?action=email', + dataType: 'json', + data: { new_email: 'a@b.c', password: 'pwd' } + }); + }); + + it('delete account', async () => { + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ errno: 0, msg: 'success' })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + const trans = jest.fn(key => key); + const url = jest.fn(path => path); + const toastr = { + info: jest.fn(), + warning: jest.fn() + }; + window.fetch = fetch; + window.swal = swal; + window.trans = trans; + window.url = url; + window.toastr = toastr; + + document.body.innerHTML = ` + + `; + const deleteAccount = require(modulePath).deleteAccount; + + deleteAccount(); + expect(swal).toBeCalledWith({ type: 'warning', html: 'user.emptyDeletePassword' }); + + $('#password').val('password'); + await deleteAccount(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/profile?action=delete', + dataType: 'json', + data: { password: 'password' } + }); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + + await deleteAccount(); + expect(swal).toBeCalledWith({ type: 'warning', html: 'warning' }); + }); +}); + +describe('tests for "sign" module', () => { + const modulePath = '../user/sign'; + + it('sign', async () => { + const url = jest.fn(path => path); + const toastr = { + success: jest.fn(), + warning: jest.fn() + }; + const trans = jest.fn(key => key); + const swal = jest.fn().mockReturnValue(Promise.resolve()); + window.url = url; + window.toastr = toastr; + window.trans = trans; + window.swal = swal; + window.debounce = fn => fn; + const fetch = jest.fn() + .mockReturnValueOnce(Promise.resolve({ + errno: 0, + msg: 'success', + score: 100, + remaining_time: 24, + storage: { + used: 50, + total: 100, + percentage: 50 + } + })) + .mockReturnValueOnce(Promise.resolve({ + errno: 0, + msg: 'success', + score: 100, + remaining_time: 24, + storage: { + used: 2000, + total: 4000, + percentage: 50 + } + })) + .mockReturnValueOnce(Promise.resolve({ errno: 1, msg: 'warning' })); + window.fetch = fetch; + + document.body.innerHTML = ` +
+ +
+
+ `; + const sign = require(modulePath); + + await sign(); + expect(fetch).toBeCalledWith({ + type: 'POST', + url: 'user/sign', + dataType: 'json' + }); + expect($('#score').html()).toBe('100'); + expect(trans).toBeCalledWith('user.signRemainingTime', { time: '24' }); + expect($('#sign-button').html()).toBe( + '  user.signRemainingTime' + ); + expect($('#sign-button').attr('disabled')).toBe('disabled'); + expect($('#user-storage').html()).toBe('50/ 100 KB'); + expect($('#user-storage-bar').css('width')).toBe('50%'); + expect(swal).toBeCalledWith({ type: 'success', html: 'success' }); + + await sign(); + expect($('#user-storage').html()).toBe('2/ 4 MB'); + + await sign(); + expect(toastr.warning).toBeCalledWith('warning'); + }); +}); diff --git a/resources/assets/src/js/admin/customize.js b/resources/assets/src/js/admin/customize.js index 2da4a1ea..6dda2a93 100644 --- a/resources/assets/src/js/admin/customize.js +++ b/resources/assets/src/js/admin/customize.js @@ -9,7 +9,7 @@ $('#layout-skins-list [data-skin]').click(function (e) { current_skin = skin_name; }); -$('#color-submit').click(() => { +function submitColor() { fetch({ type: 'POST', url: url('admin/customize?action=color'), @@ -18,4 +18,10 @@ $('#color-submit').click(() => { }).then(({ errno, msg }) => { (errno == 0) ? toastr.success(msg) : toastr.warning(msg); }).catch(err => showAjaxError(err)); -}); +} + +$('#color-submit').click(submitColor); + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = submitColor; +} diff --git a/resources/assets/src/js/admin/players.js b/resources/assets/src/js/admin/players.js index 5500da06..f8293ac7 100644 --- a/resources/assets/src/js/admin/players.js +++ b/resources/assets/src/js/admin/players.js @@ -162,3 +162,14 @@ function deletePlayer(pid) { } }).catch(err => showAjaxError(err)); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + changePreference, + changeTexture, + ajaxChangeTexture, + changePlayerName, + changeOwner, + deletePlayer + }; +} diff --git a/resources/assets/src/js/admin/plugins.js b/resources/assets/src/js/admin/plugins.js index 656e4093..d095dc12 100644 --- a/resources/assets/src/js/admin/plugins.js +++ b/resources/assets/src/js/admin/plugins.js @@ -25,7 +25,7 @@ function disablePlugin(name) { dataType: 'json' }).then(({ errno, msg }) => { if (errno == 0) { - toastr.warning(msg); + toastr.success(msg); $.pluginsTable.ajax.reload(null, false); } else { @@ -53,3 +53,11 @@ function deletePlugin(name) { } }).catch(err => showAjaxError(err)); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + enablePlugin, + disablePlugin, + deletePlugin + }; +} diff --git a/resources/assets/src/js/admin/update.js b/resources/assets/src/js/admin/update.js index 117b61f8..35b7b79d 100644 --- a/resources/assets/src/js/admin/update.js +++ b/resources/assets/src/js/admin/update.js @@ -89,3 +89,7 @@ function downloadUpdates() { }).catch(err => showAjaxError(err)); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = downloadUpdates; +} diff --git a/resources/assets/src/js/admin/users.js b/resources/assets/src/js/admin/users.js index f65f9277..3471600f 100644 --- a/resources/assets/src/js/admin/users.js +++ b/resources/assets/src/js/admin/users.js @@ -167,3 +167,15 @@ $('body').on('keypress', '.score', function(event){ changeUserScore($(this).parent().parent().attr('id'), $(this).val()); } }); + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + changeUserEmail, + changeUserNickName, + changeUserPwd, + changeUserScore, + changeBanStatus, + changeAdminStatus, + deleteUserAccount + }; +} diff --git a/resources/assets/src/js/auth/captcha.js b/resources/assets/src/js/auth/captcha.js index 1bd12edc..3d819568 100644 --- a/resources/assets/src/js/auth/captcha.js +++ b/resources/assets/src/js/auth/captcha.js @@ -9,3 +9,7 @@ function refreshCaptcha() { } $('.captcha').click(refreshCaptcha); + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = refreshCaptcha; +} diff --git a/resources/assets/src/js/auth/forgot.js b/resources/assets/src/js/auth/forgot.js new file mode 100644 index 00000000..bd9ad0da --- /dev/null +++ b/resources/assets/src/js/auth/forgot.js @@ -0,0 +1,50 @@ +/* global refreshCaptcha */ + +'use strict'; + +$('#forgot-button').click(e => { + e.preventDefault(); + + let data = { + email: $('#email').val(), + captcha: $('#captcha').val() + }; + + (function validate({ email, captcha }, callback) { + if (email == '') { + showMsg(trans('auth.emptyEmail')); + $('#email').focus(); + } else if (!/\S+@\S+\.\S+/.test(email)) { + showMsg(trans('auth.invalidEmail'), 'warning'); + } else if (captcha == '') { + showMsg(trans('auth.emptyCaptcha')); + $('#captcha').focus(); + } else { + callback(); + } + })(data, () => { + fetch({ + type: 'POST', + url: url('auth/forgot'), + dataType: 'json', + data: data, + beforeSend: () => { + $('#forgot-button').html( + ' ' + trans('auth.sending') + ).prop('disabled', 'disabled'); + } + }).then(({ errno, msg }) => { + if (errno == 0) { + showMsg(msg, 'success'); + $('#forgot-button').html(trans('auth.send')).prop('disabled', 'disabled'); + } else { + showMsg(msg, 'warning'); + refreshCaptcha(); + $('#forgot-button').html(trans('auth.send')).prop('disabled', ''); + } + }).catch(err => { + showAjaxError(err); + $('#forgot-button').html(trans('auth.send')).prop('disabled', ''); + }); + }); +}); diff --git a/resources/assets/src/js/auth/reset.js b/resources/assets/src/js/auth/reset.js index 826a1e58..bc209f16 100644 --- a/resources/assets/src/js/auth/reset.js +++ b/resources/assets/src/js/auth/reset.js @@ -1,54 +1,5 @@ -/* global refreshCaptcha */ - 'use strict'; -$('#forgot-button').click(e => { - e.preventDefault(); - - let data = { - email: $('#email').val(), - captcha: $('#captcha').val() - }; - - (function validate({ email, captcha }, callback) { - if (email == '') { - showMsg(trans('auth.emptyEmail')); - $('#email').focus(); - } else if (!/\S+@\S+\.\S+/.test(email)) { - showMsg(trans('auth.invalidEmail'), 'warning'); - } else if (captcha == '') { - showMsg(trans('auth.emptyCaptcha')); - $('#captcha').focus(); - } else { - callback(); - } - })(data, () => { - fetch({ - type: 'POST', - url: url('auth/forgot'), - dataType: 'json', - data: data, - beforeSend: () => { - $('#forgot-button').html( - ' ' + trans('auth.sending') - ).prop('disabled', 'disabled'); - } - }).then(({ errno, msg }) => { - if (errno == 0) { - showMsg(msg, 'success'); - $('#forgot-button').html(trans('auth.send')).prop('disabled', 'disabled'); - } else { - showMsg(msg, 'warning'); - refreshCaptcha(); - $('#forgot-button').html(trans('auth.send')).prop('disabled', ''); - } - }).catch(err => { - showAjaxError(err); - $('#forgot-button').html(trans('auth.send')).prop('disabled', ''); - }); - }); -}); - $('#reset-button').click(e => { e.preventDefault(); diff --git a/resources/assets/src/js/common/i18n.js b/resources/assets/src/js/common/i18n.js index ab0e6771..bdd20e6c 100644 --- a/resources/assets/src/js/common/i18n.js +++ b/resources/assets/src/js/common/i18n.js @@ -49,3 +49,10 @@ function trans(key, parameters = {}) { return temp; } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + loadLocales, + trans + }; +} diff --git a/resources/assets/src/js/common/notify.js b/resources/assets/src/js/common/notify.js index cdc5700e..85f0223c 100644 --- a/resources/assets/src/js/common/notify.js +++ b/resources/assets/src/js/common/notify.js @@ -57,3 +57,11 @@ function showModal(msg, title = 'Message', type = 'default', options = {}) { $(dom).modal(options); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + showMsg, + showAjaxError, + showModal + }; +} diff --git a/resources/assets/src/js/common/texture-preview.js b/resources/assets/src/js/common/texture-preview.js index 0ad98f1d..ed986b4b 100644 --- a/resources/assets/src/js/common/texture-preview.js +++ b/resources/assets/src/js/common/texture-preview.js @@ -98,3 +98,7 @@ $('.fa-pause').click(function () { $('.fa-forward').click(() => MSP.setStatus('running', ! MSP.getStatus('running'))); $('.fa-repeat' ).click(() => MSP.setStatus('rotation', ! MSP.getStatus('rotation'))); + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = TexturePreview; +} diff --git a/resources/assets/src/js/common/utils.js b/resources/assets/src/js/common/utils.js index 3ba9a17d..6cbace9e 100644 --- a/resources/assets/src/js/common/utils.js +++ b/resources/assets/src/js/common/utils.js @@ -74,7 +74,7 @@ function getQueryString(key, defaultValue) { */ function debounce(func, delay, args = [], context = undefined) { if (isNaN(delay) || typeof func !== 'function') { - throw new Error('Arguments type of function "debounce" is incorrent!'); + throw new Error('Arguments type of function "debounce" is incorrect!'); } let timer = null; @@ -96,3 +96,12 @@ function url(relativeUri) { return blessing.base_url + relativeUri; } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + isEmpty, + fetch, + debounce, + url + }; +} diff --git a/resources/assets/src/js/skinlib/index.js b/resources/assets/src/js/skinlib/index.js index 8c2ef749..4045e059 100644 --- a/resources/assets/src/js/skinlib/index.js +++ b/resources/assets/src/js/skinlib/index.js @@ -63,9 +63,11 @@ function renderSkinlib(items) { if (items.length === 0) { $('#skinlib-paginator').hide(); - container.html(`

- ${ trans('general.noResult') } -

`); + container.html( + '

' + + trans('general.noResult') + + '

' + ); } else { $('#skinlib-paginator').show(); @@ -217,3 +219,13 @@ function updateBreadCrumb() { $('#navbar-search-input').val(decodeURI($.skinlib.keyword)); } } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + renderSkinlib, + reloadSkinlib, + updatePaginator, + updateUrlQueryString, + updateBreadCrumb + }; +} diff --git a/resources/assets/src/js/skinlib/operations.js b/resources/assets/src/js/skinlib/operations.js index 101357b5..0f497bfa 100644 --- a/resources/assets/src/js/skinlib/operations.js +++ b/resources/assets/src/js/skinlib/operations.js @@ -104,16 +104,21 @@ function changeTextureName(tid, oldName) { /** * Update button action & likes of texture. * - * @param {int} tid - * @param {string} action add|remove + * @param {number} tid + * @param {'add'|'remove'} action * @return {null} */ function updateTextureStatus(tid, action) { let likes = parseInt($('#likes').html()) + (action == 'add' ? 1 : -1); action = (action == 'add') ? 'removeFromCloset' : 'addToCloset'; - $(`a[tid=${tid}]`).attr('href', `javascript:${action}(${tid});`).attr('title', trans(`skinlib.${action}`)).toggleClass('liked'); - $(`#${tid}`).attr('href', `javascript:${action}(${tid});`).html(trans(`skinlib.${action}`)); + $(`a[tid=${tid}]`) + .attr('href', `javascript:${action}(${tid});`) + .attr('title', trans(`skinlib.${action}`)) + .toggleClass('liked'); + $(`#${tid}`) + .attr('href', `javascript:${action}(${tid});`) + .html(trans(`skinlib.${action}`)); $('#likes').html(likes); } @@ -171,3 +176,15 @@ function deleteTexture(tid) { } }).catch(err => showAjaxError(err)); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + addToCloset, + ajaxAddToCloset, + removeFromCloset, + changeTextureName, + updateTextureStatus, + changePrivacy, + deleteTexture + }; +} diff --git a/resources/assets/src/js/user/closet.js b/resources/assets/src/js/user/closet.js index 2aa956a5..455c12ad 100644 --- a/resources/assets/src/js/user/closet.js +++ b/resources/assets/src/js/user/closet.js @@ -242,3 +242,13 @@ function setAsAvatar(tid) { } }).catch(err => showAjaxError(err)); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + renderCloset, + reloadCloset, + renameClosetItem, + removeFromCloset, + setAsAvatar + }; +} diff --git a/resources/assets/src/js/user/player.js b/resources/assets/src/js/user/player.js index e99565a3..0f63ecb1 100644 --- a/resources/assets/src/js/user/player.js +++ b/resources/assets/src/js/user/player.js @@ -56,13 +56,14 @@ $('body').on('change', '#preference', function () { }).catch(err => showAjaxError(err)); }); -function changePlayerName(pid, currentPlayerName) { +function changePlayerName(pid) { let newPlayerName = ''; + const $playerName = $(`td:contains("${pid}")`).next(); swal({ title: trans('user.changePlayerName'), text: $('#player_name').attr('placeholder'), - inputValue: currentPlayerName, + inputValue: $playerName.html(), input: 'text', showCancelButton: true, inputValidator: value => (new Promise((resolve, reject) => { @@ -77,7 +78,7 @@ function changePlayerName(pid, currentPlayerName) { if (errno == 0) { swal({ type: 'success', html: msg }); - $(`td:contains("${pid}")`).next().html(newPlayerName); + $playerName.html(newPlayerName); } else { swal({ type: 'error', html: msg }); } @@ -207,3 +208,13 @@ function setTexture() { }).catch(err => showAjaxError(err)); } } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + changePlayerName, + ajaxClearTexture, + deletePlayer, + addNewPlayer, + setTexture + }; +} diff --git a/resources/assets/src/js/user/profile.js b/resources/assets/src/js/user/profile.js index 31102021..b059c835 100644 --- a/resources/assets/src/js/user/profile.js +++ b/resources/assets/src/js/user/profile.js @@ -62,8 +62,8 @@ function changePassword() { if (errno == 0) { return swal({ type: 'success', - text: msg } - ).then(() => { + text: msg + }).then(() => { return logout(); }).then(({ errno }) => { if (errno == 0) { @@ -151,3 +151,12 @@ function deleteAccount() { } }).catch(err => showAjaxError(err)); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = { + changeNickName, + changePassword, + changeEmail, + deleteAccount + }; +} diff --git a/resources/assets/src/js/user/sign.js b/resources/assets/src/js/user/sign.js index f0f90884..d25a0620 100644 --- a/resources/assets/src/js/user/sign.js +++ b/resources/assets/src/js/user/sign.js @@ -13,7 +13,9 @@ function sign() { $('#sign-button').attr('disabled', 'disabled').html(dom); if (result.storage.used > 1024) { - $('#user-storage').html(`${Math.round(result.storage.used)}/ ${Math.round(result.storage.total)} MB`); + $('#user-storage').html( + `${Math.round(result.storage.used / 1024)}/ ${Math.round(result.storage.total / 1024)} MB` + ); } else { $('#user-storage').html(`${Math.round(result.storage.used)}/ ${Math.round(result.storage.total)} KB`); } @@ -26,3 +28,7 @@ function sign() { } }).catch(err => showAjaxError(err)); } + +if (typeof require !== 'undefined' && typeof module !== 'undefined') { + module.exports = sign; +} diff --git a/yarn.lock b/yarn.lock index cbffac4d..b6f33f62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,20 @@ # yarn lockfile v1 +abab@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" + abbrev@1: version "1.1.0" resolved "http://registry.npm.taobao.org/abbrev/download/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" +acorn-globals@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" + dependencies: + acorn "^4.0.4" + acorn-jsx@^3.0.0: version "3.0.1" resolved "http://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" @@ -16,6 +26,10 @@ acorn@^3.0.4: version "3.3.0" resolved "http://registry.npm.taobao.org/acorn/download/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" +acorn@^4.0.4: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + acorn@^5.0.1: version "5.1.1" resolved "http://registry.npm.taobao.org/acorn/download/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75" @@ -44,15 +58,27 @@ ajv@^5.2.0: json-schema-traverse "^0.3.0" json-stable-stringify "^1.0.1" +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + amdefine@>=0.0.4: version "1.0.1" resolved "http://registry.npm.taobao.org/amdefine/download/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" +ansi-escapes@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + ansi-escapes@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/ansi-escapes/download/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" -ansi-regex@^2.0.0: +ansi-regex@^2.0.0, ansi-regex@^2.1.1: version "2.1.1" resolved "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -64,12 +90,25 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0: +ansi-styles@^3.0.0, ansi-styles@^3.1.0: version "3.1.0" resolved "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" dependencies: color-convert "^1.0.0" +anymatch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + dependencies: + arrify "^1.0.0" + micromatch "^2.1.5" + +append-transform@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + dependencies: + default-require-extensions "^1.0.0" + aproba@^1.0.3: version "1.1.2" resolved "http://registry.npm.taobao.org/aproba/download/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" @@ -109,6 +148,10 @@ array-each@^1.0.1: version "1.0.1" resolved "http://registry.npm.taobao.org/array-each/download/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + array-find-index@^1.0.1: version "1.0.2" resolved "http://registry.npm.taobao.org/array-find-index/download/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -131,7 +174,7 @@ array-unique@^0.2.1: version "0.2.1" resolved "http://registry.npm.taobao.org/array-unique/download/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" -arrify@^1.0.0: +arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "http://registry.npm.taobao.org/arrify/download/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -151,6 +194,16 @@ async-foreach@^0.1.3: version "0.1.3" resolved "http://registry.npm.taobao.org/async-foreach/download/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" +async@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +async@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" + dependencies: + lodash "^4.14.0" + asynckit@^0.4.0: version "0.4.0" resolved "http://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -171,7 +224,7 @@ babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.0" -babel-core@^6.0.2, babel-core@^6.24.1: +babel-core@^6.0.0, babel-core@^6.0.2, babel-core@^6.24.1: version "6.25.0" resolved "http://registry.npm.taobao.org/babel-core/download/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729" dependencies: @@ -195,7 +248,7 @@ babel-core@^6.0.2, babel-core@^6.24.1: slash "^1.0.0" source-map "^0.5.0" -babel-generator@^6.25.0: +babel-generator@^6.18.0, babel-generator@^6.25.0: version "6.25.0" resolved "http://registry.npm.taobao.org/babel-generator/download/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" dependencies: @@ -283,6 +336,14 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" +babel-jest@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-20.0.3.tgz#e4a03b13dc10389e140fc645d09ffc4ced301671" + dependencies: + babel-core "^6.0.0" + babel-plugin-istanbul "^4.0.0" + babel-preset-jest "^20.0.3" + babel-messages@^6.23.0: version "6.23.0" resolved "http://registry.npm.taobao.org/babel-messages/download/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" @@ -295,6 +356,18 @@ babel-plugin-check-es2015-constants@^6.22.0: dependencies: babel-runtime "^6.22.0" +babel-plugin-istanbul@^4.0.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.4.tgz#18dde84bf3ce329fddf3f4103fae921456d8e587" + dependencies: + find-up "^2.1.0" + istanbul-lib-instrument "^1.7.2" + test-exclude "^4.1.1" + +babel-plugin-jest-hoist@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.3.tgz#afedc853bd3f8dc3548ea671fbe69d03cc2c1767" + babel-plugin-transform-es2015-arrow-functions@^6.22.0: version "6.22.0" resolved "http://registry.npm.taobao.org/babel-plugin-transform-es2015-arrow-functions/download/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" @@ -505,6 +578,12 @@ babel-preset-es2015@^6.24.1: babel-plugin-transform-es2015-unicode-regex "^6.24.1" babel-plugin-transform-regenerator "^6.24.1" +babel-preset-jest@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-20.0.3.tgz#cbacaadecb5d689ca1e1de1360ebfc66862c178a" + dependencies: + babel-plugin-jest-hoist "^20.0.3" + babel-register@^6.24.1: version "6.24.1" resolved "http://registry.npm.taobao.org/babel-register/download/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f" @@ -524,7 +603,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0: core-js "^2.4.0" regenerator-runtime "^0.10.0" -babel-template@^6.24.1, babel-template@^6.25.0: +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.25.0: version "6.25.0" resolved "http://registry.npm.taobao.org/babel-template/download/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" dependencies: @@ -534,7 +613,7 @@ babel-template@^6.24.1, babel-template@^6.25.0: babylon "^6.17.2" lodash "^4.2.0" -babel-traverse@^6.24.1, babel-traverse@^6.25.0: +babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.25.0: version "6.25.0" resolved "http://registry.npm.taobao.org/babel-traverse/download/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" dependencies: @@ -548,7 +627,7 @@ babel-traverse@^6.24.1, babel-traverse@^6.25.0: invariant "^2.2.0" lodash "^4.2.0" -babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.25.0: +babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.25.0: version "6.25.0" resolved "http://registry.npm.taobao.org/babel-types/download/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" dependencies: @@ -557,7 +636,7 @@ babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.25.0: lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@^6.17.2: +babylon@^6.17.2, babylon@^6.17.4: version "6.17.4" resolved "http://registry.npm.taobao.org/babylon/download/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" @@ -610,6 +689,24 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" +browser-resolve@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + dependencies: + resolve "1.1.7" + +bser@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bser/-/bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169" + dependencies: + node-int64 "^0.4.0" + +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "http://registry.npm.taobao.org/buffer-crc32/download/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -628,6 +725,10 @@ callsites@^0.2.0: version "0.2.0" resolved "http://registry.npm.taobao.org/callsites/download/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + camelcase-keys@^2.0.0: version "2.1.0" resolved "http://registry.npm.taobao.org/camelcase-keys/download/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -635,6 +736,10 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + camelcase@^2.0.0: version "2.1.1" resolved "http://registry.npm.taobao.org/camelcase/download/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -647,6 +752,13 @@ caseless@~0.12.0: version "0.12.0" resolved "http://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -665,6 +777,10 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +ci-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" + circular-json@^0.3.1: version "0.3.1" resolved "http://registry.npm.taobao.org/circular-json/download/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" @@ -685,6 +801,14 @@ cli-width@^2.0.0: version "2.1.0" resolved "http://registry.npm.taobao.org/cli-width/download/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + cliui@^3.2.0: version "3.2.0" resolved "http://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -777,7 +901,11 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "http://registry.npm.taobao.org/console-control-strings/download/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" -convert-source-map@^1.1.0: +content-type-parser@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94" + +convert-source-map@^1.1.0, convert-source-map@^1.4.0: version "1.5.0" resolved "http://registry.npm.taobao.org/convert-source-map/download/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -802,6 +930,16 @@ cryptiles@2.x.x: dependencies: boom "2.x.x" +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + +"cssstyle@>= 0.2.37 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + dependencies: + cssom "0.3.x" + currently-unhandled@^0.4.1: version "0.4.1" resolved "http://registry.npm.taobao.org/currently-unhandled/download/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -818,13 +956,13 @@ dateformat@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/dateformat/download/dateformat-2.0.0.tgz#2743e3abb5c3fc2462e527dca445e04e9f4dee17" -debug@^2.1.1, debug@^2.2.0, debug@^2.6.8: +debug@^2.1.1, debug@^2.2.0, debug@^2.6.3, debug@^2.6.8: version "2.6.8" resolved "http://registry.npm.taobao.org/debug/download/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: ms "2.0.0" -decamelize@^1.1.1, decamelize@^1.1.2: +decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "http://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -832,6 +970,12 @@ deep-is@~0.1.3: version "0.1.3" resolved "http://registry.npm.taobao.org/deep-is/download/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +default-require-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + dependencies: + strip-bom "^2.0.0" + defaults@^1.0.0: version "1.0.3" resolved "http://registry.npm.taobao.org/defaults/download/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -885,6 +1029,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +diff@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.0.tgz#056695150d7aa93237ca7e378ac3b1682b7963b9" + doctrine@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/doctrine/download/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" @@ -914,6 +1062,12 @@ end-of-stream@~0.1.5: dependencies: once "~1.3.0" +errno@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" + dependencies: + prr "~0.0.0" + error-ex@^1.2.0: version "1.3.1" resolved "http://registry.npm.taobao.org/error-ex/download/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" @@ -928,6 +1082,17 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "http://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" +escodegen@^1.6.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + eslint-scope@^3.7.1: version "3.7.1" resolved "http://registry.npm.taobao.org/eslint-scope/download/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" @@ -980,6 +1145,10 @@ espree@^3.4.3: acorn "^5.0.1" acorn-jsx "^3.0.0" +esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + esprima@^4.0.0: version "4.0.0" resolved "http://registry.npm.taobao.org/esprima/download/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" @@ -997,6 +1166,10 @@ esrecurse@^4.1.0: estraverse "^4.1.0" object-assign "^4.0.1" +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "http://registry.npm.taobao.org/estraverse/download/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -1017,6 +1190,12 @@ event-stream@latest: stream-combiner "~0.0.4" through "~2.3.1" +exec-sh@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10" + dependencies: + merge "^1.1.3" + expand-brackets@^0.1.4: version "0.1.5" resolved "http://registry.npm.taobao.org/expand-brackets/download/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -1078,6 +1257,18 @@ fast-levenshtein@~2.0.4: version "2.0.6" resolved "http://registry.npm.taobao.org/fast-levenshtein/download/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fb-watchman@^1.8.0: + version "1.9.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-1.9.2.tgz#a24cf47827f82d38fb59a69ad70b76e3b6ae7383" + dependencies: + bser "1.0.2" + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + figures@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/figures/download/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -1095,6 +1286,13 @@ filename-regex@^2.0.0: version "2.0.1" resolved "http://registry.npm.taobao.org/filename-regex/download/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" +fileset@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" + dependencies: + glob "^7.0.3" + minimatch "^3.0.3" + fill-range@^2.1.0: version "2.2.3" resolved "http://registry.npm.taobao.org/fill-range/download/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" @@ -1116,6 +1314,12 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + findup-sync@^0.4.2: version "0.4.3" resolved "http://registry.npm.taobao.org/findup-sync/download/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" @@ -1293,7 +1497,7 @@ glob@^4.3.1: minimatch "^2.0.1" once "^1.3.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.2" resolved "http://registry.npm.taobao.org/glob/download/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -1381,7 +1585,7 @@ graceful-fs@^3.0.0: dependencies: natives "^1.1.0" -graceful-fs@^4.1.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "http://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1526,6 +1730,16 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" +handlebars@^4.0.3: + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + har-schema@^1.0.5: version "1.0.5" resolved "http://registry.npm.taobao.org/har-schema/download/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" @@ -1543,6 +1757,10 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + has-flag@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -1591,6 +1809,12 @@ hosted-git-info@^2.1.4: version "2.5.0" resolved "http://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" +html-encoding-sniffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da" + dependencies: + whatwg-encoding "^1.0.1" + http-signature@~1.1.0: version "1.1.1" resolved "http://registry.npm.taobao.org/http-signature/download/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -1603,6 +1827,10 @@ icheck@^1.0.2: version "1.0.2" resolved "http://registry.npm.taobao.org/icheck/download/icheck-1.0.2.tgz#06d08da3d47ae448c153b2639b86e9ad7fdf7128" +iconv-lite@0.4.13: + version "0.4.13" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" + iconv-lite@^0.4.17: version "0.4.18" resolved "http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" @@ -1698,6 +1926,12 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" +is-ci@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + dependencies: + ci-info "^1.0.0" + is-dotfile@^1.0.0: version "1.0.3" resolved "http://registry.npm.taobao.org/is-dotfile/download/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -1844,6 +2078,280 @@ isstream@~0.1.2: version "0.1.2" resolved "http://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" +istanbul-api@^1.1.1: + version "1.1.10" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.10.tgz#f27e5e7125c8de13f6a80661af78f512e5439b2b" + dependencies: + async "^2.1.4" + fileset "^2.0.2" + istanbul-lib-coverage "^1.1.1" + istanbul-lib-hook "^1.0.7" + istanbul-lib-instrument "^1.7.3" + istanbul-lib-report "^1.1.1" + istanbul-lib-source-maps "^1.2.1" + istanbul-reports "^1.1.1" + js-yaml "^3.7.0" + mkdirp "^0.5.1" + once "^1.4.0" + +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" + +istanbul-lib-hook@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" + dependencies: + append-transform "^0.4.0" + +istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.2, istanbul-lib-instrument@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.3.tgz#925b239163eabdd68cc4048f52c2fa4f899ecfa7" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.17.4" + istanbul-lib-coverage "^1.1.1" + semver "^5.3.0" + +istanbul-lib-report@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" + dependencies: + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + path-parse "^1.0.5" + supports-color "^3.1.2" + +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" + dependencies: + debug "^2.6.3" + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.1.tgz#042be5c89e175bc3f86523caab29c014e77fee4e" + dependencies: + handlebars "^4.0.3" + +jest-changed-files@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.3.tgz#9394d5cc65c438406149bef1bf4d52b68e03e3f8" + +jest-cli@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-20.0.4.tgz#e532b19d88ae5bc6c417e8b0593a6fe954b1dc93" + dependencies: + ansi-escapes "^1.4.0" + callsites "^2.0.0" + chalk "^1.1.3" + graceful-fs "^4.1.11" + is-ci "^1.0.10" + istanbul-api "^1.1.1" + istanbul-lib-coverage "^1.0.1" + istanbul-lib-instrument "^1.4.2" + istanbul-lib-source-maps "^1.1.0" + jest-changed-files "^20.0.3" + jest-config "^20.0.4" + jest-docblock "^20.0.3" + jest-environment-jsdom "^20.0.3" + jest-haste-map "^20.0.4" + jest-jasmine2 "^20.0.4" + jest-message-util "^20.0.3" + jest-regex-util "^20.0.3" + jest-resolve-dependencies "^20.0.3" + jest-runtime "^20.0.4" + jest-snapshot "^20.0.3" + jest-util "^20.0.3" + micromatch "^2.3.11" + node-notifier "^5.0.2" + pify "^2.3.0" + slash "^1.0.0" + string-length "^1.0.1" + throat "^3.0.0" + which "^1.2.12" + worker-farm "^1.3.1" + yargs "^7.0.2" + +jest-config@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-20.0.4.tgz#e37930ab2217c913605eff13e7bd763ec48faeea" + dependencies: + chalk "^1.1.3" + glob "^7.1.1" + jest-environment-jsdom "^20.0.3" + jest-environment-node "^20.0.3" + jest-jasmine2 "^20.0.4" + jest-matcher-utils "^20.0.3" + jest-regex-util "^20.0.3" + jest-resolve "^20.0.4" + jest-validate "^20.0.3" + pretty-format "^20.0.3" + +jest-diff@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-20.0.3.tgz#81f288fd9e675f0fb23c75f1c2b19445fe586617" + dependencies: + chalk "^1.1.3" + diff "^3.2.0" + jest-matcher-utils "^20.0.3" + pretty-format "^20.0.3" + +jest-docblock@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712" + +jest-environment-jsdom@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-20.0.3.tgz#048a8ac12ee225f7190417713834bb999787de99" + dependencies: + jest-mock "^20.0.3" + jest-util "^20.0.3" + jsdom "^9.12.0" + +jest-environment-node@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-20.0.3.tgz#d488bc4612af2c246e986e8ae7671a099163d403" + dependencies: + jest-mock "^20.0.3" + jest-util "^20.0.3" + +jest-haste-map@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-20.0.4.tgz#653eb55c889ce3c021f7b94693f20a4159badf03" + dependencies: + fb-watchman "^2.0.0" + graceful-fs "^4.1.11" + jest-docblock "^20.0.3" + micromatch "^2.3.11" + sane "~1.6.0" + worker-farm "^1.3.1" + +jest-jasmine2@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-20.0.4.tgz#fcc5b1411780d911d042902ef1859e852e60d5e1" + dependencies: + chalk "^1.1.3" + graceful-fs "^4.1.11" + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-matchers "^20.0.3" + jest-message-util "^20.0.3" + jest-snapshot "^20.0.3" + once "^1.4.0" + p-map "^1.1.1" + +jest-matcher-utils@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz#b3a6b8e37ca577803b0832a98b164f44b7815612" + dependencies: + chalk "^1.1.3" + pretty-format "^20.0.3" + +jest-matchers@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-20.0.3.tgz#ca69db1c32db5a6f707fa5e0401abb55700dfd60" + dependencies: + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-message-util "^20.0.3" + jest-regex-util "^20.0.3" + +jest-message-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-20.0.3.tgz#6aec2844306fcb0e6e74d5796c1006d96fdd831c" + dependencies: + chalk "^1.1.3" + micromatch "^2.3.11" + slash "^1.0.0" + +jest-mock@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-20.0.3.tgz#8bc070e90414aa155c11a8d64c869a0d5c71da59" + +jest-regex-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-20.0.3.tgz#85bbab5d133e44625b19faf8c6aa5122d085d762" + +jest-resolve-dependencies@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-20.0.3.tgz#6e14a7b717af0f2cb3667c549de40af017b1723a" + dependencies: + jest-regex-util "^20.0.3" + +jest-resolve@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-20.0.4.tgz#9448b3e8b6bafc15479444c6499045b7ffe597a5" + dependencies: + browser-resolve "^1.11.2" + is-builtin-module "^1.0.0" + resolve "^1.3.2" + +jest-runtime@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-20.0.4.tgz#a2c802219c4203f754df1404e490186169d124d8" + dependencies: + babel-core "^6.0.0" + babel-jest "^20.0.3" + babel-plugin-istanbul "^4.0.0" + chalk "^1.1.3" + convert-source-map "^1.4.0" + graceful-fs "^4.1.11" + jest-config "^20.0.4" + jest-haste-map "^20.0.4" + jest-regex-util "^20.0.3" + jest-resolve "^20.0.4" + jest-util "^20.0.3" + json-stable-stringify "^1.0.1" + micromatch "^2.3.11" + strip-bom "3.0.0" + yargs "^7.0.2" + +jest-snapshot@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-20.0.3.tgz#5b847e1adb1a4d90852a7f9f125086e187c76566" + dependencies: + chalk "^1.1.3" + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-util "^20.0.3" + natural-compare "^1.4.0" + pretty-format "^20.0.3" + +jest-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-20.0.3.tgz#0c07f7d80d82f4e5a67c6f8b9c3fe7f65cfd32ad" + dependencies: + chalk "^1.1.3" + graceful-fs "^4.1.11" + jest-message-util "^20.0.3" + jest-mock "^20.0.3" + jest-validate "^20.0.3" + leven "^2.1.0" + mkdirp "^0.5.1" + +jest-validate@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-20.0.3.tgz#d0cfd1de4f579f298484925c280f8f1d94ec3cab" + dependencies: + chalk "^1.1.3" + jest-matcher-utils "^20.0.3" + leven "^2.1.0" + pretty-format "^20.0.3" + +jest@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-20.0.4.tgz#3dd260c2989d6dad678b1e9cc4d91944f6d602ac" + dependencies: + jest-cli "^20.0.4" + jqPaginator@^1.2.0: version "1.2.0" resolved "http://registry.npm.taobao.org/jqPaginator/download/jqPaginator-1.2.0.tgz#9ccab8024dae9e7ab5f7aca0573149c967b298d1" @@ -1860,7 +2368,7 @@ js-tokens@^3.0.0: version "3.0.2" resolved "http://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.8.4: +js-yaml@^3.7.0, js-yaml@^3.8.4: version "3.9.0" resolved "http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce" dependencies: @@ -1875,6 +2383,30 @@ jschardet@^1.4.2: version "1.4.2" resolved "http://registry.npm.taobao.org/jschardet/download/jschardet-1.4.2.tgz#2aa107f142af4121d145659d44f50830961e699a" +jsdom@^9.12.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" + dependencies: + abab "^1.0.3" + acorn "^4.0.4" + acorn-globals "^3.1.0" + array-equal "^1.0.0" + content-type-parser "^1.0.1" + cssom ">= 0.3.2 < 0.4.0" + cssstyle ">= 0.2.37 < 0.3.0" + escodegen "^1.6.1" + html-encoding-sniffer "^1.0.1" + nwmatcher ">= 1.3.9 < 2.0.0" + parse5 "^1.5.1" + request "^2.79.0" + sax "^1.2.1" + symbol-tree "^3.2.1" + tough-cookie "^2.3.2" + webidl-conversions "^4.0.0" + whatwg-encoding "^1.0.1" + whatwg-url "^4.3.0" + xml-name-validator "^2.0.1" + jsesc@^1.3.0: version "1.3.0" resolved "http://registry.npm.taobao.org/jsesc/download/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -1930,12 +2462,20 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + lcid@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/lcid/download/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" dependencies: invert-kv "^1.0.0" +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "http://registry.npm.taobao.org/levn/download/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -1967,6 +2507,13 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + lodash._basecopy@^3.0.0: version "3.0.1" resolved "http://registry.npm.taobao.org/lodash._basecopy/download/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" @@ -2087,7 +2634,7 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.4: +lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.4: version "4.17.4" resolved "http://registry.npm.taobao.org/lodash/download/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -2095,6 +2642,10 @@ lodash@~1.0.1: version "1.0.2" resolved "http://registry.npm.taobao.org/lodash/download/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + loose-envify@^1.0.0: version "1.3.1" resolved "http://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" @@ -2129,6 +2680,12 @@ make-error@^1.2.0: version "1.3.0" resolved "http://registry.npm.taobao.org/make-error/download/make-error-1.3.0.tgz#52ad3a339ccf10ce62b4040b708fe707244b8b96" +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + map-cache@^0.2.0: version "0.2.2" resolved "http://registry.npm.taobao.org/map-cache/download/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -2156,7 +2713,11 @@ meow@^3.7.0: redent "^1.0.0" trim-newlines "^1.0.0" -micromatch@^2.3.7: +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + +micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: version "2.3.11" resolved "http://registry.npm.taobao.org/micromatch/download/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" dependencies: @@ -2194,7 +2755,7 @@ minimatch@^2.0.1: dependencies: brace-expansion "^1.0.0" -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "http://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -2211,10 +2772,14 @@ minimist@0.0.8: version "0.0.8" resolved "http://registry.npm.taobao.org/minimist/download/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.0, minimist@^1.1.3: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3: version "1.2.0" resolved "http://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.1" resolved "http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -2265,7 +2830,11 @@ node-gyp@^3.3.1: tar "^2.0.0" which "1" -node-notifier@^5.0.1: +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + +node-notifier@^5.0.1, node-notifier@^5.0.2: version "5.1.2" resolved "http://registry.npm.taobao.org/node-notifier/download/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" dependencies: @@ -2337,6 +2906,10 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "http://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" +"nwmatcher@>= 1.3.9 < 2.0.0": + version "1.4.1" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.1.tgz#7ae9b07b0ea804db7e25f05cb5fe4097d4e4949f" + oauth-sign@~0.8.1: version "0.8.2" resolved "http://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -2377,13 +2950,26 @@ once@^1.3.0, once@~1.3.0: dependencies: wrappy "1" +once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + onetime@^2.0.0: version "2.0.1" resolved "http://registry.npm.taobao.org/onetime/download/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" dependencies: mimic-fn "^1.0.0" -optionator@^0.8.2: +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "http://registry.npm.taobao.org/optionator/download/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" dependencies: @@ -2427,6 +3013,16 @@ osenv@0: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + p-map@^1.1.1: version "1.1.1" resolved "http://registry.npm.taobao.org/p-map/download/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" @@ -2458,12 +3054,20 @@ parse-passwd@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/parse-passwd/download/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + path-exists@^2.0.0: version "2.1.0" resolved "http://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" dependencies: pinkie-promise "^2.0.0" +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + path-is-absolute@^1.0.0: version "1.0.1" resolved "http://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -2504,7 +3108,7 @@ performance-now@^0.2.0: version "0.2.0" resolved "http://registry.npm.taobao.org/performance-now/download/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" -pify@^2.0.0: +pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -2534,6 +3138,13 @@ preserve@^0.2.0: version "0.2.0" resolved "http://registry.npm.taobao.org/preserve/download/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" +pretty-format@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14" + dependencies: + ansi-regex "^2.1.1" + ansi-styles "^3.0.0" + pretty-hrtime@^1.0.0: version "1.0.3" resolved "http://registry.npm.taobao.org/pretty-hrtime/download/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -2550,6 +3161,10 @@ progress@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/progress/download/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" +prr@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" + pseudomap@^1.0.2: version "1.0.2" resolved "http://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -2747,7 +3362,11 @@ resolve-from@^1.0.0: version "1.0.1" resolved "http://registry.npm.taobao.org/resolve-from/download/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" -resolve@^1.1.6, resolve@^1.1.7: +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: version "1.3.3" resolved "http://registry.npm.taobao.org/resolve/download/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" dependencies: @@ -2760,7 +3379,13 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" -rimraf@2, rimraf@^2.2.8: +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@^2.2.8, rimraf@^2.6.1: version "2.6.1" resolved "http://registry.npm.taobao.org/rimraf/download/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" dependencies: @@ -2793,6 +3418,18 @@ safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +sane@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-1.6.0.tgz#9610c452307a135d29c1fdfe2547034180c46775" + dependencies: + anymatch "^1.3.0" + exec-sh "^0.2.0" + fb-watchman "^1.8.0" + minimatch "^3.0.2" + minimist "^1.1.1" + walker "~1.0.5" + watch "~0.10.0" + sass-graph@^2.1.1: version "2.2.4" resolved "http://registry.npm.taobao.org/sass-graph/download/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" @@ -2802,6 +3439,10 @@ sass-graph@^2.1.1: scss-tokenizer "^0.2.3" yargs "^7.0.0" +sax@^1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "http://registry.npm.taobao.org/scss-tokenizer/download/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -2857,16 +3498,22 @@ source-map-support@^0.4.2: dependencies: source-map "^0.5.6" -source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@~0.5.1: +source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: version "0.5.6" resolved "http://registry.npm.taobao.org/source-map/download/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" -source-map@^0.4.2: +source-map@^0.4.2, source-map@^0.4.4: version "0.4.4" resolved "http://registry.npm.taobao.org/source-map/download/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: amdefine ">=0.0.4" +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + dependencies: + amdefine ">=0.0.4" + sparkles@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/sparkles/download/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" @@ -2925,6 +3572,12 @@ stream-consume@~0.1.0: version "0.1.0" resolved "http://registry.npm.taobao.org/stream-consume/download/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" +string-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac" + dependencies: + strip-ansi "^3.0.0" + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -2966,6 +3619,10 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-bom@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + strip-bom@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/strip-bom/download/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" @@ -2993,6 +3650,12 @@ supports-color@^2.0.0: version "2.0.0" resolved "http://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" +supports-color@^3.1.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + supports-color@^4.0.0: version "4.2.0" resolved "http://registry.npm.taobao.org/supports-color/download/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037" @@ -3003,6 +3666,10 @@ sweetalert2@^6.6.5: version "6.6.6" resolved "http://registry.npm.taobao.org/sweetalert2/download/sweetalert2-6.6.6.tgz#3630279d98f04fb8b89f2e8b098fc42f03bcb555" +symbol-tree@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" + table@^4.0.1: version "4.0.1" resolved "http://registry.npm.taobao.org/table/download/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" @@ -3022,10 +3689,24 @@ tar@^2.0.0: fstream "^1.0.2" inherits "2" +test-exclude@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" + dependencies: + arrify "^1.0.1" + micromatch "^2.3.11" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" + text-table@~0.2.0: version "0.2.0" resolved "http://registry.npm.taobao.org/text-table/download/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +throat@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-3.2.0.tgz#50cb0670edbc40237b9e347d7e1f88e4620af836" + through2@2.0.3, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3: version "2.0.3" resolved "http://registry.npm.taobao.org/through2/download/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" @@ -3060,6 +3741,10 @@ tmp@^0.0.31: dependencies: os-tmpdir "~1.0.1" +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + to-fast-properties@^1.0.1: version "1.0.3" resolved "http://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -3068,12 +3753,16 @@ toastr@^2.1.2: version "2.1.2" resolved "http://registry.npm.taobao.org/toastr/download/toastr-2.1.2.tgz#fd69066ae7578a5b3357725fc9c7c335e9b681df" -tough-cookie@~2.3.0: +tough-cookie@^2.3.2, tough-cookie@~2.3.0: version "2.3.2" resolved "http://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" dependencies: punycode "^1.4.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + trim-newlines@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/trim-newlines/download/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -3106,6 +3795,15 @@ typedarray@^0.0.6: version "0.0.6" resolved "http://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +uglify-js@^2.6: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + uglify-js@^3.0.5: version "3.0.24" resolved "http://registry.npm.taobao.org/uglify-js/download/uglify-js-3.0.24.tgz#ee93400ad9857fb7a1671778db83f6a23f033121" @@ -3113,6 +3811,10 @@ uglify-js@^3.0.5: commander "~2.9.0" source-map "~0.5.1" +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + unc-path-regex@^0.1.0: version "0.1.2" resolved "http://registry.npm.taobao.org/unc-path-regex/download/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -3197,6 +3899,37 @@ vinyl@^2.0.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" +walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + dependencies: + makeerror "1.0.x" + +watch@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + +webidl-conversions@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0" + +whatwg-encoding@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4" + dependencies: + iconv-lite "0.4.13" + +whatwg-url@^4.3.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-module@^1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/which-module/download/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -3213,10 +3946,29 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2" +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + wordwrap@~1.0.0: version "1.0.0" resolved "http://registry.npm.taobao.org/wordwrap/download/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" +worker-farm@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.4.1.tgz#a438bc993a7a7d133bcb6547c95eca7cff4897d8" + dependencies: + errno "^0.1.4" + xtend "^4.0.1" + wrap-ansi@^2.0.0: version "2.1.0" resolved "http://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -3234,7 +3986,11 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" -"xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.1: +xml-name-validator@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.1, xtend@~4.0.1: version "4.0.1" resolved "http://registry.npm.taobao.org/xtend/download/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -3252,7 +4008,7 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" -yargs@^7.0.0: +yargs@^7.0.0, yargs@^7.0.2: version "7.1.0" resolved "http://registry.npm.taobao.org/yargs/download/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" dependencies: @@ -3270,6 +4026,15 @@ yargs@^7.0.0: y18n "^3.2.1" yargs-parser "^5.0.0" +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + yazl@^2.1.0: version "2.4.2" resolved "http://registry.npm.taobao.org/yazl/download/yazl-2.4.2.tgz#14cb19083e1e25a70092c1588aabe0f4e4dd4d88"