Add unit test for JavaScript files

This commit is contained in:
gplane 2017-07-20 15:36:59 +08:00
parent a8ac404c98
commit d1faaf64f5
27 changed files with 3740 additions and 95 deletions

View File

@ -25,9 +25,14 @@ module.exports = {
"getQueryString": false,
"TexturePreview": false
},
"parserOptions": {
"ecmaVersion": 2017
},
"env":{
"commonjs": true,
"es6": true,
"browser": true,
"jest": true,
"jquery": true
}
};

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.env
.sass-cache
coverage
vendor/*
plugins/*
storage/textures/*

View File

@ -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"
}
}

View File

@ -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 = `
<div id="layout-skins-list">
<a data-skin="skin-purple"></a>
</div>`;
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 = '<button id="color-submit">submit</button>';
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 = `
<div id="1">
<div><select>
<option value="default" selected></option>
<option value="slim"></option>
</select></div>
</div>
`;
$('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 = `
<div class="modal" style="display: none" id="shouldBeRemoved"></div>
<div class="modal" id="shouldNotBeRemoved"></div>
<input id="model" value="default" />
<input id="tid" value="1" />
<img id="1-default" src="" />
`;
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 = `
<table>
<thead></thead>
<tbody>
<tr id="1">
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
`;
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 = `
<table>
<thead></thead>
<tbody>
<tr id="1">
<td></td>
<td>1</td>
<td></td>
</tr>
</tbody>
</table>
<div>
<input class="swal2-input" />
<div class="swal2-content"></div>
</div>
`;
const changeOwner = require(modulePath).changeOwner;
await changeOwner(1, 'oldName');
expect(debounce).toBeCalled();
expect(swal).toBeCalledWith({
html: 'admin.changePlayerOwner<br><small>&nbsp;</small>',
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 = `
<table>
<thead></thead>
<tbody>
<tr id="1">
</tr>
</tbody>
</table>
`;
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 = `
<div id="file-size"></div>
<div id="modal-start-download"></div>
`;
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 = `
<table>
<tbody>
<tr id="user-1">
<td></td>
<td>d@e.f</td>
</tr>
</tbody>
</table>
`;
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 = `
<table>
<tbody>
<tr id="user-1">
<td></td>
<td></td>
<td>hhh</td>
</tr>
</tbody>
</table>
`;
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 = `
<table>
<tbody>
<tr id="user-1">
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
`;
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 = `
<table>
<tbody>
<tr id="user-1">
<td class="status"></td>
<td id="ban-1" data="banned"></td>
</tr>
</tbody>
</table>
`;
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 = `
<table>
<tbody>
<tr id="user-1">
<td class="status"></td>
<td id="ban-1" data="normal"></td>
</tr>
</tbody>
</table>
`;
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 = `
<table>
<tbody>
<tr id="user-1">
<td class="status"></td>
<td id="admin-1" data="admin"></td>
</tr>
</tbody>
</table>
`;
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 = `
<table>
<tbody>
<tr id="user-1">
<td class="status"></td>
<td id="admin-1" data="normal"></td>
</tr>
</tbody>
</table>
`;
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 = '<tr id="user-1"></tr>';
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 = `
<div id="user-1">
<div>
<input class="score" type="number" value="0" />
</div>
</div>
`;
require(modulePath);
$('.score').focus();
const event = $.Event('keypress');
event.which = 13;
$('.score').trigger(event);
expect($('.score').is(':focus')).toBe(false);
});
});

View File

@ -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 = `
<img class="captcha" src="" />
<input id="captcha" value="old" />
`;
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 = `
<input id="identification" />
<input id="password" />
<div id="captcha-form"></div>
<input id="captcha" />
<input id="keep" checked />
<button id="login-button"></button>
`;
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(
'<i class="fa fa-spinner fa-spin"></i> 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 = `
<input id="email" />
<input id="nickname" />
<input id="password" />
<input id="confirm-pwd" />
<div id="captcha-form"></div>
<input id="captcha" />
<button id="register-button"></button>
`;
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(
'<i class="fa fa-spinner fa-spin"></i> 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 = `
<input id="email" />
<div id="captcha-form"></div>
<input id="captcha" />
<button id="forgot-button"></button>
`;
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 = `
<input id="uid" value="1" />
<input id="password" />
<input id="confirm-pwd" />
<button id="reset-button"></button>
`;
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(
'<i class="fa fa-spinner fa-spin"></i> 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');
});
});

View File

@ -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 = '<button id="logout-button"></button>';
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 = '<div id="msg" class="a-class"></div>';
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');
});
});

View File

@ -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 = `
<div class="overlay"></div>
<div id="skinlib-container"></div>
<div id="skinlib-paginator"></div>
`;
const renderSkinlib = require(modulePath).renderSkinlib;
renderSkinlib([]);
expect($('#skinlib-container').html()).toBe(
'<p style="text-align: center; margin: 30px 0;">general.noResult</p>'
);
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 = `
<p class="pagination"></p>
<div id="skinlib-paginator"></div>
<select class="pagination"></select>
`;
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 = `
<div id="filter-indicator"></div>
<div id="uploader-indicator"></div>
<div id="sort-indicator"></div>
<div id="search-indicator"></div>
<input id="navbar-search-input" />
`;
const updateBreadCrumb = require(modulePath).updateBreadCrumb;
updateBreadCrumb();
expect($('#filter-indicator').html().replace(/\s/g, '')).toBe(
'general.skin<small>skinlib.filter.skin</small>'
);
$.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 = `
<div id="skinlib-paginator"></div>
`;
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 = `
<ul>
<li class="locale" data-code="zh_CN"><a></a></li>
<li class="locale" data-code="en"><a></a></li>
</ul>
`;
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 = `
<div class="modal" style="display: none" id="shouldBeRemoved"></div>
<div class="modal" id="shouldNotBeRemoved"></div>
`;
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 = '<div id="name"></div>';
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 = `
<div id="likes">5</div>
<a tid="1"></a>
<a id="1"></a>
`;
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 = '<a class="private-label"></a>';
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 = `
<a id="1">skinlib.setAsPrivate</a>
<a id="2">skinlib.setAsPublic</a>
`;
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' });
});
});

View File

@ -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 = `
<div id="textures-indicator"></div>
<div id="prev" class="item-selected">
<div class="item-body item-selected"></div>
</div>
<div id="next" tid="1">
<div class="item-body"></div>
</div>
`;
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 = `
<div id="textures-indicator"></div>
<div id="prev" class="item-selected">
<div class="item-body item-selected"></div>
</div>
<div id="next" tid="2">
<div class="item-body"></div>
</div>
`;
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 = `
<input name="q" />
<div id="skin-category"></div>
<div id="closet-paginator"></div>
`;
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(
'<div class="empty-msg">user.emptyClosetMsg</div>'
);
$('input').val('q');
renderCloset([], 'skin');
expect($('#skin-category').html()).toBe(
'<div class="empty-msg">general.noResult</div>'
);
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(
'<span title="name">name <small>(steve)</small></span>'
);
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 = `
<div id="skin-category">
<div tid="1">
<div class="item-footer">
<div class="texture-name">
<span></span>
</div>
</div>
</div>
</div>
<div id="closet-paginator" last-skin-page="0"></div>
`;
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 = `
<div id="skin-category">
<div tid="1">
<div class="item-footer">
<div class="texture-name">
<span></span>
</div>
</div>
</div>
</div>
`;
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 = `
<div id="skin-category">
<div id="shouldBeRemoved" tid="1"></div>
</div>
`;
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(
'<div class="empty-msg">user.emptyClosetMsg</div>'
);
});
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 = `
<img alt="User Image" src="src" />
`;
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 = `
<div id="1" class="player-selected player"></div>
<div id="2" class="player"></div>
`;
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 id="preference" pid="1">
<option value="default" selected></option>
<option value="slim"></option>
</select>
`;
$('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 = `
<input id="player_name" placeholder="placeholder" />
<table>
<tbody>
<td>1</td>
<td id="player-name">old</td>
</tbody>
</table>
`;
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 = `
<div class="modal" id="shouldBeRemoved" style="display: none"></div>
<div class="modal" id="shouldNotBeRemoved"></div>
<input id="clear-steve">
<input id="clear-alex">
<input id="clear-cape">
`;
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 = `
<tr id="1"></tr>
`;
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 = `
<input id="player_name" value="name" />
`;
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 = `
<input name="player" id="1" />
`;
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 = `
<div class="nickname"></div>
<input id="new-nickname" />
`;
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 = `
<input id="password" />
<input id="new-passwd" />
<input id="confirm-pwd" />
`;
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 = `
<input id="new-email" />
<input id="current-password" value="pwd" />
`;
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 = `
<div class="modal-body">
<input id="password" />
</div>
`;
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 = `
<div id="score"></div>
<a id="sign-button"></a>
<div id="user-storage"></div>
<div id="user-storage-bar"></div>
`;
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(
'<i class="fa fa-calendar-check-o"></i> &nbsp;user.signRemainingTime'
);
expect($('#sign-button').attr('disabled')).toBe('disabled');
expect($('#user-storage').html()).toBe('<b>50</b>/ 100 KB');
expect($('#user-storage-bar').css('width')).toBe('50%');
expect(swal).toBeCalledWith({ type: 'success', html: 'success' });
await sign();
expect($('#user-storage').html()).toBe('<b>2</b>/ 4 MB');
await sign();
expect(toastr.warning).toBeCalledWith('warning');
});
});

View File

@ -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;
}

View File

@ -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
};
}

View File

@ -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
};
}

View File

@ -89,3 +89,7 @@ function downloadUpdates() {
}).catch(err => showAjaxError(err));
}
if (typeof require !== 'undefined' && typeof module !== 'undefined') {
module.exports = downloadUpdates;
}

View File

@ -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
};
}

View File

@ -9,3 +9,7 @@ function refreshCaptcha() {
}
$('.captcha').click(refreshCaptcha);
if (typeof require !== 'undefined' && typeof module !== 'undefined') {
module.exports = refreshCaptcha;
}

View File

@ -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(
'<i class="fa fa-spinner fa-spin"></i> ' + 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', '');
});
});
});

View File

@ -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(
'<i class="fa fa-spinner fa-spin"></i> ' + 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();

View File

@ -49,3 +49,10 @@ function trans(key, parameters = {}) {
return temp;
}
if (typeof require !== 'undefined' && typeof module !== 'undefined') {
module.exports = {
loadLocales,
trans
};
}

View File

@ -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
};
}

View File

@ -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;
}

View File

@ -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
};
}

View File

@ -63,9 +63,11 @@ function renderSkinlib(items) {
if (items.length === 0) {
$('#skinlib-paginator').hide();
container.html(`<p style="text-align: center; margin: 30px 0;">
${ trans('general.noResult') }
</p>`);
container.html(
'<p style="text-align: center; margin: 30px 0;">' +
trans('general.noResult') +
'</p>'
);
} 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
};
}

View File

@ -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
};
}

View File

@ -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
};
}

View File

@ -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
};
}

View File

@ -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
};
}

View File

@ -13,7 +13,9 @@ function sign() {
$('#sign-button').attr('disabled', 'disabled').html(dom);
if (result.storage.used > 1024) {
$('#user-storage').html(`<b>${Math.round(result.storage.used)}</b>/ ${Math.round(result.storage.total)} MB`);
$('#user-storage').html(
`<b>${Math.round(result.storage.used / 1024)}</b>/ ${Math.round(result.storage.total / 1024)} MB`
);
} else {
$('#user-storage').html(`<b>${Math.round(result.storage.used)}</b>/ ${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;
}

823
yarn.lock

File diff suppressed because it is too large Load Diff