diff --git a/.gitignore b/.gitignore index fa5ff5c4f07a6..edb129f856d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,9 @@ tests/codeception/tests/acceptance/*Tester.php tests/codeception/tests/functional/*Tester.php tests/codeception/tests/unit/*Tester.php +# Node modules # +node_modules/ + # phpDocumentor Logs # phpdoc-* diff --git a/.travis.yml b/.travis.yml index fbbdd311e6c35..41e0daceb0f34 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ language: php env: global: - RUN_PHPCS="no" + - RUN_JAVASCRIPT_TESTS="no" - INSTALL_MEMCACHE="yes" - INSTALL_MEMCACHED="yes" - INSTALL_REDIS="yes" @@ -13,6 +14,9 @@ env: matrix: fast_finish: true include: + - node_js: 6.1 + sudo: true + env: RUN_JAVASCRIPT_TESTS="yes" - php: 5.3 env: INSTALL_APC="yes" - php: 5.4 @@ -50,6 +54,8 @@ services: - redis-server before_script: + # JavaScript tests + - if [[ $RUN_JAVASCRIPT_TESTS == "yes" ]]; then export DISPLAY=:99.0; bash tests/javascript/travis-tests.sh $PWD; fi # Make sure all dev dependencies are installed - composer install # Set up databases for testing @@ -74,6 +80,7 @@ before_script: script: - libraries/vendor/bin/phpunit --configuration travisci-phpunit.xml - if [[ $RUN_PHPCS == "yes" ]]; then libraries/vendor/bin/phpcs --report=full --extensions=php -p --standard=build/phpcs/Joomla .; fi + - if [[ $RUN_JAVASCRIPT_TESTS == "yes" ]]; then tests/javascript/node_modules/karma/bin/karma start karma.conf.js --single-run ; fi branches: except: diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000000000..9446fd44c157e --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,84 @@ +// Karma configuration + +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine-ajax', 'jasmine', 'requirejs'], + + // list of files / patterns to load in the browser + files: [ + {pattern: 'tests/javascript/node_modules/jquery/dist/jquery.min.js', included: false}, + {pattern: 'tests/javascript/node_modules/jasmine-jquery/lib/jasmine-jquery.js', included: false}, + {pattern: 'tests/javascript/node_modules/text/text.js', included: false}, + {pattern: 'media/jui/js/bootstrap.min.js', included: false}, + {pattern: 'media/system/js/*.js', included: false}, + {pattern: 'tests/javascript/**/fixture.html', included: false}, + {pattern: 'tests/javascript/**/spec.js', included: false}, + {pattern: 'tests/javascript/**/spec-setup.js', included: false}, + {pattern: 'images/*.png', included: false}, + + 'tests/javascript/test-main.js' + ], + + // list of files to exclude + exclude: [ + 'media/system/js/*uncompressed.js' + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + '**/system/js/*!(uncompressed).js': ['coverage'] + }, + + // coverage reporter configuration + coverageReporter: { + type : 'text' + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['verbose', 'coverage'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Firefox'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // list of plugins + plugins: [ + 'karma-jasmine', + 'karma-jasmine-ajax', + 'karma-firefox-launcher', + 'karma-coverage', + 'karma-requirejs', + 'karma-verbose-reporter' + ], + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }); +}; diff --git a/tests/javascript/caption/fixtures/fixture.html b/tests/javascript/caption/fixtures/fixture.html new file mode 100644 index 0000000000000..6ed385eb316d1 --- /dev/null +++ b/tests/javascript/caption/fixtures/fixture.html @@ -0,0 +1,22 @@ +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+ + + + + +
+
diff --git a/tests/javascript/caption/spec-setup.js b/tests/javascript/caption/spec-setup.js new file mode 100644 index 0000000000000..f11d37fa2938e --- /dev/null +++ b/tests/javascript/caption/spec-setup.js @@ -0,0 +1,17 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'text!testsRoot/caption/fixtures/fixture.html', 'libs/caption'], function ($, fixture) { + $('body').append(fixture); + + new JCaption('#single img.test'); + new JCaption('#multiple img.test'); + new JCaption('#empty img.test'); + new JCaption('#options img.test'); +}); diff --git a/tests/javascript/caption/spec.js b/tests/javascript/caption/spec.js new file mode 100644 index 0000000000000..64d50a1436635 --- /dev/null +++ b/tests/javascript/caption/spec.js @@ -0,0 +1,68 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'testsRoot/caption/spec-setup', 'jasmineJquery'], function ($) { + + describe('JCaption applied to single image', function () { + it('Should have caption as "Joomla Title 1" under image', function () { + expect($('#single').find('p')).toHaveText('Joomla Title 1'); + }); + }); + + describe('JCaption applied for multiple images', function () { + it('Should have caption "Joomla Title 1" under image 1', function () { + expect($('#multiple').find('p').first()).toHaveText('Joomla Title 1'); + }); + + it('Should have caption as "Joomla Title 2" under image 2', function() { + expect($('#multiple').find('p').last()).toHaveText('Joomla Title 2'); + }); + }); + + describe('JCaption with empty title attribute value', function () { + it('Should not have a

element inside the image container', function () { + expect($('#empty')).not.toContainElement('p'); + }); + }); + + describe('JCaption with no additional options', function () { + var $element = $('img#no-options'); + it('Should have container CSS {float: none}', function () { + expect($element.parent()).toHaveCss({ + float: 'none' + }); + }); + }); + + describe('JCaption with additional options', function () { + it('Should have 2 elements with class right', function () { + expect($('#options').find('.right').length).toEqual(2); + }); + + it('Should have container width as 100 when element width attribute is set to 100', function () { + expect($('img#width-attr').parent().width()).toEqual(100); + }); + + it('Should have container width as 90 when element style is set to width: 90px', function () { + expect($('img#width-style').parent().width()).toEqual(90); + }); + + it('Should have float: right in container CSS when element attribute align is set to right', function () { + expect($('img#align-attr').parent()).toHaveCss({ + float: 'right' + }); + }); + + it('Should have float: right in container CSS when element style is set to float: right', function () { + expect($('img#align-style').parent()).toHaveCss({ + float: 'right' + }); + }); + }); +}); diff --git a/tests/javascript/core/fixtures/fixture.html b/tests/javascript/core/fixtures/fixture.html new file mode 100644 index 0000000000000..c7e001fa3c389 --- /dev/null +++ b/tests/javascript/core/fixtures/fixture.html @@ -0,0 +1,39 @@ +

+
+
+
+
+ + + + + +
+
+ +
+ + + + +
+
+ + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
diff --git a/tests/javascript/core/spec-setup.js b/tests/javascript/core/spec-setup.js new file mode 100644 index 0000000000000..eee57e2adf70b --- /dev/null +++ b/tests/javascript/core/spec-setup.js @@ -0,0 +1,12 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'text!testsRoot/core/fixtures/fixture.html', 'libs/core'], function ($, fixture) { + $('body').append(fixture); +}); diff --git a/tests/javascript/core/spec.js b/tests/javascript/core/spec.js new file mode 100644 index 0000000000000..19ac3684b5c89 --- /dev/null +++ b/tests/javascript/core/spec.js @@ -0,0 +1,199 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'testsRoot/core/spec-setup', 'jasmineJquery'], function ($) { + + describe('Core Joomla.submitform', function () { + var form = document.getElementById('adminForm'); + form.task = {}; + + beforeEach(function () { + spyOnEvent('#adminForm', 'submit'); + form.removeChild = jasmine.createSpy('removeChild'); + + Joomla.submitform('article.add', form, true); + }); + + it('should assign task to form.task.value', function () { + expect(form.task.value).toEqual('article.add'); + }); + it('should set attribute novalidate to false', function () { + expect($(form)).toHaveAttr('novalidate', 'false'); + }); + it('should add input submit button to DOM', function () { + expect($('#adminForm')).toContainElement('input[type="submit"]'); + }); + it('should make the added input element invisible', function () { + expect($('#adminForm').children('input[type="submit"]')).not.toBeVisible(); + }); + it('should click the input element', function () { + expect('submit').toHaveBeenTriggeredOn('#adminForm'); + }); + it('should remove the input element', function () { + expect(form.removeChild).toHaveBeenCalled(); + }); + }); + + describe('Core Joomla.JText', function () { + var ob = { + 'JTOGGLE_SHOW_SIDEBAR': 'Show Sidebar', + 'Jtoggle_Hide_Sidebar': 'Hide Sidebar' + }; + + beforeAll(function () { + Joomla.JText.load(ob); + }); + + it('should add content passed via load() to the strings object', function () { + expect(Joomla.JText.strings.JTOGGLE_SHOW_SIDEBAR).toEqual('Show Sidebar'); + expect(Joomla.JText.strings.JTOGGLE_HIDE_SIDEBAR).toEqual('Hide Sidebar'); + }); + it('should return \'Show Sidebar\' on calling Joomla.JText._(\'JTOGGLE_SHOW_SIDEBAR\', \'test\')', function () { + expect(Joomla.JText._('JTOGGLE_SHOW_SIDEBAR', 'test')).toEqual('Show Sidebar'); + }); + it('should return \'Show Sidebar\' on calling Joomla.JText._(\'Jtoggle_Show_Sidebar\', \'test\')', function () { + expect(Joomla.JText._('Jtoggle_Show_Sidebar', 'test')).toEqual('Show Sidebar'); + }); + it('should return \'test\' on calling Joomla.JText._(\'JTOGGLE_REMOVE_SIDEBAR\', \'test\')', function () { + expect(Joomla.JText._('JTOGGLE_REMOVE_SIDEBAR', 'test')).toEqual('test'); + }); + }); + + describe('Core Joomla.replaceTokens', function () { + var newToken = '123456789123456789123456789ABCDE'; + + beforeAll(function () { + Joomla.replaceTokens(newToken); + }); + + it('should set name of all hidden input elements with value = 1 and name = old token to new token', function () { + var $elements =$('.replace-tokens-input'); + expect($elements[0].name).toEqual(newToken); + expect($elements[1].name).toEqual(newToken); + }); + it('should not set name of non hidden input elements to new token', function () { + expect($('.replace-tokens-input #invalid-type').name).not.toEqual(newToken); + }); + it('should not set name of input elements with value other than 1 to new token', function () { + expect($('.replace-tokens-input #invalid-value').name).not.toEqual(newToken); + }); + it('should not set name of input elements with invalid name to new token', function () { + expect($('.replace-tokens-input #invalid-name').name).not.toEqual(newToken); + }); + }); + + describe('Core Joomla.checkAll', function () { + var form = document.getElementById('check-all-form'); + form.boxchecked = {}; + var element = document.getElementById('cb0'); + + beforeAll(function () { + Joomla.checkAll(element); + }); + + it('should return false when input element is not inside a form', function () { + expect(Joomla.checkAll(document.getElementById('cb-no-form'))).toEqual(false); + }); + + it('should check all the checkboxes that has id starting with \'cb\' inside the form', function () { + expect($('#cb0')).toBeChecked(); + expect($('#cb1')).toBeChecked(); + expect($('#cb2')).toBeChecked(); + expect($('#no-cb3')).not.toBeChecked(); + }); + + it('should set the number of checked boxes in the form to form.boxchecked', function () { + expect(form.boxchecked.value).toEqual(3); + }); + + it('should use passed in stub to look for input elements', function () { + var element = document.getElementById('stub-check-test-1'); + Joomla.checkAll(element, 'stub'); + + expect($('#stub-check-test-1')).toBeChecked(); + expect($('#stub-check-test-2')).toBeChecked(); + }); + }); + + describe('Core Joomla.renderMessages and Joomla.removeMessages', function () { + var messages = { + "message": ["Message one", "Message two"], + "error": ["Error one", "Error two"] + }; + + beforeAll(function () { + Joomla.JText.load({"message": "Message"}); + Joomla.renderMessages(messages); + }); + + it('renderMessages should render titles when translated strings are available', function () { + expect($('h4.alert-heading').first()).toContainText('Message'); + }); + + it('renderMessages should render messages inside a div having class alert-message', function () { + var $messages = $('div.alert-success').children('div'); + expect($messages[0]).toContainText('Message two'); + expect($messages[1]).toContainText('Message one'); + }); + + it('renderMessages should render errors inside a div having class alert-error', function () { + var $messages = $('div.alert-error').children('div'); + expect($messages[0]).toContainText('Error two'); + expect($messages[1]).toContainText('Error one'); + }); + + it('removeMessages should remove all content from system-message-container', function () { + Joomla.removeMessages(); + expect($("#system-message-container")).toBeEmpty(); + }); + }); + + describe('Core Joomla.isChecked', function () { + var form = document.getElementById('ischecked-test-form'); + form.boxchecked = {value: 5}; + + beforeAll(function () { + Joomla.isChecked(true, form); + }); + + it('should increase form.boxchecked.value from 5 to 6', function () { + expect(form.boxchecked.value).toEqual(6); + }); + it('should set checkAllToggle.checked to false', function () { + expect(form.elements[ 'checkall-toggle' ].checked).toEqual(false); + }); + }); + + describe('Core Joomla.tableOrdering', function () { + beforeAll(function () { + submitformFn = Joomla.submitform; + Joomla.submitform = jasmine.createSpy('submitform'); + + this.form = document.getElementById('table-ordering-test-form'); + this.form.filter_order = {}; + this.form.filter_order_Dir = {}; + + Joomla.tableOrdering('order', 'dir', 'task', this.form); + }); + + afterAll(function() { + Joomla.submitform = submitformFn; + }); + + it('should call Joomla.submitform with params task and form', function () { + expect(Joomla.submitform).toHaveBeenCalledWith('task', this.form); + }); + it('should set form.filter_order.value = order', function () { + expect(this.form.filter_order.value).toEqual('order') + }); + it('should set form.filter_order_Dir.value = dir', function () { + expect(this.form.filter_order_Dir.value).toEqual('dir') + }); + }); +}); diff --git a/tests/javascript/package.json b/tests/javascript/package.json new file mode 100644 index 0000000000000..5efa1f25a0f67 --- /dev/null +++ b/tests/javascript/package.json @@ -0,0 +1,29 @@ +{ + "name": "joomla-cms", + "version": "1.0.0", + "description": "Joomla! CMS™ [![Analytics](https://ga-beacon.appspot.com/UA-544070-3/joomla-cms/readme)](https://github.com/igrigorik/ga-beacon)\r ====================", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "node node_modules/karma/bin/karma start ../../karma.conf.js --single-run" + }, + "author": "Thanuditha Ruchiranga Wickramasinghe", + "license": "GPL-2.0", + "private": true, + "devDependencies": { + "jasmine-core": "^2.4.1", + "jasmine-jquery": "^2.1.1", + "jquery": "^2.2.3", + "karma": "^0.13.22", + "karma-coverage": "^1.0.0", + "karma-firefox-launcher": "^0.1.7", + "karma-jasmine": "^0.3.8", + "karma-jasmine-ajax": "^0.1.13", + "karma-requirejs": "^1.0.0", + "karma-verbose-reporter": "0.0.3", + "requirejs": "^2.2.0", + "text": "requirejs/text" + } +} diff --git a/tests/javascript/permissions/fixtures/fixture.html b/tests/javascript/permissions/fixtures/fixture.html new file mode 100644 index 0000000000000..284c6059f9e06 --- /dev/null +++ b/tests/javascript/permissions/fixtures/fixture.html @@ -0,0 +1,18 @@ +
+ +
+ + +
+
+ + + +
+ + + +
+
+
+
diff --git a/tests/javascript/permissions/spec-setup.js b/tests/javascript/permissions/spec-setup.js new file mode 100644 index 0000000000000..90a76477d7ce3 --- /dev/null +++ b/tests/javascript/permissions/spec-setup.js @@ -0,0 +1,30 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'text!testsRoot/permissions/fixtures/fixture.html', 'libs/permissions', 'libs/core'], function ($, fixture) { + $('body').append(fixture); + + window.id = '0'; + window.value = '1'; + + event = {target: '#sendBtn'}; + + responses = { + success: { + status: 200, + statusText: 'HTTP/1.1 200 OK', + responseText: '{"data": {"result": true, "class": "test-class", "text": "Sample text"}, "messages": {}}' + }, + fail: { + status: 404, + statusText: 'HTTP/1.1 404 Not Found', + responseText: 'Error' + } + }; +}); diff --git a/tests/javascript/permissions/spec.js b/tests/javascript/permissions/spec.js new file mode 100644 index 0000000000000..0131d0a6fb896 --- /dev/null +++ b/tests/javascript/permissions/spec.js @@ -0,0 +1,135 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'testsRoot/permissions/spec-setup', 'jasmineJquery'], function ($) { + describe('sendPermissions', function () { + beforeAll(function() { + jasmine.Ajax.install(); + + renderFn = Joomla.renderMessages; + removeFn = Joomla.removeMessages; + jtxtFn = Joomla.JText._; + ajxerrFn = Joomla.ajaxErrorsMessages; + scrollFn = window.scrollTo; + + Joomla.JText._ = jasmine.createSpy('_'); + Joomla.renderMessages = jasmine.createSpy('renderMessages'); + Joomla.removeMessages = jasmine.createSpy('removeMessages'); + Joomla.ajaxErrorsMessages = jasmine.createSpy('ajaxErrorsMessages'); + window.scrollTo = jasmine.createSpy('scrollTo'); + + sendPermissions(event); + }); + + afterAll(function () { + jasmine.Ajax.uninstall(); + + Joomla.renderMessages = renderFn; + Joomla.removeMessages = removeFn; + Joomla.ajaxErrorsMessages = ajxerrFn; + window.scrollTo = scrollFn; + Joomla.JText._ = jtxtFn; + }); + + it("should remove attribute class from icon", function() { + expect($('#icon_0')).not.toHaveAttr('class'); + }); + + it("should set style attribute to display the spinner in icon", function() { + expect($('#icon_0')).toHaveAttr('style', 'background: url(../media/system/images/modal/spinner.gif); display: inline-block; width: 16px; height: 16px'); + }); + + it("should call Joomla.removeMessages()", function() { + expect(Joomla.removeMessages).toHaveBeenCalled(); + }); + + describe("on success with resp.data.result == 'true' & resp.messages an object", function() { + var $spanContainer = $('#ajax-test'); + + beforeAll(function() { + sendPermissions(event); + request = jasmine.Ajax.requests.mostRecent(); + request.respondWith(responses.success); + }); + + it("should make a AJAX request of type POST", function() { + expect(request.method).toBe('POST'); + }); + + it("should set attribute class in icon to icon-save", function() { + expect($('#icon_0')).toHaveAttr('class', 'icon-save'); + }); + + it("should add class in icon to icon-save", function() { + expect($spanContainer.find('span')).toHaveClass('test-class'); + }); + + it("should class in icon to icon-save", function() { + expect($spanContainer.find('span')).toContainText('Sample text'); + }); + + it("should call Joomla.renderMessages({})", function() { + expect(Joomla.renderMessages).toHaveBeenCalledWith({}); + }); + + it("should call window.scrollTo(0, 0)", function() { + expect(window.scrollTo).toHaveBeenCalledWith(0, 0); + }); + }); + + describe("on success with resp.data.result !== 'true' & resp.messages an object", function() { + beforeAll(function() { + sendPermissions(event); + request = jasmine.Ajax.requests.mostRecent(); + responses.success.responseText = '{"data": {"result": false}, "messages": {}}'; + request.respondWith(responses.success); + }); + + it("should set attribute class in icon to icon-cancel", function() { + expect($('#icon_0')).toHaveAttr('class', 'icon-cancel'); + }); + + it("should call Joomla.renderMessages({})", function() { + expect(Joomla.renderMessages).toHaveBeenCalledWith({}); + }); + + it("should call window.scrollTo(0, 0)", function() { + expect(window.scrollTo).toHaveBeenCalledWith(0, 0); + }); + }); + + describe("on failure", function() { + beforeAll(function() { + sendPermissions(event); + request = jasmine.Ajax.requests.mostRecent(); + request.respondWith(responses.fail); + }); + + it("should call Joomla.ajaxErrorsMessages(jqXHR, 'error', 'HTTP/1.1 404 Not Found')", function() { + expect(Joomla.ajaxErrorsMessages).toHaveBeenCalledWith(jasmine.any(Object), 'error', 'HTTP/1.1 404 Not Found'); + }); + + it("should call Joomla.renderMessages(undefined)", function() { + expect(Joomla.renderMessages).toHaveBeenCalledWith(undefined); + }); + + it("should call window.scrollTo(0, 0)", function() { + expect(window.scrollTo).toHaveBeenCalledWith(0, 0); + }); + + it("should remove attribute style from icon", function() { + expect($('#icon_0')).not.toHaveAttr('style'); + }); + + it("should set attribute class in icon to icon-cancel", function() { + expect($('#icon_0')).toHaveAttr('class', 'icon-cancel'); + }); + }); + }); +}); diff --git a/tests/javascript/repeatable/fixtures/fixture.html b/tests/javascript/repeatable/fixtures/fixture.html new file mode 100644 index 0000000000000..db91c85352d6d --- /dev/null +++ b/tests/javascript/repeatable/fixtures/fixture.html @@ -0,0 +1,40 @@ +
+
+
+ +
+ + +
+ +
diff --git a/tests/javascript/repeatable/spec-setup.js b/tests/javascript/repeatable/spec-setup.js new file mode 100644 index 0000000000000..25c815e97f078 --- /dev/null +++ b/tests/javascript/repeatable/spec-setup.js @@ -0,0 +1,30 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'text!testsRoot/repeatable/fixtures/fixture.html', 'libs/repeatable'], function ($, fixture) { + $('body').append(fixture); + + spy_weready = jasmine.createSpy('weready'); + spy_prepare_template = jasmine.createSpy('prepare-template'); + spy_prepare_modal = jasmine.createSpy('prepare-modal'); + spy_row_add = jasmine.createSpy('row-add'); + spy_row_remove = jasmine.createSpy('row-remove'); + spy_value_update = jasmine.createSpy('value-update'); + + var $element = $('input.form-field-repeatable'); + + $element.on('weready', spy_weready) + .on('prepare-template', spy_prepare_template) + .on('prepare-modal', spy_prepare_modal) + .on('row-add', spy_row_add) + .on('row-remove', spy_row_remove) + .on('value-update', spy_value_update); + + $element.JRepeatable(); +}); diff --git a/tests/javascript/repeatable/spec.js b/tests/javascript/repeatable/spec.js new file mode 100644 index 0000000000000..65764ba5a7072 --- /dev/null +++ b/tests/javascript/repeatable/spec.js @@ -0,0 +1,142 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'testsRoot/repeatable/spec-setup', 'jasmineJquery'], function ($) { + + var $modal = $('#jform_test_modal'); + + describe('Repeatable before triggering btn click', function () { + it('should take jform_test_container out of the form', function () { + expect($('#jform-test')).not.toContainElement('#jform_test_container'); + }); + + it('should register on shown event on the modal element', function () { + expect($('#jform_test_modal')).toHandle('shown'); + }); + + it('should fix name attributes on the repeatable element contents', function () { + expect($modal.find('input').first()).toHaveAttr('name', 'field1-1'); + expect($modal.find('input').last()).toHaveAttr('name', 'field2-1'); + }); + + it('should fix id attributes on the repeatable element contents', function () { + expect($modal.find('input').first()).toHaveAttr('id', 'field1-1'); + expect($modal.find('input').last()).toHaveAttr('id', 'field2-1'); + }); + + it('should fire the weready event', function () { + expect(spy_weready).toHaveBeenCalled(); + }); + + it('should fire the prepare-template event', function () { + expect(spy_prepare_template).toHaveBeenCalled(); + }); + + it('should fire the prepare-modal event', function () { + expect(spy_prepare_modal).toHaveBeenCalled(); + }); + }); + + describe('Repeatable after triggering \'open modal\' button click', function () { + beforeAll(function () { + $('#jform_test_button').click(); + }); + + it('should change the CSS of the modal window', function () { + var $modal = $('#jform_test_modal'); + var $rowsContainer = $('#jform_test').find('tbody').first(); + var modalHalfWidth = $modal.width() / 2; + var rowsHalfWidth = $rowsContainer.width() / 2; + + expect($modal).toHaveCss({ + 'overflow': rowsHalfWidth > modalHalfWidth ? 'auto' : 'visible' + }); + }); + }); + + describe('Repeatable after triggering \'Add new after\' element click', function () { + beforeAll(function () { + $modal.find('.add').first().click(); + }); + + it('should make the total number of text inputs in the modal 4', function () { + expect($modal.find('input').length).toEqual(4); + }); + + it('should fix name attributes on the new repeatable element contents', function () { + expect($modal.find('input').last()).toHaveAttr('name', 'field2-2'); + }); + + it('should fix id attributes on the new repeatable element contents', function () { + expect($modal.find('input').last()).toHaveAttr('id', 'field2-2'); + }); + + it('should fire the row-add event', function () { + expect(spy_row_add).toHaveBeenCalled(); + }); + }); + + describe('Repeatable after triggering \'Remove\' element click', function () { + beforeAll(function () { + $modal.find('.remove').first().click(); + }); + + it('should make the total number of text inputs in the modal back to 2', function () { + expect($modal.find('input').length).toEqual(2); + }); + + it('should fire the row-remove event', function () { + expect(spy_row_remove).toHaveBeenCalled(); + }); + }); + + describe('Repeatable after triggering \'Close\' element click', function () { + beforeAll(function () { + $modal.find('.close-modal').first().click(); + }); + + it('should make the modal not visible', function () { + expect($modal).not.toBeVisible(); + }); + }); + + describe('Repeatable after triggering \'Save and Close\' element click', function () { + beforeAll(function () { + $('#jform_test_button').click(); + $modal.find('input').first().val('test_input'); + $modal.find('.save-modal-data').first().click(); + }); + + it('should make the modal not visible', function () { + expect($modal).not.toBeVisible(); + }); + + it('should set the filled values in JSON format to the value attribute of hidden input', function () { + expect($('#jform_test').val()).toEqual('{"field1":["test_input"],"field2":[""]}'); + }); + + it('should fire the value-update event', function () { + expect(spy_value_update).toHaveBeenCalled(); + }); + }); + + describe('Repeatable after triggering \'open modal\' followed by \'Save and Close\'', function () { + beforeAll(function () { + $('#jform_test_button').click(); + }); + + it('should make the modal visible again', function () { + expect($modal).toBeVisible(); + }); + + it('should have the previously filled value preserved in the input element', function () { + expect($modal.find('input').first().val()).toEqual('test_input'); + }); + }); +}); diff --git a/tests/javascript/test-main.js b/tests/javascript/test-main.js new file mode 100644 index 0000000000000..96b22dc25ea47 --- /dev/null +++ b/tests/javascript/test-main.js @@ -0,0 +1,44 @@ +var allTestFiles = []; +var TEST_REGEXP = /(spec|test)\.js$/i; + +// Get a list of all the test files to include +Object.keys(window.__karma__.files).forEach(function (file) { + if (TEST_REGEXP.test(file)) { + // Normalize paths to RequireJS module names. + // If you require sub-dependencies of test files to be loaded as-is (requiring file extension) + // then do not normalize the paths + var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, ''); + allTestFiles.push(normalizedTestModule); + } +}); + +require.config({ + // Karma serves files under /base, which is the basePath from your config file + baseUrl: '/base', + + paths: { + 'jquery': 'tests/javascript/node_modules/jquery/dist/jquery.min', + 'bootstrap': 'media/jui/js/bootstrap.min', + 'jasmineJquery': 'tests/javascript/node_modules/jasmine-jquery/lib/jasmine-jquery', + 'libs': 'media/system/js', + 'testsRoot': 'tests/javascript', + 'text': 'tests/javascript/node_modules/text/text' + }, + + shim: { + jasmineJquery: ['jquery'], + bootstrap: ['jquery'], + 'libs/repeatable': { + deps: ['bootstrap', 'jquery'] + }, + 'libs/validate': { + deps: ['jquery'] + } + }, + + // dynamically load all test files + deps: allTestFiles, + + // we have to kickoff jasmine, as it is asynchronous + callback: window.__karma__.start +}); diff --git a/tests/javascript/travis-tests.sh b/tests/javascript/travis-tests.sh new file mode 100644 index 0000000000000..e44bfa7d40f1c --- /dev/null +++ b/tests/javascript/travis-tests.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Script for installing firefox and fluxbox and preparing JavaScript tests + +BASE="$1" + +set -e + +# Xvfb +sh -e /etc/init.d/xvfb start +sleep 3 # give xvfb some time to start + +# Fluxbox +sudo apt-get update -qq +sudo apt-get install -y --force-yes firefox fluxbox +fluxbox & +sleep 3 # give fluxbox some time to start + +# Install node modules for tests +cd tests/javascript +npm install diff --git a/tests/javascript/validate/fixtures/fixture.html b/tests/javascript/validate/fixtures/fixture.html new file mode 100644 index 0000000000000..aac014a4c3a5d --- /dev/null +++ b/tests/javascript/validate/fixtures/fixture.html @@ -0,0 +1,35 @@ +
+
+
+
+
+
+
+
+ +
+
+ +
+ +

+
+ +

+ +
+ + +
+ + +
+
+ +
+ + +
+
+
+
diff --git a/tests/javascript/validate/spec-setup.js b/tests/javascript/validate/spec-setup.js new file mode 100644 index 0000000000000..b53e31c0e0453 --- /dev/null +++ b/tests/javascript/validate/spec-setup.js @@ -0,0 +1,14 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'text!testsRoot/validate/fixtures/fixture.html', 'libs/validate', 'libs/core'], function ($, fixture) { + $('body').append(fixture); + + document.formvalidator = new JFormValidator(); +}); diff --git a/tests/javascript/validate/spec.js b/tests/javascript/validate/spec.js new file mode 100644 index 0000000000000..baa8d5ff1d297 --- /dev/null +++ b/tests/javascript/validate/spec.js @@ -0,0 +1,203 @@ +/** + * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @package Joomla + * @subpackage JavaScript Tests + * @since 3.6 + * @version 1.0.0 + */ + +define(['jquery', 'testsRoot/validate/spec-setup', 'jasmineJquery'], function ($) { + var $element = $('#validatejs'); + + describe('Validate', function () { + beforeAll(function () { + renderFn = Joomla.renderMessages; + jtxtFn = Joomla.JText._; + + Joomla.renderMessages = jasmine.createSpy('renderMessages'); + Joomla.JText._ = jasmine.createSpy('JText._'); + }); + + afterAll(function () { + Joomla.renderMessages = renderFn; + Joomla.JText._ = jtxtFn; + }); + + describe('The input fields in the form', function () { + it('with class \'required\' should have attributes aria-required = true', function () { + expect($element.find('#attach-to-form input')).toHaveAttr('aria-required', 'true'); + }); + + it('with class \'required\' should have attributes required = required', function () { + expect($element.find('#attach-to-form input')).toHaveAttr('required', 'required'); + }); + }); + + describe('The textarea fields in the form', function () { + it('with class \'required\' should have attributes aria-required = true', function () { + expect($element.find('#attach-to-form textarea')).toHaveAttr('aria-required', 'true'); + }); + + it('with class \'required\' should have attributes required = required', function () { + expect($element.find('#attach-to-form textarea')).toHaveAttr('required', 'required'); + }); + }); + + describe('The select fields in the form', function () { + it('with class \'required\' should have attributes aria-required = true', function () { + expect($element.find('#attach-to-form select')).toHaveAttr('aria-required', 'true'); + }); + + it('with class \'required\' should have attributes required = required', function () { + expect($element.find('#attach-to-form select')).toHaveAttr('required', 'required'); + }); + }); + + describe('The fieldset fields in the form', function () { + it('with class \'required\' should have attributes aria-required = true', function () { + expect($element.find('#attach-to-form fieldset')).toHaveAttr('aria-required', 'true'); + }); + + it('with class \'required\' should have attributes required = required', function () { + expect($element.find('#attach-to-form fieldset')).toHaveAttr('required', 'required'); + }); + }); + + describe('validate method on #validate-disabled', function () { + var res = document.formvalidator.validate($element.find('#validate-disabled')); + + it('should return true', function () { + expect(res).toEqual(true); + }); + + it('should remove class invalid from element', function () { + expect($element.find('#validate-disabled')).not.toHaveClass('invalid'); + }); + + it('should have aria-invalid = false in element', function () { + expect($element.find('#validate-disabled')).toHaveAttr('aria-invalid', 'false'); + }); + + it('should remove class invalid from the label for element', function () { + expect($element.find('#validate-test label[for=validate-disabled]')).not.toHaveClass('invalid'); + }); + }); + + describe('validate method on #validate-required-unchecked', function () { + var res = document.formvalidator.validate($element.find('#validate-required-unchecked')); + + it('should return false', function () { + expect(res).toEqual(false); + }); + + it('should add class invalid to element', function () { + expect($element.find('#validate-required-unchecked')).toHaveClass('invalid'); + }); + + it('should have aria-invalid = true in element', function () { + expect($element.find('#validate-required-unchecked')).toHaveAttr('aria-invalid', 'true'); + }); + }); + + describe('validate method on #validate-required-checked', function () { + var res = document.formvalidator.validate($element.find('#validate-required-checked')); + + it('should return true', function () { + expect(res).toEqual(true); + }); + }); + + describe('validate method on #validate-numeric-number', function () { + var res = document.formvalidator.validate($element.find('#validate-numeric-number')); + + it('should return true', function () { + expect(res).toEqual(true); + }); + + it('should remove class invalid from element', function () { + expect($element.find('#validate-numeric-number')).not.toHaveClass('invalid'); + }); + + it('should have aria-invalid = false in element', function () { + expect($element.find('#validate-numeric-number')).toHaveAttr('aria-invalid', 'false'); + }); + + it('should remove class invalid from the label for element', function () { + expect($element.find('#validate-numeric-number-lbl')).not.toHaveClass('invalid'); + }); + }); + + describe('validate method on #validate-numeric-nan', function () { + var $label = $element.find('#validate-numeric-nan-label'); + $element.find('#validate-numeric-nan').data('label', $label); + + var res = document.formvalidator.validate($element.find('#validate-numeric-nan')); + + it('should return false', function () { + expect(res).toEqual(false); + }); + + it('should add class invalid to element', function () { + expect($element.find('#validate-numeric-nan')).toHaveClass('invalid'); + }); + + it('should have aria-invalid = true in element', function () { + expect($element.find('#validate-numeric-nan')).toHaveAttr('aria-invalid', 'true'); + }); + + it('should add class invalid to the label for element', function () { + expect($element.find('#validate-numeric-nan-label')).toHaveClass('invalid'); + }); + }); + + describe('validate method on #validate-no-options', function () { + var res = document.formvalidator.validate($element.find('#validate-no-options')); + + it('should return true', function () { + expect(res).toEqual(true); + }); + + it('should remove class invalid from element', function () { + expect($element.find('#validate-no-options')).not.toHaveClass('invalid'); + }); + + it('should have aria-invalid = false in element', function () { + expect($element.find('#validate-no-options')).toHaveAttr('aria-invalid', 'false'); + }); + }); + + describe('isValid method on button click', function () { + beforeAll(function () { + $('#button').trigger( "click" ); + }); + + it('should call Joomla.JText._(\'JLIB_FORM_FIELD_INVALID\')', function () { + expect(Joomla.JText._).toHaveBeenCalledWith('JLIB_FORM_FIELD_INVALID'); + }); + + it('should call Joomla.renderMessages({error: ["undefined"]})', function () { + expect(Joomla.renderMessages).toHaveBeenCalledWith({error: ["undefined"]}); + }); + + it('should add class invalid to element #isvalid-numeric-nan', function () { + expect($element.find('#isvalid-numeric-nan')).toHaveClass('invalid'); + }); + + it('should have aria-invalid = true in element #isvalid-numeric-nan', function () { + expect($element.find('#isvalid-numeric-nan')).toHaveAttr('aria-invalid', 'true'); + }); + + it('should not add class invalid to element #isvalid-novalidate', function () { + expect($element.find('#isvalid-novalidate')).not.toHaveClass('invalid'); + }); + + it('should remove class invalid from element #isvalid-numeric-nan after correcting value', function () { + $('#isvalid-numeric-nan').val('12345'); + $('#button').trigger( "click" ); + + expect($element.find('#isvalid-numeric-nan')).not.toHaveClass('invalid'); + }); + }); + }); +});