diff --git a/web/.eslintrc.js b/web/.eslintrc.js new file mode 100644 index 00000000..c60569db --- /dev/null +++ b/web/.eslintrc.js @@ -0,0 +1,45 @@ +module.exports = { + 'env': { + 'browser': true, + 'es6': true, + 'amd': true, + 'jasmine': true, + }, + 'extends': 'eslint:recommended', + 'parserOptions': { + 'ecmaFeatures': { + 'experimentalObjectRestSpread': true, + 'jsx': true + }, + 'sourceType': 'module' + }, + 'plugins': [ + 'react' + ], + 'globals': { + '_': true, + 'module': true, + }, + 'rules': { + 'indent': [ + 'error', + 2 + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' + ], + 'comma-dangle': [ + 'error', + 'always-multiline' + ] + } +}; \ No newline at end of file diff --git a/web/Gruntfile.js b/web/Gruntfile.js new file mode 100644 index 00000000..79b8276d --- /dev/null +++ b/web/Gruntfile.js @@ -0,0 +1,67 @@ +const webpackConfig = require('./webpack.config.js'); + +var srcFiles = [ + 'pgadmin/static/jsx/*.jsx', + 'pgadmin/static/js/selection/*.js', + 'regression/javascript/**.js', + 'regression/javascript/**/*.js', + '*.js', +]; + +module.exports = function (grunt) { + grunt.initConfig({ + watch: { + files: srcFiles, + tasks: ['eslint', 'webpack'], + }, + babel: { + options: { + sourceMap: true, + presets: ['react', 'es2015'], + }, + dist: { + files: { + 'pgadmin/static/js/generated/reactComponents.js': 'pgadmin/static/jsx/components.jsx', + }, + }, + }, + webpack: { + app: webpackConfig, + }, + eslint: { + target: srcFiles, + }, + karma: { + dev: { + configFile: 'karma.conf.js', + }, + ci: { + configFile: 'karma.conf.js', + singleRun: true, + }, + }, + uglify: { + my_target: { + files: [{ + expand: true, + cwd: 'pgadmin/static/js/generated', + src: '**/*.js', + dest: 'pgadmin/static/js/generated', + }], + }, + }, + }); + + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-webpack'); + grunt.loadNpmTasks('grunt-eslint'); + grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + + grunt.registerTask('test', ['eslint', 'karma:dev']); + grunt.registerTask('test-ci', ['eslint', 'karma:ci']); + + grunt.registerTask('bundle', ['eslint', 'webpack']); + grunt.registerTask('default', ['bundle', 'watch']); + grunt.registerTask('minify', ['bundle', 'uglify']); +}; diff --git a/web/karma.conf.js b/web/karma.conf.js index 95613f77..7fa51b50 100644 --- a/web/karma.conf.js +++ b/web/karma.conf.js @@ -1,75 +1,34 @@ -// Karma configuration -// Generated on Wed Mar 01 2017 14:19:28 GMT-0500 (EST) +const webpackConfig = require('./webpack.test.config.js'); -module.exports = function(config) { +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', 'requirejs'], - - - // list of files / patterns to load in the browser - files: [ - 'regression/javascript/test-main.js', - {pattern: 'regression/javascript/**/*.js', included: false}, - {pattern: 'pgadmin/static/vendor/**/*.js', included: false}, - {pattern: 'pgadmin/static/js/**/*.js', included: false} + frameworks: ['jasmine'], + plugins: [ + 'karma-webpack', + 'karma-phantomjs-launcher', + 'karma-jasmine', + 'karma-jasmine-html-reporter', ], - - - // list of files to exclude - exclude: [ - 'pgadmin/static/js/pgadmin.js', - 'pgadmin/static/vendor/**/*[Tt]est.js', - 'pgadmin/static/vendor/**/*[Ss]pec.js' + files: [ + {pattern: 'pgadmin/static/**/*.js', included: false}, + 'regression/javascript/**/*.jsx', + 'regression/javascript/**/*.js', ], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { + 'regression/javascript/**/*.js': ['webpack'], + // 'regression/javascript/**/*.jsx': ['webpack'], }, - - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress'], - - - // web server port + webpack: webpackConfig, + webpackMiddleware: { + stats: 'errors-only', + }, + reporters: ['progress', 'kjhtml'], 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: false, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + logLevel: config.LOG_WARN, + autoWatch: true, browsers: ['PhantomJS'], - - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: true, - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity - }) -} + singleRun: false, + concurrency: Infinity, + }); +}; diff --git a/web/package.json b/web/package.json index 80e4e5ad..760e1e0c 100644 --- a/web/package.json +++ b/web/package.json @@ -1,10 +1,44 @@ { "devDependencies": { - "jasmine-core": "^2.5.2", - "karma": "^1.5.0", - "karma-jasmine": "^1.1.0", - "karma-phantomjs-launcher": "^1.0.2", - "karma-requirejs": "^1.1.0", - "requirejs": "^2.3.3" + "babel-core": "~6.24.0", + "babel-loader": "~6.4.1", + "babel-preset-es2015": "~6.24.0", + "babel-preset-react": "~6.23.0", + "enzyme": "~2.8.2", + "eslint": "^3.19.0", + "eslint-plugin-react": "^6.10.3", + "grunt": "~1.0.1", + "grunt-contrib-uglify": "^3.0.0", + "grunt-contrib-watch": "~1.0.0", + "grunt-eslint": "^19.0.0", + "grunt-karma": "^2.0.0", + "grunt-webpack": "~2.0.1", + "jasmine-core": "~2.5.2", + "karma": "~1.5.0", + "karma-babel-preprocessor": "^6.0.1", + "karma-browserify": "~5.1.1", + "karma-jasmine": "~1.1.0", + "karma-jasmine-html-reporter": "~0.2.2", + "karma-phantomjs-launcher": "~1.0.2", + "karma-requirejs": "~1.1.0", + "karma-sourcemap-loader": "~0.3.7", + "karma-webpack": "~2.0.3", + "react-addons-test-utils": "~15.4.2", + "webpack": "~2.3.1" + }, + "dependencies": { + "axios": "^0.16.1", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-polyfill": "^6.23.0", + "babel-preset-es2015-without-strict": "~0.0.4", + "babelify": "~7.3.0", + "browserify": "~14.1.0", + "exports-loader": "~0.6.4", + "imports-loader": "git+https://github.com/webpack-contrib/imports-loader.git#44d6f48463b256a17c1ba6fd9b5cc1449b4e379d", + "react": "~15.4.2", + "react-dom": "~15.4.2", + "requirejs": "~2.3.3", + "underscore": "~1.8.3", + "watchify": "~3.9.0" } } diff --git a/web/pgAdmin4.py b/web/pgAdmin4.py index 24e1564a..e56a6bbe 100644 --- a/web/pgAdmin4.py +++ b/web/pgAdmin4.py @@ -14,25 +14,40 @@ to start a web server.""" import os import sys -# We need to include the root directory in sys.path to ensure that we can -# find everything we need when running in the standalone runtime. +from pgadmin.tools.javascript.javascript_bundler import JavascriptBundler, JsState + root = os.path.dirname(os.path.realpath(__file__)) if sys.path[0] != root: sys.path.insert(0, root) import config from pgadmin import create_app +from pgadmin.utils import u, fs_encoding, file_quote # Get the config database schema version. We store this in pgadmin.model # as it turns out that putting it in the config files isn't a great idea from pgadmin.model import SCHEMA_VERSION config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION +########################################################################## +# Sanity checks +########################################################################## + +# Check if the database exists. If it does not, create it. +if not os.path.isfile(config.SQLITE_PATH): + setupfile = os.path.join( + os.path.dirname(os.path.realpath(u(__file__, fs_encoding))), u'setup.py' + ) + exec(open(file_quote(setupfile), 'r').read()) ########################################################################## -# Server starup +# Server startup ########################################################################## +# Build Javascript files +javascriptBundler = JavascriptBundler() +javascriptBundler.bundle() + # Create the app! app = create_app() @@ -41,6 +56,13 @@ if config.DEBUG: else: app.debug = False +# respond to JS +if javascriptBundler.report() == JsState.NONE: + app.logger.error("Unable to generate javascript") + app.logger.error("To run the app ensure that grunt is installed globally " + "and that yarn install command runs successfully") + raise Exception("No generated javascript, aborting") + # Start the web server. The port number should have already been set by the # runtime if we're running in desktop mode, otherwise we'll just use the # Flask default. diff --git a/web/pgadmin/browser/static/vendor/aciTree/jquery.aciPlugin.min.js b/web/pgadmin/browser/static/vendor/aciTree/jquery.aciPlugin.min.js index b315a857..ea3fd412 100755 --- a/web/pgadmin/browser/static/vendor/aciTree/jquery.aciPlugin.min.js +++ b/web/pgadmin/browser/static/vendor/aciTree/jquery.aciPlugin.min.js @@ -9,4 +9,4 @@ * Require jQuery Library >= v1.2.3 http://jquery.com */ -(function(d,c,e){if(typeof aciPluginClass!=="undefined"){return}var a;this.aciPluginClass=function(){};aciPluginClass.extend=function(g,j){i.extend=arguments.callee;function i(){if(a){this._instance={};return this.__construct.apply(this,arguments)}}a=false;i.prototype=new this();a=true;var h=this.prototype;for(var f in g){i.prototype[f]=((typeof g[f]=="function")&&(f!="proxy"))?(function(k){return function(){var p=this._parent;this._parent=h;var n=this._super;this._super=h[k];var o=this._private;if(this._instance&&j){var m=this._instance._private;if(m[j]===e){m[j]={nameSpace:"."+j}}this._private=m[j]}var l=g[k].apply(this,arguments);this._parent=p;this._super=n;this._private=o;return l}})(f):g[f]}return i};var b=0;aciPluginClass.aciPluginUi=aciPluginClass.extend({__construct:function(k,l,i,h,j){var f="."+k;var g=l.data(f);if(g){this._instance=g._instance;return g.__request(i,h,j)}l.data(f,this);d.extend(true,this._instance,{_private:{},nameSpace:f,jQuery:l,options:d.extend(true,{},d.fn[k].defaults,(typeof i=="object")?i:{}),index:b++,wasInit:false});this.__extend();return this.__request(i,h,j)},__extend:function(){},__request:function(g,f,h){if((g===e)||(typeof g=="object")){if(this._instance.options.autoInit){this.init()}}else{if(typeof g=="string"){switch(g){case"init":this.init();break;case"api":return{object:this};case"options":if(f===e){return{object:this.options()}}else{if(typeof f=="string"){return{object:this.options(f)}}else{this.options(f)}}break;case"option":this.option(f,h);break;case"destroy":this.destroy();break}}}return this._instance.jQuery},proxy:function(j,h){var m=c.Array.prototype.slice;var f=m.call(arguments,2);var i=this,g=i._parent,l=i._super,k=i._private;return function(){i._parent=g;i._super=l;i._private=k;return j.apply(i,h?f.concat([this],m.call(arguments)):f.concat(m.call(arguments)))}},init:function(){if(!this._instance.wasInit){this._instance.wasInit=true;return true}return false},wasInit:function(){return this._instance.wasInit},__parent:function(h,f,l){var m=f.split(".");if(m.length>1){var j=h,k;for(var g in m){k=j;j=j[m[g]]}l.name=m[g];return k}l.name=f;return h},options:function(f){if(f){var i={name:null};var h;if(typeof f=="string"){h=this.__parent(this._instance.options,f,i);return h[i.name]}else{for(var g in f){h=this.__parent(this._instance.options,g,i);h[i.name]=f[g]}}}else{return this._instance.options}},option:function(g,i){var h={name:null};var f=this.__parent(this._instance.options,g,h);f[h.name]=i},destroy:function(){if(this._instance.wasInit){this._instance.wasInit=false;this._instance.jQuery.removeData(this._instance.nameSpace);return true}return false}});aciPluginClass.plugins={};aciPluginClass.publish=function(f,g){d.fn[f]=function(j,m,n){var h=null;for(var l=0,k=this.length;l1){var j=h,k;for(var g in m){k=j;j=j[m[g]]}l.name=m[g];return k}l.name=f;return h},options:function(f){if(f){var i={name:null};var h;if(typeof f=="string"){h=this.__parent(this._instance.options,f,i);return h[i.name]}else{for(var g in f){h=this.__parent(this._instance.options,g,i);h[i.name]=f[g]}}}else{return this._instance.options}},option:function(g,i){var h={name:null};var f=this.__parent(this._instance.options,g,h);f[h.name]=i},destroy:function(){if(this._instance.wasInit){this._instance.wasInit=false;this._instance.jQuery.removeData(this._instance.nameSpace);return true}return false}});aciPluginClass.plugins={};aciPluginClass.publish=function(f,g){d.fn[f]=function(j,m,n){var h=null;for(var l=0,k=this.length;l" + - " " + - " " + columnDefinition.name + "" + - ""; + '' + + ' ' + + ' ' + columnDefinition.name + '' + + ''; return _.extend(columnDefinition, { - name: name + name: name, }); } else { return columnDefinition; @@ -84,8 +84,8 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func }; $.extend(this, { - "init": init, - "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes + 'init': init, + 'getColumnDefinitionsWithCheckboxes': getColumnDefinitionsWithCheckboxes, }); }; return ColumnSelector; diff --git a/web/pgadmin/static/js/selection/copy_data.js b/web/pgadmin/static/js/selection/copy_data.js index 018efead..0ccd4b96 100644 --- a/web/pgadmin/static/js/selection/copy_data.js +++ b/web/pgadmin/static/js/selection/copy_data.js @@ -5,48 +5,48 @@ define([ 'sources/selection/range_selection_helper', 'sources/selection/range_boundary_navigator'], function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) { - var copyData = function () { - var self = this; - - var grid = self.slickgrid; - var columnDefinitions = grid.getColumns(); - var selectedRanges = grid.getSelectionModel().getSelectedRanges(); - var data = grid.getData(); - var rows = grid.getSelectedRows(); + var copyData = function () { + var self = this; + + var grid = self.slickgrid; + var columnDefinitions = grid.getColumns(); + var selectedRanges = grid.getSelectionModel().getSelectedRanges(); + var data = grid.getData(); + var rows = grid.getSelectedRows(); + + + if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) { + self.copied_rows = rows.map(function (rowIndex) { + return data[rowIndex]; + }); + setPasteRowButtonEnablement(self.can_edit, true); + } else { + self.copied_rows = []; + setPasteRowButtonEnablement(self.can_edit, false); + } + + var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges); + if (csvText) { + clipboard.copyTextToClipboard(csvText); + } + }; + + var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) { + if (canEditFlag) { + $('#btn-paste-row').prop('disabled', !isEnabled); + } + }; + + var allTheRangesAreFullRows = function (ranges, columnDefinitions) { + var colRangeBounds = ranges.map(function (range) { + return [range.fromCell, range.toCell]; + }); + if(RangeSelectionHelper.isFirstColumnData(columnDefinitions)) { + return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]); + } + return _.isEqual(_.union.apply(null, colRangeBounds), [1, columnDefinitions.length - 1]); + }; - if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) { - self.copied_rows = rows.map(function (rowIndex) { - return data[rowIndex]; - }); - setPasteRowButtonEnablement(self.can_edit, true); - } else { - self.copied_rows = []; - setPasteRowButtonEnablement(self.can_edit, false); - } - - var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges); - if (csvText) { - clipboard.copyTextToClipboard(csvText); - } - }; - - var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) { - if (canEditFlag) { - $("#btn-paste-row").prop('disabled', !isEnabled); - } - }; - - var allTheRangesAreFullRows = function (ranges, columnDefinitions) { - var colRangeBounds = ranges.map(function (range) { - return [range.fromCell, range.toCell]; - }); - - if(RangeSelectionHelper.isFirstColumnData(columnDefinitions)) { - return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]); - } - return _.isEqual(_.union.apply(null, colRangeBounds), [1, columnDefinitions.length - 1]); - }; - - return copyData; -}); + return copyData; + }); diff --git a/web/pgadmin/static/js/selection/grid_selector.js b/web/pgadmin/static/js/selection/grid_selector.js index 31aee69f..6317f44e 100644 --- a/web/pgadmin/static/js/selection/grid_selector.js +++ b/web/pgadmin/static/js/selection/grid_selector.js @@ -31,7 +31,7 @@ define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_se }; function handleSelectedRangesChanged(grid) { - $("[data-id='checkbox-select-all']").prop("checked", isEntireGridSelected(grid)); + $('[data-id=\'checkbox-select-all\']').prop('checked', isEntireGridSelected(grid)); } function isEntireGridSelected(grid) { @@ -44,7 +44,7 @@ define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_se if (isEntireGridSelected(grid)) { deselect(grid); } else { - selectAll(grid) + selectAll(grid); } } @@ -70,8 +70,8 @@ define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_se } $.extend(this, { - "init": init, - "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes + 'init': init, + 'getColumnDefinitionsWithCheckboxes': getColumnDefinitionsWithCheckboxes, }); }; diff --git a/web/pgadmin/static/js/selection/range_boundary_navigator.js b/web/pgadmin/static/js/selection/range_boundary_navigator.js index a268d245..f69540b9 100644 --- a/web/pgadmin/static/js/selection/range_boundary_navigator.js +++ b/web/pgadmin/static/js/selection/range_boundary_navigator.js @@ -99,13 +99,13 @@ define(['sources/selection/range_selection_helper'], function (RangeSelectionHel var val = data[rowId][columnDefinitions[colId].pos]; if (val && _.isObject(val)) { - val = "'" + JSON.stringify(val) + "'"; - } else if (val && typeof val != "number" && typeof val != "boolean") { - val = "'" + val.toString() + "'"; + val = '\'' + JSON.stringify(val) + '\''; + } else if (val && typeof val != 'number' && typeof val != 'boolean') { + val = '\'' + val.toString() + '\''; } else if (_.isNull(val) || _.isUndefined(val)) { val = ''; } return val; - } + }, }; }); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/range_selection_helper.js b/web/pgadmin/static/js/selection/range_selection_helper.js index 31ad3bf7..4bd55975 100644 --- a/web/pgadmin/static/js/selection/range_selection_helper.js +++ b/web/pgadmin/static/js/selection/range_selection_helper.js @@ -8,14 +8,14 @@ define(['slickgrid'], function () { var isRangeSelected = function (selectedRanges, range) { return _.any(selectedRanges, function (selectedRange) { - return isSameRange(selectedRange, range) - }) + return isSameRange(selectedRange, range); + }); }; var removeRange = function (selectedRanges, range) { return _.filter(selectedRanges, function (selectedRange) { - return !(isSameRange(selectedRange, range)) - }) + return !(isSameRange(selectedRange, range)); + }); }; var addRange = function (ranges, range) { @@ -26,15 +26,15 @@ define(['slickgrid'], function () { var areAllRangesRows = function (ranges, grid) { return _.every(ranges, function (range) { return range.fromRow == range.toRow && - range.fromCell == 1 && range.toCell == grid.getColumns().length - 1 - }) + range.fromCell == 1 && range.toCell == grid.getColumns().length - 1; + }); }; var areAllRangesColumns = function (ranges, grid) { return _.every(ranges, function (range) { return range.fromCell == range.toCell && - range.fromRow == 0 && range.toRow == grid.getDataLength() - 1 - }) + range.fromRow == 0 && range.toRow == grid.getDataLength() - 1; + }); }; var rangeForRow = function (grid, rowId) { @@ -46,8 +46,8 @@ define(['slickgrid'], function () { }; function rangeForColumn(grid, columnIndex) { - return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex) - }; + return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex); + } var getRangeOfWholeGrid = function (grid) { return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1); @@ -73,6 +73,6 @@ define(['slickgrid'], function () { rangeForColumn: rangeForColumn, isEntireGridSelected: isEntireGridSelected, getRangeOfWholeGrid: getRangeOfWholeGrid, - isFirstColumnData: isFirstColumnData - } + isFirstColumnData: isFirstColumnData, + }; }); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/row_selector.js b/web/pgadmin/static/js/selection/row_selector.js index 76a8c1a7..583f8a75 100644 --- a/web/pgadmin/static/js/selection/row_selector.js +++ b/web/pgadmin/static/js/selection/row_selector.js @@ -8,18 +8,18 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func grid.getSelectionModel() .onSelectedRangesChanged.subscribe(handleSelectedRangesChanged.bind(null, grid)); gridEventBus - .subscribe(grid.onClick, handleClick.bind(null, grid)) + .subscribe(grid.onClick, handleClick.bind(null, grid)); }; var handleClick = function (grid, event, args) { if (grid.getColumns()[args.cell].id === 'row-header-column') { - if (event.target.type != "checkbox") { + if (event.target.type != 'checkbox') { var checkbox = $(event.target).find('input[type="checkbox"]'); toggleCheckbox($(checkbox)); } updateRanges(grid, args.row); } - } + }; var handleSelectedRangesChanged = function (grid, event, ranges) { $('[data-cell-type="row-header-checkbox"]:checked') @@ -32,7 +32,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func toggleCheckbox($checkbox); } }); - } + }; var updateRanges = function (grid, rowId) { var selectionModel = grid.getSelectionModel(); @@ -51,13 +51,13 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func } } selectionModel.setSelectedRanges(newRanges); - } + }; var toggleCheckbox = function (checkbox) { - if (checkbox.prop("checked")) { - checkbox.prop("checked", false) + if (checkbox.prop('checked')) { + checkbox.prop('checked', false); } else { - checkbox.prop("checked", true) + checkbox.prop('checked', true); } }; @@ -70,15 +70,15 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func formatter: function (rowIndex) { return '' - } + 'data-cell-type="row-header-checkbox"/>'; + }, }); return columnDefinitions; }; $.extend(this, { - "init": init, - "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes + 'init': init, + 'getColumnDefinitionsWithCheckboxes': getColumnDefinitionsWithCheckboxes, }); }; return RowSelector; diff --git a/web/pgadmin/static/jsx/components.jsx b/web/pgadmin/static/jsx/components.jsx new file mode 100644 index 00000000..5bcb5208 --- /dev/null +++ b/web/pgadmin/static/jsx/components.jsx @@ -0,0 +1,8 @@ + +import React from 'react'; +import {render} from 'react-dom'; + +export { + render, + React, +}; \ No newline at end of file diff --git a/web/pgadmin/tools/javascript/__init__.py b/web/pgadmin/tools/javascript/__init__.py new file mode 100644 index 00000000..9e55d2b2 --- /dev/null +++ b/web/pgadmin/tools/javascript/__init__.py @@ -0,0 +1,6 @@ +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2017, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# diff --git a/web/pgadmin/tools/javascript/javascript_bundler.py b/web/pgadmin/tools/javascript/javascript_bundler.py new file mode 100644 index 00000000..d74e3cbe --- /dev/null +++ b/web/pgadmin/tools/javascript/javascript_bundler.py @@ -0,0 +1,63 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2017, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import os +from contextlib import contextmanager +from subprocess import call +from pgadmin.utils import u, fs_encoding, file_quote + + +# enum-like for tracking whether we have +class JsState: + NONE = 0 + OLD = 1 + NEW = 2 + + +class JavascriptBundler: + """Builds Javascript bundle files by delegating to webpack""" + + def __init__(self): + self.jsState = JsState.NONE + + def bundle(self): + try: + try_building_js() + self.jsState = JsState.NEW + except OSError: + webdir_path() + generatedJavascriptDir = os.path.join(webdir_path(), 'pgadmin', 'static', 'js', 'generated') + if os.path.exists(generatedJavascriptDir) and os.listdir(generatedJavascriptDir): + self.jsState = JsState.OLD + else: + self.jsState = JsState.NONE + + def report(self): + return self.jsState + + +@contextmanager +def pushd(new_dir): + previous_dir = os.getcwd() + os.chdir(new_dir) + yield + os.chdir(previous_dir) + + +def webdir_path(): + dirname = os.path.dirname + thisPath = os.path.realpath(u(__file__, fs_encoding)) + return dirname(dirname(dirname(dirname(thisPath)))) + + +def try_building_js(): + node_modules_dir = os.path.join(webdir_path(), 'node_modules', '.bin') + with pushd(node_modules_dir): + if call(['grunt', 'webpack']) != 0: + raise OSError('Error executing grunt') diff --git a/web/pgadmin/tools/javascript/tests/__init__.py b/web/pgadmin/tools/javascript/tests/__init__.py new file mode 100644 index 00000000..9e55d2b2 --- /dev/null +++ b/web/pgadmin/tools/javascript/tests/__init__.py @@ -0,0 +1,6 @@ +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2017, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# diff --git a/web/pgadmin/tools/javascript/tests/test_javascript_bundler.py b/web/pgadmin/tools/javascript/tests/test_javascript_bundler.py new file mode 100644 index 00000000..8c8533c4 --- /dev/null +++ b/web/pgadmin/tools/javascript/tests/test_javascript_bundler.py @@ -0,0 +1,114 @@ +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2017, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# + +import sys + +from pgadmin.utils.route import BaseTestGenerator +if sys.version_info < (3, 3): + import mock +else: + import unittest.mock as mock + + +class JavascriptBundlerTestCase(BaseTestGenerator): + """This tests that the javascript bundler tool causes grunt to build, + and can be invoked before and after app start correctly""" + + scenarios = [('scenario name: JavascriptBundlerTestCase', dict())] + + def setUp(self): + self.mockSubprocess = mock.Mock() + self.mockOs = mock.Mock() + sys.modules['subprocess'] = self.mockSubprocess + sys.modules['os'] = self.mockOs + + def runTest(self): + from pgadmin.tools.javascript.javascript_bundler import JavascriptBundler + from pgadmin.tools.javascript.javascript_bundler import JsState + self.JavascriptBundler = JavascriptBundler + self.JsState = JsState + + self._bundling_succeeds() + self.resetTestState() + self._bundling_fails_and_there_is_no_existing_bundle() + self.resetTestState() + self._bundling_fails_when_grunt_returns_nonzero() + self.resetTestState() + self._bundling_fails_and_there_is_no_existing_bundle_directory() + self.resetTestState() + self._bundling_fails_but_there_was_existing_bundle() + self.resetTestState() + + def resetTestState(self): + self.mockSubprocess.reset_mock() + self.mockSubprocess.call.side_effect = None + self.mockOs.reset_mock() + self.mockOs.listdir.side_effect = None + self.mockOs.path.exists.side_effect = None + + def _bundling_succeeds(self): + javascriptBundler = self.JavascriptBundler() + self.assertEqual(len(self.mockSubprocess.method_calls), 0) + self.mockSubprocess.call.return_value = 0 + + self.mockOs.listdir.return_value = [u'history.js', u'reactComponents.js'] + + javascriptBundler.bundle() + self.mockSubprocess.call.assert_called_once_with(['grunt', 'webpack']) + + reportedState = javascriptBundler.report() + expectedState = self.JsState.NEW + self.assertEqual(reportedState, expectedState) + + def _bundling_fails_when_grunt_returns_nonzero(self): + javascriptBundler = self.JavascriptBundler() + self.assertEqual(len(self.mockSubprocess.method_calls), 0) + self.mockOs.listdir.return_value = [] + self.mockSubprocess.call.return_value = 99 + + javascriptBundler.bundle() + + reportedState = javascriptBundler.report() + expectedState = self.JsState.NONE + self.assertEqual(reportedState, expectedState) + + def _bundling_fails_and_there_is_no_existing_bundle(self): + javascriptBundler = self.JavascriptBundler() + self.mockSubprocess.call.side_effect = OSError("mock exception behavior") + self.mockOs.path.exists.return_value = True + self.mockOs.listdir.return_value = [] + + javascriptBundler.bundle() + + reportedState = javascriptBundler.report() + expectedState = self.JsState.NONE + self.assertEqual(reportedState, expectedState) + + def _bundling_fails_and_there_is_no_existing_bundle_directory(self): + javascriptBundler = self.JavascriptBundler() + self.mockSubprocess.call.side_effect = OSError("mock exception behavior") + self.mockOs.path.exists.return_value = False + self.mockOs.listdir.side_effect = OSError("mock exception behavior") + + javascriptBundler.bundle() + + reportedState = javascriptBundler.report() + expectedState = self.JsState.NONE + self.assertEqual(reportedState, expectedState) + + def _bundling_fails_but_there_was_existing_bundle(self): + javascriptBundler = self.JavascriptBundler() + self.mockSubprocess.call.side_effect = OSError("mock exception behavior") + self.mockOs.path.exists.return_value = True + self.mockOs.listdir.return_value = [u'history.js', u'reactComponents.js'] + + javascriptBundler.bundle() + self.mockSubprocess.call.assert_called_once_with(['grunt', 'webpack']) + + reportedState = javascriptBundler.report() + expectedState = self.JsState.OLD + self.assertEqual(reportedState, expectedState) diff --git a/web/regression/README b/web/regression/README index f0d7282e..92ea5715 100644 --- a/web/regression/README +++ b/web/regression/README @@ -177,16 +177,20 @@ Javascript Tests: sudo port install nodejs7 yarn +- See also the top-level pgadmin/README : Bundling Javascript + - Javascript tests must be run from the web directory (since that is where node_modules and karma.conf reside): cd web/ - Install the JS modules required for testing: - yarn + yarn install - Now run the tests: - yarn run karma start --single-run + grunt test-ci +- While developing run the following command to watch for file changes and continually run tests: + grunt test diff --git a/web/regression/javascript/fake_translations.js b/web/regression/javascript/fake_translations.js index 9516fd63..a3d4f5bd 100644 --- a/web/regression/javascript/fake_translations.js +++ b/web/regression/javascript/fake_translations.js @@ -8,5 +8,5 @@ ////////////////////////////////////////////////////////////////////////// define(function () { - return {} + return {}; }); \ No newline at end of file diff --git a/web/regression/javascript/gettext_spec.js b/web/regression/javascript/gettext_spec.js index 2ce98a23..54fc498d 100644 --- a/web/regression/javascript/gettext_spec.js +++ b/web/regression/javascript/gettext_spec.js @@ -7,45 +7,46 @@ // ////////////////////////////////////////////////////////////////////////// -define(["sources/gettext", "translations"], function (gettext, translations) { - describe("translate", function () { - describe("when there is no translation", function () { - it("returns the original string", function () { - expect(gettext("something to be translated")).toEqual("something to be translated"); - }); - - describe("when there are substitutions", function () { - it("interpolates a substitution", function () { - expect(gettext("translate text for %(person)s", {"person": "Sarah"})).toEqual("translate text for Sarah") - }); - - it("interpolates multiple substitutions", function () { - expect(gettext("translate '%(text)s' for %(person)s", - { - "text": "constitution", - "person": "Sarah" - } - )).toEqual("translate 'constitution' for Sarah") - }); - }); +import gettext from 'sources/gettext'; +import translations from 'translations'; +describe('translate', function () { + describe('when there is no translation', function () { + it('returns the original string', function () { + expect(gettext('something to be translated')).toEqual('something to be translated'); }); - describe("when there is a translation", function () { - beforeEach(function () { - translations['something to be translated'] = 'etwas zum uebersetzen'; - translations['another translation for %(person)s'] = 'eine weitere Uebersetzung fuer %(person)s'; + describe('when there are substitutions', function () { + it('interpolates a substitution', function () { + expect(gettext('translate text for %(person)s', {'person': 'Sarah'})).toEqual('translate text for Sarah'); }); - it("returns the translation", function () { - expect(gettext("something to be translated")).toEqual("etwas zum uebersetzen"); + it('interpolates multiple substitutions', function () { + expect(gettext('translate \'%(text)s\' for %(person)s', + { + 'text': 'constitution', + 'person': 'Sarah', + } + )).toEqual('translate \'constitution\' for Sarah'); }); + }); + + }); + + describe('when there is a translation', function () { + beforeEach(function () { + translations['something to be translated'] = 'etwas zum uebersetzen'; + translations['another translation for %(person)s'] = 'eine weitere Uebersetzung fuer %(person)s'; + }); + + it('returns the translation', function () { + expect(gettext('something to be translated')).toEqual('etwas zum uebersetzen'); + }); - describe("when there is a substitution", function () { - it("interpolates the substitution", function () { - expect(gettext("another translation for %(person)s", {"person": "Sarah"})) - .toEqual("eine weitere Uebersetzung fuer Sarah"); - }); + describe('when there is a substitution', function () { + it('interpolates the substitution', function () { + expect(gettext('another translation for %(person)s', {'person': 'Sarah'})) + .toEqual('eine weitere Uebersetzung fuer Sarah'); }); }); }); diff --git a/web/regression/javascript/jasmine_capture_warnings_beforeall.js b/web/regression/javascript/jasmine_capture_warnings_beforeall.js new file mode 100644 index 00000000..a83e6f61 --- /dev/null +++ b/web/regression/javascript/jasmine_capture_warnings_beforeall.js @@ -0,0 +1,23 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +/* eslint-disable no-console */ + +beforeAll(function () { + spyOn(console, 'warn').and.callThrough(); + spyOn(console, 'error').and.callThrough(); +}); + +afterEach(function (done) { + setTimeout(function () { + expect(console.warn).not.toHaveBeenCalled(); + expect(console.error).not.toHaveBeenCalled(); + done(); + }, 0); +}); \ No newline at end of file diff --git a/web/regression/javascript/selection/column_selector_spec.js b/web/regression/javascript/selection/column_selector_spec.js index 947f4852..2cba0af5 100644 --- a/web/regression/javascript/selection/column_selector_spec.js +++ b/web/regression/javascript/selection/column_selector_spec.js @@ -1,235 +1,243 @@ -define( - ["jquery", - "underscore", - "slickgrid/slick.grid", - "sources/selection/column_selector", - "slickgrid/slick.rowselectionmodel", - "slickgrid" - ], - function ($, _, SlickGrid, ColumnSelector, RowSelectionModel, Slick) { - describe("ColumnSelector", function () { - var container, data, columns, options; +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import $ from 'jquery'; + +import Slick from 'slickgrid'; +import 'slickgrid.grid'; + +import RowSelectionModel from '../../../pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel'; +import ColumnSelector from '../../../pgadmin/static/js/selection/column_selector'; + + +describe('ColumnSelector', function () { + var container, data, columns, options; + beforeEach(function () { + container = $('
'); + container.height(9999); + + data = [{'some-column-name': 'first value', 'second column': 'second value'}]; + + columns = [ + { + id: '1', + name: 'some-column-name', + }, + { + id: '2', + name: 'second column', + }, + { + name: 'some-non-selectable-column', + selectable: false, + }, + ]; + }); + + describe('when a column is not selectable', function () { + it('does not create a checkbox for selecting the column', function () { + var checkboxColumn = { + name: 'some-column-name-4', + selectable: false, + }; + columns.push(checkboxColumn); + + setupGrid(columns); + + expect(container.find('.slick-header-columns input').length).toBe(2); + }); + }); + + it('renders a checkbox in the column header', function () { + setupGrid(columns); + + expect(container.find('.slick-header-columns input').length).toBe(2); + }); + + it('displays the name of the column', function () { + setupGrid(columns); + + expect($(container.find('.slick-header-columns .slick-column-name')[0]).text()) + .toContain('some-column-name'); + expect($(container.find('.slick-header-columns .slick-column-name')[1]).text()) + .toContain('second column'); + }); + + it('preserves the other attributes of column definitions', function () { + var columnSelector = new ColumnSelector(); + var selectableColumns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); + + expect(selectableColumns[0].id).toBe('1'); + }); + + describe('selecting columns', function () { + var grid, rowSelectionModel; + beforeEach(function () { + var columnSelector = new ColumnSelector(); + columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); + data = []; + for (var i = 0; i < 10; i++) { + data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); + } + grid = new Slick.Grid(container, data, columns, options); + + rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); + + grid.registerPlugin(columnSelector); + grid.invalidate(); + $('body').append(container); + }); + + afterEach(function () { + $('body').find(container).remove(); + }); + + describe('when the user clicks a column header', function () { + it('selects the column', function () { + container.find('.slick-header-column')[0].click(); + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expectOnlyTheFirstColumnToBeSelected(selectedRanges); + }); + }); + + describe('when the user clicks additional column headers', function () { + beforeEach(function () { + container.find('.slick-header-column')[1].click(); + }); + + it('selects additional columns', function () { + container.find('.slick-header-column')[0].click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + var column1 = selectedRanges[0]; + + expect(selectedRanges.length).toEqual(2); + expect(column1.fromCell).toBe(1); + expect(column1.toCell).toBe(1); + + var column2 = selectedRanges[1]; + + expect(column2.fromCell).toBe(0); + expect(column2.toCell).toBe(0); + }); + }); + + describe('when the user clicks a column header checkbox', function () { + it('selects the column', function () { + container.find('.slick-header-columns input')[0].click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expectOnlyTheFirstColumnToBeSelected(selectedRanges); + }); + + it('checks the checkbox', function () { + container.find('.slick-header-column')[1].click(); + expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeTruthy(); + }); + }); + + describe('when a row is selected', function () { beforeEach(function () { - container = $("
"); - container.height(9999); - - data = [{'some-column-name': 'first value', 'second column': 'second value'}]; - - columns = [ - { - id: '1', - name: 'some-column-name', - }, - { - id: '2', - name: 'second column', - }, - { - name: 'some-non-selectable-column', - selectable: false - } - ] + var selectedRanges = [new Slick.Range(0, 0, 0, 1)]; + rowSelectionModel.setSelectedRanges(selectedRanges); }); - describe("when a column is not selectable", function () { - it("does not create a checkbox for selecting the column", function () { - var checkboxColumn = { - name: 'some-column-name-4', - selectable: false - }; - columns.push(checkboxColumn); + it('deselects the row', function () { + container.find('.slick-header-column')[1].click(); + var selectedRanges = rowSelectionModel.getSelectedRanges(); - setupGrid(columns); + expect(selectedRanges.length).toBe(1); - expect(container.find('.slick-header-columns input').length).toBe(2) - }); + var column = selectedRanges[0]; + + expect(column.fromCell).toBe(1); + expect(column.toCell).toBe(1); + expect(column.fromRow).toBe(0); + expect(column.toRow).toBe(9); }); + }); - it("renders a checkbox in the column header", function () { - setupGrid(columns); + describe('clicking a second time', function () { + beforeEach(function () { + container.find('.slick-header-column')[1].click(); + }); - expect(container.find('.slick-header-columns input').length).toBe(2) + it('unchecks checkbox', function () { + container.find('.slick-header-column')[1].click(); + expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy(); }); - it("displays the name of the column", function () { - setupGrid(columns); + it('deselects the column', function () { + container.find('.slick-header-column')[1].click(); + var selectedRanges = rowSelectionModel.getSelectedRanges(); - expect($(container.find('.slick-header-columns .slick-column-name')[0]).text()) - .toContain('some-column-name'); - expect($(container.find('.slick-header-columns .slick-column-name')[1]).text()) - .toContain('second column'); + expect(selectedRanges.length).toEqual(0); }); + }); - it("preserves the other attributes of column definitions", function () { - var columnSelector = new ColumnSelector(); - var selectableColumns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); + describe('when the column is not selectable', function () { + it('does not select the column', function () { + $(container.find('.slick-header-column:contains(some-non-selectable-column)')).click(); + var selectedRanges = rowSelectionModel.getSelectedRanges(); - expect(selectableColumns[0].id).toBe('1'); + expect(selectedRanges.length).toEqual(0); }); + }); - describe("selecting columns", function () { - var grid, rowSelectionModel; - beforeEach(function () { - var columnSelector = new ColumnSelector(); - columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); - data = []; - for (var i = 0; i < 10; i++) { - data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); - } - grid = new SlickGrid(container, data, columns, options); - - rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); - - grid.registerPlugin(columnSelector); - grid.invalidate(); - $("body").append(container); - }); - - afterEach(function () { - $("body").find(container).remove(); - }); - - describe("when the user clicks a column header", function () { - it("selects the column", function () { - container.find('.slick-header-column')[0].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expectOnlyTheFirstColumnToBeSelected(selectedRanges); - }); - }); - - describe("when the user clicks additional column headers", function () { - beforeEach(function () { - container.find('.slick-header-column')[1].click(); - }); - - it("selects additional columns", function () { - container.find('.slick-header-column')[0].click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - var column1 = selectedRanges[0]; - - expect(selectedRanges.length).toEqual(2); - expect(column1.fromCell).toBe(1); - expect(column1.toCell).toBe(1); - - var column2 = selectedRanges[1]; - - expect(column2.fromCell).toBe(0); - expect(column2.toCell).toBe(0); - }); - }); - - describe("when the user clicks a column header checkbox", function () { - it("selects the column", function () { - container.find('.slick-header-columns input')[0].click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expectOnlyTheFirstColumnToBeSelected(selectedRanges); - }); - - it("checks the checkbox", function () { - container.find('.slick-header-column')[1].click(); - expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeTruthy(); - }); - }); - - describe("when a row is selected", function () { - beforeEach(function () { - var selectedRanges = [new Slick.Range(0, 0, 0, 1)]; - rowSelectionModel.setSelectedRanges(selectedRanges); - }); - - it("deselects the row", function () { - container.find('.slick-header-column')[1].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - - expect(selectedRanges.length).toBe(1); - - var column = selectedRanges[0]; - - expect(column.fromCell).toBe(1); - expect(column.toCell).toBe(1); - expect(column.fromRow).toBe(0); - expect(column.toRow).toBe(9); - }) - }); - - describe("clicking a second time", function () { - beforeEach(function () { - container.find('.slick-header-column')[1].click(); - }); - - it("unchecks checkbox", function () { - container.find('.slick-header-column')[1].click(); - expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy(); - }); - - it("deselects the column", function () { - container.find('.slick-header-column')[1].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - - expect(selectedRanges.length).toEqual(0); - }) - }); - - describe("when the column is not selectable", function () { - it("does not select the column", function () { - $(container.find('.slick-header-column:contains(some-non-selectable-column)')).click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - - expect(selectedRanges.length).toEqual(0); - }); - }); - - describe("when the column is deselected through setSelectedRanges", function () { - beforeEach(function () { - container.find('.slick-header-column')[1].click(); - }); - - it("unchecks the checkbox", function () { - rowSelectionModel.setSelectedRanges([]); - - expect($(container.find('.slick-header-columns input')[1]) - .is(':checked')).toBeFalsy(); - }); - }); - - describe("when a non-column range was already selected", function () { - beforeEach(function () { - var selectedRanges = [new Slick.Range(0, 0, 1, 0)]; - rowSelectionModel.setSelectedRanges(selectedRanges); - }); - - it("deselects the non-column range", function () { - container.find('.slick-header-column')[0].click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expectOnlyTheFirstColumnToBeSelected(selectedRanges); - }) - }); + describe('when the column is deselected through setSelectedRanges', function () { + beforeEach(function () { + container.find('.slick-header-column')[1].click(); }); - var setupGrid = function (columns) { - var columnSelector = new ColumnSelector(); - columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); - var grid = new SlickGrid(container, data, columns, options); + it('unchecks the checkbox', function () { + rowSelectionModel.setSelectedRanges([]); - var rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); + expect($(container.find('.slick-header-columns input')[1]) + .is(':checked')).toBeFalsy(); + }); + }); - grid.registerPlugin(columnSelector); - grid.invalidate(); - }; + describe('when a non-column range was already selected', function () { + beforeEach(function () { + var selectedRanges = [new Slick.Range(0, 0, 1, 0)]; + rowSelectionModel.setSelectedRanges(selectedRanges); + }); - function expectOnlyTheFirstColumnToBeSelected(selectedRanges) { - var row = selectedRanges[0]; + it('deselects the non-column range', function () { + container.find('.slick-header-column')[0].click(); - expect(selectedRanges.length).toEqual(1); - expect(row.fromCell).toBe(0); - expect(row.toCell).toBe(0); - expect(row.fromRow).toBe(0); - expect(row.toRow).toBe(9); - } + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expectOnlyTheFirstColumnToBeSelected(selectedRanges); + }); }); }); + + var setupGrid = function (columns) { + var columnSelector = new ColumnSelector(); + columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); + var grid = new Slick.Grid(container, data, columns, options); + + var rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); + + grid.registerPlugin(columnSelector); + grid.invalidate(); + }; + + function expectOnlyTheFirstColumnToBeSelected(selectedRanges) { + var row = selectedRanges[0]; + + expect(selectedRanges.length).toEqual(1); + expect(row.fromCell).toBe(0); + expect(row.toCell).toBe(0); + expect(row.fromRow).toBe(0); + expect(row.toRow).toBe(9); + } +}); diff --git a/web/regression/javascript/selection/copy_data_spec.js b/web/regression/javascript/selection/copy_data_spec.js index affad66e..ffb0deca 100644 --- a/web/regression/javascript/selection/copy_data_spec.js +++ b/web/regression/javascript/selection/copy_data_spec.js @@ -1,119 +1,128 @@ -define( - ["jquery", - "slickgrid/slick.grid", - "slickgrid/slick.rowselectionmodel", - "sources/selection/copy_data", - "sources/selection/clipboard", - "sources/selection/range_selection_helper" - ], - function ($, SlickGrid, RowSelectionModel, copyData, clipboard, RangeSelectionHelper) { - describe('copyData', function () { - var grid, sqlEditor; - - beforeEach(function () { - var data = [[1, "leopord", "12"], - [2, "lion", "13"], - [3, "puma", "9"]]; - - var columns = [{ - name: "id", - pos: 0, - label: "id
numeric", - cell: "number", - can_edit: false, - type: "numeric" - }, { - name: "brand", - pos: 1, - label: "flavor
character varying", - cell: "string", - can_edit: false, - type: "character varying" - }, { - name: "size", - pos: 2, - label: "size
numeric", - cell: "number", - can_edit: false, - type: "numeric" - } - ] - ; - var gridContainer = $("
"); - $("body").append(gridContainer); - $("body").append(""); - grid = new Slick.Grid("#grid", data, columns, {}); - grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false})); - sqlEditor = {slickgrid: grid}; - }); +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import $ from 'jquery'; + +import Slick from 'slickgrid'; +import 'slickgrid.grid'; + +import clipboard from '../../../pgadmin/static/js/selection/clipboard'; +import copyData from '../../../pgadmin/static/js/selection/copy_data'; + +import RangeSelectionHelper from 'sources/selection/range_selection_helper'; + +describe('copyData', function () { + var grid, sqlEditor; + + beforeEach(function () { + var data = [[1, 'leopord', '12'], + [2, 'lion', '13'], + [3, 'puma', '9']]; + + var columns = [{ + name: 'id', + pos: 0, + label: 'id
numeric', + cell: 'number', + can_edit: false, + type: 'numeric', + }, { + name: 'brand', + pos: 1, + label: 'flavor
character varying', + cell: 'string', + can_edit: false, + type: 'character varying', + }, { + name: 'size', + pos: 2, + label: 'size
numeric', + cell: 'number', + can_edit: false, + type: 'numeric', + }, + ] + ; + var gridContainer = $('
'); + $('body').append(gridContainer); + $('body').append(''); + grid = new Slick.Grid('#grid', data, columns, {}); + grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false})); + sqlEditor = {slickgrid: grid}; + }); - afterEach(function() { - $("body").remove('#grid'); - $("body").remove('#btn-paste-row'); - }); + afterEach(function () { + $('body').remove('#grid'); + $('body').remove('#btn-paste-row'); + }); - describe("when rows are selected", function () { - beforeEach(function () { - grid.getSelectionModel().setSelectedRanges([ - RangeSelectionHelper.rangeForRow(grid, 0), - RangeSelectionHelper.rangeForRow(grid, 2)] - ); - }); + describe('when rows are selected', function () { + beforeEach(function () { + grid.getSelectionModel().setSelectedRanges([ + RangeSelectionHelper.rangeForRow(grid, 0), + RangeSelectionHelper.rangeForRow(grid, 2)] + ); + }); - it("copies them", function () { - spyOn(clipboard, 'copyTextToClipboard'); + it('copies them', function () { + spyOn(clipboard, 'copyTextToClipboard'); - copyData.apply(sqlEditor); + copyData.apply(sqlEditor); - expect(sqlEditor.copied_rows.length).toBe(2); + expect(sqlEditor.copied_rows.length).toBe(2); - expect(clipboard.copyTextToClipboard).toHaveBeenCalled(); - expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain("1,'leopord','12'"); - expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain("3,'puma','9'"); - }); + expect(clipboard.copyTextToClipboard).toHaveBeenCalled(); + expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain('1,\'leopord\',\'12\''); + expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain('3,\'puma\',\'9\''); + }); - describe("when the user can edit the grid", function () { - it("enables the paste row button", function () { - copyData.apply(_.extend({can_edit: true}, sqlEditor)); + describe('when the user can edit the grid', function () { + it('enables the paste row button', function () { + copyData.apply(_.extend({can_edit: true}, sqlEditor)); - expect($("#btn-paste-row").prop('disabled')).toBe(false); - }); - }); + expect($('#btn-paste-row').prop('disabled')).toBe(false); }); + }); + }); - describe("when a column is selected", function () { - beforeEach(function () { - var firstColumn = new Slick.Range(0, 0, 2, 0); - grid.getSelectionModel().setSelectedRanges([firstColumn]) - }); + describe('when a column is selected', function () { + beforeEach(function () { + var firstColumn = new Slick.Range(0, 0, 2, 0); + grid.getSelectionModel().setSelectedRanges([firstColumn]); + }); - it("copies text to the clipboard", function () { - spyOn(clipboard, 'copyTextToClipboard'); + it('copies text to the clipboard', function () { + spyOn(clipboard, 'copyTextToClipboard'); - copyData.apply(sqlEditor); + copyData.apply(sqlEditor); - expect(clipboard.copyTextToClipboard).toHaveBeenCalled(); + expect(clipboard.copyTextToClipboard).toHaveBeenCalled(); - var copyArg = clipboard.copyTextToClipboard.calls.mostRecent().args[0]; - var rowStrings = copyArg.split('\n'); - expect(rowStrings[0]).toBe("1"); - expect(rowStrings[1]).toBe("2"); - expect(rowStrings[2]).toBe("3"); - }); + var copyArg = clipboard.copyTextToClipboard.calls.mostRecent().args[0]; + var rowStrings = copyArg.split('\n'); + expect(rowStrings[0]).toBe('1'); + expect(rowStrings[1]).toBe('2'); + expect(rowStrings[2]).toBe('3'); + }); - it("sets copied_rows to empty", function () { - copyData.apply(sqlEditor); + it('sets copied_rows to empty', function () { + copyData.apply(sqlEditor); - expect(sqlEditor.copied_rows.length).toBe(0); - }); + expect(sqlEditor.copied_rows.length).toBe(0); + }); - describe("when the user can edit the grid", function () { - it("disables the paste row button", function () { - copyData.apply(_.extend({can_edit: true}, sqlEditor)); + describe('when the user can edit the grid', function () { + it('disables the paste row button', function () { + copyData.apply(_.extend({can_edit: true}, sqlEditor)); - expect($("#btn-paste-row").prop('disabled')).toBe(true); - }); - }); + expect($('#btn-paste-row').prop('disabled')).toBe(true); }); }); }); +}); \ No newline at end of file diff --git a/web/regression/javascript/selection/grid_selector_spec.js b/web/regression/javascript/selection/grid_selector_spec.js index a74a66f9..22886b77 100644 --- a/web/regression/javascript/selection/grid_selector_spec.js +++ b/web/regression/javascript/selection/grid_selector_spec.js @@ -1,126 +1,127 @@ -define(["jquery", - "underscore", - "slickgrid/slick.grid", - "slickgrid/slick.rowselectionmodel", - "sources/selection/grid_selector" - ], - function ($, _, SlickGrid, RowSelectionModel, GridSelector) { - describe("GridSelector", function () { - var container, data, columns, gridSelector, rowSelectionModel; +import $ from 'jquery'; - beforeEach(function () { - container = $("
"); - container.height(9999); - columns = [{ - id: '1', - name: 'some-column-name', - }, { - id: '2', - name: 'second column', - }]; - - gridSelector = new GridSelector(); - columns = gridSelector.getColumnDefinitionsWithCheckboxes(columns); - - data = []; - for (var i = 0; i < 10; i++) { - data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); - } - var grid = new SlickGrid(container, data, columns); - - rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); - - grid.registerPlugin(gridSelector); - grid.invalidate(); - - $("body").append(container); - }); +import Slick from 'slickgrid'; +import 'slickgrid.grid'; - afterEach(function () { - $("body").find(container).remove(); - }); +import RowSelectionModel from 'slickgrid.rowselectionmodel'; - it("renders an additional column on the left for selecting rows", function () { - expect(columns.length).toBe(3); +import GridSelector from 'sources/selection/grid_selector'; - var leftmostColumn = columns[0]; - expect(leftmostColumn.id).toBe('row-header-column'); - }); +describe('GridSelector', function () { + var container, data, columns, gridSelector, rowSelectionModel; - it("renders checkboxes for selecting columns", function () { - expect(container.find('[data-test="output-column-header"] input').length).toBe(2) - }); + beforeEach(function () { + container = $('
'); + container.height(9999); + columns = [{ + id: '1', + name: 'some-column-name', + }, { + id: '2', + name: 'second column', + }]; - it("renders a checkbox for selecting all the cells", function () { - expect(container.find("[title='Select/Deselect All']").length).toBe(1); - }); + gridSelector = new GridSelector(); + columns = gridSelector.getColumnDefinitionsWithCheckboxes(columns); + + data = []; + for (var i = 0; i < 10; i++) { + data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); + } + var grid = new Slick.Grid(container, data, columns); + + rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); + + grid.registerPlugin(gridSelector); + grid.invalidate(); + + $('body').append(container); + }); + + afterEach(function () { + $('body').find(container).remove(); + }); + + it('renders an additional column on the left for selecting rows', function () { + expect(columns.length).toBe(3); + + var leftmostColumn = columns[0]; + expect(leftmostColumn.id).toBe('row-header-column'); + }); + + it('renders checkboxes for selecting columns', function () { + expect(container.find('[data-test="output-column-header"] input').length).toBe(2); + }); + + it('renders a checkbox for selecting all the cells', function () { + expect(container.find('[title=\'Select/Deselect All\']').length).toBe(1); + }); + + describe('when the cell for the select/deselect all is clicked', function () { + it('selects the whole grid', function () { + container.find('[title=\'Select/Deselect All\']').parent().click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expect(selectedRanges.length).toBe(1); + var selectedRange = selectedRanges[0]; + expect(selectedRange.fromCell).toBe(1); + expect(selectedRange.toCell).toBe(2); + expect(selectedRange.fromRow).toBe(0); + expect(selectedRange.toRow).toBe(9); + }); + + it('checks the checkbox', function () { + container.find('[title=\'Select/Deselect All\']').parent().click(); + + expect($(container.find('[data-id=\'checkbox-select-all\']')).is(':checked')).toBeTruthy(); + }); + }); - describe("when the cell for the select/deselect all is clicked", function () { - it("selects the whole grid", function () { - container.find("[title='Select/Deselect All']").parent().click(); + describe('when the main checkbox in the corner gets selected', function () { + it('unchecks all the columns', function () { + container.find('[title=\'Select/Deselect All\']').click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expect(selectedRanges.length).toBe(1); - var selectedRange = selectedRanges[0]; - expect(selectedRange.fromCell).toBe(1); - expect(selectedRange.toCell).toBe(2); - expect(selectedRange.fromRow).toBe(0); - expect(selectedRange.toRow).toBe(9); - }); + expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy(); + expect($(container.find('.slick-header-columns input')[2]).is(':checked')).toBeFalsy(); + }); - it("checks the checkbox", function () { - container.find("[title='Select/Deselect All']").parent().click(); + it('selects all the cells', function () { + container.find('[title=\'Select/Deselect All\']').click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expect(selectedRanges.length).toBe(1); + var selectedRange = selectedRanges[0]; + expect(selectedRange.fromCell).toBe(1); + expect(selectedRange.toCell).toBe(2); + expect(selectedRange.fromRow).toBe(0); + expect(selectedRange.toRow).toBe(9); + }); - expect($(container.find("[data-id='checkbox-select-all']")).is(':checked')).toBeTruthy(); - }) + describe('when the main checkbox in the corner gets deselected', function () { + beforeEach(function () { + container.find('[title=\'Select/Deselect All\']').click(); }); - describe("when the main checkbox in the corner gets selected", function () { - it("unchecks all the columns", function () { - container.find("[title='Select/Deselect All']").click(); - - expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy(); - expect($(container.find('.slick-header-columns input')[2]).is(':checked')).toBeFalsy(); - }); - - it("selects all the cells", function () { - container.find("[title='Select/Deselect All']").click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expect(selectedRanges.length).toBe(1); - var selectedRange = selectedRanges[0]; - expect(selectedRange.fromCell).toBe(1); - expect(selectedRange.toCell).toBe(2); - expect(selectedRange.fromRow).toBe(0); - expect(selectedRange.toRow).toBe(9); - }); - - describe("when the main checkbox in the corner gets deselected", function () { - beforeEach(function () { - container.find("[title='Select/Deselect All']").click(); - }); - - it("deselects all the cells", function () { - container.find("[title='Select/Deselect All']").click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expect(selectedRanges.length).toBe(0); - }); - }); - - describe("and then the underlying selection changes", function () { - beforeEach(function () { - container.find("[title='Select/Deselect All']").click(); - }); - - it("unchecks the main checkbox", function () { - var ranges = [new Slick.Range(0, 0, 0, 1)]; - rowSelectionModel.setSelectedRanges(ranges); - - expect($(container.find("[title='Select/Deselect All']")).is(':checked')).toBeFalsy(); - }); - }); + it('deselects all the cells', function () { + container.find('[title=\'Select/Deselect All\']').click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expect(selectedRanges.length).toBe(0); + }); + }); + + describe('and then the underlying selection changes', function () { + beforeEach(function () { + container.find('[title=\'Select/Deselect All\']').click(); + }); + + it('unchecks the main checkbox', function () { + var ranges = [new Slick.Range(0, 0, 0, 1)]; + rowSelectionModel.setSelectedRanges(ranges); + + expect($(container.find('[title=\'Select/Deselect All\']')).is(':checked')).toBeFalsy(); }); }); }); +}); \ No newline at end of file diff --git a/web/regression/javascript/selection/range_boundary_navigator_spec.js b/web/regression/javascript/selection/range_boundary_navigator_spec.js index 8376d0a7..6e9173d1 100644 --- a/web/regression/javascript/selection/range_boundary_navigator_spec.js +++ b/web/regression/javascript/selection/range_boundary_navigator_spec.js @@ -1,8 +1,20 @@ -define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNavigator) { - - describe("#getUnion", function () { - describe("when the ranges completely overlap", function () { - it("returns a list with that range", function () { +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +import rangeBoundaryNavigator from 'sources/selection/range_boundary_navigator'; +import Slick from 'slickgrid'; + +describe('RangeBoundaryNavigator', function () { + + describe('#getUnion', function () { + describe('when the ranges completely overlap', function () { + it('returns a list with that range', function () { var ranges = [[1, 4], [1, 4], [1, 4]]; var union = rangeBoundaryNavigator.getUnion(ranges); @@ -11,8 +23,8 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa }); }); - describe("when the ranges all overlap partially or touch", function () { - it("returns one long range", function () { + describe('when the ranges all overlap partially or touch', function () { + it('returns one long range', function () { var rangeBounds = [[3, 6], [1, 4], [7, 14]]; var union = rangeBoundaryNavigator.getUnion(rangeBounds); @@ -20,7 +32,7 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa expect(union).toEqual([[1, 14]]); }); - it("returns them in order from lowest to highest", function () { + it('returns them in order from lowest to highest', function () { var rangeBounds = [[3, 6], [2, 3], [10, 12]]; var union = rangeBoundaryNavigator.getUnion(rangeBounds); @@ -28,9 +40,9 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa expect(union).toEqual([[2, 6], [10, 12]]); }); - describe("when one range completely overlaps another", function() { + describe('when one range completely overlaps another', function() { - it("returns them in order from lowest to highest", function () { + it('returns them in order from lowest to highest', function () { var rangeBounds = [[9, 14], [2, 3], [11, 13]]; var union = rangeBoundaryNavigator.getUnion(rangeBounds); @@ -39,19 +51,19 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa }); }); - describe("when one range is a subset of another", function () { - it("returns the larger range", function () { + describe('when one range is a subset of another', function () { + it('returns the larger range', function () { var rangeBounds = [[2, 6], [1, 14], [8, 10]]; var union = rangeBoundaryNavigator.getUnion(rangeBounds); expect(union).toEqual([[1, 14]]); - }) - }) + }); + }); }); - describe("when the ranges do not touch", function () { - it("returns them in order from lowest to highest", function () { + describe('when the ranges do not touch', function () { + it('returns them in order from lowest to highest', function () { var rangeBounds = [[3, 6], [1, 1], [8, 10]]; var union = rangeBoundaryNavigator.getUnion(rangeBounds); @@ -62,8 +74,8 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa }); - describe("#mapDimensionBoundaryUnion", function () { - it("returns a list of the results of the callback", function () { + describe('#mapDimensionBoundaryUnion', function () { + it('returns a list of the results of the callback', function () { var rangeBounds = [[0, 1], [3, 3]]; var callback = function () { return 'hello'; @@ -72,7 +84,7 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa expect(result).toEqual(['hello', 'hello', 'hello']); }); - it("calls the callback with each index in the dimension", function () { + it('calls the callback with each index in the dimension', function () { var rangeBounds = [[0, 1], [3, 3]]; var callback = jasmine.createSpy('callbackSpy'); rangeBoundaryNavigator.mapDimensionBoundaryUnion(rangeBounds, callback); @@ -80,7 +92,7 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa }); }); - describe("#mapOver2DArray", function () { + describe('#mapOver2DArray', function () { var data, rowCollector, processCell; beforeEach(function () { data = [[0, 1, 2, 3], [2, 2, 2, 2], [4, 5, 6, 7]]; @@ -92,66 +104,66 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa }; }); - it("calls the callback for each item in the ranges", function () { + it('calls the callback for each item in the ranges', function () { var rowRanges = [[0, 0], [2, 2]]; var colRanges = [[0, 3]]; var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector); - expect(selectionResult).toEqual(["[0,1,2,3]", "[4,5,6,7]"]); + expect(selectionResult).toEqual(['[0,1,2,3]', '[4,5,6,7]']); }); - describe("when the ranges are out of order/duplicated", function () { + describe('when the ranges are out of order/duplicated', function () { var rowRanges, colRanges; beforeEach(function () { rowRanges = [[2, 2], [2, 2], [0, 0]]; colRanges = [[0, 3]]; }); - it("uses the union of the ranges", function () { - spyOn(rangeBoundaryNavigator, "getUnion").and.callThrough(); + it('uses the union of the ranges', function () { + spyOn(rangeBoundaryNavigator, 'getUnion').and.callThrough(); var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector); expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(rowRanges); expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(colRanges); - expect(selectionResult).toEqual(["[0,1,2,3]", "[4,5,6,7]"]); + expect(selectionResult).toEqual(['[0,1,2,3]', '[4,5,6,7]']); }); }); }); - describe("#rangesToCsv", function () { + describe('#rangesToCsv', function () { var data, columnDefinitions, ranges; beforeEach(function () { - data = [[1, "leopard", "12"], - [2, "lion", "13"], - [3, "cougar", "9"], - [4, "tiger", "10"]]; + data = [[1, 'leopard', '12'], + [2, 'lion', '13'], + [3, 'cougar', '9'], + [4, 'tiger', '10']]; columnDefinitions = [{name: 'id', pos: 0}, {name: 'animal', pos: 1}, {name: 'size', pos: 2}]; ranges = [new Slick.Range(0, 0, 0, 2), new Slick.Range(3, 0, 3, 2)]; }); - it("returns csv for the provided ranges", function () { + it('returns csv for the provided ranges', function () { var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges); - expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'"); + expect(csvResult).toEqual('1,\'leopard\',\'12\'\n4,\'tiger\',\'10\''); }); - describe("when there is an extra column with checkboxes", function () { + describe('when there is an extra column with checkboxes', function () { beforeEach(function () { columnDefinitions = [{name: 'not-a-data-column'}, {name: 'id', pos: 0}, {name: 'animal', pos: 1}, { name: 'size', - pos: 2 + pos: 2, }]; ranges = [new Slick.Range(0, 0, 0, 3), new Slick.Range(3, 0, 3, 3)]; }); - it("returns csv for the columns with data", function () { + it('returns csv for the columns with data', function () { var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges); - expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'"); + expect(csvResult).toEqual('1,\'leopard\',\'12\'\n4,\'tiger\',\'10\''); }); }); }); diff --git a/web/regression/javascript/selection/row_selector_spec.js b/web/regression/javascript/selection/row_selector_spec.js index 10697e6a..be741d42 100644 --- a/web/regression/javascript/selection/row_selector_spec.js +++ b/web/regression/javascript/selection/row_selector_spec.js @@ -1,174 +1,172 @@ -define( - ["jquery", - "underscore", - "slickgrid/slick.grid", - "sources/selection/row_selector", - "slickgrid/slick.rowselectionmodel", - "slickgrid", - ], - function ($, _, SlickGrid, RowSelector, RowSelectionModel, Slick) { - describe("RowSelector", function () { - var container, data, columnDefinitions, grid, rowSelectionModel; +import $ from 'jquery'; + +import Slick from 'slickgrid'; +import 'slickgrid.grid'; +import RowSelectionModel from 'slickgrid.rowselectionmodel'; + +import RowSelector from 'sources/selection/row_selector'; + +describe('RowSelector', function () { + var container, data, columnDefinitions, grid, rowSelectionModel; + + beforeEach(function () { + container = $('
'); + container.height(9999); + + columnDefinitions = [{ + id: '1', + name: 'some-column-name', + selectable: true, + }, { + id: '2', + name: 'second column', + selectable: true, + }]; + + var rowSelector = new RowSelector(); + data = []; + for (var i = 0; i < 10; i++) { + data.push(['some-value-' + i, 'second value ' + i]); + } + columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions); + grid = new Slick.Grid(container, data, columnDefinitions); + + rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); + grid.registerPlugin(rowSelector); + grid.invalidate(); + + $('body').append(container); + }); + + afterEach(function () { + $('body').find(container).remove(); + }); + + it('renders an additional column on the left', function () { + expect(columnDefinitions.length).toBe(3); + + var leftmostColumn = columnDefinitions[0]; + expect(leftmostColumn.id).toBe('row-header-column'); + expect(leftmostColumn.name).toBe(''); + expect(leftmostColumn.selectable).toBe(false); + }); + + it('renders a checkbox the leftmost column', function () { + expect(container.find('.sr').length).toBe(11); + expect(container.find('.sr .sc:first-child input[type="checkbox"]').length).toBe(10); + }); + + it('preserves the other attributes of column definitions', function () { + expect(columnDefinitions[1].id).toBe('1'); + expect(columnDefinitions[1].selectable).toBe(true); + }); + + describe('selecting rows', function () { + describe('when the user clicks a row header checkbox', function () { + it('selects the row', function () { + container.find('.sr .sc:first-child input[type="checkbox"]')[0].click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expectOnlyTheFirstRowToBeSelected(selectedRanges); + }); - beforeEach(function () { - container = $("
"); - container.height(9999); - - columnDefinitions = [{ - id: '1', - name: 'some-column-name', - selectable: true - }, { - id: '2', - name: 'second column', - selectable: true - }]; - - var rowSelector = new RowSelector(); - data = []; - for (var i = 0; i < 10; i++) { - data.push(['some-value-' + i, 'second value ' + i]); - } - columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions); - grid = new SlickGrid(container, data, columnDefinitions); - - rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); - grid.registerPlugin(rowSelector); - grid.invalidate(); - - $("body").append(container); + it('checks the checkbox', function () { + container.find('.sr .sc:first-child input[type="checkbox"]')[5].click(); + + expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[5]) + .is(':checked')).toBeTruthy(); + }); + }); + + describe('when the user clicks a row header', function () { + it('selects the row', function () { + container.find('.sr .sc:first-child')[0].click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expectOnlyTheFirstRowToBeSelected(selectedRanges); }); - afterEach(function () { - $("body").find(container).remove(); + it('checks the checkbox', function () { + container.find('.sr .sc:first-child')[7].click(); + + expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[7]) + .is(':checked')).toBeTruthy(); }); + }); + + describe('when the user clicks multiple row headers', function () { + it('selects another row', function () { + container.find('.sr .sc:first-child')[4].click(); + container.find('.sr .sc:first-child')[0].click(); - it("renders an additional column on the left", function () { - expect(columnDefinitions.length).toBe(3); + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expect(selectedRanges.length).toEqual(2); + + var row1 = selectedRanges[0]; + expect(row1.fromRow).toBe(4); + expect(row1.toRow).toBe(4); + + var row2 = selectedRanges[1]; + expect(row2.fromRow).toBe(0); + expect(row2.toRow).toBe(0); + }); + }); - var leftmostColumn = columnDefinitions[0]; - expect(leftmostColumn.id).toBe('row-header-column'); - expect(leftmostColumn.name).toBe(''); - expect(leftmostColumn.selectable).toBe(false); + describe('when a column was already selected', function () { + beforeEach(function () { + var selectedRanges = [new Slick.Range(0, 0, 0, 1)]; + rowSelectionModel.setSelectedRanges(selectedRanges); }); - it("renders a checkbox the leftmost column", function () { - expect(container.find('.sr').length).toBe(11); - expect(container.find('.sr .sc:first-child input[type="checkbox"]').length).toBe(10); + it('deselects the column', function () { + container.find('.sr .sc:first-child')[0].click(); + var selectedRanges = rowSelectionModel.getSelectedRanges(); + + expectOnlyTheFirstRowToBeSelected(selectedRanges); }); + }); - it("preserves the other attributes of column definitions", function () { - expect(columnDefinitions[1].id).toBe('1'); - expect(columnDefinitions[1].selectable).toBe(true); + describe('when the row is deselected through setSelectedRanges', function () { + beforeEach(function () { + container.find('.sr .sc:first-child')[4].click(); }); - describe("selecting rows", function () { - describe("when the user clicks a row header checkbox", function () { - it("selects the row", function () { - container.find('.sr .sc:first-child input[type="checkbox"]')[0].click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expectOnlyTheFirstRowToBeSelected(selectedRanges); - }); - - it("checks the checkbox", function () { - container.find('.sr .sc:first-child input[type="checkbox"]')[5].click(); - - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[5]) - .is(':checked')).toBeTruthy(); - }); - }); - - describe("when the user clicks a row header", function () { - it("selects the row", function () { - container.find('.sr .sc:first-child')[0].click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expectOnlyTheFirstRowToBeSelected(selectedRanges); - }); - - it("checks the checkbox", function () { - container.find('.sr .sc:first-child')[7].click(); - - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[7]) - .is(':checked')).toBeTruthy(); - }); - }); - - describe("when the user clicks multiple row headers", function () { - it("selects another row", function () { - container.find('.sr .sc:first-child')[4].click(); - container.find('.sr .sc:first-child')[0].click(); - - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expect(selectedRanges.length).toEqual(2); - - var row1 = selectedRanges[0]; - expect(row1.fromRow).toBe(4); - expect(row1.toRow).toBe(4); - - var row2 = selectedRanges[1]; - expect(row2.fromRow).toBe(0); - expect(row2.toRow).toBe(0); - }); - }); - - describe("when a column was already selected", function () { - beforeEach(function () { - var selectedRanges = [new Slick.Range(0, 0, 0, 1)]; - rowSelectionModel.setSelectedRanges(selectedRanges); - }); - - it("deselects the column", function () { - container.find('.sr .sc:first-child')[0].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - - expectOnlyTheFirstRowToBeSelected(selectedRanges); - }) - }); - - describe("when the row is deselected through setSelectedRanges", function () { - beforeEach(function () { - container.find('.sr .sc:first-child')[4].click(); - }); - - it("should uncheck the checkbox", function () { - rowSelectionModel.setSelectedRanges([]); - - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[4]) - .is(':checked')).toBeFalsy(); - }); - }); - - describe("click a second time", function () { - beforeEach(function () { - container.find('.sr .sc:first-child')[1].click(); - }); - - it("unchecks checkbox", function () { - container.find('.sr .sc:first-child')[1].click(); - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[1]) - .is(':checked')).toBeFalsy(); - }); - - it("unselects the row", function () { - container.find('.sr .sc:first-child')[1].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - - expect(selectedRanges.length).toEqual(0); - }) - }); + it('should uncheck the checkbox', function () { + rowSelectionModel.setSelectedRanges([]); + + expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[4]) + .is(':checked')).toBeFalsy(); }); }); - function expectOnlyTheFirstRowToBeSelected(selectedRanges) { - var row = selectedRanges[0]; + describe('click a second time', function () { + beforeEach(function () { + container.find('.sr .sc:first-child')[1].click(); + }); + + it('unchecks checkbox', function () { + container.find('.sr .sc:first-child')[1].click(); + expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[1]) + .is(':checked')).toBeFalsy(); + }); - expect(selectedRanges.length).toEqual(1); - expect(row.fromCell).toBe(1); - expect(row.toCell).toBe(2); - expect(row.fromRow).toBe(0); - expect(row.toRow).toBe(0); - } - }); \ No newline at end of file + it('unselects the row', function () { + container.find('.sr .sc:first-child')[1].click(); + var selectedRanges = rowSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toEqual(0); + }); + }); + }); +}); + +function expectOnlyTheFirstRowToBeSelected(selectedRanges) { + var row = selectedRanges[0]; + + expect(selectedRanges.length).toEqual(1); + expect(row.fromCell).toBe(1); + expect(row.toCell).toBe(2); + expect(row.fromRow).toBe(0); + expect(row.toRow).toBe(0); +} \ No newline at end of file diff --git a/web/regression/javascript/size_prettify_spec.js b/web/regression/javascript/size_prettify_spec.js index 7d370fcb..f60c79c0 100644 --- a/web/regression/javascript/size_prettify_spec.js +++ b/web/regression/javascript/size_prettify_spec.js @@ -7,59 +7,59 @@ // ////////////////////////////////////////////////////////////////////////// -define(["sources/size_prettify"], function (sizePrettify) { - describe("sizePrettify", function () { - describe("when size is 0", function () { - it("returns 0 bytes", function () { - expect(sizePrettify(0)).toEqual("0 bytes"); +define(['sources/size_prettify'], function (sizePrettify) { + describe('sizePrettify', function () { + describe('when size is 0', function () { + it('returns 0 bytes', function () { + expect(sizePrettify(0)).toEqual('0 bytes'); }); }); - describe("when size >= 10kB and size < 10 MB", function () { - it("returns size in kB", function () { - expect(sizePrettify(10240)).toEqual("10 kB"); + describe('when size >= 10kB and size < 10 MB', function () { + it('returns size in kB', function () { + expect(sizePrettify(10240)).toEqual('10 kB'); }); - it("returns size in kB", function () { - expect(sizePrettify(99999)).toEqual("98 kB"); + it('returns size in kB', function () { + expect(sizePrettify(99999)).toEqual('98 kB'); }); }); - describe("when size >= 10MB and size < 10 GB", function () { - it("returns size in MB", function () { - expect(sizePrettify(10485760)).toEqual("10 MB"); + describe('when size >= 10MB and size < 10 GB', function () { + it('returns size in MB', function () { + expect(sizePrettify(10485760)).toEqual('10 MB'); }); - it("returns size in MB", function () { - expect(sizePrettify(44040192)).toEqual("42 MB"); + it('returns size in MB', function () { + expect(sizePrettify(44040192)).toEqual('42 MB'); }); }); - describe("when size >= 10GB and size < 10 TB", function () { - it("returns size in GB", function () { - expect(sizePrettify(10737418240)).toEqual("10 GB"); + describe('when size >= 10GB and size < 10 TB', function () { + it('returns size in GB', function () { + expect(sizePrettify(10737418240)).toEqual('10 GB'); }); - it("returns size in GB", function () { - expect(sizePrettify(10736344498176)).toEqual("9999 GB"); + it('returns size in GB', function () { + expect(sizePrettify(10736344498176)).toEqual('9999 GB'); }); }); - describe("when size >= 10TB and size < 10 PB", function () { - it("returns size in TB", function () { - expect(sizePrettify(10995116277760)).toEqual("10 TB"); + describe('when size >= 10TB and size < 10 PB', function () { + it('returns size in TB', function () { + expect(sizePrettify(10995116277760)).toEqual('10 TB'); }); - it("returns size in TB", function () { - expect(sizePrettify(29995116277760)).toEqual("27 TB"); + it('returns size in TB', function () { + expect(sizePrettify(29995116277760)).toEqual('27 TB'); }); }); - describe("when size >= 10 PB", function () { - it("returns size in PB", function () { - expect(sizePrettify(11258999068426200)).toEqual("10 PB"); + describe('when size >= 10 PB', function () { + it('returns size in PB', function () { + expect(sizePrettify(11258999068426200)).toEqual('10 PB'); }); }); diff --git a/web/regression/javascript/test-main.js b/web/regression/javascript/test-main.js deleted file mode 100644 index fe28a5f8..00000000 --- a/web/regression/javascript/test-main.js +++ /dev/null @@ -1,125 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2017, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////////////////// - -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) - } -}); - -var sourcesDir = '/base/pgadmin/static/'; -require.config({ - // Karma serves files under /base, which is the basePath from your config file - baseUrl: '/base', - - paths: { - 'alertify': sourcesDir + 'vendor/alertifyjs/alertify', - 'jquery': sourcesDir + 'vendor/jquery/jquery-1.11.2', - 'jquery.ui': sourcesDir + 'vendor/jquery-ui/jquery-ui-1.11.3', - 'jquery.event.drag': sourcesDir + 'vendor/jquery-ui/jquery.event.drag-2.2', - 'underscore': sourcesDir + 'vendor/underscore/underscore', - 'underscore.string': sourcesDir + 'vendor/underscore/underscore.string', - 'slickgrid': sourcesDir + 'vendor/slickgrid/slick.core', - 'slickgrid/slick.grid': sourcesDir + 'vendor/slickgrid/slick.grid', - 'slickgrid/slick.rowselectionmodel': sourcesDir + 'vendor/slickgrid/plugins/slick.rowselectionmodel', - 'translations': '/base/regression/javascript/fake_translations', - 'sources': sourcesDir + 'js' - }, - - shim: { - 'underscore': { - exports: '_' - }, - "slickgrid": { - "deps": [ - 'jquery', "jquery.ui", "jquery.event.drag" - ], - "exports": 'window.Slick' - }, - "slickgrid/slick.grid": { - "deps": [ - 'jquery', "jquery.ui", "jquery.event.drag", "slickgrid" - ], - "exports": 'window.Slick.Grid' - }, - "slickgrid/slick.rowselectionmodel": { - "deps": [ - "jquery" - ], - "exports": 'window.Slick.RowSelectionModel' - }, - "backbone": { - "deps": ['underscore', 'jquery'], - "exports": 'Backbone' - }, - "backbone.paginator": { - "deps": ['underscore', 'jquery', 'backbone'] - }, - "bootstrap": { - "deps": ['jquery'], - }, - "backgrid": { - "deps": ['backform'], - "exports": 'Backgrid', - }, - "backgrid.select.all": { - "deps": ['backgrid'] - }, - "backgrid.paginator": { - "deps": ['backgrid', 'backbone.paginator'] - }, - "backgrid.filter": { - "deps": ['backgrid'] - }, - "backgrid.sizeable.columns": { - "deps": ['backgrid'] - }, - "bootstrap.switch": { - "deps": ['jquery', 'bootstrap'], - "exports": 'jQuery.fn.bootstrapSwitch' - }, - "select2": { - "deps": ['jquery'], - "exports": 'jQuery.fn.select2' - }, - "bootstrap.datepicker": { - "deps": ['jquery', 'bootstrap'], - "exports": 'jQuery.fn.datepicker' - }, - "bootstrap.datetimepicker": { - "deps": ['jquery', 'bootstrap', 'moment'], - "exports": 'jQuery.fn.datetimepicker' - }, - "pgadmin.backgrid": { - "deps": ["backgrid", "bootstrap.datetimepicker", "bootstrap.switch"], - }, - "pgadmin.backform": { - "deps": ['backform', "pgadmin.backgrid", "select2"], - }, - "jquery.event.drag": { - "deps": ['jquery'], "exports": 'jQuery.fn.drag' - }, - "jquery.ui": {"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/web/regression/requirements.txt b/web/regression/requirements.txt index 6babf987..fd5ed5d9 100644 --- a/web/regression/requirements.txt +++ b/web/regression/requirements.txt @@ -4,6 +4,7 @@ testscenarios==0.5.0 testtools==2.0.0 traceback2==1.4.0 unittest2==1.1.0 +mock~=2.0.0 # Leave this at the end because there is a bug where the '--install-option' is applied to all subsequent requirements chromedriver_installer==0.0.6 --install-option='--chromedriver-version=2.29' diff --git a/web/webpack.config.js b/web/webpack.config.js new file mode 100644 index 00000000..91586592 --- /dev/null +++ b/web/webpack.config.js @@ -0,0 +1,29 @@ +/* eslint-env node */ + +module.exports = { + context: __dirname + '/pgadmin/static/jsx', + entry: './components.jsx', + output: { + libraryTarget: 'amd', + path: __dirname + '/pgadmin/static/js/generated', + filename: 'reactComponents.js', + }, + + module: { + rules: [{ + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['es2015', 'react'], + }, + }, + }, + ], + }, + + resolve: { + extensions: ['.js', '.jsx'], + }, +}; \ No newline at end of file diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js new file mode 100644 index 00000000..49bef069 --- /dev/null +++ b/web/webpack.test.config.js @@ -0,0 +1,65 @@ +/* eslint-env node */ +const path = require('path'); +const webpack = require('webpack'); + +const sourcesDir = path.resolve(__dirname, 'pgadmin/static'); +const regressionDir = path.resolve(__dirname, 'regression'); + +module.exports = { + plugins: [ + new webpack.ProvidePlugin({ + jQuery: 'jquery', + _: 'underscore', + }), + ], + + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: [/node_modules/, /vendor/], + use: { + loader: 'babel-loader', + options: { + presets: ['es2015'], + }, + }, + }, + { + test: /.*slickgrid\/slick\.(?!core)*/, + loader: 'imports-loader?' + + 'jquery.ui' + + ',jquery.event.drag' + + ',slickgrid', + }, { + test: /.*slickgrid\/plugins\/slick\.rowselectionmodel/, + loader: 'imports-loader?' + + 'jquery.ui' + + ',jquery.event.drag' + + ',slickgrid' + + '!exports-loader?' + + 'Slick.RowSelectionModel', + }, { + test: /.*slickgrid\/slick\.core.*/, + loader: 'imports-loader?' + + 'jquery.ui' + + ',jquery.event.drag' + + '!exports-loader?' + + 'Slick', + }], + }, + + resolve: { + alias: { + 'alertify': sourcesDir + '/vendor/alertifyjs/alertify', + 'jquery': sourcesDir + '/vendor/jquery/jquery-1.11.2', + 'jquery.ui': sourcesDir + '/vendor/jquery-ui/jquery-ui-1.11.3', + 'jquery.event.drag': sourcesDir + '/vendor/jquery-ui/jquery.event.drag-2.2', + 'sources': sourcesDir + '/js', + 'translations': regressionDir + '/javascript/fake_translations', + 'slickgrid': sourcesDir + '/vendor/slickgrid/slick.core', + 'slickgrid.grid': sourcesDir + '/vendor/slickgrid/slick.grid', + 'slickgrid.rowselectionmodel': sourcesDir + '/vendor/slickgrid/plugins/slick.rowselectionmodel', + }, + }, +}; \ No newline at end of file