diff --git a/js/test/Alert.js b/js/test/Alert.js
new file mode 100644
index 00000000..2ee42de6
--- /dev/null
+++ b/js/test/Alert.js
@@ -0,0 +1,223 @@
+'use strict';
+var common = require('../common');
+
+describe('Alert', function () {
+ describe('showStatus', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows a status message',
+ jsc.array(common.jscAlnumString()),
+ jsc.array(common.jscAlnumString()),
+ function (icon, message) {
+ icon = icon.join('');
+ message = message.join('');
+ var expected = '
' + message + '
';
+ $('body').html(
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showStatus(message, icon);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('showError', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows an error message',
+ jsc.array(common.jscAlnumString()),
+ jsc.array(common.jscAlnumString()),
+ function (icon, message) {
+ icon = icon.join('');
+ message = message.join('');
+ var expected = ' ' + message + '
';
+ $('body').html(
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showError(message, icon);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('showRemaining', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows remaining time',
+ jsc.array(common.jscAlnumString()),
+ jsc.array(common.jscAlnumString()),
+ 'integer',
+ function (message, string, number) {
+ message = message.join('');
+ string = string.join('');
+ var expected = '' +
+ ' ' + string + message + number + '
';
+ $('body').html(
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showRemaining(['%s' + message + '%d', string, number]);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('showLoading', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows a loading message',
+ jsc.array(common.jscAlnumString()),
+ jsc.array(common.jscAlnumString()),
+ 'integer',
+ function (icon, message, number) {
+ icon = icon.join('');
+ message = message.join('');
+ var default_message = 'Loading…';
+ if (message.length == 0) {
+ message = default_message;
+ }
+ var expected = '';
+ $('body').html(
+ '- ' +
+ default_message + '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.showLoading(message, number, icon);
+ var result = $('body').html();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('hideLoading', function () {
+ before(function () {
+ cleanup();
+ });
+
+ it(
+ 'hides the loading message',
+ function() {
+ $('body').html(
+ ''
+ );
+ $('body').addClass('loading');
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.hideLoading();
+ return !$('body').hasClass('loading') &&
+ $('#loadingindicator').hasClass('hidden');
+ }
+ );
+ });
+
+ describe('hideMessages', function () {
+ before(function () {
+ cleanup();
+ });
+
+ it(
+ 'hides all messages',
+ function() {
+ $('body').html(
+ '
' +
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.hideMessages();
+ return $('#statusmessage').hasClass('hidden') &&
+ $('#errormessage').hasClass('hidden');
+ }
+ );
+ });
+
+ describe('setCustomHandler', function () {
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'calls a given handler function',
+ 'nat 3',
+ jsc.array(common.jscAlnumString()),
+ function (trigger, message) {
+ message = message.join('');
+ var handlerCalled = false,
+ default_message = 'Loading…',
+ functions = [
+ $.PrivateBin.Alert.showStatus,
+ $.PrivateBin.Alert.showError,
+ $.PrivateBin.Alert.showRemaining,
+ $.PrivateBin.Alert.showLoading
+ ];
+ if (message.length == 0) {
+ message = default_message;
+ }
+ $('body').html(
+ '- ' +
+ default_message + '
' +
+ '
' +
+ '
' +
+ '
'
+ );
+ $.PrivateBin.Alert.init();
+ $.PrivateBin.Alert.setCustomHandler(function(id, $element) {
+ handlerCalled = true;
+ return jsc.random(0, 1) ? true : $element;
+ });
+ functions[trigger](message);
+ return handlerCalled;
+ }
+ );
+ });
+});
+
diff --git a/js/test/Editor.js b/js/test/Editor.js
new file mode 100644
index 00000000..60e424f1
--- /dev/null
+++ b/js/test/Editor.js
@@ -0,0 +1,74 @@
+'use strict';
+var common = require('../common');
+
+describe('Editor', function () {
+ describe('show, hide, getText, setText & isPreview', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'returns text fed into the textarea, handles editor tabs',
+ 'string',
+ function (text) {
+ var clean = jsdom(),
+ results = [];
+ $('body').html(
+ '+++ no paste text +++
' +
+ ''
+ );
+ $.PrivateBin.Editor.init();
+ results.push(
+ $('#editorTabs').hasClass('hidden') &&
+ $('#message').hasClass('hidden')
+ );
+ $.PrivateBin.Editor.show();
+ results.push(
+ !$('#editorTabs').hasClass('hidden') &&
+ !$('#message').hasClass('hidden')
+ );
+ $.PrivateBin.Editor.hide();
+ results.push(
+ $('#editorTabs').hasClass('hidden') &&
+ $('#message').hasClass('hidden')
+ );
+ $.PrivateBin.Editor.show();
+ $.PrivateBin.Editor.focusInput();
+ results.push(
+ $.PrivateBin.Editor.getText().length == 0
+ );
+ $.PrivateBin.Editor.setText(text);
+ results.push(
+ $.PrivateBin.Editor.getText() == $('#message').val()
+ );
+ $.PrivateBin.Editor.setText();
+ results.push(
+ !$.PrivateBin.Editor.isPreview() &&
+ !$('#message').hasClass('hidden')
+ );
+ $('#messagepreview').click();
+ results.push(
+ $.PrivateBin.Editor.isPreview() &&
+ $('#message').hasClass('hidden')
+ );
+ $('#messageedit').click();
+ results.push(
+ !$.PrivateBin.Editor.isPreview() &&
+ !$('#message').hasClass('hidden')
+ );
+ clean();
+ return results.every(element => element);
+ }
+ );
+ });
+});
+
diff --git a/js/test/PasteStatus.js b/js/test/PasteStatus.js
new file mode 100644
index 00000000..dbc21483
--- /dev/null
+++ b/js/test/PasteStatus.js
@@ -0,0 +1,104 @@
+'use strict';
+var common = require('../common');
+
+describe('PasteStatus', function () {
+ describe('createPasteNotification', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'creates a notification after a successfull paste upload',
+ common.jscSchemas(),
+ jsc.nearray(common.jscA2zString()),
+ jsc.array(common.jscQueryString()),
+ 'string',
+ common.jscSchemas(),
+ jsc.nearray(common.jscA2zString()),
+ jsc.array(common.jscQueryString()),
+ function (
+ schema1, address1, query1, fragment1,
+ schema2, address2, query2
+ ) {
+ var expected1 = schema1 + '://' + address1.join('') + '/?' +
+ encodeURI(query1.join('').replace(/^&+|&+$/gm,'') + '#' + fragment1),
+ expected2 = schema2 + '://' + address2.join('') + '/?' +
+ encodeURI(query2.join('')),
+ clean = jsdom();
+ $('body').html('');
+ $.PrivateBin.PasteStatus.init();
+ $.PrivateBin.PasteStatus.createPasteNotification(expected1, expected2);
+ var result1 = $('#pasteurl')[0].href,
+ result2 = $('#deletelink a')[0].href;
+ clean();
+ return result1 == expected1 && result2 == expected2;
+ }
+ );
+ });
+
+ describe('showRemainingTime', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'shows burn after reading message or remaining time',
+ 'bool',
+ 'nat',
+ jsc.nearray(common.jscA2zString()),
+ jsc.nearray(common.jscA2zString()),
+ jsc.nearray(common.jscQueryString()),
+ 'string',
+ function (
+ burnafterreading, remaining_time,
+ schema, address, query, fragment
+ ) {
+ var clean = jsdom('', {
+ url: schema.join('') + '://' + address.join('') +
+ '/?' + query.join('') + '#' + fragment
+ });
+ $('body').html('');
+ $.PrivateBin.PasteStatus.init();
+ $.PrivateBin.PasteStatus.showRemainingTime({
+ 'burnafterreading': burnafterreading,
+ 'remaining_time': remaining_time,
+ 'expire_date': remaining_time ? ((new Date()).getTime() / 1000) + remaining_time : 0
+ });
+ if (burnafterreading) {
+ var result = $('#remainingtime').hasClass('foryoureyesonly') &&
+ !$('#remainingtime').hasClass('hidden');
+ } else if (remaining_time) {
+ var result =!$('#remainingtime').hasClass('foryoureyesonly') &&
+ !$('#remainingtime').hasClass('hidden');
+ } else {
+ var result = $('#remainingtime').hasClass('hidden') &&
+ !$('#remainingtime').hasClass('foryoureyesonly');
+ }
+ clean();
+ return result;
+ }
+ );
+ });
+
+ describe('hideMessages', function () {
+ before(function () {
+ cleanup();
+ });
+
+ it(
+ 'hides all messages',
+ function() {
+ $('body').html(
+ ''
+ );
+ $.PrivateBin.PasteStatus.init();
+ $.PrivateBin.PasteStatus.hideMessages();
+ return $('#remainingtime').hasClass('hidden') &&
+ $('#pastesuccess').hasClass('hidden');
+ }
+ );
+ });
+});
+
diff --git a/js/test/Prompt.js b/js/test/Prompt.js
new file mode 100644
index 00000000..d093b716
--- /dev/null
+++ b/js/test/Prompt.js
@@ -0,0 +1,40 @@
+'use strict';
+var common = require('../common');
+
+describe('Prompt', function () {
+ // TODO: this does not test the prompt() fallback, since that isn't available
+ // in nodejs -> replace the prompt in the "page" template with a modal
+ describe('requestPassword & getPassword', function () {
+ this.timeout(30000);
+ before(function () {
+ cleanup();
+ });
+
+ jsc.property(
+ 'returns the password fed into the dialog',
+ 'string',
+ function (password) {
+ password = password.replace(/\r+/g, '');
+ var clean = jsdom('', {url: 'ftp://example.com/#0'});
+ $('body').html(
+ '{}
'
+ );
+ $.PrivateBin.Model.init();
+ $.PrivateBin.Prompt.init();
+ $.PrivateBin.Prompt.requestPassword();
+ $('#passworddecrypt').val(password);
+ $('#passwordform').submit();
+ var result = $.PrivateBin.Prompt.getPassword();
+ clean();
+ return result == password;
+ }
+ );
+ });
+});
+
diff --git a/js/test/UiHelper.js b/js/test/UiHelper.js
new file mode 100644
index 00000000..013d062f
--- /dev/null
+++ b/js/test/UiHelper.js
@@ -0,0 +1,124 @@
+'use strict';
+var common = require('../common');
+
+describe('UiHelper', function () {
+ // TODO: As per https://github.com/tmpvar/jsdom/issues/1565 there is no navigation support in jsdom, yet.
+ // for now we use a mock function to trigger the event
+ describe('historyChange', function () {
+ this.timeout(30000);
+ before(function () {
+ $.PrivateBin.Helper.reset();
+ cleanup();
+ });
+
+ jsc.property(
+ 'redirects to home, when the state is null',
+ common.jscSchemas(),
+ jsc.nearray(common.jscA2zString()),
+ function (schema, address) {
+ var expected = schema + '://' + address.join('') + '/',
+ clean = jsdom('', {url: expected});
+
+ // make window.location.href writable
+ Object.defineProperty(window.location, 'href', {
+ writable: true,
+ value: window.location.href
+ });
+ $.PrivateBin.UiHelper.mockHistoryChange();
+ $.PrivateBin.Helper.reset();
+ var result = window.location.href;
+ clean();
+ return expected === result;
+ }
+ );
+
+ jsc.property(
+ 'does not redirect to home, when a new paste is created',
+ common.jscSchemas(),
+ jsc.nearray(common.jscA2zString()),
+ jsc.array(common.jscQueryString()),
+ jsc.nearray(common.jscBase64String()),
+ function (schema, address, query, fragment) {
+ var expected = schema + '://' + address.join('') + '/' + '?' +
+ query.join('') + '#' + fragment.join(''),
+ clean = jsdom('', {url: expected});
+
+ // make window.location.href writable
+ Object.defineProperty(window.location, 'href', {
+ writable: true,
+ value: window.location.href
+ });
+ $.PrivateBin.UiHelper.mockHistoryChange([
+ {type: 'newpaste'}, '', expected
+ ]);
+ $.PrivateBin.Helper.reset();
+ var result = window.location.href;
+ clean();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('reloadHome', function () {
+ this.timeout(30000);
+ before(function () {
+ $.PrivateBin.Helper.reset();
+ });
+
+ jsc.property(
+ 'redirects to home',
+ common.jscSchemas(),
+ jsc.nearray(common.jscA2zString()),
+ jsc.array(common.jscQueryString()),
+ jsc.nearray(common.jscBase64String()),
+ function (schema, address, query, fragment) {
+ var expected = schema + '://' + address.join('') + '/',
+ clean = jsdom('', {
+ url: expected + '?' + query.join('') + '#' + fragment.join('')
+ });
+
+ // make window.location.href writable
+ Object.defineProperty(window.location, 'href', {
+ writable: true,
+ value: window.location.href
+ });
+ $.PrivateBin.UiHelper.reloadHome();
+ $.PrivateBin.Helper.reset();
+ var result = window.location.href;
+ clean();
+ return expected === result;
+ }
+ );
+ });
+
+ describe('isVisible', function () {
+ // TODO As per https://github.com/tmpvar/jsdom/issues/1048 there is no layout support in jsdom, yet.
+ // once it is supported or a workaround is found, uncomment the section below
+ /*
+ before(function () {
+ $.PrivateBin.Helper.reset();
+ });
+
+ jsc.property(
+ 'detect visible elements',
+ jsc.nearray(common.jscAlnumString()),
+ jsc.nearray(common.jscA2zString()),
+ function (id, element) {
+ id = id.join('');
+ element = element.join('');
+ var clean = jsdom(
+ '<' + element + ' id="' + id + '">' + element + '>'
+ );
+ var result = $.PrivateBin.UiHelper.isVisible($('#' + id));
+ clean();
+ return result;
+ }
+ );
+ */
+ });
+
+ describe('scrollTo', function () {
+ // TODO Did not find a way to test that, see isVisible test above
+ });
+});
+