From ee6df3ca58faafa50eda181e796c56fc1393b197 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 7 Apr 2011 14:12:22 +0200 Subject: [PATCH] [TEST] that by default the list view renders each row with a selection checkbox bzr revid: xmo@openerp.com-20110407121222-eipzf31zf3e9x45s --- addons/base/static/src/js/list.js | 60 ++++++++--- addons/base/static/src/xml/base.xml | 22 +++-- addons/base/static/test/list.js | 48 +++++++++ addons/base/static/test/qunit-doc.js | 143 +++++++++++++++++++++++++++ addons/base/static/test/test.html | 2 + 5 files changed, 254 insertions(+), 21 deletions(-) create mode 100644 addons/base/static/test/list.js create mode 100644 addons/base/static/test/qunit-doc.js diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 19dbde26f5d..65f503649b8 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -1,9 +1,22 @@ - openerp.base.list = function (openerp) { - openerp.base.views.add('list', 'openerp.base.ListView'); -openerp.base.ListView = openerp.base.Controller.extend({ - init: function(view_manager, session, element_id, dataset, view_id) { +openerp.base.ListView = openerp.base.Controller.extend( + /** @lends openerp.base.ListView# */ { + defaults: { + // records can be selected one by one + 'selectable': true + }, + /** + * @constructs + * @param view_manager + * @param session An OpenERP session object + * @param element_id the id of the DOM elements this view should link itself to + * @param {openerp.base.DataSet} dataset the dataset the view should work with + * @param {String} view_id the listview's identifier, if any + * @param {Object} options A set of options used to configure the view + * @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox) + */ + init: function(view_manager, session, element_id, dataset, view_id, options) { this._super(session, element_id); this.view_manager = view_manager; this.dataset = dataset; @@ -12,6 +25,8 @@ openerp.base.ListView = openerp.base.Controller.extend({ this.columns = []; this.rows = []; + + this.options = _.extend({}, this.defaults, options || {}); }, start: function() { //this.log('Starting ListView '+this.model+this.view_id) @@ -30,6 +45,12 @@ openerp.base.ListView = openerp.base.Controller.extend({ }).value(); this.$element.html(QWeb.render("ListView", this)); + this.$element.find('table').delegate( + 'th.oe-record-selector', 'click', function (e) { + // A click in the selection cell should not activate the + // linking feature + e.stopImmediatePropagation(); + }); this.$element.find('table').delegate( 'tr', 'click', this.on_select_row); @@ -37,36 +58,49 @@ openerp.base.ListView = openerp.base.Controller.extend({ if (this.view_manager.sidebar) this.view_manager.sidebar.load_multi_actions(); }, + /** + * Fills the table with the provided records after emptying it + * + * @param {Array} records the records to fill the list view with + * @returns {Promise} promise to the end of view rendering (list views are asynchronously filled for improved responsiveness) + */ do_fill_table: function(records) { this.rows = records; - var $table = this.$element.find('table'); // remove all data lines $table.find('tbody').remove(); // add new content - var columns = this.columns; - var rows = this.rows; + var columns = this.columns, + rows = this.rows, + options = this.options; + // Paginate by groups of 50 for rendering - var PAGE_SIZE = 50; - var bodies_count = Math.ceil(this.rows.length / PAGE_SIZE); - var body = 0; - var $body = $('').appendTo($table); + var PAGE_SIZE = 50, + bodies_count = Math.ceil(this.rows.length / PAGE_SIZE), + body = 0, + $body = $('').appendTo($table); + var render_body = function () { + var rendered = $.Deferred(); setTimeout(function () { $body.append( QWeb.render("ListView.rows", { columns: columns, - rows: rows.slice(body*PAGE_SIZE, (body+1)*PAGE_SIZE) + rows: rows.slice(body*PAGE_SIZE, (body+1)*PAGE_SIZE), + options: options })); ++body; if (body < bodies_count) { render_body(); + } else { + rendered.resolve(); } }, 0); + return rendered.promise(); }; - render_body(); + return render_body(); }, on_select_row: function (event) { var $target = $(event.currentTarget); diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index e04de920943..1dcb30006d4 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -166,24 +166,30 @@ - +
- - - - - - - + + + + + + + + + + + + + diff --git a/addons/base/static/test/list.js b/addons/base/static/test/list.js new file mode 100644 index 00000000000..ed055d0bb8a --- /dev/null +++ b/addons/base/static/test/list.js @@ -0,0 +1,48 @@ +$(document).ready(function () { + /** + * Tests a jQuery collection against a selector ("ands" the .is() of each + * member of the collection, instead of "or"-ing them) + * + * @param {jQuery} $c a jQuery collection object + * @param {String} selector the selector to test the collection against + */ + var are = function ($c, selector) { + return ($c.filter(function () { return $(this).is(selector); }).length + === $c.length); + }; + + var fvg = {fields_view: { + 'fields': [], + 'arch': { + 'attrs': {string: ''} + } + }}; + + var openerp; + module("ListView", { + setup: function () { + openerp = window.openerp.init(true); + window.openerp.base.chrome(openerp); + // views loader stuff + window.openerp.base.views(openerp); + window.openerp.base.list(openerp); + } + }); + + asyncTest('render selection checkboxes', 2, function () { + var listview = new openerp.base.ListView( + {}, null, + 'qunit-fixture', {model: null}); + + listview.on_loaded(fvg); + + listview.do_fill_table([{}, {}, {}]).then(function () { + ok(are(listview.$element.find('tbody th'), + '.oe-record-selector')); + ok(are(listview.$element.find('tbody th input'), + ':checkbox:not([name])')); + start(); + }); + }); + +}); diff --git a/addons/base/static/test/qunit-doc.js b/addons/base/static/test/qunit-doc.js new file mode 100644 index 00000000000..3667cc3eb40 --- /dev/null +++ b/addons/base/static/test/qunit-doc.js @@ -0,0 +1,143 @@ +/** + * Defines a module scope (which lasts until the next call to module). + * + * This module scopes implies setup and teardown callbacks running for each test. + * + * @function + * @param {String} name the name of the module + * @param {Object} [lifecycle] callbacks to run before and after each test of the module + * @param {Function} lifecycle.setup function running before each test of this module + * @param {Function} lifecycle.teardown function running after each test of this module + */ +var module; +/** + * Defines a given test to run. Runs all the assertions present in the test + * + * @function + * @param {String} name the name of the test + * @param {Number} [expected] number of assertions expected to run in this test (useful for asynchronous tests) + * @param {Function} test the testing code to run, holding a sequence of assertions (at least one) + */ +var test; +/** + * Defines an asynchronous test: equivalent to calling stop() at the start of + * a normal test(). + * + * The test code needs to restart the test runner via start() + * + * @function + * @param {String} name the name of the test + * @param {Number} [expected] number of assertions expected to run in this test (useful for asynchronous tests) + * @param {Function} test the testing code to run, holding a sequence of assertions (at least one) + */ +var asyncTest; +/** + * The most basic boolean assertion (~assertTrue or assert). + * + * Passes if its argument is truthy + * + * @function + * @param {Boolean} state an arbitrary expression, evaluated in a boolean context + * @param {String} [message] the message to output with the assertion result + */ +var ok; +/** + * Equality assertion (~assertEqual) + * + * Passes if both arguments are equal (via ==) + * + * @function + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var equal; +/** + * Inequality assertion (~assertNotEqual) + * + * Passes if the arguments are different (via !=) + * + * @function + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var notEqual; +/** + * Recursive equality assertion. + * + * Works on primitive types using === and traversing through + * Objects and Arrays as well checking their components + * + * @function + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var deepEqual; +/** + * Recursive inequality assertion. + * + * Works on primitive types using !== and traversing through + * Objects and Arrays as well checking their components + * + * @function + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var notDeepEqual; +/** + * Strict equality assertion (~assertEqual) + * + * Passes if both arguments are identical (via ===) + * + * @function + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var strictEqual; +/** + * Strict inequality assertion (~assertNotEqual) + * + * Passes if both arguments are identical (via !==) + * + * @function + * @param {Object} actual the object to check for correctness (processing result) + * @param {Object} expected the object to check against + * @param {String} [message] message output with the assertion result + */ +var notStrictEqual; +/** + * Passes if the provided block raised an exception. + * + * The expect argument can be provided to perform further assertion checks on the exception itself: + * * If it's a RegExp test the exception against the regexp (message?) + * * If it's a constructor, check if the exception is an instance of it + * * If it's an other type of function, call it with the exception as first parameter + * - If the function returns true, the assertion validates + * - Otherwise it fails + * + * @function + * @param {Function} block function which should raise an exception when called + * @param {Object} [expect] a RegExp, a constructor or a Function + * @param {String} [message] message output with the assertion result + */ +var raises; +/** + * Starts running the test runner again from the point where it was + * stopped. + * + * Used to resume testing after a callback. + * + * @function + */ +var start; +/** + * Stops the test runner in order to wait for an asynchronous test to run + * + * @function + * @param {Number} [timeout] fails the test after the timeout triggers, only for debugging tests + */ +var stop; diff --git a/addons/base/static/test/test.html b/addons/base/static/test/test.html index 9681be8e389..776b03146ff 100644 --- a/addons/base/static/test/test.html +++ b/addons/base/static/test/test.html @@ -21,6 +21,7 @@ + @@ -35,4 +36,5 @@ +